3053 Commits

Author SHA1 Message Date
Karl Seguin
a84708e99d Merge pull request #1359 from lightpanda-io/crash_handler
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Improve crash handling
2026-01-19 16:50:08 +08:00
Halil Durak
6b6c0e930e Merge pull request #1376 from lightpanda-io/nikneym/attribute-ns
Add simplified `setAttributeNS` and `getAttributeNS`
2026-01-19 11:08:49 +03:00
Halil Durak
926892be01 add not_implemented warnings 2026-01-19 10:57:48 +03:00
Karl Seguin
2894bef9ef Update src/crash_handler.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2026-01-19 15:06:43 +08:00
Karl Seguin
a6e7ecd9e5 Move more asserts to custom asserter.
Deciding what should be an lp.assert, vs an std.debug.assert, vs a debug-only
assert is a little arbitrary.

debug-only asserts, guarded with an `if (comptime IS_DEBUG)` obviously avoid the
check in release and thus have a performance advantage. We also use them at
library boundaries. If libcurl says it will always emit a header line with a
trailing \r\n, is that really a check we need to do in production? I don't think
so. First, that code path is checked _a lot_ in debug. Second, it feels a bit
like we're testing libcurl (in production!)..why? A debug-only assertion should
be good enough to catch any changes in libcurl.
2026-01-19 09:12:16 +08:00
Karl Seguin
9b000a002e Hook v8 crashes into new crash handler 2026-01-19 07:37:10 +08:00
Karl Seguin
0f9c9e2089 Improve crash handling
This adds a crash handler which reports a crash (if telemetry is enabled). On a
crash, this looks for `curl` (using the PATH env), and forks the process to then
call execve. This relies on a new endpoint to be setup to accept the "report".
Also, we include very little data..I figured just knowing about crashes would
be a good place to start.

A panic handler is provided, which override's Zig default handler and hooks
into the crash handler.

An `assert` function is added and hooks into the crash handler. This is
currently only used in one place (Session.zig) to demonstrate its use. In
addition to reporting a failed assert, the assert aborts execution in
ReleaseFast (as opposed to an undefined behavior with std.debug.assert).

I want to hook this into the v8 global error handler, but only after direct_v8
is merged.

Much of this is inspired by bun's code. They have their own assert (1) and
a [more sophisticated] crashHandler (2).
:

(1) beccd01647/src/bun.zig (L2987)
(2) beccd01647/src/crash_handler.zig (L198)
2026-01-19 07:36:46 +08:00
Karl Seguin
393227a786 Merge pull request #1373 from lightpanda-io/explicit_globals
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Explicit globals
2026-01-19 07:26:04 +08:00
Karl Seguin
c5870353e3 update v8 dep 2026-01-19 07:17:45 +08:00
Karl Seguin
7c9941c629 Make Promise, PromiseResolver and Module have explicit globals.
See bb06900b6f84abaccc7ecfd386af1a9dc0029c50 for an explanation.
2026-01-19 07:15:48 +08:00
Karl Seguin
c7dbb6792d Make js.Object and js.Value have explicit global
See: bb06900b6f84abaccc7ecfd386af1a9dc0029c50 for details on this change.
2026-01-19 07:15:48 +08:00
Karl Seguin
728b2b7089 update v8 dep 2026-01-19 07:15:48 +08:00
Karl Seguin
5def997bed Make Global Function explicit.
This is the first in a series of changes to make globals explicit. The ultimate
goal of having explicit Globals is to move away from the global HandleScope and
to explicit HandleScopes.

Currently, we treat globals and locals interchangeably. In fact, for Global ->
Local, we just ptrCast. This works because we have 1 global HandleScope, which
effectively disables V8's GC and thus nothing ever gets moved.

If we're going to introduce explicit HandleScopes, then we need to first have
correct Globals. Specifically, when we want to act on the global, we need to
get the local value, and that will eventually mean making sure there's a
HandleScope.

While adding explicit globals, we're keeping the global HandleScope so that we
can minimize the change. So, given that we still have the global HandleScope
the change is largely two things:
1 - js.Function.persit() returns a js.Function.Global. Types that persist global
   functions must be updated to js.Function.Global.
2 - To turn js.Function.Global -> js.Function, we need to call .local() on it.

The bridge has been updated to support js.Function.Global for both input and
output parameters. Thus, window.setOnLoad can now directly take a
js.Function.Global, and window.getOnLoad can directly return that
js.Function.Global.
2026-01-19 07:15:48 +08:00
Karl Seguin
a30c65966b Merge pull request #1380 from lightpanda-io/static_accessor_fix
Fix static accessors
2026-01-19 07:15:09 +08:00
Karl Seguin
cd67ed8a27 Fix static accessors
These are called without a self from v8, and should match that in Zig code.
2026-01-19 07:08:58 +08:00
Pierre Tachoire
5400dc783e Merge pull request #1379 from lightpanda-io/textarea_setDefaultValue
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add TextArea.defaultValue  setter
2026-01-18 17:12:49 +01:00
Pierre Tachoire
2880e9867d Merge pull request #1378 from lightpanda-io/performance_observer_use_after_free
Fix potential use-after-free with PerformanceObserver.
2026-01-18 17:03:36 +01:00
Karl Seguin
58f9469a6f Add TextArea.defaultValue setter 2026-01-18 07:49:58 +08:00
Karl Seguin
30d052db99 Fix potential use-after-free with PerformanceObserver.
TL;DR - use page.arena instead of page.call_arena

This probably comes from copying the implementation of MutationObserver and/or
IntersectionObserver. But those dispatches are different in that they directly
dispatch a slice (e.g. of MutationRecords) which gets mapped to a v8::Array when
doing the callback. The MutationRecords exist on the heap, not in
_pending_records, so the call_arena is fine.

PerformanceObserver returns an Zig object, not a slice. Therefore it gets mapped
to a v8::Object which references the Zig object. The state of that object, the
_entries list, has to exist for the lifetime of that object, not the call_arena.
2026-01-17 15:57:43 +08:00
Karl Seguin
744311f107 Merge pull request #1375 from lightpanda-io/nikneym/audio-constructor
Some checks failed
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-test / zig build release (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add `Audio` constructor
2026-01-16 23:25:56 +00:00
Karl Seguin
656674a477 Merge pull request #1356 from lightpanda-io/nikneym/subtle-crypto
Initial support for `SubtleCrypto` API
2026-01-16 23:21:35 +00:00
Karl Seguin
0e4aa38aaa Merge pull request #1312 from lightpanda-io/stagehand-zigdom
Stagehand zigdom
2026-01-16 23:10:19 +00:00
Pierre Tachoire
fdc267fa1f Merge pull request #1308 from lightpanda-io/axtree-backport
cdp: add AXTree
2026-01-16 17:56:40 +01:00
Pierre Tachoire
4325b80d64 axnode: small fixes 2026-01-16 17:30:43 +01:00
Pierre Tachoire
fbe07836f9 cdp: return a valide response for Page.getFrameTree on STARTUP
Stagehand expects a valid response for this specific command.
Add also `Target.activateTarget`
2026-01-16 16:27:55 +01:00
Halil Durak
304681bd21 add simplified setAttributeNS and getAttributeNS
This ignores namespaces for now, we have to come up with a solution if it becomes a necessity.
2026-01-16 18:13:43 +03:00
Halil Durak
05a01bb7c4 add Audio constructor 2026-01-16 17:38:54 +03:00
Pierre Tachoire
cbc028b040 cdp: accept multiple attachToTarget calls 2026-01-16 09:10:41 +01:00
Pierre Tachoire
2074c0149f axnode: add aria-labelledby support 2026-01-16 09:01:39 +01:00
Pierre Tachoire
61ed97dd45 axnode: use writeString for content's name 2026-01-16 09:00:57 +01:00
Pierre Tachoire
a358c46b9f axnode: ignore script and style children 2026-01-16 08:28:16 +01:00
Pierre Tachoire
50c1e2472b axnode: encode json string into stripWhitespaces 2026-01-16 08:27:43 +01:00
Halil Durak
ea2fc76d3c don't @panic! 2026-01-15 20:40:53 +03:00
Halil Durak
58634b54ec add tests for implemented bits of SubtleCrypto 2026-01-15 19:10:02 +03:00
Halil Durak
4b4bc1a4d3 don't allocate new SubtleCrypto for each access 2026-01-15 19:10:01 +03:00
Halil Durak
0549e07a90 implement deriveBits for X25519 2026-01-15 19:10:01 +03:00
Halil Durak
42666b1d30 add bindings needed for X25519 deriveBits implementation 2026-01-15 19:10:01 +03:00
Halil Durak
0a8be77233 create public/private key objects out of raw keys
This is needed for `deriveKey()` and `deriveBits()`.
2026-01-15 19:10:01 +03:00
Halil Durak
b26fb0e6c7 add more libcrypto bindings 2026-01-15 19:10:00 +03:00
Halil Durak
1699a92822 support x25519 init
Created a mess in previous commit.
2026-01-15 19:10:00 +03:00
Halil Durak
7ae3e8cb47 code cleanup, support keypairs, init support for X25519 2026-01-15 19:10:00 +03:00
Halil Durak
fd26ae4b5b parse keyUsages properly 2026-01-15 19:10:00 +03:00
Halil Durak
9945a5f9cc implement sign and verify for HMAC 2026-01-15 19:09:59 +03:00
Halil Durak
d5e9ae23ef ground zero SubtleCrypto 2026-01-15 19:09:59 +03:00
Pierre Tachoire
d50e056114 axnode: ignore non-html tags 2026-01-15 16:42:40 +01:00
Pierre Tachoire
d7d956d966 axnode: fix invalid enum 2026-01-15 15:40:52 +01:00
Pierre Tachoire
bd3966bf8d axnode: add focus on webroot 2026-01-15 15:37:49 +01:00
Pierre Tachoire
74578ba274 axnode: implement list marker 2026-01-15 15:37:49 +01:00
Pierre Tachoire
cb89742d2f axnode: add li level 2026-01-15 15:37:48 +01:00
Pierre Tachoire
6d0f991c17 axnode: add hr properties 2026-01-15 15:37:48 +01:00
Pierre Tachoire
d126d2a0f9 axnode: ignore hidden input 2026-01-15 15:37:47 +01:00
Pierre Tachoire
b51cca5617 axnode: use select.getValue 2026-01-15 15:37:47 +01:00
Pierre Tachoire
dc54dad290 axnode: add more attributes for input elements 2026-01-15 15:37:47 +01:00
Pierre Tachoire
7d6ab5a708 axnode: force manual formatting in switches
In order to uses less space and improve the readability.

zig fmt allows only 1 switch case per line or all in one line.
When having a lot of conditions, splitting the line is useful.
2026-01-15 15:37:46 +01:00
Pierre Tachoire
07acb9308d axnode: fallback button name to their tagname 2026-01-15 15:37:46 +01:00
Pierre Tachoire
ef315a46bc axnode: don't extract all text content as name
ignore name extraction for more elements
2026-01-15 15:37:45 +01:00
Pierre Tachoire
eb45bd051c axtree: simpler AXValue 2026-01-15 15:37:45 +01:00
Pierre Tachoire
65102edc98 axtree: remove useless error return 2026-01-15 15:37:44 +01:00
Pierre Tachoire
04eda96416 axtree: reverse writeNode return logic 2026-01-15 15:37:44 +01:00
Pierre Tachoire
f5036bdf5e axtree: use a simpler union switch 2026-01-15 15:37:44 +01:00
Pierre Tachoire
b6df85da7a axtree: add improvements 2026-01-15 15:37:43 +01:00
Pierre Tachoire
9775b39a8d axnode: use absolute urls 2026-01-15 15:37:43 +01:00
Pierre Tachoire
d6d74c5024 first version of AXTree 2026-01-15 15:37:42 +01:00
Pierre Tachoire
e09d15b12a add more generic HTML types 2026-01-15 15:37:35 +01:00
Karl Seguin
6d33d23935 Merge pull request #1371 from lightpanda-io/reject_non_new_constructor
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Reject constructor calls without new
2026-01-15 12:06:55 +00:00
Karl Seguin
47760e00f7 Reject constructor calls without new
This was previously a fixed bug, but it got lost in the direct_v8 merging.

https://github.com/lightpanda-io/browser/pull/1316
2026-01-15 19:25:43 +08:00
Karl Seguin
72e8421099 Merge pull request #1366 from lightpanda-io/details_are_values
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
use js.Value when input can be a value
2026-01-14 23:25:17 +00:00
Karl Seguin
844b0ed457 Merge pull request #1368 from lightpanda-io/dupe_remove_id
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Make removeIds lookup own the key
2026-01-14 11:52:06 +00:00
Karl Seguin
7e37db796f Make removeIds lookup own the key
Virtually all string are page-owned (in the page.arena), but because of the
Small String Optimization we use (string.zig), a string could be stack-allocated

The correct solution is probably to change the key to be a string.String. But
I want to give more thought to memory in general, and strings specifically need
to be thought about. So this is a quick fix for crashing.
2026-01-14 18:35:26 +08:00
Karl Seguin
3e5b506675 Merge pull request #1367 from lightpanda-io/readable_stream_cancel_persist
persist the readable stream's cancel callback
2026-01-14 10:20:14 +00:00
Karl Seguin
d356dbfc06 Merge pull request #1365 from lightpanda-io/try_catch_caught
Try catch caught
2026-01-14 09:59:19 +00:00
Karl Seguin
f5aee1f4c0 persist the readable stream's cancel callback 2026-01-14 17:58:41 +08:00
Karl Seguin
de4926d87d fix legacy runner, manual merge 2026-01-14 17:49:27 +08:00
Karl Seguin
56a39e2cc7 Apply tryCatch change to wpt runner 2026-01-14 17:34:08 +08:00
Karl Seguin
8e14dacc32 Improve ergonomics of try catch (and Function's tryCall)
It now returns a Caught struct which contains all information. The Caught struct
can be logged directly, providing more consistent logs for caught errors.
2026-01-14 17:34:02 +08:00
Karl Seguin
05102c673a use js.Value when input can be a value
We previously treated v8::Object and v8::Values interchangeably, and would just
ptrCast one to the other. So, if an API was defined with a js.Object but was
given a non-object value, e.g. 9001, it would still work.

This has since been tightened. If an API takes a js.Object, than the v8 value
must be an object. Passing a non-object will result in a InvalidArgument error.

CustomEvent.detail and PerformanceMark.detail can both be any value, so the
apis/fields have been updated from js.Object -> js.Value.
2026-01-14 15:38:34 +08:00
Karl Seguin
db2ecfe159 Merge pull request #1307 from lightpanda-io/direct_v8
Direct v8
2026-01-14 07:27:42 +00:00
Karl Seguin
640cb0d489 Merge pull request #1364 from lightpanda-io/observer_try_catch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add trycatch to Intersection and Performance Observers
2026-01-14 02:10:56 +00:00
Karl Seguin
223a6170d5 Fix use-after free
On CDP.BrowserContext.deinit, clear the isolated world ExecutionContext before
terminating the session. This is important as the isolated_world list is
allocated from the session.arena.

Also, semi-revert 63f1c85964. Before all this
we were running microtasks on ExecutionWorld.removeContext. That didn't seem
right (and I thought it was the original source of the bug). But, for the "real"
Page context, this is critical, since Microtasks can reference the Page object.
Since microTasks are isolation-level, it's possible for a microtasks for Page1
to execute after Page1 goes away (if we create a new page, Page2). This re-adds
the microtask "draining", but only for the Page (i.e. in Page.deinit).
2026-01-14 09:37:10 +08:00
Karl Seguin
63f1c85964 Remove unnecessary microtask run.
This crashes linux in releasesafe without an embedded snapshot. Not sure why,
but it shouldn't be necessary. This was added back when we were executing
microtasks on a schedule, rather than manually at explicit points.
2026-01-13 18:09:58 +08:00
Karl Seguin
c252c8e870 update v8 dep version 2026-01-13 16:12:28 +08:00
Karl Seguin
801c019150 update v8 2026-01-13 16:07:49 +08:00
Karl Seguin
d77a6620f3 merge main 2026-01-13 13:05:16 +08:00
Karl Seguin
4e4a615df8 Move Env's FunctionTemplate from Global -> Eternal
(we'll move more to Eternal's, this is just a first teaser)
2026-01-13 12:58:31 +08:00
Karl Seguin
1b0ea44519 merge main 2026-01-13 12:58:31 +08:00
Karl Seguin
86f4ea108d Store snapshot templates in isolate, not context.
This lets us load the isolate without having to create a temp/dummy context
just to get the templates.

Call ContextDisposedNotification when a context is removed. Supposedly this can
help/hint to the isolate about memory management.
2026-01-13 12:58:30 +08:00
Karl Seguin
2322cb9b83 remove unused code, remove references to v8::Persistent 2026-01-13 12:58:30 +08:00
Karl Seguin
4720268426 Don't dupe StartupData, use what v8 gives us directly. 2026-01-13 12:58:30 +08:00
Karl Seguin
b4f134bff6 Prefer js.Value over js.Object in History/Navigation
Persist function callback in PerformanceObserver
2026-01-13 12:58:30 +08:00
Karl Seguin
f2a9125b99 js.v8 is not equal to js.v8.c
This means the C funtions/types now sit in the root of v8.
2026-01-13 12:58:30 +08:00
Karl Seguin
8438b7d561 remove remaining direct v8 references 2026-01-13 12:58:30 +08:00
Karl Seguin
18c846757b migrate almost all types 2026-01-13 12:58:28 +08:00
Karl Seguin
bc11a48e6b migrate most cases, merge Caller into bridge 2026-01-13 12:57:06 +08:00
Karl Seguin
01ecd725b8 cleanup resolvers 2026-01-13 12:57:06 +08:00
Karl Seguin
e6af7d1bd0 import more types 2026-01-13 12:57:06 +08:00
Karl Seguin
701de08e8a have our js.Context directly hold a js handle 2026-01-13 12:57:06 +08:00
Karl Seguin
363b95bdef Isolate and HandleScope 2026-01-13 12:57:06 +08:00
Karl Seguin
ca5a385b51 Port js.Object
Use js.Value in apis that should take values (not objects), like console.log
and setTimeout and reportError.
2026-01-13 12:57:03 +08:00
Karl Seguin
93f0d24673 port TryCatch 2026-01-13 12:56:07 +08:00
Karl Seguin
a5038893fe port Snapshot 2026-01-13 12:56:07 +08:00
Karl Seguin
3442f99a49 remove unused js.This 2026-01-13 12:56:07 +08:00
Karl Seguin
6ecf52cc03 port Platform and Inspector to use v8's C handles/functions directly 2026-01-13 12:56:07 +08:00
Karl Seguin
8aaef674fe Migrate Function and String 2026-01-13 12:56:07 +08:00
Karl Seguin
3b1cd06615 Make js.Array and js.Value directly contain their v8 handles. 2026-01-13 12:56:06 +08:00
Karl Seguin
4841f8cc8f add trycatch to Intersection and Performance Observers 2026-01-13 12:55:10 +08:00
Karl Seguin
d9d8f68bf8 Merge pull request #1361 from lightpanda-io/mutation-observer-trycall
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
use tryCall for MutationObserver records callback
2026-01-12 22:42:33 +00:00
Pierre Tachoire
cf726d9813 fix double slash in import path 2026-01-12 17:59:49 +01:00
Pierre Tachoire
92be2c45d6 use tryCall for MutationObserver records callback
Instead of `call` to avoid uncaught error
2026-01-12 17:58:40 +01:00
Pierre Tachoire
914092b538 Merge pull request #1355 from lightpanda-io/console_apis
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
add some missing APIs to Console
2026-01-12 15:15:24 +01:00
Pierre Tachoire
a8cd5fc266 Merge pull request #1354 from lightpanda-io/node_document
Node document
2026-01-12 15:14:57 +01:00
Pierre Tachoire
643f07fa10 Merge pull request #1352 from lightpanda-io/mutation_character_data
Mutation character data
2026-01-12 15:13:32 +01:00
Pierre Tachoire
0d77ff661b Merge pull request #1360 from lightpanda-io/wpt-v8
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: fix wpt path
2026-01-12 10:53:51 +01:00
Pierre Tachoire
70d84b2f72 ci: fix wpt build path 2026-01-12 10:38:13 +01:00
Pierre Tachoire
41905ef735 Merge pull request #1358 from lightpanda-io/wpt-v8
ci: move fetch test from integration to e2e
2026-01-12 09:26:55 +01:00
Pierre Tachoire
2a468cc750 ci: split wpt build and run
vv8 build can pollute stdout output.
2026-01-12 09:18:16 +01:00
Pierre Tachoire
32520000c6 ci: use releaseFast mode for wpt 2026-01-12 09:09:36 +01:00
Pierre Tachoire
14db7a8eb3 ci: move fetch test from integration to e2e 2026-01-12 08:53:24 +01:00
Pierre Tachoire
8460e9a385 Merge pull request #1357 from lightpanda-io/wpt-v8
CI changes
2026-01-12 08:42:53 +01:00
Pierre Tachoire
933a93a703 ci: move fetch tests into 2e2 2026-01-12 08:32:52 +01:00
Pierre Tachoire
c2e09d3084 ci: build only run cmd forbuild dev test 2026-01-12 08:28:03 +01:00
Pierre Tachoire
98397401b8 ci: use compiled v8 with wpt tests 2026-01-12 08:11:55 +01:00
Karl Seguin
e042b1105a Merge pull request #1311 from lightpanda-io/nikneym/backport-canvas
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Backport dummy canvas APIs
2026-01-10 23:26:42 +00:00
Halil Durak
ee4775eb1a prefer underscore on fields 2026-01-10 14:13:41 +03:00
Halil Durak
6ff6232316 move isHexColor to color.zig 2026-01-10 14:13:09 +03:00
Karl Seguin
10035ab2f4 add some missing APIs to Console 2026-01-10 17:45:25 +08:00
Karl Seguin
2679175ae9 make createElement return DOMException on error 2026-01-10 16:00:11 +08:00
Karl Seguin
8d3aa1f3fa validate tag name given to document.createElement 2026-01-10 10:43:43 +08:00
Karl Seguin
75e78795ec Add Document.replaceChildren
Improve correctness (hierarchy validation) of various Document functions.
2026-01-10 10:32:02 +08:00
Karl Seguin
05f0f8901e make Node.isConnected() shadowroot-aware 2026-01-10 08:24:12 +08:00
Karl Seguin
6917aeb47b Walk document for doctype 2026-01-10 08:05:03 +08:00
Karl Seguin
516a86e33f Merge pull request #1331 from lightpanda-io/zigdom-selector-case-insensitive
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Adds case insensitivity to `Element.querySelector`
2026-01-09 23:20:39 +00:00
Halil Durak
7184a91c95 finalize canvas backport 2026-01-09 19:46:11 +03:00
Halil Durak
83e9d705cf backport dummy canvas APIs 2026-01-09 16:47:19 +03:00
Karl Seguin
bb907f5adb Support range mutation across nodes
Range mutation will trigger MutationObserver

MutationObserver with characterDataOldValue=true implicitly means
characterData=true

For MutationObserver-characterData test.
2026-01-09 20:42:23 +08:00
Karl Seguin
f1b60453bd Add getAttributeNamespace to MutationRecord 2026-01-09 20:25:49 +08:00
Pierre Tachoire
0ef339f12a Merge pull request #1349 from lightpanda-io/build-timeout
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
ci: increase timeout limit for build
2026-01-09 12:34:18 +01:00
Pierre Tachoire
5c0169ee05 ci: force release to be the latest 2026-01-09 11:44:38 +01:00
Pierre Tachoire
daf959ee90 Merge pull request #1347 from lightpanda-io/registerProtocolHandler
Add Navigator.registerProtocolHandler and unregisterProtocolHandler p…
2026-01-09 11:42:49 +01:00
Pierre Tachoire
89b43b6102 Merge pull request #1348 from lightpanda-io/wpt_events
Wpt events
2026-01-09 11:41:41 +01:00
Pierre Tachoire
d3b05201b9 Merge pull request #1350 from lightpanda-io/css_selector_escape_sequence
Support escape sequences in CSS selector for id and class selectors
2026-01-09 11:40:33 +01:00
Karl Seguin
127e53cf3a Merge pull request #1344 from lightpanda-io/indexed_fix
Define the index handler on the instance, not the prototype.
2026-01-09 10:38:54 +00:00
Pierre Tachoire
29281fe3ec Merge pull request #1346 from lightpanda-io/more_cloneNode
Support cloneNode for DocumentType and Attribute
2026-01-09 11:38:18 +01:00
Pierre Tachoire
a0fb55802f Merge pull request #1345 from lightpanda-io/add_more_explicit_types
Adds a number of HTML elements
2026-01-09 11:37:51 +01:00
Pierre Tachoire
90ec068367 Merge pull request #1351 from lightpanda-io/inspector-deinit-handlescope
use temporary handle scope to deinit inspector
2026-01-09 11:37:00 +01:00
Pierre Tachoire
f57cf1be75 use temporary handlescope to deinit inspector 2026-01-09 11:25:34 +01:00
Karl Seguin
3f44dee367 Support escape sequences in CSS selector for id and class selectors
Improves dom/nodes/ParentNode-querySelector-escapes.html from 20/68 -> 64/68.

Previous main had 66/68..but the last 4 are really edge cases that add a lot
of complexity.
2026-01-09 16:42:57 +08:00
Pierre Tachoire
82161ce94c ci: increase timeout limit for build 2026-01-09 09:14:01 +01:00
Karl Seguin
27b8e2a38c fix test 2026-01-09 14:38:47 +08:00
Karl Seguin
e5f2fbdcb2 clear isTrusted on redispatch and prevent redispatching while dispatching 2026-01-09 14:37:34 +08:00
Karl Seguin
cdf0cdd0ea Don't require handleEvent function to be on object event listener
When registering an object as an event listener, the handleEvent function
doesn't have to be defined then and there. The handleEvent function can be added
at any point in the future.
2026-01-09 14:27:00 +08:00
Karl Seguin
f12ff2c7bd Add Navigator.registerProtocolHandler and unregisterProtocolHandler placeholders 2026-01-09 13:40:19 +08:00
Karl Seguin
6c7c507d32 Support cloneNode for DocumentType and Attribute 2026-01-09 11:10:50 +08:00
Karl Seguin
0c97b8238b Adds a number of HTML elements
Instead of being mapped to HTMLUnknownElement, these will all be mapped to the
correct type. This is important for many WPT tests. But it's not impossible that
some script checks `if (x instanceof HTMLBaseElement)` and, without this, that
would error since HTMLBaseElement wouldn't be defined.
2026-01-09 10:56:23 +08:00
Karl Seguin
967a2030e6 Define the index handler on the instance, not the prototype.
While it sorta works if done on the prototype, it's incorrect as these are no
longer "own" properties (which some WPT tests care about). NamedIndexes were
already correctly defined on the instance.
2026-01-09 10:31:01 +08:00
Karl Seguin
78ebd5faf8 Merge pull request #1342 from lightpanda-io/better_namespace_support
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Improve support for non-HTML namespace
2026-01-09 07:05:36 +08:00
Karl Seguin
9d498fa069 Improve support for non-HTML namespace
This does a better job of tracking the implicit namespace based on the context.
For example, when using DOMParser.parseFromString with an XML namespace, all
subsequent elements will be in the XML namespace.

Adds support for null namespace.

Rather than defaulting to HTML, unknown namespaces now map to a special unknown
type. We don't currently preserve the original namespace, but we're at least
able to properly handle the casing in this case.
2026-01-09 07:03:52 +08:00
Karl Seguin
0db1ceaea7 Merge pull request #1339 from lightpanda-io/remove_className
Remove className function from every type
2026-01-09 06:57:40 +08:00
Karl Seguin
df27aeef6c Merge pull request #1343 from lightpanda-io/navigator-ua-suffix
return the app's user agent on Navigator.userAgent
2026-01-09 06:56:01 +08:00
Karl Seguin
5ae0df53bb Remove className function from every type
The toString symbol is now automatically implemented on any type with a
JsApi.Meta.Name, so className is no longer used.
2026-01-09 06:55:07 +08:00
Karl Seguin
48df6ae159 Merge pull request #1338 from lightpanda-io/HTMLSpanElement
add an explicit HTMLSpanElement
2026-01-09 06:52:17 +08:00
Pierre Tachoire
6cae2fcea7 return the app's user agent on Navigator.userAgent 2026-01-08 15:23:02 +01:00
Pierre Tachoire
d1d4d4894d Merge pull request #1341 from lightpanda-io/test-bench
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
tests: re-enable metrics JSON output
2026-01-08 14:01:56 +01:00
Pierre Tachoire
adfcf7bb2c tests: re-enable metrics JSON output
METRICS=true zig build test
2026-01-08 13:07:27 +01:00
Karl Seguin
c8f75cd266 Merge pull request #1340 from lightpanda-io/nikneym/parse-from-string-return
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Return `*Document` instead of tagged union in `parseFromString`
2026-01-08 19:16:14 +08:00
Halil Durak
282a9bbf65 return *Document instead of tagged union in parseFromString
Did a detour to XML PR and realized this is simpler.
2026-01-08 12:46:46 +03:00
Karl Seguin
d4c8af2a61 add an explicit HTMLSpanElement 2026-01-08 16:03:50 +08:00
Karl Seguin
060afcd459 Merge pull request #1313 from lightpanda-io/nikneym/xml-parsing
Support XML parsing
2026-01-08 08:42:21 +08:00
Karl Seguin
5d1522a61f Don't dispatch to listeners added during dispatching
Use the last current listener as a sentinel, so that any listener added during
dispatching can be skipped.
2026-01-08 08:39:49 +08:00
Karl Seguin
b1b54afc56 Merge pull request #1335 from lightpanda-io/build
Embed v8 snapshot in builds
2026-01-08 06:54:38 +08:00
Pierre Tachoire
2abc490732 ci: support build on tag push 2026-01-07 21:11:20 +01:00
Pierre Tachoire
d4807df2e9 add v8 snapshot instructions into the README 2026-01-07 17:22:50 +01:00
Pierre Tachoire
d5f4ca15cc add v8 snapshot in build processes 2026-01-07 17:22:49 +01:00
Pierre Tachoire
e642c85ebd use ReleaseFast build 2026-01-07 17:22:49 +01:00
Muki Kiboigo
3930524bbf use tokenizeAny instead of tokenizeScalar in Selector 2026-01-07 06:12:49 -08:00
Halil Durak
7ea0cdba36 update DomParser test 2026-01-07 14:37:44 +03:00
Halil Durak
612b3a26b7 allow other XML MIMEs in parseFromString 2026-01-07 14:37:44 +03:00
Halil Durak
56d89895a8 initial XML parsing support in DOMParser 2026-01-07 14:37:43 +03:00
Karl Seguin
21d502b81f Merge pull request #1326 from lightpanda-io/wp/mrdimidim/use-css-tokenizer
Use css tokenizer for parsing style attrs
2026-01-07 18:09:06 +08:00
Karl Seguin
dd3de6efea Merge pull request #1327 from lightpanda-io/zigdom-cdata-length
Fix `dom/nodes/CharacterData-appendData` WPT
2026-01-07 18:04:05 +08:00
Karl Seguin
d934fe6d4e Merge pull request #1330 from lightpanda-io/zigdom-element-get-by-class-name-fix
fix `dom/nodes/Element-getElementsByClassName` wpt
2026-01-07 18:02:50 +08:00
Karl Seguin
dab6345885 dispatch events with proper this 2026-01-07 17:57:34 +08:00
Karl Seguin
39874137d6 Merge pull request #1333 from lightpanda-io/attribute_removeNamedItem
add attribute.removeNamedItem
2026-01-07 17:47:33 +08:00
Karl Seguin
89f215c3ee Merge pull request #1332 from lightpanda-io/getElementById
getElementById duplicate-id handling
2026-01-07 17:47:19 +08:00
Karl Seguin
408d3f0a53 Track owning documents for nodes which aren't the default document
Track this in a lookup on the page, to avoid having to store a pointer for
_every_ node, given that most nodes _are_ owned by the document.

This helps us ensure nodes can be properly adopted.
2026-01-07 17:46:09 +08:00
Karl Seguin
a010684ce9 Add deprecated Node constants
Remove toString where the [new] auto-generated toString symbol works.

Reject node mutation on attributes.
2026-01-07 17:36:26 +08:00
Karl Seguin
a4a98da4a4 add attribute.removeNamedItem 2026-01-07 16:51:12 +08:00
Karl Seguin
6f30d459d5 getElementById duplicate-id handling
If 2 elements have the same id then,
1 - The first in document-order has to be retrieved. We were ordering by
    insertion order.

2 - When the element is removed, then document.getElementById should return the
    next element with that id in document-order. We were returning null
2026-01-07 15:49:17 +08:00
Muki Kiboigo
622ca3121f add case insensitivity support to selector parsing 2026-01-06 23:31:34 -08:00
Muki Kiboigo
71f27a55e1 fix duping of string for getElementsByClassName 2026-01-06 23:07:32 -08:00
Karl Seguin
c92903aae5 Merge pull request #1328 from lightpanda-io/set_outerHTML
add outerHTML setter
2026-01-07 15:04:14 +08:00
Karl Seguin
518e0aa07a add outerHTML setter 2026-01-07 14:49:30 +08:00
Nikolay Govorov
b908b0bf8a Used css tokenizer for parse html attributes 2026-01-07 05:46:18 +00:00
Muki Kiboigo
f9fa5be324 count utf8 codepoints for CData getLength 2026-01-06 21:29:15 -08:00
Karl Seguin
8ec6bb1577 Merge pull request #1322 from lightpanda-io/input_clone
Add 'clone' callback to build, implement for Input
2026-01-07 13:08:20 +08:00
Karl Seguin
70f8c53703 add deprecated properties to Event and improve initEvent 2026-01-07 12:05:11 +08:00
Karl Seguin
6d5a984413 Merge pull request #1323 from lightpanda-io/document_title
Improve document.title getter
2026-01-07 10:42:03 +08:00
Karl Seguin
5fa8fbc6f8 Merge pull request #1324 from lightpanda-io/elements_by_name_nodelist
getElementsByName now returns a NodeList rather than an HTMLCollection
2026-01-07 10:41:54 +08:00
Karl Seguin
7050d5fc68 Merge pull request #1325 from lightpanda-io/tokenlist_treewalker
support element.relList and improve TreeWalker
2026-01-07 10:41:42 +08:00
Karl Seguin
6af9d12f71 support element.relList and improve TreeWalker 2026-01-07 10:34:47 +08:00
Karl Seguin
a54e1db784 getElementsByName now returns a NodeList rather than an HTMLCollection
Auto-implement a toString accessor for any type that has a JsApi.Meta.name
2026-01-07 09:17:51 +08:00
Karl Seguin
2319b0fda5 Improve document.title getter
Collapse whitespace and find the first title, no matter where it is.
2026-01-07 07:52:20 +08:00
Karl Seguin
6864a22721 fix datetime-local input type 2026-01-07 07:39:42 +08:00
Karl Seguin
c9d0e2097d add input indeterminate accessor 2026-01-07 07:35:24 +08:00
Karl Seguin
d8f7eb3f24 Add 'clone' callback to build, implement for Input 2026-01-07 07:29:43 +08:00
Karl Seguin
90ee919f45 Merge pull request #1321 from lightpanda-io/event-init
set _time_stamp in the Event factory
2026-01-07 07:14:34 +08:00
Karl Seguin
ddc6431720 Merge pull request #1316 from lightpanda-io/reject_non_new_constructor
Reject constructors called as function (i.e. without 'new')
2026-01-07 07:14:13 +08:00
Pierre Tachoire
2ea6557fb7 add initEvent into Factory
and remove default value for Event._time_stamp
2026-01-06 15:30:13 +01:00
Karl Seguin
15358c1928 Improve Range, adding missing functions and more validation 2026-01-06 20:27:16 +08:00
Karl Seguin
d65025b3cb Merge pull request #1320 from lightpanda-io/fix-replaceChildren
remove children from previous parent
2026-01-06 19:07:15 +08:00
Pierre Tachoire
54fa3bc054 remove children from previous parent 2026-01-06 11:52:47 +01:00
Pierre Tachoire
68f5fa738c remove dead code Page._appendNode 2026-01-06 11:49:13 +01:00
Karl Seguin
2ea57ba979 update v8 dep 2026-01-06 18:30:25 +08:00
Karl Seguin
1acc0b0dc8 Merge pull request #1310 from lightpanda-io/zigdom-named-access
Named Access on the Window Object
2026-01-06 18:07:43 +08:00
Karl Seguin
645ec79fce access page from context, document call_depth usage 2026-01-06 18:04:17 +08:00
Karl Seguin
97e897e80e Merge pull request #1318 from lightpanda-io/node-self-replace
handle Node self replacement in insertBefore
2026-01-06 17:23:12 +08:00
Karl Seguin
6f72eeae65 Merge pull request #1319 from lightpanda-io/script_list_cleanup
Handle immediate call to Script.errorCallback
2026-01-06 17:22:14 +08:00
Karl Seguin
a845b2e35e Handle immediate call to Script.errorCallback
It's possible for Script.errorCallback to be called as part of the call to
`client.request`. This happens because we eagerly pump the libcurl message loop
to get the request going ASAP. For very obvious failures (e.g. an invalid URL)
this means that the error callback can be called from `client.request`.

Previously, we were only adding the script to its list _after_ the call to
`client.request`, but the error handler tries to remove the script from the list
.

This commit changes the order so that the script is first added to the list
and then the request is made.
2026-01-06 17:03:27 +08:00
Pierre Tachoire
b164ffeb95 handle Node self replacement in insertBefore 2026-01-06 09:49:08 +01:00
Karl Seguin
7ba34af884 Merge pull request #1317 from lightpanda-io/zigValueToJs-opts-pass-down
pass down opts to zigValueToJs
2026-01-06 16:36:44 +08:00
Pierre Tachoire
7f543ac7c8 pass down opts to zigValueToJs 2026-01-06 09:35:38 +01:00
Karl Seguin
a1bf92c07f Reject constructors called as function (i.e. without 'new')
Previously, `MessageEvent('')` would have been allowed, but invalid. This caused
problems as the receiver was the window. All such calls are now rejected.

Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/131
2026-01-06 16:03:37 +08:00
Karl Seguin
0b221615b7 Merge pull request #1315 from lightpanda-io/replaceChild-itself
fix Node.replaceChild when of new child equals old
2026-01-06 08:00:03 +08:00
Pierre Tachoire
f81a9b54a7 fix Node.replaceChild when of new child equals old 2026-01-05 21:48:59 +01:00
Muki Kiboigo
05da040ce1 increment call_depth on callWithThis 2026-01-05 09:27:17 -08:00
Muki Kiboigo
b911051842 add named access shadowing test 2026-01-05 09:08:57 -08:00
Muki Kiboigo
a67f46b550 add named access on the Window object 2026-01-05 08:41:42 -08:00
Karl Seguin
dcde19de3c Merge pull request #1309 from lightpanda-io/zigdom-anchor-click
Anchor Click
2026-01-05 12:30:30 +08:00
Muki Kiboigo
a8b4e8c1bc fix handleclick for hash href on anchor 2026-01-04 20:28:34 -08:00
Karl Seguin
7b0e256408 copy history test from legacy 2026-01-05 10:12:41 +08:00
Karl Seguin
5a974f0d77 Merge pull request #1282 from lightpanda-io/nikneym/performance-observer
Support `PerformanceObserver` API
2026-01-05 07:52:58 +08:00
Halil Durak
f7fe8d00fb add a PerformanceObserver test 2026-01-04 18:09:45 +03:00
Halil Durak
946b6d8226 PerformanceObserver changes 2026-01-04 18:09:28 +03:00
Karl Seguin
25366f0e47 Support multiple blocking scripts waiting for the same response
I thought we eliminated this from being possible, but I guess not. It seems to
be related to multiple async scripts direct or indirectly (or a specific
combination?) waiting for the same synchronous script. The first async script
to block, grabs it and deletes it, making it invalid for the next.

This puts back a counter on blocking scripts which I took out when I thought
this could no longer happen.
2026-01-02 20:57:29 +08:00
Karl Seguin
562e8e8d87 ignore empty pseudo_element in getComputedStyle 2026-01-02 16:47:04 +08:00
Karl Seguin
11ff9ed366 update log src for fetch (xhr -> fetch) 2025-12-30 17:08:27 +08:00
Halil Durak
9a9f2ab94b rm ring_buffer.zig 2025-12-30 11:46:58 +03:00
Karl Seguin
27048fb06d Merge pull request #1305 from lightpanda-io/nikneym/observer-lists
prefer `DoublyLinkedList` for storing `MutationObserver`s in `Page`
2025-12-30 16:43:40 +08:00
Halil Durak
e103ee1ffa prefer low priority queue to execute performance observer 2025-12-30 11:43:24 +03:00
Halil Durak
acebbb9041 don't prefer microtask queue for execution
This still needs investigation. Spec doesn't refer usage of microtask queue for this, yet the current behavior doesn't match to Firefox and Chrome.
2025-12-30 11:43:24 +03:00
Halil Durak
0264c94426 proper interested function 2025-12-30 11:42:21 +03:00
Halil Durak
88de72a9ea core performance observer logic
Heavily based on MutationObserver and IntersectionObserver.
2025-12-30 11:42:21 +03:00
Halil Durak
9306adc786 add an overwriting ring buffer implementation 2025-12-30 11:42:20 +03:00
Halil Durak
43c30f8a34 avoid inline + don't initialize node 2025-12-30 11:39:09 +03:00
Karl Seguin
7c7240d5ab Try to protect against invalid use of document.write
Specifically, try to block multiple document.write which, when combined, have
multiple html documents.
2025-12-30 10:07:56 +08:00
Karl Seguin
169582c992 DOMRect constructor
More default computed styles (color and backgroundColor)

HTMLMetaElement properties

Case-insensitive findAdjacentNodes position parameter

Allow computedStyle pseudo_element parameter (ignore, log not implemented)

Window.isSecureContext always returns false
2025-12-30 09:33:00 +08:00
Karl Seguin
7b74161e9c Merge pull request #1270 from lightpanda-io/wp/mrdimidium/css-parsing
CSS parsing
2025-12-30 07:05:56 +08:00
Karl Seguin
633e98c8f4 Merge pull request #1306 from lightpanda-io/generic-tags
add more generic tags
2025-12-30 07:01:52 +08:00
Pierre Tachoire
5743c4fc93 add more generic tags 2025-12-29 18:15:02 +01:00
Nikolay Govorov
9984b3445f Add css tokenazer for parse style attribute 2025-12-29 15:06:07 +00:00
Pierre Tachoire
90a7e96181 Merge pull request #1301 from lightpanda-io/backport-zig-versions
update ci scripts
2025-12-29 16:00:36 +01:00
Pierre Tachoire
00d4ac6137 update ci scripts
* use checkout v6
* remove useless target from Makefile
2025-12-29 15:17:19 +01:00
Halil Durak
ee432c54b8 prefer DoublyLinkedList for storing MutationObservers in Page 2025-12-29 16:18:09 +03:00
Pierre Tachoire
76ec3eb738 Merge pull request #1303 from lightpanda-io/Makefile
build: standardize ansi escape sequences in makefile
2025-12-29 13:42:02 +01:00
Pierre Tachoire
37832c63a4 Merge pull request #1302 from lightpanda-io/backport-graceful-shutdown
Add a synchronous signal handler for graceful shutdown
2025-12-29 13:41:47 +01:00
Pierre Tachoire
d1c33f0872 build: standardize ansi escape sequences in makefile 2025-12-29 12:55:56 +01:00
Pierre Tachoire
4684b8611d Add a synchronous signal handler for graceful shutdown 2025-12-29 12:43:52 +01:00
Karl Seguin
f4961ee8b2 Merge pull request #1299 from lightpanda-io/cdp-inserttext
backport cdp Input.insertText
2025-12-29 19:11:46 +08:00
Pierre Tachoire
27f6f4243f Apply suggestions from code review
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2025-12-29 12:08:07 +01:00
Karl Seguin
dcf1d34889 Merge pull request #1292 from lightpanda-io/nikneym/script-execution-changes
Run microtasks after each script execution
2025-12-29 18:42:03 +08:00
Pierre Tachoire
76f30dc985 zig fmt 2025-12-29 11:40:32 +01:00
Pierre Tachoire
2d6c37fa6f handle input selection when keydown 2025-12-29 11:40:32 +01:00
Pierre Tachoire
3e52abf471 cdp: add input.insertText 2025-12-29 11:40:27 +01:00
Pierre Tachoire
d697944b5a add Input.select() 2025-12-29 10:35:26 +01:00
Pierre Tachoire
cf14b9e762 add Document.hasFocus placeholder 2025-12-29 10:35:05 +01:00
Pierre Tachoire
121cf40062 Merge pull request #1291 from lightpanda-io/docker-update
Docker update for zigdom
2025-12-29 10:01:00 +01:00
Halil Durak
abc89b7eae run tasks after microtasks
Also removes `page.tick`.
2025-12-29 11:04:22 +03:00
Karl Seguin
dc33c4d5fd improve console.log output when using logfmt 2025-12-29 12:58:20 +08:00
Karl Seguin
087086c308 remove some unused imports 2025-12-26 12:40:20 +08:00
Karl Seguin
05cb5221d4 Quick-check sameness in Node.isEqualNode
Exclusively use the not_implemented log filter.
2025-12-26 09:57:33 +08:00
Karl Seguin
0fff379ee0 dummy createAttributeNS 2025-12-26 09:30:54 +08:00
Karl Seguin
0c23818470 Merge branch 'zigdom-history-fixes' into zigdom 2025-12-26 09:19:30 +08:00
Karl Seguin
25dbac9945 event isTrusted support and better composedPath for shadowroots 2025-12-26 08:45:57 +08:00
Karl Seguin
b379b775f9 Merge pull request #1296 from lightpanda-io/v8-json-parser
Backport: Use V8 to parse JSON with fetch/xhr
2025-12-25 20:48:38 +08:00
Pierre Tachoire
7cc2c2344e use V8 json parser with xhr/fetch webAPIs
The pure zig JSON parser didn't generate the same type of values than JS
JSON.parse command.
Using directly V8's JSON parser gives the assurance to have the right
JS types.
Moreover, it avoid data transformations between Zig and V8.
2025-12-25 12:50:45 +01:00
Pierre Tachoire
d50f6b830a add Value.persist 2025-12-25 12:50:44 +01:00
Pierre Tachoire
8f2921f61f add test for big json number with fetch/xhr 2025-12-25 12:50:44 +01:00
Karl Seguin
e9ec089f76 legacy keyboard and mouse events 2025-12-25 18:52:34 +08:00
Karl Seguin
dca99c338e more Animation accessors 2025-12-25 17:00:27 +08:00
Karl Seguin
cc3a498294 legacy tests 2025-12-25 16:47:08 +08:00
Karl Seguin
c88cb35b84 add AbstractRange 2025-12-25 13:15:57 +08:00
Karl Seguin
8be7a9f2bc more legacy test fixes 2025-12-25 11:39:32 +08:00
Karl Seguin
899567328e more legacy test tweaks (mostly around CSS) 2025-12-25 10:02:04 +08:00
Karl Seguin
9f3cb4349d more legacy test fixes 2025-12-25 09:08:01 +08:00
Karl Seguin
b2b890b8b1 Merge pull request #1294 from lightpanda-io/zigdom-history-scroll-restoration
backport `ScrollRestoration` to `History`
2025-12-25 07:40:33 +08:00
Karl Seguin
f266dbc171 remove unecessary task execution in legacy_tests 2025-12-25 07:35:02 +08:00
Halil Durak
b28ac8ca19 run microtasks after each script execution
This don't change the behavior for async and deferred scripts.

just run microtasks after a script execution
2025-12-24 21:50:44 +03:00
Muki Kiboigo
248ce4f1a8 add .skip.html to skip files in legacy tests 2025-12-24 09:04:33 -08:00
Muki Kiboigo
872ec33662 use scheduleNavigation instead of navigate 2025-12-24 09:04:33 -08:00
Muki Kiboigo
b3e6186c78 history tests pass without crash 2025-12-24 09:04:33 -08:00
Muki Kiboigo
a31497937b use session arena instead of storing arena in Navigation 2025-12-24 09:04:25 -08:00
Karl Seguin
90088c5d7c Merge pull request #1290 from lightpanda-io/zigdom_request_interception
Zigdom request interception
2025-12-25 00:40:48 +08:00
Muki Kiboigo
4c8abd4680 add scrollRestoration to History 2025-12-24 07:46:34 -08:00
Karl Seguin
a25fb4a8e4 re-enable crashing legacy tests 2025-12-24 18:41:44 +08:00
Karl Seguin
29efb467f0 Various input fixes (support for more attributes) based on legacy tests
AbortSignal.timeout function

LocalStorage named getter/setter
2025-12-24 18:36:46 +08:00
Pierre Tachoire
ffe2bc9a02 update README 2025-12-24 10:35:11 +01:00
Pierre Tachoire
8105dff167 remove useless step from README 2025-12-24 10:04:30 +01:00
Pierre Tachoire
8d992d74c0 update Dockerfile for zigdom 2025-12-24 10:04:10 +01:00
Karl Seguin
296fa2a2f4 Update src/http/Client.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-12-24 16:37:16 +08:00
Karl Seguin
a9e6051867 HTMLElement.click() 2025-12-24 16:17:17 +08:00
Karl Seguin
0fcb316837 Move HTML-specific behavior from Element to HTMLElement. 2025-12-24 16:17:17 +08:00
Karl Seguin
c0704f822b Merge pull request #1288 from lightpanda-io/pseudo-frames
backport frames access from Window
2025-12-24 15:51:41 +08:00
Pierre Tachoire
ba974f695d use a better comparison 2025-12-24 08:46:46 +01:00
Karl Seguin
3ca82b9ab5 Tweak CSS
Give default styles to visibility properties. Unblocks various playwright
behavior
2025-12-24 15:04:06 +08:00
Karl Seguin
df4e5d859f Enable blocking auth request interception 2025-12-24 12:19:11 +08:00
Karl Seguin
67875036c5 Rework request interception for Zigdom
Zigdom broke request interception. It isn't zigdom specifically, but in zigdom
we properly block the parser when executing a normal (not async, not defer)
script. This does not work well with request interception, because an
intercepted request isn't blocked on HTTP data, it's blocked on a message from
CDP. Generally, neither our Page nor ScriptManager are CDP-aware. And, even if
they were, it would be hard to break out of our parsing and return control to
the CDP server.

To fix this, we expand on the HTTP Client's basic awareness of CDP (via its
extra_socket field). The HTTP client is now able to block until an intercepted
request is continued/aborted/fulfilled. it does this by being able to ask the
CDP client to read/process data.

This does not yet work for intercepted authentication requests.
2025-12-24 11:49:05 +08:00
Karl Seguin
83f008de1f Correctly handle setting textContent to empty for DocFrag and Element
Fixes an [non-critical] error on old.reddit.com
2025-12-24 11:43:43 +08:00
Karl Seguin
7183b0339b fix crash on amazon product page 2025-12-24 08:00:26 +08:00
Karl Seguin
9969ff7165 implement html5ever append_based_on_parent_node and append_before_sibling 2025-12-24 07:37:44 +08:00
Karl Seguin
0ca97d01ac Merge pull request #1287 from lightpanda-io/window.scrollTo
Add Window.scrollTo
2025-12-24 07:16:13 +08:00
Karl Seguin
fc4dbb6184 Merge pull request #1286 from lightpanda-io/zigdom-single-build
Single Build Command
2025-12-24 07:09:59 +08:00
Karl Seguin
9b16212d4b Merge pull request #1285 from lightpanda-io/base_url
implement base_url
2025-12-24 07:09:39 +08:00
Pierre Tachoire
4d67cfa340 backport frames access from Window 2025-12-23 17:14:34 +01:00
Pierre Tachoire
2bd38608e9 throttle scroll event 2025-12-23 16:13:02 +01:00
Karl Seguin
6ce117e5fa Add padding to DOMImplementation to prevent ptr collision with other empty types 2025-12-23 21:36:27 +08:00
Pierre Tachoire
2b10b1c17a webapi: add window.scrollTo 2025-12-23 12:07:07 +01:00
Karl Seguin
bbf58a2807 Move page out of arena so that the arena can be reset between navigates 2025-12-23 16:26:28 +08:00
Pierre Tachoire
44ffcaeed8 fix legacy test expected port 2025-12-23 08:44:25 +01:00
Pierre Tachoire
a597d31505 set page base_url during HTML parsing 2025-12-23 08:44:24 +01:00
Pierre Tachoire
6dbd008724 page: use optional base_url to resolve urls 2025-12-23 08:44:24 +01:00
Pierre Tachoire
7d47f8623a webapi: add Node.baseURI accessor 2025-12-23 08:18:06 +01:00
Karl Seguin
7c755483b1 Register HTMLImageElement name.
Handle DOMParser with empty string

This gets DDG results working.
2025-12-23 14:33:47 +08:00
Karl Seguin
e387e005d8 try to improve page re-navigate (reset) memory usage 2025-12-23 12:32:16 +08:00
Muki Kiboigo
c9f6cb7520 fix single build with rust in ci 2025-12-22 10:41:22 -08:00
Muki Kiboigo
596ee82a52 zig build builds everything 2025-12-22 09:57:34 -08:00
Karl Seguin
79b62e0dfc Merge pull request #1284 from lightpanda-io/fix-page-navigate
Fix page navigate with legacy_test
2025-12-22 22:58:41 +08:00
Karl Seguin
e67cf21917 quick fix for segfault 2025-12-22 22:52:41 +08:00
Pierre Tachoire
8fb1c3971c fix page.navigate into legacy_test and wpt 2025-12-22 15:39:46 +01:00
Karl Seguin
437df18a07 form submitt 2025-12-22 19:45:29 +08:00
Karl Seguin
8215f2fd8f Merge branch 'snapshots_v2' into zigdom 2025-12-22 17:03:38 +08:00
Karl Seguin
af7f51a647 start handling page clicks and key presses 2025-12-22 17:02:20 +08:00
Karl Seguin
3ab09d87f2 Update src/browser/js/ExecutionWorld.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-12-22 16:44:55 +08:00
Karl Seguin
4c1d82162f Update src/browser/js/Snapshot.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-12-22 16:44:49 +08:00
Karl Seguin
3830e2610b Update src/browser/js/Snapshot.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-12-22 16:44:42 +08:00
Karl Seguin
e3265d400e Update src/browser/js/Env.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-12-22 16:44:33 +08:00
Karl Seguin
d9c53a3def Page.scheduleNavigation for location changes 2025-12-22 12:19:08 +08:00
Karl Seguin
da32440a14 pass IdleDeadline to idle callback 2025-12-21 18:26:54 +08:00
Karl Seguin
25ad3559f7 Add Document.gettype 2025-12-21 17:13:36 +08:00
Karl Seguin
8fbd64955f Dynamically added scripts default to async 2025-12-21 16:51:39 +08:00
Karl Seguin
32c83d166d implement html5ever createPI callback 2025-12-21 16:04:59 +08:00
Karl Seguin
d95b19d31b update CI zig-v8-fork version, improve XHR state management 2025-12-21 15:26:26 +08:00
Karl Seguin
9e62e72d1f Merge branch 'fix_ci' into zigdom 2025-12-19 21:36:15 +08:00
Karl Seguin
29259c23d7 update zig-v8-fork version 2025-12-19 21:36:09 +08:00
Karl Seguin
3d6af216dc document.write, document.close, document.open
Add support for both modes - parsing and post-parsing. In post-parsing mode,
document.write implicitly calls document open, and document.open wipes the
document. This mode is probably rarely, if ever, used.

However, while parsing, document.write does not call document.open and does not
remove all existing nodes. It just writes the html into the document where the
parser is. That isn't something we can properly do..but we can hack it. We
create a new DocumentFragment, parse the html into the document fragment, then
transfer the children into the document where we currently are.

Our hack probably doesn't work for some advance usage of document.write (e.g
nested calls), but it should work for more common cases, e.g. injecting a script
tag.
2025-12-19 21:29:28 +08:00
Karl Seguin
f475aa09e8 backport https://github.com/lightpanda-io/browser/pull/1265 2025-12-19 16:06:25 +08:00
Pierre Tachoire
1278dc28cd cdp: add accessibility domain 2025-12-19 10:34:41 +08:00
Pierre Tachoire
33ee2fb1a0 ci: use macos-14-intel for building macos x86
macos-13 is unsupported. We Have to switch for payed instance.
see https://github.com/actions/runner-images/issues/13046
2025-12-19 10:33:42 +08:00
Pierre Tachoire
2ac90262b7 ci: add nightly integration test 2025-12-19 10:32:39 +08:00
Karl Seguin
bb1ea39c54 backport a variety of smaller CDP changes 2025-12-19 10:31:07 +08:00
Pierre Tachoire
a087386af3 cdp: implement DOM.requestNode 2025-12-19 10:15:21 +08:00
Pierre Tachoire
fe96bc7895 cdp: use default value for grantUniveralAccess
In createIsolatedWorld, we set  a default value to false for optional
grantUniveralAccess parameter.
2025-12-19 10:10:41 +08:00
Pierre Tachoire
7a69e3fc9b cdp: add browser permissions noop 2025-12-19 10:07:04 +08:00
Karl Seguin
566fa72bcd various small backports from main 2025-12-19 10:05:42 +08:00
Karl Seguin
520e197e0e build html5ever in CI 2025-12-19 08:25:22 +08:00
Karl Seguin
c15ef590c2 build html5ever in CI 2025-12-19 08:16:36 +08:00
Karl Seguin
098eeea8f7 remove some mimalloc, netsurf and iconv references 2025-12-19 07:18:47 +08:00
Karl Seguin
c3f8f9de54 merge https://github.com/lightpanda-io/browser/pull/1275 2025-12-18 21:17:13 +08:00
Karl Seguin
ba4900b61f import template parsing test from 'legacy' 2025-12-18 21:14:41 +08:00
Karl Seguin
3e03f7559f Document log_filter_scope argument
Add fetch logging
2025-12-18 20:48:14 +08:00
Karl Seguin
46f8a11339 Merge pull request #1277 from lightpanda-io/zigdom-ui-events
`UIEvent`, `MouseEvent` and `KeyboardEvent`
2025-12-18 20:26:42 +08:00
Karl Seguin
b3a0aaaeea Enable v8 snapshots
There are two layers here. The first is that, on startup, a v8 SnapshotCreator
is created, and a snapshot-specific isolate/context is setup with our browser
environment. This contains most of what was in Env.init and a good chunk of
what was in ExecutionWorld.createContext. From this, we create a v8.StartupData
which is used for the creation of all subsequent contexts. The snapshot sits
at the application level, above the Env - it's re-used for all envs/isolates, so
this gives a nice performance boost for both 1 connection opening multiple pages
or multiple connections opening 1 page.

The second layer is that the Snapshot data can be embedded into the binary, so
that it doesn't have to be created on startup, but rather created at build-time.
This improves the startup time (though, I'm not really sure how to measure that
accurately...).

The first layer is the big win (and just works as-is without any build / usage
changes).

with snapshot
total runs 1000
total duration (ms) 7527
avg run duration (ms) 7
min run duration (ms) 5
max run duration (ms) 41

without snapshot
total runs 1000
total duration (ms) 9350
avg run duration (ms) 9
min run duration (ms) 8
max run duration (ms) 42

To embed a snapshot into the binary, we first need to create the snapshot file:

zig build -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin

And then build using the new snapshot_path argument:

zig build -Dsnapshot_path=../../snapshot.bin -Doptimize=ReleaseFast

The paths are weird, I know...since it's embedded, it needs to be inside the
project path, hence we put it in src/snapshot.bin. And since it's embedded
relative to the embedder (src/browser/js/Snapshot.zig) the path has to be
relative to that, hence ../../snapshot.bin. I'm open to suggestions on
improving this.
2025-12-18 20:10:38 +08:00
Karl Seguin
aa5e71112e v8 symbol -> []const support 2025-12-18 11:59:30 +08:00
Pierre Tachoire
22303d2ae8 Merge pull request #1236 from lightpanda-io/v8-build-with-zig-gclient-ci
V8 build with zig gclient ci
2025-12-18 11:55:55 +08:00
Muki Kiboigo
9dbfac02b2 add KeyboardEvent 2025-12-17 14:45:36 -08:00
Muki Kiboigo
6f43d9979d add MouseEvent 2025-12-17 14:11:49 -08:00
Muki Kiboigo
d63a045534 proper UIEvent 2025-12-17 11:51:55 -08:00
Muki Kiboigo
fe2d309d33 begin UIEvent 2025-12-17 11:49:04 -08:00
Karl Seguin
94ca2c41e4 Element.slot, Element.assignedSlot and slotchange event 2025-12-17 07:42:29 +08:00
Karl Seguin
8873e613d2 improve domexception 2025-12-16 19:16:42 +08:00
Karl Seguin
761b35b199 zig fmt 2025-12-16 17:54:14 +08:00
Karl Seguin
8a2641d213 fetch/request/response improvement (legacy) 2025-12-16 17:54:05 +08:00
Karl Seguin
e47091f9a1 legacy for request/response/fetch 2025-12-16 16:24:49 +08:00
Karl Seguin
ea399390ef Improve DOMImplementation, DocumentType and DOMException 2025-12-16 14:58:36 +08:00
Karl Seguin
d26869278f dummy HTMLCanvasElement 2025-12-16 11:13:57 +08:00
Karl Seguin
1639ff1b98 improve XMLHTTPRequest. Legacy xhr.html pass 2025-12-15 17:56:23 +08:00
Karl Seguin
9b3107d4fe build FormData from optional form and optional submitter 2025-12-15 12:31:30 +08:00
Karl Seguin
4bebc4c142 simplify / standardized how HTMLFormControlsCollection 'inherits' from HTMLCollection 2025-12-15 10:35:41 +08:00
Karl Seguin
ac0601b141 add RadioNodeList 2025-12-15 10:31:44 +08:00
Karl Seguin
6040cd3338 improve Form, notably form.elements 2025-12-14 20:02:39 +08:00
Karl Seguin
f93403d3dc Remove thread local
Rework node.isConnected(), this now [correctly] returns true as long as a node
is part of _a_ document (it doesn't have to be the 'main' document). This
requires changes around id lookup optimization.
2025-12-14 16:16:54 +08:00
Karl Seguin
82cd5d4bab fix legacy intersection observer test 2025-12-13 21:23:16 +08:00
Karl Seguin
0d3055716e tweak timing of intersection observer and how it handles disconnected nodes 2025-12-13 20:33:43 +08:00
Karl Seguin
c9b4067686 Event listener can now be an object with a handleEvent function 2025-12-13 17:19:53 +08:00
Karl Seguin
52dcc6765a URLSearchParams from FormData 2025-12-13 12:47:54 +08:00
Karl Seguin
eab328e2b5 Tweak URL, refactor Anchor and URL to share more common code 2025-12-12 21:50:13 +08:00
Karl Seguin
23146f64ab Screen and ScreenOrientation (legacy) 2025-12-12 18:21:30 +08:00
Karl Seguin
a6d3a3d0ab Add properties to HTMLStyleelement 2025-12-12 18:01:12 +08:00
Karl Seguin
5eb54bbc95 Media/Audio/Video elements 2025-12-12 17:34:57 +08:00
Karl Seguin
a4fa40743a ErrorEvent error as undefined 2025-12-12 07:58:34 +08:00
Karl Seguin
6d8c6a947e Merge pull request #1271 from lightpanda-io/zigdom-event-opts-inherit
Inherit Prototype Event Options
2025-12-12 07:26:10 +08:00
Karl Seguin
13cf0096ad Merge pull request #1272 from lightpanda-io/zigdom-remove-mbedtls
Properly remove mbedtls from `zigdom`
2025-12-12 07:22:21 +08:00
Muki Kiboigo
bd0f1d2884 remove mbedtls stuff for build.zig 2025-12-11 12:25:34 -08:00
Muki Kiboigo
5671580c2d properly remove mbedtls submodule 2025-12-11 12:25:25 -08:00
Muki Kiboigo
669c934ae0 Event Options dont need to be pub 2025-12-11 12:17:07 -08:00
Muki Kiboigo
b568eb4e1e migrate events to use new inheritOptions 2025-12-11 12:14:00 -08:00
Muki Kiboigo
4d8d6c10c6 add option inheriting for Events 2025-12-11 12:13:01 -08:00
muki
3667fbc49e Merge pull request #1253 from lightpanda-io/zigdom-navigation
Backport Navigation (and friends)
2025-12-11 12:01:57 -08:00
Karl Seguin
269c880ee0 Merge pull request #1246 from lightpanda-io/nikneym/is-equal-node
Support `isEqualNode`
2025-12-11 21:04:23 +08:00
Halil Durak
fe89aad621 add isEqualNode
rework `isEqualNode`

Splits equality logic by node types and groups comparisons nicer.
prefer ancestor's`isEqualNode`

`nodeType` => `getNodeType`

fix attribute comparison logic

Also introduces attribute counting.

remove debug logging

add `isEqualNode` test
2025-12-11 15:55:33 +03:00
Karl Seguin
38fb5b101e add Document.elementFromPoint and elementsFromPoint 2025-12-11 19:49:51 +08:00
Karl Seguin
3d8b1abda4 More legacy tests
Largely around how URL attributes (a.href, img.href, link.href) handle empty
values.
2025-12-11 16:45:19 +08:00
Karl Seguin
0b141e44ae Merge pull request #1267 from lightpanda-io/blob
port remaining blob functionality
2025-12-11 15:31:11 +08:00
Karl Seguin
695ed817e4 port remaining blob functionality 2025-12-11 15:30:43 +08:00
Karl Seguin
f0d9d53588 Merge pull request #1268 from lightpanda-io/nikneym/zigdom-boringssl
backport: Prefer BoringSSL as TLS backend
2025-12-11 15:28:10 +08:00
Karl Seguin
471e94d58e Merge pull request #1269 from lightpanda-io/nikneym/zigdom-fix-kludge-hack
backport: Remove `_TYPED_ARRAY_ID_KLUDGE` hack
2025-12-11 15:27:57 +08:00
Halil Durak
7b6776345a backport: Remove _TYPED_ARRAY_ID_KLUDGE hack
Bonus: Add `ArrayBuffer`.
2025-12-11 10:26:13 +03:00
Karl Seguin
68763d9a30 speed up tests 2025-12-11 15:23:39 +08:00
Halil Durak
bead805680 backport: Prefer BoringSSL as TLS backend 2025-12-11 10:19:07 +03:00
Karl Seguin
34f0857b4f Element legacy test passing 2025-12-11 12:51:56 +08:00
Karl Seguin
b25e46de2e zig fmt 2025-12-11 11:48:09 +08:00
Karl Seguin
86ae004825 new Comment(?[]const u8) 2025-12-11 07:41:08 +08:00
Karl Seguin
a355d9e517 Handle infinitely recursive mutation observer
FireFox hangs in these cases, but we'd rather handle it gracefully.
2025-12-11 07:13:59 +08:00
Karl Seguin
61aca85632 Pass Headers legacy tests 2025-12-10 18:43:24 +08:00
Karl Seguin
159165490d Allow event listener to remove itself or other pending listeners 2025-12-10 17:56:49 +08:00
Karl Seguin
9c8299f13f Change to linear scaling for renderer.
With the previous exponential approach, a deep site (the deepest element in
amazon's product page is 36 levels deep) would be unrealistic.
2025-12-10 16:39:27 +08:00
Karl Seguin
27e58181fb Properly resolve inspector ObjectId back to a DOM Node
Tweak element boundingRect and "renderer" based on what puppeteer needs.
2025-12-10 15:44:08 +08:00
Muki Kiboigo
02a0727870 eqlDocument slicing at hash 2025-12-09 17:11:04 -08:00
Muki Kiboigo
7c9d7259e6 add NavigationActivation 2025-12-09 17:11:04 -08:00
Muki Kiboigo
ddb83cf9c5 add assert and note on getCurrentEntry 2025-12-09 17:11:04 -08:00
Muki Kiboigo
3662d1681e no need to run microtasks before onload 2025-12-09 17:11:04 -08:00
Muki Kiboigo
6534dc4c4f use Navigation ptr instead of fat copy 2025-12-09 17:11:04 -08:00
Muki Kiboigo
395f93240d minor Navigation style changes 2025-12-09 17:11:03 -08:00
Muki Kiboigo
ac85341cab add NavigationKind to navigate 2025-12-09 17:10:59 -08:00
Muki Kiboigo
01d71323fc complete History impl backed by Navigation 2025-12-09 16:51:05 -08:00
Muki Kiboigo
ee7852665e fix GPL headers 2025-12-09 16:51:05 -08:00
Muki Kiboigo
9d7b80c1ac backport Location getHash 2025-12-09 16:51:05 -08:00
Muki Kiboigo
907298c6b1 backport pageshow event 2025-12-09 16:51:04 -08:00
Muki Kiboigo
cc53fec08d backport run microtasks before firing onload 2025-12-09 16:51:04 -08:00
Muki Kiboigo
ab165d3f1f getNavigationType return string 2025-12-09 16:51:04 -08:00
Muki Kiboigo
7c34cb5852 fix getState on NavigationHistoryEntry 2025-12-09 16:51:04 -08:00
Muki Kiboigo
71d57c1e27 add Navigation to Window 2025-12-09 16:51:04 -08:00
Muki Kiboigo
6a5e088c52 update wpt to include Navigation 2025-12-09 16:51:04 -08:00
Muki Kiboigo
8ec9f634b4 backport URL eqlDocument tests 2025-12-09 16:51:04 -08:00
Muki Kiboigo
0e4cfbfe6b backport the resolve/stitch regression test 2025-12-09 16:51:03 -08:00
Muki Kiboigo
370c3a49a7 initial Navigation 2025-12-09 16:51:01 -08:00
Pierre Tachoire
a7e0110acb Merge pull request #1260 from lightpanda-io/cdp-lifecycle-backport
support url on createTarget and send lifecycle events
2025-12-09 13:35:45 +01:00
Pierre Tachoire
3769715582 Page.reset msut create context and window once 2025-12-09 12:24:01 +01:00
Pierre Tachoire
0d8dd84df5 support url on createTarget and send lifecycle events
Support url parameter on createTarget. we now navigate on createTarget
to dispatch events correctly, even in case of about:blank
2025-12-09 11:29:00 +01:00
Karl Seguin
e98bb16255 Merge pull request #1259 from lightpanda-io/cdp-security-ignore-cert-err-backport
cdp: implement Security.setIgnoreCertificateErrors
2025-12-09 17:58:14 +08:00
Pierre Tachoire
6a098665fa http: remove inflight conn check when enable/disable TLS 2025-12-09 10:47:34 +01:00
Karl Seguin
47b4b68e60 add parsed DocType to document (and handle dumping it) 2025-12-09 16:38:56 +08:00
Pierre Tachoire
53ccefc15c cdp: implement Security.setIgnoreCertificateErrors
ensure no inflight conns is running before set TLS verify
2025-12-09 08:50:58 +01:00
Karl Seguin
0e1b966dce re-enable CDP dom domain 2025-12-09 13:04:01 +08:00
Karl Seguin
9132bc2375 re-enable CDP node registry 2025-12-09 11:50:33 +08:00
Karl Seguin
49c0e95664 Merge pull request #1242 from lightpanda-io/nikneym/element-apis
add `insertAdjacentHTML` and other variants
2025-12-09 06:52:48 +08:00
Karl Seguin
97e920b68f Merge pull request #1252 from lightpanda-io/ignore-comment
improve a little bit Element.innerText
2025-12-09 06:49:55 +08:00
Pierre Tachoire
3538c77b78 innerText: ignore CDATA section 2025-12-08 17:45:53 +01:00
Halil Durak
4bc4b2aeac Merge pull request #1245 from lightpanda-io/nikneym/performance-api-changes
Performance API changes
2025-12-08 17:59:21 +03:00
Pierre Tachoire
38030c7d21 improve Element.innerText formatting 2025-12-08 15:56:18 +01:00
Pierre Tachoire
e74d45d6c2 implement Script.innerText 2025-12-08 15:54:19 +01:00
Karl Seguin
0479813494 add CDATASection 2025-12-08 22:09:15 +08:00
Karl Seguin
ef3ba13979 Add keys/values/entries/forEach/toString to DOMTokenList 2025-12-08 21:31:58 +08:00
Halil Durak
f82cfca2ee support overloads of Performance#measure
`init1` => `init`

support `navigationStart` in `getMarkTime`

Turns out this is just an alias to 0.

prefer `self` instead of `page.window._performance`
2025-12-08 16:26:49 +03:00
Karl Seguin
4b204265c9 handle error in normal scripts 2025-12-08 19:01:59 +08:00
Karl Seguin
fbb37633f0 Add start/cancel/fill/strategy support for ReadableStream 2025-12-08 18:24:15 +08:00
Karl Seguin
09328aeb5a Merge pull request #1248 from lightpanda-io/ignore-comment
textContent and innerText adjustements
2025-12-08 17:58:05 +08:00
Pierre Tachoire
5284d75cb7 use CData.render for innerText 2025-12-08 09:55:31 +01:00
Pierre Tachoire
aac35ae868 implement CData.render 2025-12-08 09:41:06 +01:00
Karl Seguin
65751a69ae document.cookie 2025-12-08 16:28:31 +08:00
Karl Seguin
121c49e9c3 Remove std.Uri from cookies
Everything now works on a [:0]const u8, with browser/URL.zig for parsing
2025-12-08 16:23:19 +08:00
Karl Seguin
0beae3b1a6 Various legacy document tests
document.embeds, document.plugins, document.anchor, document.getElementsByName

getElementsByClassName support for multiple class names

various document getters
2025-12-08 14:22:24 +08:00
Arjun Komath
57ce4e16a9 feat: support listening on ipv6 2025-12-08 09:08:57 +08:00
Karl Seguin
9370e298d2 improve HTMLOption and HTMLOptionCollection 2025-12-08 09:07:56 +08:00
Pierre Tachoire
240e8b3502 use a better comparison to detect comment 2025-12-07 09:52:59 +01:00
Karl Seguin
eecadb3962 Merge pull request #1249 from lightpanda-io/comment-format
fix comment formatting
2025-12-07 07:10:38 +08:00
Pierre Tachoire
08d7f544dd fix comment formatting 2025-12-06 19:15:24 +01:00
Pierre Tachoire
a673eb89b6 element: innerText which must return rendered text 2025-12-06 19:10:16 +01:00
Pierre Tachoire
f5d3dede6b node: textContent must ignore comments for elements 2025-12-06 19:10:16 +01:00
Karl Seguin
e41d53019f CompositionEvent 2025-12-05 18:18:54 +08:00
Karl Seguin
637a105e5d getRootNode composed support 2025-12-05 18:11:45 +08:00
Karl Seguin
8e16c587c8 encode property as u32 whenever possible 2025-12-05 18:02:27 +08:00
Karl Seguin
1cde0bb8b8 preserve casing (tags/attributes) for SVG/XML/MathML namespace 2025-12-05 17:53:40 +08:00
Karl Seguin
61a1a2564e Fix typos
Encode unicode nonbreaking space
2025-12-05 17:48:49 +08:00
Karl Seguin
dd3781a1ea Higher performance.now() precision (closer to FFs behavior)
Much better v8 object debugging/printing in debug mode

Window.requestIdleCallback and cancelIdleCallback

Don't prematurely close stream on empty read - queue promises.
2025-12-05 16:09:00 +08:00
Karl Seguin
ff9f9bae1d fetch with body 2025-12-04 16:10:56 +08:00
Karl Seguin
aa3a402f70 Link get/set rel
Include stack trace on console.error

Don't unnecessarily copy request header on fetch
2025-12-04 15:38:47 +08:00
Karl Seguin
c9882e10a4 Properly handle insertion of DocumentFragment
Add various CData methods

XHR and Fetch request headers

Animation mocks
2025-12-04 14:40:22 +08:00
Karl Seguin
7cb06f3e58 MediaError and :scope pseudoclass 2025-12-03 22:30:08 +08:00
Karl Seguin
60c1f19581 add TextTrackCue and VTTCue (for reddit) 2025-12-03 20:04:07 +08:00
Halil Durak
b6420f75e2 add insertAdjacentText test 2025-12-03 13:59:37 +03:00
Halil Durak
45e74d3336 add insertAdjacentElement and insertAdjacentHTML tests 2025-12-03 13:59:37 +03:00
Halil Durak
dc040dfc37 add insertAdjacentElement and insertAdjacentText 2025-12-03 13:59:37 +03:00
Halil Durak
9071d98cbe port insertAdjacentHTML 2025-12-03 13:59:33 +03:00
Karl Seguin
74ffc273ef Add stack & line number to script eval failure 2025-12-03 18:53:25 +08:00
Karl Seguin
2a4cbbe569 Performance.measure 2025-12-03 18:24:28 +08:00
Karl Seguin
63eeadad1d Fix comment dump, improve dump of shadowroot and slots 2025-12-03 16:10:11 +08:00
Karl Seguin
2de0d4bc48 Header case insensitive 2025-12-03 09:59:55 +08:00
Karl Seguin
c0da6994da Element.setInnerText 2025-12-03 08:52:51 +08:00
Karl Seguin
568a4428ba custom element registry 'whenDefine' function 2025-12-02 22:19:58 +08:00
Karl Seguin
4823e0b188 Merge branch 'unknown_property_ignore_list' into zigdom 2025-12-02 16:06:48 +08:00
Karl Seguin
0690dd9550 Merge branch 'performance_mark' into zigdom 2025-12-02 16:06:19 +08:00
Karl Seguin
b5eceb52fb try safer http cleanup on page deinit 2025-12-02 16:05:57 +08:00
Karl Seguin
c90e9c165b add Performance.Mark 2025-12-02 15:13:55 +08:00
Karl Seguin
a61e87c5dd Don't break wait on scheduler callback error
Allow recursive parsing
2025-12-02 13:25:48 +08:00
Karl Seguin
abd3ee9c5d Add ignore list for unkown global property
This is for often-seen globals which we _know_ come from client-side libraries,
e.g. litNonce.
2025-12-02 11:24:26 +08:00
Karl Seguin
3dd61aeb71 css.zig -> CSS.zig 2025-12-02 11:14:06 +08:00
Karl Seguin
6a46a9ba47 HTMLDataElement 2025-12-02 11:08:56 +08:00
Karl Seguin
fd39168106 Range 2025-12-02 10:57:20 +08:00
Karl Seguin
6a48f6df25 Element.hasAttributes 2025-12-02 07:01:14 +08:00
Karl Seguin
e807c9b6be Add XmlSerializer, add Response.type, tweak HTMLTemplate to redirect some calls to its Content (DocumentFragment) 2025-12-02 00:08:57 +08:00
muki
af8970bbb9 Merge pull request #1238 from lightpanda-io/zigdom-chain-allocation
Prototype Chain Allocations
2025-12-01 07:09:38 -08:00
Karl Seguin
07931dd75f Merge pull request #1240 from lightpanda-io/nikneym/html5ever-build-optimize
html5ever: prefer `dev` build only on `Debug` optimization
2025-12-01 16:21:56 +08:00
Karl Seguin
bfa2e6b4dd Merge pull request #1235 from lightpanda-io/fix-ci-install
Cleanup installation instructions
2025-12-01 16:21:22 +08:00
Halil Durak
129b59a43f html5ever: prefer dev build only on Debug optimization 2025-12-01 11:17:59 +03:00
Pierre Tachoire
4b60f56e5f ci: use releaseFast for hmtl5ever release mode 2025-12-01 09:06:52 +01:00
Pierre Tachoire
ee7c38045f zig fmt 2025-12-01 08:59:21 +01:00
Pierre Tachoire
d18253d50b fix import for rename CSS.zig insto css.zig 2025-12-01 08:59:21 +01:00
Pierre Tachoire
c9b9ef9934 ci: build html5ever typo 2025-12-01 08:59:21 +01:00
Pierre Tachoire
f968db63e9 ci: use setup-zig v2.0.5 2025-12-01 08:59:20 +01:00
Pierre Tachoire
92572c977b update zig-v8 version 2025-12-01 08:59:20 +01:00
Karl Seguin
493c5b41f8 Merge pull request #1232 from lightpanda-io/ns_prefix
element.prefix
2025-12-01 15:56:23 +08:00
Karl Seguin
92ae2c46b6 ReadableStream 2025-12-01 15:16:33 +08:00
Karl Seguin
613428c54c Execute script.onload/onerror
Add object-support for URLSearchParams. Start to treat js.Value as a first
class object (instead of js.Object, where appropriate).
2025-11-30 12:48:15 +08:00
Pierre Tachoire
bde8b64ba3 update html5ever instructions 2025-11-29 15:20:28 +01:00
Pierre Tachoire
e74a286d70 ci: add install-html5ever-dev 2025-11-29 15:20:28 +01:00
Pierre Tachoire
1e090f9d30 add html5ever install method 2025-11-29 15:20:28 +01:00
Pierre Tachoire
a1064a54cc cleanup README 2025-11-29 15:20:27 +01:00
Pierre Tachoire
dbd500cab9 update docker file 2025-11-29 15:20:27 +01:00
Pierre Tachoire
0bc0a38704 ci: update installation workflow 2025-11-29 15:20:26 +01:00
Karl Seguin
9f587ab24b MessageChannel and MessagePort 2025-11-28 22:11:59 +08:00
Karl Seguin
8858f889b4 Window.scrollX/Y, postMessage, more custom element edge cases 2025-11-28 18:01:50 +08:00
Karl Seguin
833a33678c call AttributeChangedCallback on upgrade 2025-11-28 13:04:42 +08:00
Muki Kiboigo
34c10e1e48 fix svgElement + allow base tags 2025-11-27 13:10:35 -08:00
Muki Kiboigo
8ce8c7a0f3 use _prototype_root decl everywhere 2025-11-27 12:55:48 -08:00
Karl Seguin
94bcb30f11 fetch response headers 2025-11-27 18:54:11 +08:00
Karl Seguin
819424fd3b Support Image constructor (i.e. new Image(..)) 2025-11-27 18:16:03 +08:00
Karl Seguin
f25b8fc7b0 Event.composedPath and adjusted target when crossing shadowroot boundary 2025-11-27 16:57:33 +08:00
Karl Seguin
0d57356c11 Response constructor, window.CSS 2025-11-27 15:13:16 +08:00
Karl Seguin
8775564e04 merge module loading tweaks that were made to main 2025-11-27 10:53:31 +08:00
Muki Kiboigo
15dff342a6 shrink EventTarget back to 16 2025-11-26 12:09:09 -08:00
Muki Kiboigo
45c7184fde use nullable slice for tracking chain allocations 2025-11-26 11:14:30 -08:00
Muki Kiboigo
2ddaa351ab use stream for logging stats 2025-11-26 11:05:20 -08:00
Muki Kiboigo
afe9ee5367 fix freeing with new combined chains 2025-11-26 11:05:20 -08:00
Muki Kiboigo
8348f2dcc8 fix slot alignment in slab chunks 2025-11-26 11:05:20 -08:00
Muki Kiboigo
63f489d39f initial with full chain allocations 2025-11-26 11:05:17 -08:00
muki
8bbf57c199 Merge pull request #1233 from lightpanda-io/zigdom-factory-allocators
`zigdom` Factory Slab Allocator
2025-11-26 08:45:40 -08:00
Karl Seguin
67f63a6bb3 improve parsed (i.e. static) custom element callbacks 2025-11-26 19:43:22 +08:00
Karl Seguin
18b51de696 Merge pull request #1234 from lightpanda-io/nikneym/html5ever-build-changes
Add a build step for `html5ever` in `build.zig`
2025-11-26 19:29:52 +08:00
Halil Durak
d23eacbd37 update .gitignore
LSPs seem to generate the `target` directory when navigating these files through editor.
2025-11-26 14:28:18 +03:00
Halil Durak
444ae00129 mv vendor/html5ever src/html5ever 2025-11-26 14:27:28 +03:00
Halil Durak
6280232e91 add a build step for html5ever in build.zig 2025-11-26 14:20:39 +03:00
Halil Durak
23e3a1d012 move html5ever/ under vendor/ 2025-11-26 14:20:39 +03:00
Karl Seguin
71af78caea adoptNode and importNode 2025-11-26 07:46:24 +08:00
Karl Seguin
e1d9732a60 PerformanceObserver.supportedEntryTypes 2025-11-26 07:42:19 +08:00
Muki Kiboigo
058f86ec5f new exponential SlabAllocator 2025-11-25 13:40:51 -08:00
Muki Kiboigo
0da87e1d5e add slab statistics 2025-11-25 12:13:13 -08:00
Karl Seguin
be0a808f01 Add HTMLSlotElement, PerformanceObserver and Script get/set type 2025-11-25 19:50:53 +08:00
Pierre Tachoire
a0fa232a3a element: upper case only the suffix part of the tagname 2025-11-25 12:13:23 +01:00
Pierre Tachoire
4a4602137b element: add prefix and localName accessors 2025-11-25 11:46:54 +01:00
Karl Seguin
6d6f1340af window.screen 2025-11-25 15:58:34 +08:00
Karl Seguin
35a728e69f explicitly run microtasks 2025-11-25 15:54:25 +08:00
Karl Seguin
218d08b1f6 add some skeleton implementations for various CSS WebAPIs 2025-11-25 13:00:32 +08:00
Muki Kiboigo
219245be95 standardize slab testing names 2025-11-24 20:36:15 -08:00
Muki Kiboigo
aa1742db63 use SlabAllocator 2025-11-24 13:14:45 -08:00
Karl Seguin
e336c67857 various small api fixes/tweaks 2025-11-24 20:12:43 +08:00
Karl Seguin
871fd46c89 fix 0-size structs all having the same identity (the same pointer 2025-11-24 15:11:16 +08:00
Karl Seguin
f536f16926 Correct exception on custom element re-definition 2025-11-22 23:04:17 +08:00
Karl Seguin
d3c00cdd52 Link get/set href 2025-11-22 22:56:58 +08:00
Karl Seguin
6b990f8f12 CustomEvent and document.createEvent 2025-11-22 12:33:35 +08:00
Karl Seguin
3c010f0e73 tweak custom element callbacks 2025-11-22 12:25:12 +08:00
Karl Seguin
357df22fab more pseudoclass support 2025-11-21 22:23:34 +08:00
Karl Seguin
470f5b5029 Headers and improved Request 2025-11-21 20:25:57 +08:00
Karl Seguin
216b1664bd :checked pseudoclass 2025-11-21 20:25:57 +08:00
Karl Seguin
cbe2124387 make crypto callable from the window 2025-11-21 20:25:56 +08:00
Karl Seguin
11934233a0 Merge pull request #1229 from lightpanda-io/nikneym/blob-zigdom
Port `Blob` to zigdom
2025-11-21 20:09:11 +08:00
Halil Durak
de9a0c0166 bring back import for ResizeObserver
No idea how I removed this single line while rebasing...
2025-11-21 14:59:29 +03:00
Halil Durak
5c9ff9d1a2 fix Blob#slice return type 2025-11-21 14:55:05 +03:00
Halil Durak
0142520bb8 change how Blob and File initialized 2025-11-21 14:55:05 +03:00
Halil Durak
b4f9f968f6 add Blob ancestor initializer to Factory 2025-11-21 14:55:05 +03:00
Halil Durak
9a7bafb02c Blob is in another castle 2025-11-21 14:55:05 +03:00
Halil Durak
3e44d5bfdf move Blob out of files/ + provide subclasses of Blob in _type 2025-11-21 14:54:36 +03:00
Halil Durak
f4d58c8823 prefer get prefix in getter accessors 2025-11-21 14:54:36 +03:00
Halil Durak
4d192f5930 port File API tests 2025-11-21 14:54:36 +03:00
Halil Durak
20cbf99cdf port Blob functions 2025-11-21 14:54:35 +03:00
Halil Durak
6784388a42 initial Blob support on zigdom 2025-11-21 14:54:35 +03:00
Karl Seguin
b504a79bf7 dummy ResizeObserver 2025-11-21 19:03:11 +08:00
Karl Seguin
1b9b49f045 customElements.upgrade 2025-11-21 15:38:41 +08:00
Karl Seguin
095413c6c5 Element.toggleAttribute and InteresectionObserver init param overload 2025-11-21 14:35:52 +08:00
Karl Seguin
b9486e8935 add HTMLDialogElement 2025-11-21 08:55:39 +08:00
Karl Seguin
302b9f9dd7 micro-optimize pseudoclass parsing, add :modal 2025-11-20 22:01:14 +08:00
Karl Seguin
57aa267f24 iframe.contentWindow 2025-11-20 21:46:28 +08:00
Karl Seguin
ce351afb9a fix build 2025-11-20 21:32:50 +08:00
Karl Seguin
7b513bd29d support long svg element types 2025-11-20 20:00:20 +08:00
Karl Seguin
0e65bfc78b Merge pull request #1225 from lightpanda-io/nikneym/rework-types-zigdom
JS API table changes for zigdom
2025-11-20 19:42:17 +08:00
Karl Seguin
afaf105cb0 ShadowRoot 2025-11-20 19:41:14 +08:00
Halil Durak
629297e0c2 uncomment the assertion
Forgot to revert this...
2025-11-20 14:22:32 +03:00
Halil Durak
1aca22f219 JS API table changes for zigdom
Applies the `rework-types` changes to zigdom branch.
2025-11-20 13:49:36 +03:00
Karl Seguin
bd3da38fc8 add native custom elements 2025-11-19 22:50:52 +08:00
Karl Seguin
991c2c18de More MutationObserver options, Performance API 2025-11-18 20:46:17 +08:00
Karl Seguin
54a2e7650a MutationObserver and IntersectionObserver 2025-11-18 11:54:14 +08:00
Karl Seguin
5819cfb438 Merge pull request #1211 from lightpanda-io/zigdom-nix
`zigdom` building on NixOS
2025-11-17 23:30:48 +08:00
Muki Kiboigo
38ca58d71e use llvm on tests 2025-11-17 07:25:44 -08:00
Muki Kiboigo
c1c0edab9f fix curl dependency issue with zlib 2025-11-17 07:22:40 -08:00
Muki Kiboigo
8670938397 add rust support into nix flake 2025-11-17 07:22:37 -08:00
Karl Seguin
83b552780e enhance Anchor API 2025-11-17 23:19:32 +08:00
Karl Seguin
b8cc74f377 allow filtering legacy tests, fix location tests, improve URLSearchParams 2025-11-17 21:41:24 +08:00
Karl Seguin
c3ba39c80f add reparent_children html5ever callback 2025-11-16 07:53:55 +08:00
Karl Seguin
ff3a9c51f3 add remove_from_parent html5ever callback 2025-11-16 07:40:07 +08:00
Karl Seguin
19dfea7762 Work on HTMLTemplateElement
Implement Html5ever get_template_contents and add_attrs_if_missing callbacks
2025-11-15 22:04:39 +08:00
Karl Seguin
c311828217 add add_attrs_if_missing callback 2025-11-14 23:33:31 +08:00
Karl Seguin
5ae74d6924 improve form element support 2025-11-14 17:56:09 +08:00
Karl Seguin
04f719c33c wpt runner 2025-11-14 16:14:12 +08:00
Karl Seguin
7ab88e9a71 add legacy tests, optimize empty types 2025-11-14 15:55:02 +08:00
Karl Seguin
1164da5e7a copyright notices 2025-11-14 10:52:43 +08:00
Karl Seguin
6742646e89 DOMParser 2025-11-13 20:57:17 +08:00
Karl Seguin
6cf01631ad Document.activeElement, focus and blur 2025-11-13 20:37:00 +08:00
Karl Seguin
7a5cade510 remove 16 bytes from Element 2025-11-13 20:30:02 +08:00
Karl Seguin
c5a1d8a8bd Element.checkVisibility and Element.checkVisibility 2025-11-13 20:18:34 +08:00
Karl Seguin
32bad5f8bb Element.matches, Element.hasAttributes and DOMStringMap (Element.dataset) 2025-11-13 20:09:38 +08:00
Karl Seguin
5ec5647395 Merge ScriptManager/Module loading changes
Get tests passing.
2025-11-13 17:21:08 +08:00
Karl Seguin
4e9f7c729d Intl and IFrame skeleton 2025-11-03 08:15:15 +08:00
Karl Seguin
4c0437b3fb History placeholder 2025-11-02 11:49:35 +08:00
Karl Seguin
de71b97b1f optional listener, or object listener 2025-11-02 00:17:45 +08:00
Karl Seguin
21d008c6c2 class_index => class_id 2025-11-01 20:37:45 +08:00
Karl Seguin
9138a3c881 MediaQueryLis dummy 2025-11-01 20:35:06 +08:00
Karl Seguin
8b3f36c1f8 url/location host getter 2025-11-01 20:25:41 +08:00
Karl Seguin
d397d75aca DOMImplementation and DocumentType skeletons 2025-11-01 14:02:18 +08:00
Karl Seguin
618b28a292 add FormData and base KeyValueList 2025-10-31 22:25:19 +08:00
Karl Seguin
c966211481 HTMLAllCollection 2025-10-30 11:30:06 +08:00
Karl Seguin
5ae1190ddd HTMLDocument 2025-10-29 22:23:05 +08:00
Karl Seguin
fb9cce747d Scripts now properly block rendering
Re-enabled CDP tests

Fixed more tests
2025-10-29 16:37:11 +08:00
Karl Seguin
1a04ebce35 fix Node.contains 2025-10-28 19:12:47 +08:00
Karl Seguin
59bbfc4e06 fix casing 2025-10-28 19:07:58 +08:00
Karl Seguin
d3973172e8 re-enable minimum viable CDP server 2025-10-28 18:56:03 +08:00
Karl Seguin
cdd31353c5 get fetch campire working 2025-10-28 11:24:29 +08:00
Karl Seguin
b047cb6dc1 remove libdom 2025-10-27 22:14:59 +08:00
Karl Seguin
c52dce1c48 Merge pull request #1154 from lightpanda-io/module_evalute_error_handling
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Handle (log) module evaluation errors directly
2025-10-16 19:26:14 +08:00
Karl Seguin
0b4a1b4a1b Handle (log) module evaluation errors directly
Some module evluation errors aren't handled by the normal TryCatch mechanism.
Instead, the exception needs to be retrieved directly from the module.
2025-10-16 15:10:30 +08:00
Karl Seguin
cc0c1bcf3a Merge pull request #1153 from lightpanda-io/normalized_specifier_lifetime
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix a potential segfault on log message for failing to load module
2025-10-16 15:01:50 +08:00
Karl Seguin
55746f1a1d log the normalized specifier now that we've extended its lifetime to the page.arena 2025-10-16 14:34:07 +08:00
Karl Seguin
7bb8581a95 Fix referrer in log (was printing using the src instead :/) 2025-10-16 14:31:09 +08:00
Karl Seguin
521c0f8460 Fix a potential segfault on log message for failing to load module
Using the `call_arena` here is unsafe in the case of a failure. It's possible
for the call_arena to be reset during module processing, making the log crash.

The issue is that the lifetime of a URL is often conditional. If the stitched
URL has already been seen (i.e. is in the module_cache), then it can be short-
lived. EXCEPT, URL.stitch might require an allocation..and then you start to
think, well, if URL.stitch is going to allocate anyways...If we stitch with
the `page.arena`, and end up not needing a long lifetime, we've wasted memory.
If we stitch with `page.call_arena` and end up needing a long lifetime, we need
to dupe.

It's a bit messy, and I'd like to take a stab at improving it after:
https://github.com/lightpanda-io/browser/pull/1127.

I'm thinking that we need a URL intern pool. HashMap with a composite key of
base + path -> resolved. Then all URLs are resolved using the page.arena, but
we don't have any duplicates, so it isn't wasteful.
2025-10-16 14:15:38 +08:00
Karl Seguin
4bfe3b6fe1 Merge pull request #1151 from lightpanda-io/unicode_nbsp_encoding
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Encode UTF8 non breaking space (194, 160) as &nbsp; - same as chrome
2025-10-15 18:28:45 +08:00
Karl Seguin
b610aa1c0c Encode UTF8 non breakingspace (194, 160) as &nbsp; - same as chrome 2025-10-15 17:34:23 +08:00
Karl Seguin
73da04bea2 Merge pull request #1150 from lightpanda-io/isdone-async
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
isDone must be run after script's deinit
2025-10-15 15:58:42 +08:00
Karl Seguin
18c851e53f Merge pull request #1149 from lightpanda-io/iterators_and_walker_fix
Improve correctness of NodeIterator and Treewalker
2025-10-15 15:58:12 +08:00
Pierre Tachoire
41f4533bc0 isDone must be run after script's deinit 2025-10-15 09:50:17 +02:00
Karl Seguin
4db8a967b6 update netsurf deps 2025-10-15 14:35:58 +08:00
Karl Seguin
ff70f4e79f Merge pull request #1147 from lightpanda-io/svg_tag_name_test
Add tests for svg tag names
2025-10-15 09:47:07 +08:00
Karl Seguin
c9517aff7d Add tests for svg tag names
Depends on: https://github.com/lightpanda-io/libdom/pull/46
2025-10-15 09:37:56 +08:00
Karl Seguin
3657a49a2c Improve correctness of NodeIterator and Treewalker
In their current implementation, both the NodeIterator and TreeWalker would
skip over ignored nodes. However, while the node itself should have been ignored
its children should still be iterated.

For example, when going over:

```
<div id="container">
  <!-- comment1 -->
  <span>
    <!-- comment2 -->
  </span>
</div>
```

With `SHOW_COMMENT`, the previous version would completely skip over `container`
and its children. Now the code still won't emit the `container` div itself,
it will still iterate through its children (and thus emit the two comments).

This change relates to ongoing react compatibility.
2025-10-15 09:23:54 +08:00
Karl Seguin
71e7aa5262 Merge pull request #1146 from lightpanda-io/test_normalized_text_nodes
add a test for the changes to parsing adjascent text ndoes
2025-10-15 08:13:52 +08:00
Karl Seguin
2e435f5d4e Merge pull request #1145 from lightpanda-io/page_events
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Fire page lifecycle events when all scripts are either inline or async
2025-10-14 19:48:59 +08:00
Karl Seguin
859b03c4a6 update libdom and libhubbub 2025-10-14 19:46:21 +08:00
Karl Seguin
ee8786444f add another test 2025-10-14 13:48:23 +08:00
Karl Seguin
d87d782fd5 Merge pull request #1137 from lightpanda-io/profiler
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Expose v8 CpuProfiler + add fast properties for some window properties
2025-10-14 05:45:31 +08:00
Karl Seguin
afac4fc37f add a test for the changes to parsing adjascent text ndoes 2025-10-14 00:23:35 +08:00
Karl Seguin
de83521e08 Fire page lifecycle events when all scripts are either inline or async
This is how, for example, scripts on lightpanda.io are. Though fixing this
doesn't really change anything, it's good to make sure these events are firing
correctly.
2025-10-13 21:53:58 +08:00
Karl Seguin
99f8fe1592 Merge pull request #1139 from lightpanda-io/inspector-deinit
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
cdp: drain microtasks before inspector deinit
2025-10-11 08:14:37 +08:00
Pierre Tachoire
02c092a122 Merge pull request #1140 from lightpanda-io/invalid-errdefer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
remove invalid errdefer
2025-10-10 18:54:16 +02:00
Pierre Tachoire
70ca74747f remove invalid errdefer 2025-10-10 18:09:57 +02:00
Pierre Tachoire
594d754022 cdp: drain microtasks before inspector deinit 2025-10-10 17:43:08 +02:00
Karl Seguin
c381e4153d Expose v8 CpuProfiler + add fast properties for some window properties
First, this exposes the v8 Profiler. Right now it's just a commented-out block
in `fetch` and meant for internal debugging.
Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/105

Use postAttach on Window to attach "static" properties. This comes from
profiling (lightpanda.io) and seeing window.get_self called tens of thousands
of times.
2025-10-10 19:51:29 +08:00
Halil Durak
e761c7e8f4 Merge pull request #1115 from lightpanda-io/nikneym/url-changes
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Small URL & Location changes
2025-10-10 10:54:47 +03:00
Halil Durak
b8d4e3ac50 change after rebase 2025-10-10 10:43:04 +03:00
Halil Durak
4c2b95d00b always prefer navigateFromWebAPI when navigating from a web API 2025-10-10 10:40:11 +03:00
nikneym
cea4f052ba location: add href setter
* `page.navigateFromWebAPI` seem to be not working while testing; `page.navigate` is preferred instead.
2025-10-10 10:40:11 +03:00
nikneym
9b4ea7a040 add an invalid url test
* this test should not pass; it's related to implementation of `std.Uri` though.
2025-10-10 10:40:10 +03:00
nikneym
26c2b258b4 get_protocol: don't allocate for protocol string 2025-10-10 10:40:04 +03:00
Halil Durak
27c9e18535 Merge pull request #1134 from lightpanda-io/nikneym/default-location
Location: prefer `about:blank` when not navigated yet
2025-10-10 10:33:36 +03:00
Pierre Tachoire
b53c2bfa0c Merge pull request #1135 from lightpanda-io/importmap
Importmap support
2025-10-10 09:33:23 +02:00
Pierre Tachoire
80605633c4 update wpt 2025-10-10 08:46:06 +02:00
Pierre Tachoire
acf06fdd8f Resolve importmap against page's url
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2025-10-10 08:34:56 +02:00
Pierre Tachoire
58cc5b4684 typo fix 2025-10-10 08:02:45 +02:00
Karl Seguin
c502bd901e Merge pull request #1136 from lightpanda-io/update_libdom
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Update libdom
2025-10-10 13:15:11 +08:00
Karl Seguin
55027747fd Update libdom
Apply case-preservation on all SVG elements.
2025-10-10 12:46:27 +08:00
Karl Seguin
f6d77afe2e Merge pull request #1130 from lightpanda-io/intersection_observer
Rework IntersectionObserver
2025-10-10 11:10:08 +08:00
Pierre Tachoire
cd9466dafa free importmap on reset and don't retain capacity 2025-10-09 16:30:29 +02:00
Pierre Tachoire
4bf79e4bc9 add importmap support 2025-10-09 16:09:25 +02:00
Pierre Tachoire
7afecf0f85 move mod specifier resolution js/context => script manager 2025-10-09 16:09:24 +02:00
Halil Durak
0b38b7d473 location: prefer about:blank when not navigated yet 2025-10-09 16:55:05 +03:00
Karl Seguin
1b462da4aa Merge pull request #1133 from lightpanda-io/nikneym/cookie-validation
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add a fast path for validating cookie strings
2025-10-09 20:25:52 +08:00
Halil Durak
07948304b2 fix misleading comment
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2025-10-09 14:00:39 +03:00
Halil Durak
0634acdac4 add a fast path for validating cookie strings
This prefers `suggestVectorLength` in order to pick a vector size; for cookie strings shorter than, say 64, this might cause it to fallback to slow path on architectures that support larger vector sizes (like AVX-512). We may also add checks for smaller vector sizes if desired in the future.
2025-10-09 12:03:14 +03:00
Karl Seguin
75e0637d2d Ensure page background tasks are re-registered on reset 2025-10-09 16:29:09 +08:00
Karl Seguin
852c30b2e5 Rework IntersectionObserver
1 - Always fire the callback on the next tick. This is probably the most
important change, as frameworks like React don't react well if the callback is
fired immediately (they expect to continue processing the page in its current
state, not in the mutated state from the callback)

2 - Always fire the callback for observed elements with a parent, whether or
not those intersect or are connected. From MDN, the callback is fired
"The first time the observer is initially asked to watch a target element."

3 - Add a mutation observer so that if a node is added to the root (or removed)
the callback is fired. This, I think, is the best we can currently do for
"intersection".
2025-10-09 14:17:03 +08:00
Karl Seguin
dc85c6552a Merge pull request #1132 from lightpanda-io/reduce_http_tick_blocking
Remove potential processing blocking with CDP
2025-10-09 14:14:05 +08:00
Karl Seguin
76e8506022 Remove potential processing blocking with CDP
When using CDP, we poll the HTTP clients along with the CDP socket. Because this
polling can be long, we first process any pending message. This can end up
processing _all_ messages, in which case the poll will block for a long time.

This change makes it so that when the initial processing processes 1+ message,
we do not poll, but rather return. This allows the page lifecycle to be
processed normally (and not just blocking on poll, waiting for the CDP client
to send data).
2025-10-09 13:18:47 +08:00
Karl Seguin
2d6e2551f6 Merge pull request #1131 from lightpanda-io/microtask-queue-drain
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
drain micro task queue before reset ExecutionWorld and page
2025-10-09 08:36:26 +08:00
Pierre Tachoire
080b1d9a7c drain micro task queue before reset ExecutionWorld and page 2025-10-08 13:55:17 +02:00
Karl Seguin
fe008b0966 Merge pull request #1128 from lightpanda-io/console_trace_svg_test
Some checks failed
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add console.trace and svg attribute test
2025-10-08 00:25:20 +08:00
Karl Seguin
4ad10d057b Add console.trace and svg attribute test
update to latest libdom
2025-10-07 22:26:38 +08:00
Karl Seguin
a65aa9f312 Merge pull request #1126 from lightpanda-io/add_debug_context
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Attempt to add more context to debug logs.
2025-10-06 17:48:41 +08:00
Karl Seguin
5b43c16f35 Merge pull request #1125 from lightpanda-io/call_arena
Move the call_arena to the page.
2025-10-06 17:22:41 +08:00
Karl Seguin
9cb37dc011 Attempt to add more context to debug logs.
1 - On `unkown global property`, include the stack trace (this might be WAY too
verbose)

2 - On script get, include stack trace (when available)

3 - On script get, include referrer

4 - Stack traces will now include the script name/src when available
2025-10-06 16:56:54 +08:00
Karl Seguin
2ba6737c41 Merge pull request #1119 from lightpanda-io/cdp_log_entry
Emit Log.addEntry
2025-10-06 16:45:48 +08:00
Karl Seguin
33d737f957 Merge pull request #1123 from lightpanda-io/blocking_scripts
Remove the single-blocking-import restrictions
2025-10-06 15:57:29 +08:00
Karl Seguin
381a18a40e Move the call_arena to the page.
The call_arena was previously owned by the js.Context, but it has to exist on
the page, and the page is created before the context, so it's set to undefined
on the page. While this has never caused an issue, there's no reason for the
page not to own this, and the context to simply reference it.

Also, renamed the js.Context.context_arena to simply `arena`, which is more
consistent with other arena names (e.g. page.arena).
2025-10-06 15:52:56 +08:00
Karl Seguin
207f0655dd Merge pull request #1117 from lightpanda-io/cleanup_js
This is the last of the big changes to the js code
2025-10-06 15:33:21 +08:00
Karl Seguin
88d64da257 Merge pull request #1124 from lightpanda-io/brotli
Supports brotli compression
2025-10-06 14:33:25 +08:00
Karl Seguin
cf378dfd6d add brotli include path 2025-10-06 12:39:30 +08:00
Karl Seguin
a3939d9a66 Supports brotli compression
Adds bortli as a submodules, and compiles the decoder, making it available to
libcurl.

Some websites appear to sent brotli encoded responses even though we don't
advertise support for it (e.g. https://movie.douban.com).
2025-10-06 12:30:06 +08:00
Karl Seguin
ef363209a4 Remove the single-blocking-import restrictions
Remove the is_blocking variable (and checks) from the ScriptManager. This is a
holdover to when blocking imports had a dedicated connections, and thus couldn't
be loaded concurrently.

This changes two obvious things (and probably a few subtle ones). The first is
that plain async scripts are now free to be executed as soon as they are
completed. As far as I can tell, this is a safe behavior, is simpler and should
be a bit faster.

More importantly, it allows for chains of imports. normal import (A) -> dynamic
import (B) -> normal import (C). This would previously fail an assertion. The
superficial issue was that dynamic import handling didn't respect the
`is_blocking` flag. But if we did respect it, than module B would never be able
to load module C, which would block module A from ever completing. By removing
the flag, module C will now be properly evaluated, which unblocks module B which
allows module A to unblock.

(1) I believe this is the issue seen here:
    https://github.com/lightpanda-io/browser/issues/1121
2025-10-06 09:48:57 +08:00
Karl Seguin
fe9a10c617 Emit Log.addEntry
Currently, this hooks a single log.Interceptor into the logging framework, but
changing it to take a list shouldn't be too hard. Biggest issue is who will own
it, as we'd need an allocator to maintain a list / lookup (which log doesn't
currently have).

Uses logFmt format, and, for now, always filters out debug messages and a few
particularly verbose scopes.
2025-10-03 17:29:01 +08:00
Karl Seguin
2e734fae57 This is the last of the big changes to the js code
This Pr largely tightens up a lot of the code. 'v8' is no longer imported
outside of js. A number of helper functions have been moved to the js.Context.
For example, js.Function.getName used to call:

```zig
return js.valueToString(allocator, name, self.context.isolate, self.context.v8_context);
```

It now calls:

```zig
return self.context.valueToString(name, .{ .allocator = allocator });
```

Page.main_context has been renamed to `Page.js`. This, in combination with new
promise helpers, turns:

```zig
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve({});
return resolver.promise();
```

into:

```zig
return page.js.resolvePromise({});
```
2025-10-03 15:06:16 +08:00
Karl Seguin
432e3c3a5e Merge pull request #1118 from lightpanda-io/inspector_linking
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Make sure inspector implementation is always exported
2025-10-03 14:10:47 +08:00
Karl Seguin
a4b13a80ce fix sloppiness 2025-10-03 13:50:53 +08:00
Karl Seguin
a6997a7e85 Make sure inspector implementation is always exported 2025-10-03 13:32:03 +08:00
Karl Seguin
a60d06af6b Merge pull request #1114 from lightpanda-io/extract_js_structs_to_files
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Start extract JS structs into their own files
2025-10-03 09:54:21 +08:00
Karl Seguin
dab8012b6a Start extract JS structs into their own files
Renames JsContext -> js.Context, JsObject -> js.Object and JsThis -> js.This
which is more consistent with the other types. The JsObject -> js.Object is
the reason so many files were touched.

This is still a [messy] transition, with more refactoring planned to clean it
up.
2025-10-02 12:48:50 +08:00
Karl Seguin
66f82fd9cc Merge pull request #1109 from lightpanda-io/remove_generic_js
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Remove the generic nature of Env and most of the JS classes
2025-10-02 10:58:34 +08:00
Karl Seguin
0bff8ba632 Merge pull request #1113 from lightpanda-io/url-stitch-fix
Fix URL `stitch` Issue with parent traversal
2025-10-02 10:21:23 +08:00
Karl Seguin
32226297ab Remove the generic nature of Env and most of the JS classes
Back in the zig-js-runtime days, globals were used for the state and webapi
declarations. This caused problems largely because it was done across
compilation units (using @import("root")...).

The generic Env(S, WebApi) was used to solve these problems, while still making
it work for different States and WebApis.

This change removes the generics and hard-codes the *Page as the state and
only supports our WebApis for the class declarations.

To accommodate this change, the runtime/*tests* have been removed. I don't
consider this a huge loss - whatever behavior these were testing, already
exists in the browser/**/*.zig web api.

As we write more complex/complete WebApis, we're seeing more and more cases
that need to rely on js objects directly (JsObject, Function, Promises, etc...).
The goal is to make these easier to use. Rather than using Env.JsObject, you
now import "js.zig" and use js.JsObject (TODO: rename JsObject to Object).
Everything is just a plain Zig struct, rather than being nested in a generic.

After this change, I plan on:

1 - Renaming the js objects, JsObject -> Object. These should be referenced in
    the webapi as js.Object, js.This, ...

2 - Splitting the code across multiple files (Env.zig, Context.zig,
    Caller.zig, ...)
2025-10-02 10:16:58 +08:00
Karl Seguin
ab18c90b36 Merge pull request #1112 from lightpanda-io/window_scroll
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Improve window scroll
2025-10-02 09:22:39 +08:00
Karl Seguin
27b6fd561a Merge pull request #1104 from lightpanda-io/fetch_wait
Add Session.fetchWait so that 'fetch' mode will follow navigation
2025-10-02 09:22:29 +08:00
Karl Seguin
15b64d5a25 Improve window scroll
scroll alias for scrollTo

add get_scrollX and get_scrollY, along with their aliases: pageXOffset and
pageYOffset. These always return 0, unless scroll or scrollTo are called.
2025-10-01 18:41:56 +08:00
Karl Seguin
08a50a8ada Merge pull request #1110 from lightpanda-io/telemetry_leak
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fixes a 'leak' with telemetry
2025-10-01 17:29:59 +08:00
Karl Seguin
9d172bb29d Fixes a 'leak' with telemetry
This is just something that isn't cleaned up on exit, so it isn't a "leak",
but better to be explicit with the free.
2025-10-01 16:41:20 +08:00
Karl Seguin
c891322129 Merge pull request #1108 from lightpanda-io/wpt_panic_handler
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add custom panic handler to printt which file caused a panic
2025-10-01 15:04:24 +08:00
Muki Kiboigo
77434850f7 url traverse down to the root 2025-09-30 22:13:25 -07:00
Karl Seguin
69b65dbd41 Add custom panic handler to printt which file caused a panic 2025-10-01 11:24:41 +08:00
Karl Seguin
c335a545a3 Merge pull request #1107 from lightpanda-io/mutation_observer_improvement
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Use correct 'this' on MutationObserver callback
2025-10-01 08:44:07 +08:00
Karl Seguin
5bcccec610 Merge pull request #1103 from lightpanda-io/text_decode_view
Text decode view
2025-10-01 08:42:54 +08:00
Karl Seguin
20ae9c3a53 fix dep link 2025-09-30 21:41:08 +08:00
Karl Seguin
92ca7c5a4b update zig-v8-form 2025-09-30 19:47:41 +08:00
Karl Seguin
37fa41b4a2 fix buffer ranges 2025-09-30 19:47:41 +08:00
Karl Seguin
298f959e13 Add broken TextDecoder test that should pass 2025-09-30 19:47:26 +08:00
Karl Seguin
1cb431f204 Better support for Uint8Array in ReadableStream
There's always going to be ambiguity between a string and a Uint8Array. We
already had TypedArray(u8) as a discriminator when _returning_ values. But now
the type is also used by mapping JS values to Zig. To support this efficiently
when probing the union, the typed array mapping logic was extracted into its
own function (so that it can be used by the probe).
2025-09-30 19:47:22 +08:00
Karl Seguin
74dc7b278b Merge pull request #1105 from lightpanda-io/fix_bad_window_test
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
fix typo and wrong API in window test
2025-09-30 19:44:10 +08:00
Karl Seguin
b47d8a794c Use correct 'this' on MutationObserver callback
Add support for MutationObserver.disconnect
2025-09-30 19:36:06 +08:00
Halil Durak
eaf845959c Merge pull request #1106 from lightpanda-io/nikneym/window-onload-fix
Don't allow object to be set on `window.onload`
2025-09-30 14:12:01 +03:00
Karl Seguin
651521d346 Merge pull request #1102 from lightpanda-io/readable_stream_uint8array
Better support for Uint8Array in ReadableStream
2025-09-30 19:03:46 +08:00
nikneym
fb37b29671 don't allow object to be set on window.onload 2025-09-30 12:38:08 +03:00
Karl Seguin
2ecf9016ba Better support for Uint8Array in ReadableStream
There's always going to be ambiguity between a string and a Uint8Array. We
already had TypedArray(u8) as a discriminator when _returning_ values. But now
the type is also used by mapping JS values to Zig. To support this efficiently
when probing the union, the typed array mapping logic was extracted into its
own function (so that it can be used by the probe).
2025-09-30 16:32:55 +08:00
Karl Seguin
444b08be32 fix typo and wrong API in window test 2025-09-30 16:28:47 +08:00
Karl Seguin
2b84712eee Add Session.fetchWait so that 'fetch' mode will follow navigation 2025-09-30 13:36:05 +08:00
Karl Seguin
20cb6cdd8b Merge pull request #1091 from lightpanda-io/concurrent_blocking_imports
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Concurrent blocking imports
2025-09-30 12:30:42 +08:00
Karl Seguin
477a5e5338 Merge pull request #1088 from lightpanda-io/nonblocking_dynamic_imports
nonblocking dynamic imports
2025-09-30 12:30:31 +08:00
Karl Seguin
2a151229cb Merge pull request #1101 from lightpanda-io/nikneym/window-onload
Add `window.onload` getter and setter
2025-09-30 09:15:40 +08:00
nikneym
1d50e091c7 add window.onload test 2025-09-29 14:45:47 +03:00
nikneym
c587e380a0 add window.onload getter and setter 2025-09-29 14:45:35 +03:00
Karl Seguin
54f9bfba84 Merge pull request #1099 from lightpanda-io/nikneym/qol-changes
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Small changes
2025-09-29 17:39:32 +08:00
Karl Seguin
489ba131c5 Merge pull request #1097 from lightpanda-io/check_visibility_opts
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add missing checkVisibility options
2025-09-29 15:18:10 +08:00
Karl Seguin
5eac1a146f Merge pull request #1098 from lightpanda-io/html_collection_indexed_accessor
Replace HTMLCollection postAttach's with indexed/named getter
2025-09-29 15:17:57 +08:00
Karl Seguin
d7ce6bdeff Replace HTMLCollection postAttach's with indexed/named getter
This solves two issues. First, it's more correct, the indexers should be live.
Second, it makes sure that anything with an HTMLCollection prototype, like
HTMLOptionsCollection, also gets access to the index getters.

We could solve the 2nd issue by making `postAttach` work up the prototype
chain, but since postAttach is wrong (not live), I prefer this solution.
2025-09-29 14:03:59 +08:00
Karl Seguin
e88473d090 add missing checkVisibility options 2025-09-29 12:04:11 +08:00
nikneym
b9024ab032 set_innerHTML: simpler iteration 2025-09-26 15:38:23 +03:00
nikneym
98906be0f6 parseData: remove iterator variant 2025-09-26 15:38:22 +03:00
Pierre Tachoire
220775715d Merge pull request #1094 from lightpanda-io/wpt-debug
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
ci: use debug mode for WPT tests
2025-09-26 13:54:17 +02:00
Pierre Tachoire
ecbf52157b ci: use debug mode for WPT tests 2025-09-26 13:33:13 +02:00
Pierre Tachoire
a579977f66 Merge pull request #1086 from lightpanda-io/history
Implement `History` WebAPI.
2025-09-26 12:15:07 +02:00
Karl Seguin
418dc6fdc2 Start downloading all synchronous imports ASAP
This changes how non-async module loading works. In general, module loading
is triggered by a v8 callback. We ask it to process a module (a <script type=
module>) and then for every module that it depends on, we get a callback. This
callback expects the nested v8.Module instance, so we need to load it then and
there (as opposed to dynamic imports, where we only have to return a promise).

Previously, we solved this by issuing a blocking HTTP get in each callback. The
HTTP loop was able to continuing downloading already-queued resources, but if
a module depended on 20 nested modules, we'd issue 20 blocking gets one after
the other.

Once a module is compiled, we can ask v8 for a list of its dependent module. We
can them immediately start to download all of those modules. We then evaluate
the original module, which will trigger our callback. At this point, we still
need to block and wait for the response, but we've already started the download
and it's much faster. Sure, for the first module, we might need to wait the same
amount of time, but for the other 19, chances are by the time the callback
executes, we already have it downloaded and ready.
2025-09-26 15:38:50 +08:00
Karl Seguin
2aa4b03673 try to cleanup persisted references 2025-09-26 15:34:32 +08:00
Karl Seguin
f236a65a79 Merge pull request #1092 from lightpanda-io/nikneym/insert-adjacent-html
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Support `Element#insertAdjacentHTML`
2025-09-26 14:51:08 +08:00
nikneym
f7b08a1160 prefer orelse return instead of orelse unreachable 2025-09-26 09:43:30 +03:00
Karl Seguin
eed10dd1bb Apply suggestions from code review
fix typos

Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-09-26 10:37:31 +08:00
Muki Kiboigo
9992bd0999 clean up history api 2025-09-25 12:33:30 -07:00
nikneym
6912175e7e prefer $ instead of document.querySelector 2025-09-25 19:30:10 +03:00
nikneym
a59c32757e assert that nodes exist 2025-09-25 19:29:44 +03:00
nikneym
2438a0e60b fix comment 2025-09-25 19:17:08 +03:00
nikneym
a850a902ce make sure parent is not Document in beforebegin and afterend 2025-09-25 15:04:26 +03:00
nikneym
b7ba993ba6 improve insertAdjacentHTML test 2025-09-25 14:42:58 +03:00
nikneym
3eb0d57d5b correct element insertation in insertAdjacentHTML
* also DRY since the loop is repeated multiple times.
2025-09-25 14:41:50 +03:00
Karl Seguin
6bf2ff9168 Protect against context changing during module resolution. 2025-09-25 13:39:02 +08:00
Karl Seguin
92226a8d06 Merge pull request #1090 from lightpanda-io/script_data_url_test
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add script dataurl test
2025-09-25 10:15:47 +08:00
Karl Seguin
134424dfdc add script dataurl test 2025-09-25 08:18:59 +08:00
Karl Seguin
58ceb66452 Merge pull request #1089 from lightpanda-io/fix-datauri
fix data uri scripts
2025-09-25 08:15:36 +08:00
nikneym
902b8fc789 add insertAdjacentHTML test 2025-09-24 20:26:05 +03:00
nikneym
923491a510 make ref_node of nodeInsertBefore nullable 2025-09-24 20:21:48 +03:00
nikneym
255b45d07b initial insertAdjacentHTML attempt 2025-09-24 20:21:08 +03:00
Pierre Tachoire
8f68b5b289 fix data uri scripts 2025-09-24 17:29:23 +02:00
Karl Seguin
252fd78473 remove duplicate put, add more assertions 2025-09-24 22:44:46 +08:00
Karl Seguin
b692c5db60 nonblocking dynamic imports
Allows dynamic imports to be loading asynchronously. I know reddit isnt the
best example, since it doesn't fully load, but this reduced the load time from
~7.2s to ~4.8s.
2025-09-24 22:28:22 +08:00
Pierre Tachoire
eff7d58f4b Merge pull request #1087 from lightpanda-io/fix-beyboardevent
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
fix pointer parameter into KeyboardEvent contructor
2025-09-24 09:56:47 +02:00
Pierre Tachoire
17e9bdf8e8 fix pointer parameter into MouseEvent contructor 2025-09-24 09:40:24 +02:00
Pierre Tachoire
22d2694b71 fix pointer parameter into KeyboardEvent contructor 2025-09-24 09:29:37 +02:00
Muki Kiboigo
e74d7fa454 add popstate event for History 2025-09-24 00:22:20 -07:00
Muki Kiboigo
464f42a121 add history tests 2025-09-24 00:21:16 -07:00
Muki Kiboigo
05e7079178 functional history WebAPI 2025-09-24 00:21:16 -07:00
Muki Kiboigo
f03fcc9a31 support for returning Env.Value 2025-09-24 00:21:16 -07:00
Muki Kiboigo
c3ad054bb3 add toJson object and fromJson value 2025-09-24 00:21:16 -07:00
Karl Seguin
202e137d77 Merge pull request #1084 from lightpanda-io/slotchange
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Dispatch slotchange event
2025-09-24 09:23:28 +08:00
Karl Seguin
6b35664e37 Merge pull request #1079 from lightpanda-io/dynamic_import_caching
Dynamic import caching
2025-09-24 09:23:16 +08:00
Karl Seguin
1a7dbd56ac Dispatch slotchange event
The first time a `slotchange` event is registered, we setup a SlotChangeMonitor
on the page. This uses a global (ugh) MutationEvent to detect slot changes.

We could improve the perfomance of this by installing a MutationEvent per
custom element, but a global is obviously a lot easier.

Our MutationEvent currently fired _during_ the changes. This is problematic
(in general, but specifically for slotchange). You can image something like:

```
slot.addEventListener('slotchange', () => {
   // do something with slot.assignedNodes()
});
```

But, if we dispatch the `slotchange` during the MutationEvent, assignedNodes
will return old nodes. So, our SlotChangeMonitor uses the page scheduler to
schedule dispatches on the next tick.
2025-09-23 17:41:05 +08:00
Karl Seguin
1a40853aae Merge pull request #1082 from lightpanda-io/response_type
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Set Response.type to basic on same-origin requests
2025-09-23 14:23:16 +08:00
Karl Seguin
6bad2b16e4 Set Response.type to basic on same-origin requests 2025-09-23 11:35:51 +08:00
Karl Seguin
db166b4633 Merge pull request #1081 from lightpanda-io/nikneym/link-rel
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add `rel` property to `HTMLLinkElement`
2025-09-22 22:35:34 +08:00
nikneym
71bc624a74 add a link element test 2025-09-22 16:35:06 +03:00
nikneym
907a941795 add rel setter to HTMLLinkElement 2025-09-22 16:34:37 +03:00
Pierre Tachoire
559783eed7 Merge pull request #1080 from lightpanda-io/bump-netsurf
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
update libdom version
2025-09-22 14:26:24 +02:00
nikneym
68585c8837 add rel getter to HTMLLinkElement 2025-09-22 15:08:07 +03:00
Pierre Tachoire
eccbc9d9b3 update libdom version 2025-09-22 11:19:28 +02:00
Karl Seguin
e7d1d55170 update zig-v8-fork 2025-09-22 15:19:28 +08:00
Karl Seguin
f04754c254 Correct dynamic module loading/caching
Refactors some of the module loading logic. Both normal modules import and
dynamic module import now share more of the same code - they both go through
the slightly modified `module` function.

Dynamic modules now check the cache first, before loading, and when cached,
resolve the correct promise. This can now happen regardless of the module
loading state.

Also tried to replace some page arenas with call arenas and added some basic
tests for both normal and dynamic module loading.
2025-09-22 15:15:00 +08:00
Karl Seguin
a8e5a48b87 Merge pull request #1074 from lightpanda-io/cdp-nodeid
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
cdp: start nodeId from 1 instead of 0
2025-09-20 07:21:20 +08:00
Pierre Tachoire
283a9af406 cdp: start nodeId from 1 instead of 0
chromedp expects the nodeId starts to 1.
A start to 0 make it enter in infinite loop b/c it expects the Go's
default int, ie 0, to be nil from a map to stop the loop.
If the 0 index is set, it will loop...
2025-09-19 17:58:37 +02:00
Karl Seguin
e3896455db Merge pull request #1073 from lightpanda-io/increase_mimalloc_get_rss_buffer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Seems 4K isn't always enough
2025-09-19 19:38:28 +08:00
Karl Seguin
5e6d2700a2 Merge pull request #1070 from lightpanda-io/dump_strip_mode
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Replace --noscript with more advanced --strip_mode
2025-09-19 19:25:06 +08:00
Karl Seguin
dfd0dfe0f6 Seems 4K isn't always enough 2025-09-19 19:22:02 +08:00
Pierre Tachoire
e6b9be5020 Merge pull request #1072 from lightpanda-io/assert_corretly_set_exit_when_done
Ensure extra_socket can't happen when exit_when_done == true
2025-09-19 12:20:33 +02:00
Pierre Tachoire
6f7c87516f Merge pull request #1067 from lightpanda-io/more_testing_metrics
Add libdom RSS and v8 total_physical_size to testing --json output
2025-09-19 12:16:47 +02:00
Pierre Tachoire
516a78326d Merge pull request #1066 from lightpanda-io/nikneym/relaxed-post-message
Relaxed `MessagePort.postMessage`
2025-09-19 11:14:35 +02:00
Karl Seguin
853b7f84ef Ensure extra_socket can't happen when exit_when_done == true
exit_when_done is pretty much a sneaky way to get CDP knowledge into the page.
exit_when_done == true means "this isn't a CDP session".

extra_socket is another sneaky weay to get CDP knowledge into the page. When
we get an `extra_socket` message it means "Return control to the CDP server".

Therefore it should be impossible to get an `extra_socket` message (return to
CDP) when `exit_when_done == true` (this isn't a CDP session).
2025-09-19 16:59:36 +08:00
Karl Seguin
b248a2515e Merge pull request #1071 from lightpanda-io/nikneym/element-dir
Add `element.dir` getter & setter
2025-09-19 16:51:32 +08:00
nikneym
6826c42c65 check for correct dir in HTML elements 2025-09-19 11:30:15 +03:00
nikneym
4f041e48a3 make sure dir attribute is parsed if provided 2025-09-19 11:26:53 +03:00
nikneym
ec6800500b add a test for element.dir 2025-09-19 11:11:58 +03:00
nikneym
856d65a8e9 add element.dir getter & setter 2025-09-19 10:48:37 +03:00
Karl Seguin
8a2efde365 Merge pull request #1069 from lightpanda-io/response-gettype
Adds `Response.type`
2025-09-19 15:12:10 +08:00
Karl Seguin
2ddcc6d9e6 Replace --noscript with more advanced --strip_mode
--noscript is deprecated (warning) and automatically maps to --strip_mode js

--strip_mode takes a comma separated list of values. From the help:

- "js" script and link[as=script, rel=preload]
- "ui" includes img, picture, video, css and svg
- "css" includes style and link[rel=stylesheet]
- "full" includes js, ui and css

Maybe this is overkill, but i sometimes find myself looking --dump outputs over
and over again, and removing noise (like HUGE svgs) seems like a small
improvement.
2025-09-19 14:27:53 +08:00
Muki Kiboigo
25962326d2 add support for Response.type 2025-09-18 22:27:51 -07:00
Karl Seguin
bbc2fbf984 Merge pull request #1068 from lightpanda-io/fix_wpt_runner_user_agent
git wpt runner a (not required) user_agent
2025-09-19 13:07:14 +08:00
Karl Seguin
edc53d6de3 git wpt runner a (not required) user_agent 2025-09-19 12:38:40 +08:00
Karl Seguin
47710210bd Add libdom RSS and v8 total_physical_size to testing --json output
https://github.com/lightpanda-io/browser/issues/1057
2025-09-19 10:21:39 +08:00
Pierre Tachoire
823b7f0670 Merge pull request #1064 from lightpanda-io/testing_metrics
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Re-enable test metrics
2025-09-18 18:03:57 +02:00
Pierre Tachoire
f5130ce48f Merge pull request #1061 from lightpanda-io/remove_inline
Remove all inlines
2025-09-18 17:59:35 +02:00
Halil Durak
347524a5b3 Add setImmediate, clearImmediate (#1065) 2025-09-18 17:56:09 +02:00
nikneym
51830f5907 relaxed MessagePort.postMessage 2025-09-18 17:07:12 +03:00
Karl Seguin
346f538c3b Re-enable test metrics
Both the durations and allocations will be _much_ higher with the new htmlRunner
which, for example, does 2 HTTP requests per test (html, testing.js).

https://github.com/lightpanda-io/browser/issues/1057
2025-09-18 19:55:37 +08:00
Karl Seguin
9d2948ff50 Remove all inlines
Following Zig recommendation not to inline except in specific cases, none of
which I think applies to use.

Also, mimalloc.create can't fail (it used to be possible, but that changed a
while ago), so removed its error return.
2025-09-18 19:10:22 +08:00
Karl Seguin
36ce227bf6 Merge pull request #1055 from lightpanda-io/env_string
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Introduces an Env.String for persistent strings
2025-09-18 19:06:46 +08:00
Karl Seguin
024f7ad9ef Merge pull request #1056 from lightpanda-io/DOM_NO_ERR
Convert more DOM_NO_ERR cases to assertions
2025-09-18 19:06:32 +08:00
Karl Seguin
f8425fe614 Merge pull request #1063 from lightpanda-io/remove_jsrunner
Remove JSRunner
2025-09-18 18:46:59 +08:00
Karl Seguin
7802a1b5a4 Merge pull request #1062 from lightpanda-io/fetch_newHeaders
use client.newHeaders
2025-09-18 15:56:35 +08:00
Karl Seguin
17549d8a43 Remove JSRunner
It only had a few fetch tests still using it. But now everything is migrated
to the htmlRunner
2025-09-18 15:50:19 +08:00
Karl Seguin
f6ed706855 use client.newHeaders 2025-09-18 15:46:23 +08:00
Pierre Tachoire
89ef25501b Merge pull request #1060 from lightpanda-io/fetch-ua
fetch: init headers w page's client UA
2025-09-18 09:44:00 +02:00
Pierre Tachoire
4870125e64 fetch: init headers w page's client UA 2025-09-18 09:34:55 +02:00
Pierre Tachoire
2d24e3c7f7 Merge pull request #972 from lightpanda-io/fetch
Fetch + ReadableStream
2025-09-18 09:29:05 +02:00
Karl Seguin
cdb3f46506 Merge pull request #1059 from lightpanda-io/user_agent_suffix
Add --user_agent_suffix argument
2025-09-18 15:06:21 +08:00
Karl Seguin
e225ed9f19 fix for telemetry and one-off requests 2025-09-18 11:40:25 +08:00
Karl Seguin
17bebf4f3a Merge pull request #1058 from lightpanda-io/test_doctype
Give tests <!DOCTYPE html> so they work correct in browser
2025-09-18 11:29:31 +08:00
Karl Seguin
26550129ea Add --user_agent_suffix argument
Allows appending a value (separated by a space) to the existing Lightpanda/X.Y
user agent.
2025-09-18 11:28:27 +08:00
Karl Seguin
66362c2762 Give tests <!DOCTYPE html> so they work correct in browser 2025-09-18 10:53:29 +08:00
Muki Kiboigo
f6f0e141a1 PeristentPromiseResolver with page lifetime 2025-09-17 12:12:10 -07:00
Muki Kiboigo
f22ee54bd8 use fetch logging scope, clean some comments 2025-09-17 08:46:35 -07:00
Muki Kiboigo
2a969f911e stop using destructor callback for fetch 2025-09-17 08:46:29 -07:00
Muki Kiboigo
2a0964f66b htmlRunner for ReadableStream tests, fix ReadableStream enqueue 2025-09-17 08:46:25 -07:00
Muki Kiboigo
c553a2cd38 use Env.PersistentPromiseResolver 2025-09-17 08:46:20 -07:00
Karl Seguin
24330a7491 remove meaningless text from test 2025-09-17 08:46:16 -07:00
Karl Seguin
cd763a7a35 fix arena, add fetch test 2025-09-17 08:46:03 -07:00
Muki Kiboigo
ed11eab0a7 use content length to reserve body size 2025-09-17 08:45:53 -07:00
Muki Kiboigo
a875ce4d68 copy our Request headers into the HTTP client 2025-09-17 08:45:46 -07:00
Muki Kiboigo
969bfb4e53 migrate fetch tests to htmlRunner 2025-09-17 08:45:42 -07:00
Muki Kiboigo
76dae43103 properly handle closed for ReadableStream 2025-09-17 08:45:37 -07:00
Muki Kiboigo
af75ce79ac deinit persistent promise resolver 2025-09-17 08:45:30 -07:00
Muki Kiboigo
fe89c2ff9b simplify cloning of Req/Resp 2025-09-17 08:45:25 -07:00
Muki Kiboigo
bb2595eca5 use call arena for json in Req/Resp 2025-09-17 08:45:20 -07:00
Muki Kiboigo
618fff0191 simplify Headers 2025-09-17 08:45:14 -07:00
Muki Kiboigo
9bbd06ce76 headers iterators should not allocate 2025-09-17 08:45:05 -07:00
Muki Kiboigo
20463a662b use destructor callback for FetchContext 2025-09-17 08:45:00 -07:00
Muki Kiboigo
9251180501 support object as HeadersInit 2025-09-17 08:44:54 -07:00
Muki Kiboigo
2659043afd add logging on fetch error callback 2025-09-17 08:44:47 -07:00
sjorsdonkers
7766892ad2 retain value, avoid str alloc 2025-09-17 08:44:36 -07:00
sjorsdonkers
a7848f43cd avoid explicit memcpy 2025-09-17 08:44:31 -07:00
sjorsdonkers
cf8f76b454 remove length check of fixed size 2025-09-17 08:44:26 -07:00
sjorsdonkers
f68f184c68 jsValueToZig for fixed sized arrays 2025-09-17 08:44:12 -07:00
Muki Kiboigo
463440bce4 implement remaining ReadableStream functionality 2025-09-17 08:43:42 -07:00
Muki Kiboigo
51ee313910 working Header iterators 2025-09-17 08:43:36 -07:00
Muki Kiboigo
744b0bfff7 TypeError when Stream is locked 2025-09-17 08:43:31 -07:00
Muki Kiboigo
949479aa81 cleaning up various Headers routines 2025-09-17 08:43:22 -07:00
Muki Kiboigo
8743841145 use proper Headers in fetch() 2025-09-17 08:43:16 -07:00
Muki Kiboigo
6225cb38ae expand Request/Response interfaces 2025-09-17 08:43:05 -07:00
Muki Kiboigo
8dcba37672 expand Headers interface 2025-09-17 08:42:59 -07:00
Muki Kiboigo
38b922df75 remove debug logging in ReadableStream 2025-09-17 08:42:50 -07:00
Muki Kiboigo
6d884382a1 move fetch() into fetch.zig 2025-09-17 08:42:41 -07:00
Muki Kiboigo
752e75e94b add bodyUsed checks on Request and Response 2025-09-17 08:42:36 -07:00
Muki Kiboigo
5ca41b5e13 more Headers compatibility 2025-09-17 08:42:30 -07:00
Muki Kiboigo
1b3707ad33 add fetch to cdp domain 2025-09-17 08:42:20 -07:00
Muki Kiboigo
c6e82d5af6 add json response method 2025-09-17 08:42:12 -07:00
Muki Kiboigo
814e41122a basic readable stream working 2025-09-17 08:42:07 -07:00
Muki Kiboigo
a133a71eb9 proper fetch method and body setting 2025-09-17 08:41:22 -07:00
Muki Kiboigo
dc2addb0ed fetch callback logging 2025-09-17 08:41:16 -07:00
Muki Kiboigo
f9014bb90c request url as null terminated 2025-09-17 08:41:11 -07:00
Muki Kiboigo
df0b6d5b07 initial fetch in zig 2025-09-17 08:40:32 -07:00
Muki Kiboigo
56c6e8be06 remove polyfill and add req/resp 2025-09-17 08:40:10 -07:00
Pierre Tachoire
b47b8297d6 Merge pull request #1021 from lightpanda-io/patchright
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Patchright compatibility
2025-09-17 16:14:00 +02:00
Pierre Tachoire
5d1e17c598 cdp: use for...else instead of found bool 2025-09-17 14:42:08 +02:00
Pierre Tachoire
94fe34bd10 cdp: multiple isolated worlds 2025-09-17 14:42:08 +02:00
Pierre Tachoire
e68ff62723 cdp: use depth param on DOM.describeNode 2025-09-17 14:42:08 +02:00
Pierre Tachoire
04487b6b91 cdp: allow double isolated world with same world name
In this case we reuse the existing isolated world and isolated context
and we log a warning
2025-09-17 14:42:07 +02:00
Pierre Tachoire
49a27a67bc cdp: send a warning for pierce parameter 2025-09-17 14:42:07 +02:00
Pierre Tachoire
745de2ede2 cdp: add Runtime.getProperties 2025-09-17 14:42:07 +02:00
Pierre Tachoire
82e5698f1d cdp: accept neg depth in describeNode 2025-09-17 14:42:06 +02:00
Pierre Tachoire
c4090851c5 css: accept digit as name start 2025-09-17 14:42:06 +02:00
Pierre Tachoire
9cb4431e89 cdp: add initiator on request will be send 2025-09-17 14:42:06 +02:00
Pierre Tachoire
2221d0cb6f cdp: send the chrome's error on missing node 2025-09-17 14:42:05 +02:00
Pierre Tachoire
5ea97c4910 cdp: add send error options with session id by default 2025-09-17 14:42:05 +02:00
Pierre Tachoire
a40590b4bf cdp: add DOM.getFrameOwner 2025-09-17 14:42:00 +02:00
Karl Seguin
58acb2b821 Convert more DOM_NO_ERR cases to assertions
There is some risk to this change. The first is that I made a mistake. The
other is that one of the APIs that doesn't currently return an error changes
in the future.
2025-09-17 13:37:48 +08:00
Karl Seguin
6b9dc90639 Introduces an Env.String for persistent strings
If a webapi has a []const u8 parameter, then the page.call_arena is used. This
is our favorite arena to use, but if the string value has a lifetime beyond the
call, it then needs to be duped again (using page.arena).

When a webapi has a Env.String parameter, the page.arena will be used directly
to get the value from V8, removing the need for an intermediary dupe in the
call_arena.

This allows HTMLCollections to be streamlined. They no longer need to dupe the
filter (tag name, class name, ...), which means they can no longer fail. It also
means that we no longer need to needlessly dupe the string literals.
2025-09-17 12:12:42 +08:00
Karl Seguin
b7d26cf0d5 Merge pull request #1053 from lightpanda-io/nikneym/create-event-html-events
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
allow `HTMLEvents` in `createEvent`
2025-09-17 00:31:36 +08:00
Pierre Tachoire
59b4033ab2 Merge pull request #1052 from lightpanda-io/fix-auth-interception-overflow
Fix auth interception integer overflow
2025-09-16 16:31:07 +02:00
nikneym
13a7219dbd allow HTMLEvents in createEvent 2025-09-16 17:24:50 +03:00
Pierre Tachoire
eae8a90a89 ci: add request interception through proxy test 2025-09-16 16:24:19 +02:00
Karl Seguin
a87f4abd5f Merge pull request #1050 from lightpanda-io/event_window_bubble
Event window bubble
2025-09-16 18:44:22 +08:00
Karl Seguin
1b73691c69 update libdom dep 2025-09-16 18:21:16 +08:00
Pierre Tachoire
e00066466b http: decrement intercepted on auth abortion 2025-09-16 12:18:49 +02:00
Pierre Tachoire
b87a8ba97d http: increment intercepted counter on auth interception 2025-09-16 12:18:49 +02:00
Karl Seguin
57aa270032 Merge pull request #1048 from lightpanda-io/nikneym/mime-changes
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Mime: charset identification changes
2025-09-16 16:13:46 +08:00
nikneym
90a96fd8a7 set a zero char right after attrib value instead of memset 2025-09-16 10:41:49 +03:00
nikneym
c05470515f double quotes must be first and last argument of slice if provided 2025-09-16 10:40:38 +03:00
Pierre Tachoire
81ed4f3699 Merge pull request #1051 from lightpanda-io/explicit_microtask
Set Isolate Microtask to Explicit
2025-09-16 09:38:33 +02:00
Karl Seguin
c9ac1eab11 Set Isolate Microtask to Explicit
This defaults to Auto, which means it runs when the call stack reaches 0.
It appears that both Node and Deno set this to explicit.

I don't really understand why Auto doesn't work. It says the call stack is the
C++/C callstack, and I don't see what would block the current code from reaching
a depth of 0. Still, we already have explicit calls to performMicrotasksCheckpoint
which ties it holistically with our scheduler, so having it be explicit like
this should...well make it more explicit

This broke a test, but since the tests are being redone in the [fetch PR](https://github.com/lightpanda-io/browser/pull/972) I simply removed the offending one.
2025-09-16 14:52:31 +08:00
Karl Seguin
1ba542fb3b use redispatch, check for stopped 2025-09-16 10:31:37 +08:00
Karl Seguin
4f127c9de3 Bubble events to the Window 2025-09-15 22:24:35 +08:00
Karl Seguin
16656f6c13 Merge pull request #1049 from lightpanda-io/netsurf_event_errors
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Remove unnecessary error handling from non-erroring netsurf event fun…
2025-09-15 21:55:18 +08:00
Karl Seguin
0f13e062fe Remove unnecessary error handling from non-erroring netsurf event functions 2025-09-15 21:37:53 +08:00
nikneym
2e68407fbe update Mime tests 2025-09-15 15:15:29 +03:00
nikneym
974f350f27 store charset value directly in Mime 2025-09-15 15:15:08 +03:00
nikneym
27ffea9052 add vectorized parseCharset impl 2025-09-15 11:15:09 +03:00
Pierre Tachoire
9b2b35e8a2 Merge pull request #1047 from lightpanda-io/ci-cache-libiconv
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: cache libiconv
2025-09-15 09:19:46 +02:00
Pierre Tachoire
3b51ca3947 make: download libiconv from GH fork 2025-09-15 08:59:30 +02:00
Pierre Tachoire
62a2d08b53 ci: cache libiconv 2025-09-15 08:43:48 +02:00
Pierre Tachoire
e790bde717 Merge pull request #1046 from lightpanda-io/fork-netsurf
submodule: use GH fork of netsurf buildsystem
2025-09-15 08:23:39 +02:00
Pierre Tachoire
0ab6b15292 submodule: use GH fork of netsurf buildsystem 2025-09-15 08:18:22 +02:00
Karl Seguin
2aeeb14c21 Merge pull request #1043 from lightpanda-io/html_slot_assigned_elements
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add assignedElements to HTMLSlotElement
2025-09-13 10:12:20 +08:00
Karl Seguin
e5e57ab3bd Merge pull request #1044 from lightpanda-io/script_nonce_and_df_host
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add Script get/set nonce
2025-09-12 20:45:50 +08:00
Karl Seguin
f3ce5dcfbd Add Script get/set nonce 2025-09-12 19:19:36 +08:00
Halil Durak
bc341e98fc Merge pull request #1041 from lightpanda-io/nikneym/keyboard-event
KeyboardEvent support
2025-09-12 13:57:39 +03:00
nikneym
80851f4861 don't inline keyboardEventKeyIsSet 2025-09-12 13:39:15 +03:00
nikneym
22b4456bce correct indentation in tests 2025-09-12 13:39:05 +03:00
nikneym
8d67502997 don't expose DOMErr function 2025-09-12 13:38:58 +03:00
nikneym
8f31fd778b add KeyboardEvent tests 2025-09-12 13:38:47 +03:00
nikneym
f79f25bcf4 implement KeyboardEvent properties and methods 2025-09-12 13:38:41 +03:00
nikneym
68e237eec5 add license 2025-09-12 13:38:31 +03:00
nikneym
8895c70c7f make toInterface be aware of KeyboardEvent 2025-09-12 13:38:04 +03:00
nikneym
3964f8649d initial keyboard event 2025-09-12 13:33:30 +03:00
Karl Seguin
b7fb0ef1d3 add assignedElements to HTMLSlotElement 2025-09-12 17:40:29 +08:00
Karl Seguin
66e403c5b4 Merge pull request #1042 from lightpanda-io/textdecoder_decode
Improve TextDecoder.decode
2025-09-12 14:34:14 +08:00
Karl Seguin
0913abe806 Improve TextDecoder.decode
1 - Optional input (why? I don't know, but it's part of the spec and happens)
2 - Optional stream parameter
3 - More test cases
2025-09-12 12:31:28 +08:00
Karl Seguin
6d3065c4c6 Merge pull request #1037 from lightpanda-io/upgrade_v8
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Upgrade v8
2025-09-12 08:29:20 +08:00
Karl Seguin
9092d1f8eb update v8 deps 2025-09-12 07:55:27 +08:00
Karl Seguin
1bd1f123a3 Upgrade v8
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/93
2025-09-12 07:53:22 +08:00
Karl Seguin
44c072dcbb Merge pull request #1040 from lightpanda-io/event_timestamp_resolution
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Increase event timeStamp resolution
2025-09-11 21:08:09 +08:00
Karl Seguin
45c59e2990 update libdom 2025-09-11 20:43:32 +08:00
Karl Seguin
75f0cd6e62 fix test 2025-09-11 16:10:06 +08:00
Karl Seguin
80f758018c Increase event timeStamp resolution
Depends on https://github.com/lightpanda-io/libdom/pull/36

The spec says this should be a High Definition timestamp. But browsers avoid
that in order to avoid fingerprinting. By default, FireFox rounds to 2ms (which
is what this PR does).

Previously, the timestamp was seconds, so you'd think: isn't that better? Well,
it's pretty far off the spec and what browsers do, but more importantly, it
crashes our WPT test. If you look at `Event-timestamp-safe-resolution.html`
you'll see that it's trying to find the delta between two timestamps, in an
endless loop (without a loop of many iterations). With second-resolution, it
just takes too long (and crashes..memory).
2025-09-11 15:53:34 +08:00
Karl Seguin
b5e2c62fdd Merge pull request #1039 from lightpanda-io/migrate_some_tests_11
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
migrate more tests to htmlRunner
2025-09-11 14:12:39 +08:00
Karl Seguin
ede35718ae migrate more tests to htmlRunner 2025-09-11 12:07:17 +08:00
Karl Seguin
31fe2807aa Merge pull request #1038 from lightpanda-io/migrate_some_tests_10
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
migrate more tests to htmlRunner
2025-09-11 00:24:17 +08:00
Karl Seguin
f77693d768 migrate more tests to htmlRunner 2025-09-10 20:32:15 +08:00
Pierre Tachoire
96e3c16cca Merge pull request #1036 from lightpanda-io/css-contains
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
CSS: move tests + implement :containsOwn
2025-09-10 13:59:27 +02:00
Karl Seguin
edd41b37f0 Merge pull request #1033 from lightpanda-io/nikneym/custom-event
Support for CustomEvent in document.createEvent
2025-09-10 15:23:34 +08:00
Karl Seguin
139d0038f2 Merge pull request #1035 from lightpanda-io/migrate_some_tests_9
migrate more tests to htmlRunner
2025-09-10 15:21:27 +08:00
Pierre Tachoire
d25fc64d7a css: implement :containsOwn pseudo-selector
:containsOwn is implemented with case sensitive comparison.
2025-09-10 08:55:44 +02:00
nikneym
9c83b268b9 persist the detail if provided 2025-09-10 09:49:31 +03:00
Pierre Tachoire
42092ac16a css: move match_test into selector 2025-09-10 08:41:15 +02:00
Pierre Tachoire
e4860d5bae css: move libdom_test into libdom
To be added by the test_runner as part of used files.
2025-09-10 08:41:14 +02:00
Karl Seguin
a5d9b658fb migrate more tests to htmlRunner 2025-09-10 11:54:03 +08:00
Karl Seguin
f464e89415 Merge pull request #1034 from lightpanda-io/persistent-promise
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Clean Up Persistent Promises in Dynamic Import
2025-09-10 08:39:50 +08:00
Karl Seguin
cdc439c4ef Merge pull request #1026 from lightpanda-io/network_idle_500ms_delay
Send NetworkIdle and NetworkAlmostIdle notifications after 500ms delay
2025-09-10 08:22:34 +08:00
Karl Seguin
746168f9ed Merge pull request #1031 from lightpanda-io/migrate_some_tests_8
migrate more tests to htmlRunner
2025-09-10 08:22:21 +08:00
Karl Seguin
5ad4885102 Merge pull request #1028 from lightpanda-io/wpt_runner_tweak
Try to address WPT running OOM
2025-09-10 08:22:08 +08:00
Muki Kiboigo
7eb53ca2bc deinit persistent in dynamic import 2025-09-09 15:02:26 -07:00
nikneym
10fc056184 createEvent should increase tag count by 1 2025-09-09 21:56:10 +03:00
nikneym
7517937155 add createEvent tests 2025-09-09 21:45:09 +03:00
nikneym
a86fa8cc37 add support for CustomEvent#initCustomEvent 2025-09-09 21:44:51 +03:00
nikneym
e1c765e78a support CustomEvent in createEvent 2025-09-09 21:44:09 +03:00
Karl Seguin
56b08bddd8 migrate more tests to htmlRunner 2025-09-09 20:40:19 +08:00
Karl Seguin
2ed8a1c0ec Merge pull request #1030 from lightpanda-io/update_libdom
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
this was already updated, but subsequent PR (by me) accidentally reve…
2025-09-09 19:53:54 +08:00
Pierre Tachoire
389dff7031 Merge pull request #1029 from lightpanda-io/remove_telemetry_debug_output
Remove a std.debug.print
2025-09-09 13:48:20 +02:00
Karl Seguin
123d69e595 this was already updated, but subsequent PR (by me) accidentally reverted it 2025-09-09 19:44:54 +08:00
Karl Seguin
4ab7fe26fc Merge pull request #1025 from lightpanda-io/migrate_some_tests_7
migrate more tests to htmlRunner
2025-09-09 19:41:56 +08:00
Karl Seguin
0aa1e0200f Merge branch 'wpt' into wpt_runner_tweak 2025-09-09 19:24:36 +08:00
Karl Seguin
575f827958 disable telemetry when running WPT action 2025-09-09 19:23:31 +08:00
Karl Seguin
7136851893 Remove a std.debug.print
Probably added in the Zig 0.15 migration. Sorry.
2025-09-09 19:19:36 +08:00
Karl Seguin
67935b11c9 Try to address WPT running OOM
- Continue to reuse the Browser/Env/Isolate, but no start a new session per test.
- Test http server now properly closes the sendFile fd
- Run WPT in ReleaseMode
- Add --quiet option to WPT and some commented out debug code for dumping v8
  memory stats
2025-09-09 19:15:35 +08:00
Pierre Tachoire
85f60cbc7b Merge pull request #1027 from lightpanda-io/libcurl-readme
add libcurl in the readme
2025-09-09 11:24:55 +02:00
Pierre Tachoire
9c35f8a24e add libcurl in the readme 2025-09-09 11:22:56 +02:00
Karl Seguin
9971de2ccd Send NetworkIdle and NetworkAlmostIdle notifications after 500ms delay
Like Chrome, the NetworkIdle and NetworkAlmostIdle will only be sent if the
condition (no network requests / <= 2 network requests) holds for at least 500ms

Also merged runHighPriority and runLowPriority as they are now always run
together (but we still only block/wait for high priority tasks).
2025-09-09 14:06:03 +08:00
Karl Seguin
1ca8dc0ac0 Merge pull request #1022 from lightpanda-io/slot
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Start working on HTMLSlotElement
2025-09-09 11:52:04 +08:00
Karl Seguin
85d148822e migrate more tests to htmlRunner 2025-09-09 11:48:08 +08:00
Karl Seguin
1e738dcf79 Merge pull request #1023 from lightpanda-io/migrate_some_tests_6
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
migrate more tests to htmlRunner
2025-09-08 20:58:41 +08:00
Karl Seguin
b5ffd8d046 Merge pull request #1024 from lightpanda-io/run_distant_tasks
Ability to run tasks even in the "distant" future.
2025-09-08 20:58:30 +08:00
Karl Seguin
21e354d252 Ability to run tasks even in the "distant" future.
We previously ignored tasks scheduled more than 5 seconds away. These tasks are
now scheduled on the low priority queue. This means that they won't stop a
page.wait for returning, but they'll still [eventually] be run if page.wait is
called multiple times.

Practically, this means that they'll never be run in `fetch` mode, but they
might be run from CDP if the driver waits.

Make queue names consistent, primary => high_priority, secondary => low_priority
(the same names used by the page)
2025-09-08 18:55:48 +08:00
Karl Seguin
15628d9b07 migrate more tests to htmlRunner 2025-09-08 18:40:59 +08:00
Karl Seguin
950182986a Start working on HTMLSlotElement 2025-09-08 17:36:45 +08:00
Pierre Tachoire
bc82023878 Merge pull request #1020 from lightpanda-io/inline_script_ignore_defer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Inline script tags ignore defer/async
2025-09-05 17:44:45 +02:00
Pierre Tachoire
d5363e5993 Merge pull request #1018 from lightpanda-io/fix_screen_event_target_prototype
Fix the Screen and ScreenOrientation prototype
2025-09-05 17:44:09 +02:00
Pierre Tachoire
80adee8558 Merge pull request #1017 from lightpanda-io/fix_async_script_processing
Fix blockingGet during blockingGet
2025-09-05 17:43:40 +02:00
Pierre Tachoire
37fe6a661b Merge pull request #1013 from lightpanda-io/reset_request_method
Reset CURLOPT_CUSTOMREQUEST for each request
2025-09-05 17:43:30 +02:00
Karl Seguin
eb453f471b Inline script tags ignore defer/async
According to MDN, inline script tags should not have defer/async attributes. But
some do. This ignores those attributes for inline script tags.

(Previously, we were only half ignoring them. We were treating them as inline,
but flagging them as deferred or async, which was causing issues with cleanup)

Fixes: https://github.com/lightpanda-io/browser/issues/1014
2025-09-05 23:23:31 +08:00
Karl Seguin
afd278ca4e Fix the Screen and ScreenOrientation prototype 2025-09-05 19:08:07 +08:00
Karl Seguin
ca8877da2d Fix blockingGet during blockingGet
ScriptManager should only ever has one in-flight blockingGet. The is_blocking
flag is used to assert this, as well as enforce it in evaluate(). If is_blocking
is true, evaluate() exits.

This doesn't work for async scripts however, as they aren't executed via
evaluate(), but rather execute directly once complete.

This PR changes the execution behavior of async scripts. They are now only
executed in evaluate() (and thus won't execute when is_blocking == true).
However, unlike normal/deferred scripts, async scripts continue to execute in
their completion order (not their declared order).

Fixes https://github.com/lightpanda-io/browser/issues/1016
2025-09-05 18:17:55 +08:00
Pierre Tachoire
42828c64fb Merge pull request #1012 from lightpanda-io/cdp_detached
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Don't assume that page events means the BrowserContext has a page
2025-09-05 10:19:18 +02:00
Karl Seguin
6600626f4f Reset CURLOPT_CUSTOMREQUEST for each request 2025-09-05 15:45:28 +08:00
Karl Seguin
ac10d5b2a3 Don't assume that page events means the BrowserContext has a page
CDP currently assumes that if we get a page-related notification (like a
request interception, or page lifecycle event), then we must have a session
and page.

But, Target.detachFromTarget can remove the session from the BrowserContext
while still having the page run (I wonder if we should stop the page at this
point??). So, remove these assumptions and make sure we have a page/session
in the handling of page events.
2025-09-05 15:07:30 +08:00
Pierre Tachoire
9f040025e7 Merge pull request #1010 from lightpanda-io/update_transfer_uri_on_redirect
Update the transfer.uri on redirect
2025-09-05 08:35:13 +02:00
Karl Seguin
2522e7fe9c Merge pull request #1011 from lightpanda-io/migrate_some_tests_5
migrate to htmlRunne (plus zig fmt)
2025-09-05 14:16:10 +08:00
Karl Seguin
dd22c55d23 migrate to htmlRunne (plus zig fmt) 2025-09-05 13:52:08 +08:00
Karl Seguin
a6efa9e9b2 Update the transfer.uri on redirect
Ensures that cookies set on the redirect page use the correct host and we don't
incorrectly reject cookies.

https://github.com/lightpanda-io/browser/issues/947
2025-09-05 08:55:36 +08:00
Karl Seguin
5087b8004a Merge pull request #1009 from lightpanda-io/migrate_some_tests_4
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
migrate to htmlRunner
2025-09-04 18:32:17 +08:00
Karl Seguin
d4c40242d0 Merge pull request #1008 from lightpanda-io/network_idle_page_lifecycle
Emit networkIdle and networkAlmostIdle Page.lifecycleEvent
2025-09-04 17:48:02 +08:00
Karl Seguin
5af55f1d5d migrate to htmlRunner 2025-09-04 17:46:42 +08:00
Karl Seguin
55ef0a5e9e fix some spelling in comments 2025-09-04 16:44:00 +08:00
Karl Seguin
5dda86bf4a Emit networkIdle and networkAlmostIdle Page.lifecycleEvent
Most CDP drivers have a mechanism to wait for idle network, or an almost idle
network (sometimes called networkIdle2). These are events the browser must emit.

The page will now emit `networkIdle` when we are reasonably sure there's no more
network activity (this requires some slight changes to request interception,
since, I believe, intercepted requests should be considered).

`networkAlmostIdle` is currently _always_ emitted prior to emitting
`networkIdle`. We should tweak this but I can't, at a glance, think of a great
heuristic for when this should be emitted.
2025-09-04 16:36:29 +08:00
Karl Seguin
d81377b10d Merge pull request #1007 from lightpanda-io/timeout_limit
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Limit serve timeout to 1 week
2025-09-04 16:02:39 +08:00
Karl Seguin
da128f5d49 remove unecessary @intCast 2025-09-04 15:52:08 +08:00
Karl Seguin
6e5fe8e4a2 Add timeout limit to --help text 2025-09-04 15:48:01 +08:00
Karl Seguin
b3d350d41e Limit serve timeout to 1 week 2025-09-04 15:27:03 +08:00
Karl Seguin
7c6870f8eb Merge pull request #1006 from lightpanda-io/migrate_some_tests_3
migrate to htmlRunner
2025-09-04 13:18:44 +08:00
Karl Seguin
327b4e4e37 migrate to htmlRunner 2025-09-04 13:11:15 +08:00
Karl Seguin
7fdc857326 Merge pull request #1004 from lightpanda-io/migrate_some_tests_2
Migrate some tests 2
2025-09-04 12:19:36 +08:00
Karl Seguin
0382c2775e Migrate more tests to html runner
Implement LocalStorage named get/set (i.e. localStorage["hi"])
2025-09-03 22:54:41 +08:00
Karl Seguin
a0374133cd migrate tests to new html runner 2025-09-03 22:54:40 +08:00
Karl Seguin
055f697340 Merge pull request #1005 from lightpanda-io/e2e-output
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
ci: remove go runner verbose mode
2025-09-03 22:44:35 +08:00
Pierre Tachoire
ec8a9862c7 ci: remove go runner verbose mode 2025-09-03 15:42:35 +02:00
Karl Seguin
f393eb7b7d Merge pull request #1003 from lightpanda-io/http_always_monitor_cdp
Http always monitor cdp
2025-09-03 20:35:49 +08:00
Karl Seguin
78285d7b1e fix tests 2025-09-03 20:23:59 +08:00
Karl Seguin
b6137b03cd Rework page wait again
Further reducing bouncing between page and server for loop polling. If there is
a page, the page polls. If there isn't a page, the server polls. Simpler.
2025-09-03 19:38:01 +08:00
Karl Seguin
e237e709b6 Change loader id on navigation
This appears to be what chrome is doing. I don't know why we weren't before.
2025-09-03 08:17:14 +08:00
Karl Seguin
2ac9b2088a Always monitor the CDP client socket, even on page.wait 2025-09-03 08:17:13 +08:00
Karl Seguin
a791212d89 Merge pull request #1002 from lightpanda-io/nix-0.15.1
Some checks failed
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Update Nix Flake (Zig 0.15.1)
2025-09-03 08:07:34 +08:00
Muki Kiboigo
5cc5f45ef3 update zig-v8-fork 2025-09-02 09:25:33 -07:00
Muki Kiboigo
a11e50c087 nix flake for zig 0.15.1 2025-09-02 08:58:31 -07:00
Pierre Tachoire
4dc09360a1 Merge pull request #1001 from lightpanda-io/fix_abort_crash
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
fix segfault on abort if there are queued transfers
2025-09-02 15:45:51 +02:00
Karl Seguin
3a5528cc4d Merge pull request #1000 from lightpanda-io/log_unhandled_promise_rejections
Log unhandled promise rejection
2025-09-02 21:18:28 +08:00
Karl Seguin
de533755e5 fix segfault on abort if there are queued transfers 2025-09-02 21:18:02 +08:00
Karl Seguin
024b69ee3e update v8 dep 2025-09-02 19:48:56 +08:00
Karl Seguin
d7e7832e9f Log unhandled promise rejection 2025-09-02 18:19:28 +08:00
Karl Seguin
8d4d72bf15 Merge pull request #998 from lightpanda-io/migrate_some_tests_1
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Migrate some tests to the new htmlRunner
2025-09-02 16:11:08 +08:00
Pierre Tachoire
86a82d55fa Merge pull request #999 from lightpanda-io/handle_no_certs
Don't panic if no certs are available
2025-09-02 08:03:44 +02:00
Karl Seguin
5a15066da3 Don't panic if no certs are available
https://github.com/lightpanda-io/browser/issues/982
2025-09-02 13:50:53 +08:00
Karl Seguin
81766c8517 Migrate some tests to the new htmlRunner
Fix events.get_timeStamp (was events.get_timestamp, wrong casing).

Rename `newRunner` to `htmlRunner`.

move tests to src/tests (from src/browser/tests). src/runtime and possibly other
parts might want to have html tests too.
2025-09-02 10:40:04 +08:00
Karl Seguin
e486f28a41 Merge pull request #995 from lightpanda-io/improved_test_runner
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Looking for feedback on new test runner
2025-09-02 07:45:41 +08:00
Karl Seguin
8a9cbaf413 explicitly load testing.js 2025-09-02 07:38:03 +08:00
Karl Seguin
3a0a930b79 don't log 'long timeout ignored' during testing 2025-09-02 07:38:03 +08:00
Karl Seguin
c40704d2f3 Prototype new test runner
Follows up on https://github.com/lightpanda-io/browser/pull/994 and replaces
the jsRunner with a new page.navigation-based test runner.

Currently only implemented for the Window tests, looking for feedback and
converting every existing test will take time - so for a while, newRunner (to be
renamed) will sit side-by-side with jsRunner.

In addition to the benefits outlined in 994, largely around code simplicity and
putting more of the actual code under tests, I think our WebAPI tests
particularly benefit from:
1 - No need to recompile when modifying the html tests
2 - Much better assertions, e.g. you can assert that something is actually an
    array, not just a string representation of an array
3 - Ability to test some edge cases (e.g. dynamic script loading)

I've put some effort into testing.js to make sure that, if the encapsulating
zig test passes, it's because it actually passed, not because it didn't run.

For the time being, console tests are removed. I think it's more useful to have
access to the console within tests, than it is to test the console (which is
just a wrapper around log, which is both tested and heavily used).
2025-09-02 07:38:02 +08:00
Karl Seguin
c0f0630e17 Merge pull request #997 from lightpanda-io/fix_build
fix build
2025-09-02 07:24:02 +08:00
Karl Seguin
19dbb3a778 fix build 2025-09-02 07:06:57 +08:00
Karl Seguin
d4fc6f1b35 Merge pull request #996 from lightpanda-io/revert-document-element
Revert "document.documentElement returns a *parser.Element"
2025-09-02 06:52:16 +08:00
Karl Seguin
7c82942912 Merge pull request #994 from lightpanda-io/test_http_server
Test http server
2025-09-02 06:51:52 +08:00
Karl Seguin
87d48b028b Merge pull request #992 from lightpanda-io/http_buffer_presize
Pre-size the destination buffer when we know the response content length
2025-09-02 06:51:15 +08:00
Pierre Tachoire
d6640f4d15 Revert "document.documentElement returns a *parser.Element"
This reverts commit c1752ae5eb.
2025-09-01 15:46:16 +02:00
Karl Seguin
785a8da623 remove content-length limit 2025-09-01 18:53:00 +08:00
Karl Seguin
57dc303d90 Make getContentLength work on fulfilled responses 2025-09-01 18:40:50 +08:00
Pierre Tachoire
ce08cc9659 Merge pull request #993 from lightpanda-io/remove_unsafe_undefine
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Remove [some] unsafe undefines from netsurf wrapper
2025-09-01 08:26:41 +02:00
Pierre Tachoire
866393743c Merge pull request #991 from lightpanda-io/mimalloc_assertions
Switch mimalloc guards to assertions
2025-09-01 08:12:21 +02:00
Pierre Tachoire
ba255aa653 Merge pull request #989 from lightpanda-io/clocks
Improve clocks
2025-09-01 08:11:05 +02:00
Karl Seguin
7d46e8fe80 Start unifying test and code
Depends on https://github.com/lightpanda-io/browser/pull/993

There's currently 3 ways to execute a page:
1 - page.navigate (as used in both the 'fetch' and 'serve' commands)
2 - jsRunner as used in unit tests
3 - main_wpt as used in the WPT runner

Both jsRunner and main_wpt replicate the page.navigate code, but in their own
hack-ish way. main_wpt re-implements the DOM walking in order to extract and
execute <script> tags, as well as the needed page lifecycle events.

This PR replaces the existing main_wpt loader with a call to page.navigate. To
support this, a test HTTP server was added. (The test HTTP server is extracted
from the existing unit test test server, and re-used between the two).

There are benefits to this approach:
1 - The code is simpler
2 - More of the actual code and flow is tested
3 - There's 1 way to do things (page.navigate)
4 - Having an HTTP server might unlock some WPT tests

Technically, we're replacing file IO with network IO i.e. http requests). This
has potential downsides:
1 - The tests might be more brittle
2 - The tests might be slower

I think we need to run it for a while to see if we get flaky behavior.

The goal for following PRs is to bring this unification to the jsRunner.
2025-09-01 13:01:08 +08:00
Karl Seguin
6c41245c73 Remove [some] unsafe undefines from netsurf wrapper
Code like this:

```
var body: ?*ElementHTML = undefined;
const err = documentHTMLVtable(doc_html).get_body.?(doc_html, &body);
try DOMErr(err);
if (body == null) return null;
return @as(*Body, @ptrCast(body.?));
```

Isn't safe. It assumes that libdom will either return an error, or set body
to null or a value. However, there are cases (specifically for this API) where
libdom returns DOM_NO_ERR without ever setting body. In such cases, we return
a `body` initialized to a random (invalid) value.

This PR replaces the initial value of optional types from undefined to null
(within the libdom wrapper).
2025-09-01 11:41:37 +08:00
Karl Seguin
2a8e51c2d2 Pre-size the destination buffer when we know the response content length 2025-08-31 20:14:55 +08:00
Karl Seguin
b2cf5df612 Switch mimalloc guards to assertions
The thin mimalloc API is currently defensive around incorrect setup/teardown by
guarding against using/destroying the arena when the heap is null, or creating
an arena when it already exists.

The only time these checks will fail is when the code is wrong, e.g. trying
to use libdom before or after freeing the arena. The current behavior can mask
these errors, plus add runtime overhead.
2025-08-31 19:35:53 +08:00
Karl Seguin
ada9ddd5b8 Improve clocks
There's a flaky performance test that I wanted to fix (1). This led to a couple
changes.

1 - Add timestamp() and milliTimestamp() to datetime.zig. Reduce some code
    duplication and use better clock_ids where available

2 - Change Performance API to use milliTimestamp and store a u64 instead of a
    f64. While the spec says a float, Firefox deals with u64 and implicit
    conversion is always available. Makes our APIs simpler.

(1) - https://github.com/lightpanda-io/browser/actions/runs/17313296490/job/49151366798#step:4:131
2025-08-30 13:45:12 +08:00
Karl Seguin
f66f4d9aeb Merge pull request #987 from lightpanda-io/improve_server_shutdown
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Ignore ConnectionClosed error on server shutdown
2025-08-30 12:35:12 +08:00
Pierre Tachoire
d02ba777f2 Merge pull request #984 from lightpanda-io/zig.0.15.1
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Zig 0.15.1
2025-08-29 10:33:00 +02:00
Karl Seguin
aef614823b Ignore ConnectionClosed error on server shutdown
Our shutdown could be cleaner, but this at least removes a meaningless (because
we're shutting down) log.err that was happening on every test run.
2025-08-29 16:21:26 +08:00
Karl Seguin
431db85ecb Merge pull request #978 from lightpanda-io/dynamic_cdp_read_buffer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Make the CDP read buffer heap allocated & dynamic
2025-08-29 12:20:58 +08:00
Karl Seguin
1ebac06f4b add debug line on cdp buffer growth 2025-08-29 10:55:36 +08:00
Karl Seguin
c7c5af4708 zig fmt 2025-08-29 10:51:19 +08:00
Karl Seguin
0b6a9d3a0b use llvm. The new x86 backend crashes with v8. 2025-08-29 10:42:07 +08:00
Karl Seguin
23d6362058 fix telemetry, link libc and libcpp 2025-08-29 10:42:06 +08:00
Karl Seguin
1443f38e5f Zig 0.15.1
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/89
2025-08-29 10:42:06 +08:00
Karl Seguin
94960cc842 Merge pull request #979 from lightpanda-io/app_owns_platform
App owns platform
2025-08-29 10:33:55 +08:00
Karl Seguin
efc983b009 Start with 16K buffer (down from 32K). Use array list growth algorithm 2025-08-29 10:33:27 +08:00
Karl Seguin
74d90f2892 fix tests 2025-08-29 10:14:59 +08:00
Karl Seguin
56f1b6cc19 Make the CDP read buffer heap allocated & dynamic
Rather than stack-allocating MAX_MESSAGE_SIZE upfront, we now allocate 32KB
and grow the buffer as needed for larger messages, up to MAX_MESSAGE_SIZE.

This will reduce memory usage for drivers that don't send huge payloads (like
playwright does).

While not implemented, this would also enable us to set the MAX_MESSAGE_SIZE
at runtime (e.g. via a command line option).
2025-08-29 10:14:58 +08:00
Karl Seguin
fa2cd9dfd9 Ability to start/stop CDP server.
Exists for cleaning up after tests.
2025-08-29 10:14:08 +08:00
Karl Seguin
687f09d952 Make the App own the Platform
Removes optional platform, which only existed for tests.

There is now a global `@import("testing.zig").test_app` available. This is setup
when the test runner starts, and cleaned up at the end of tests. Individual
tests don't have to worry about creating app, which I assume was the reason I
Platform optional, since that woul dhave been something else that needed to be
setup.
2025-08-29 10:14:06 +08:00
Karl Seguin
67b479b5c8 Merge pull request #983 from lightpanda-io/sigint
exit the browser on SIGINT signal
2025-08-29 10:10:49 +08:00
Pierre Tachoire
eac2140693 Merge pull request #986 from lightpanda-io/readme-interception
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
README: check request interception
2025-08-28 17:19:32 +02:00
Pierre Tachoire
7a3f5de9c2 Merge pull request #985 from lightpanda-io/fulfill-content-type-len
http: set content_type len on fulfill request
2025-08-28 17:19:23 +02:00
Pierre Tachoire
7005bf2481 README: check request interception 2025-08-28 17:18:42 +02:00
Pierre Tachoire
b80ee3342c http: set content_type len on fulfill request 2025-08-28 16:28:41 +02:00
Pierre Tachoire
4c7b7b1e60 handle graceful shutdown 2025-08-28 12:44:16 +02:00
Pierre Tachoire
1a4a3608c8 exit the browser on SIGINT signal 2025-08-28 12:44:12 +02:00
Pierre Tachoire
6800d50339 Merge pull request #981 from lightpanda-io/page-navigate-event
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
page: ensure page navigate events order
2025-08-27 18:23:22 +02:00
Pierre Tachoire
036f808ec6 page: ensure page navigate events order 2025-08-27 17:36:36 +02:00
Pierre Tachoire
7647ce9e6d Merge pull request #960 from lightpanda-io/auth-challenge
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
auth required interception
2025-08-27 15:34:51 +02:00
Karl Seguin
545d3f81ce Merge pull request #977 from lightpanda-io/selector_by_ref
Select is relatively large (64 bytes), pass it by ref
2025-08-27 19:37:36 +08:00
Pierre Tachoire
455615b9c1 Merge pull request #980 from lightpanda-io/update-docker-readme
Update docker readme
2025-08-27 09:32:41 +02:00
Pierre Tachoire
d0e2a03da5 README: proxy support is ready 2025-08-27 09:30:43 +02:00
Pierre Tachoire
fa408e644c cs fix 2025-08-27 09:26:10 +02:00
Pierre Tachoire
a22416584d README: --privileged is not needed anymore 2025-08-27 09:25:51 +02:00
Karl Seguin
b8fc60df45 Merge pull request #975 from lightpanda-io/dynamic_script
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Support dynamic scripts which are added to the DOM before src is set
2025-08-27 05:59:28 +08:00
Karl Seguin
c6455cf02e Select is relatively large (64 bytes), pass it by ref 2025-08-27 05:55:04 +08:00
Pierre Tachoire
2ac1d39367 Merge pull request #976 from lightpanda-io/webapi_file_placeholder
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
The most basic File implementation.
2025-08-26 18:20:53 +02:00
Pierre Tachoire
041e014d68 Merge pull request #970 from lightpanda-io/remove_loop
Remove the loop
2025-08-26 18:17:32 +02:00
Pierre Tachoire
5defb5c442 http: build headers when auth challenge fails 2025-08-26 18:05:45 +02:00
Pierre Tachoire
520a572bb4 http: add reset and tries for transfer 2025-08-26 18:05:45 +02:00
Pierre Tachoire
4c602256da http: remove useless field 2025-08-26 18:05:45 +02:00
Pierre Tachoire
5a40cbd655 cdp: use enum for AuthChallengeResponse 2025-08-26 18:05:45 +02:00
Pierre Tachoire
a75f9dd48d cdp: set default username/passwd for authChallengeResponse 2025-08-26 18:05:44 +02:00
Pierre Tachoire
6b47aa2446 cdp: add auth required interception process 2025-08-26 18:05:44 +02:00
Pierre Tachoire
a847a1faae http: replace _forbidden with _auth_challenge struct 2025-08-26 18:05:44 +02:00
Pierre Tachoire
bb381e522c http: add creds into request 2025-08-26 18:05:39 +02:00
Karl Seguin
6962cfb91a Merge pull request #973 from lightpanda-io/no-body-response
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Handle response without body
2025-08-26 18:44:22 +08:00
Pierre Tachoire
302c50a90e Merge pull request #964 from lightpanda-io/proxy-header
http: refacto headerCallback and get proxy CONNECT request details
2025-08-26 10:53:43 +02:00
sjorsdonkers
e2d47e1c86 fix merge conflict 2025-08-26 10:12:07 +02:00
Pierre Tachoire
7d51da1efb Merge pull request #974 from lightpanda-io/ignore_non_js_script_tags
Removes the log for unknown script tags
2025-08-26 08:53:29 +02:00
Karl Seguin
c7674926c3 The most basic File implementation.
Almost silly as-is, but handles this case:

```
if (input instanceof File) {
   throw Error('file not supported')
}
```

as seen on reddit.
2025-08-26 13:25:30 +08:00
Karl Seguin
f0ca9728ae Support dynamic scripts which are added to the DOM before src is set
This should load the "src.js":

```
const s = document.createElement('script');
document.getElementsByTagName('body')[0].appendChild(s);
s.src = "src.js"
```

Notice that src is set AFTER the element is added to the DOM. This PR enables
the above, by
1 - skipping dynamically added scripts which don't have a src
2 - trying to load a script whenever `set_src` is called.

(2) is safe because the ScriptManager already prevents scripts from being
processed multiple times.

Additionally, not only can the src be set after the script is added to the DOM,
but onload and onerror can be set after the src:

```
s.src = "src.js"
s.onload = ...;
s.onerror = ...;
```

This PR also delays reading the onload/onerror callbacks until the script is
done loading.

This behavior is seen on reddit.
2025-08-26 13:10:55 +08:00
Karl Seguin
5fa8567801 Removes the log for unknown script tags
Some sites have a lot of text/template or application/json, and it just adds
noise to the logs.
2025-08-26 08:48:29 +08:00
sjorsdonkers
e5b1acb6e1 Handle response without body 2025-08-25 18:07:02 +02:00
Karl Seguin
8fdbaef4c7 Use posix.TCP.NODELAY now that it's available in MacOS also 2025-08-25 22:03:58 +08:00
Pierre Tachoire
7869159657 add e2e test through proxy 2025-08-25 14:18:15 +02:00
Pierre Tachoire
7046e18d7e http: simplify header parsing 2025-08-25 14:18:14 +02:00
Pierre Tachoire
a7516061d0 http: move use_proxy from connection to client 2025-08-25 14:18:14 +02:00
Pierre Tachoire
e61d787ff0 http: move header done callback in its own func
And call it only after the headers are parsed, either from data callback
or end of the request.
2025-08-25 14:18:14 +02:00
Pierre Tachoire
25ad420f85 http: ajust header callback according to review 2025-08-25 14:18:14 +02:00
Pierre Tachoire
fcd49c000f page: avoid crash on empty body 2025-08-25 14:18:13 +02:00
Pierre Tachoire
e2320ebe66 http: handle proxy's request header callback 2025-08-25 14:18:13 +02:00
Pierre Tachoire
5e78a26e3d http: refacto http header parsing 2025-08-25 14:18:13 +02:00
Pierre Tachoire
159bd06a56 http: add use_proxy bool in connection 2025-08-25 14:18:12 +02:00
Pierre Tachoire
bc7e1e07f4 typo fix 2025-08-25 14:18:08 +02:00
Karl Seguin
ccc9618102 Merge pull request #971 from lightpanda-io/fix-send-error-json-format
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Fix sendError message's format
2025-08-25 19:05:47 +08:00
sjorsdonkers
0ad09cca9d Fix sendError message's format 2025-08-25 12:51:47 +02:00
Karl Seguin
0959eea677 Remove the loop
Previously, the IO loop was doing three things:
1 - Managing timeouts (either from scripts or for our own needs)
2 - Handling browser IO events (page/script/xhr)
3 - Handling CDP events (accept, read, write, timeout)

With the libcurl merge, 1 was moved to an in-process scheduler and 2 was moved
to libcurl's own event loop. That means the entire loop code, including
the dependency on tigerbeetle-io existed for handling a single TCP client.
Not only is that a lot of code, there was also friction between the two loops
(the libcurl one and our IO loop), which would result in latency - while one
loop is waiting for the events, any events on the other loop go un-processed.

This PR removes our IO loop. To accomplish this:

1 - The main accept loop is blocking. This is simpler and works perfectly well,
given we only allow 1 active connection.
2 - The client socket is passed to libcurl - yes, libcurl's loop can take
arbitrary FDs and poll them along with its own.

In addition to having one less dependency, the CDP code is quite a bit simpler,
especially around shutdowns and writes. This also removes _some_ of the latency
caused by the friction between page process and CDP processing. Specifically,
when CDP now blocks for input, http page events (script loading, xhr, ...) will
still be processed.

There's still friction. For one, the reverse isn't true: when the page is
waiting for events, CDP events aren't going to be processed. But the page.wait
already have some sensitivity to this (e.g. the page.request_intercepted flag).
Also, when CDP waits, while we will process network events, page timeouts are
still not processed. Because of both these remaining issues, we still need to
jump between the two loops - but being able to block on CDP (even for a short
time) WITHOUT stopping the page's network I/O, should reduce some latency.
2025-08-25 17:27:28 +08:00
Pierre Tachoire
3316f2fcf4 Merge pull request #968 from lightpanda-io/normalize_cdp_response_headers
Normalize CDP response headers
2025-08-25 09:31:56 +02:00
Karl Seguin
390a21e4aa Merge pull request #969 from lightpanda-io/fix_wpt_runner
Handle all case status (not just Pass and Fail)
2025-08-25 10:49:46 +08:00
Karl Seguin
70ce54a5cd Handle all case status (not just Pass and Fail) 2025-08-25 10:40:23 +08:00
Karl Seguin
087e42a641 Normalize CDP response headers
chromedb doesn't support duplicate header names. Although servers _will_ send
this (e.g. Cache-Control: public\r\nCache-Control: max-age=60\r\n), Chrome
seems to join them with a "\n". So we do the same.

A note on curl_easy_nextheader, which this code ultimately uses to iterate
and collect the headers. The documentation says:

    Applications must copy the data if they want it to survive subsequent API
    calls or the life-time of the easy handle.

As-is, I'd understand this to mean that a given header name/value is only
valid until any API call, including another call to curl_easy_nextheader. So,
from this comment, we _should_ be duping the name/value. But we don't. Why?
Because, despite the note in the documentation, this doesn't appear to be how
it actually works, nor does it really make sense. If it's just a linked list,
there's no reason curl_easy_nextheader should invalidate previous results. I'm
guessing this is just a general lack of guarantee libcurl is willing to make re
lifetimes.

https://github.com/lightpanda-io/browser/issues/966
2025-08-25 09:25:15 +08:00
Karl Seguin
e26d4afce2 Merge pull request #963 from lightpanda-io/wpt_runner_fix_and_nodeiterator_tweak
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Improves correctness of NodeIterator
2025-08-22 15:29:42 +08:00
Karl Seguin
b9ae4c6077 Update src/runtime/js.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-08-22 15:17:59 +08:00
Pierre Tachoire
11485d24f5 Merge pull request #962 from lightpanda-io/compareBoundaryPoints
Add Range.compareBoundaryPoints
2025-08-22 09:08:22 +02:00
Karl Seguin
ce14f0b380 Improves correctness of NodeIterator
Minor improvement to correctness of TreeWalker.

Fun fact, this is the first time, that I've run into, where we have to default
null and undefined to different values.

Also, tweaked the WPT test runner. WPT test results use | as a field delimiter.
But a WPT test (and, I assume a message) can contain a |. So we had at least
a few tests that were being reported as failed, only because the result line
was weird / unexpected. No great robust way to parse this, but I changed it
to look explicitly for |Pass or |Fail and use those positions as anchor points.
2025-08-21 18:11:48 +08:00
Karl Seguin
8bb2158a2a Add Range.compareBoundaryPoints
Also rename start_container and end_container to start_node and end_node.
2025-08-21 16:47:33 +08:00
Karl Seguin
1a9d4af565 Merge pull request #961 from lightpanda-io/cdp_getResponseBody
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Implement Network.getResponseBody
2025-08-21 16:19:07 +08:00
Pierre Tachoire
a6f37633a1 Merge pull request #959 from lightpanda-io/html-pre
handle text content type with HTML
2025-08-21 09:38:36 +02:00
Pierre Tachoire
3182a47858 typo fix 2025-08-21 08:52:35 +02:00
Pierre Tachoire
7335b1d0a4 escape incoming plain text 2025-08-21 08:52:34 +02:00
Karl Seguin
cd33e9ad0e Implement Network.getResponseBody
Add response_data event, CDP now captures the full body so that it can respond
to the Network.getResponseBody. This isn't memory efficient, but I don't see
another way to do it. At least this way, it's only capturing/storing every
response body when (a) CDP is used and (b) Network.enabled is called. That is,
as opposed to baking this into Http/Client.zig, which would force the memory
consumption for all use-cases.

There's arguably some optimizations we could make for XHR requests, which also
dupe/own the response. As of now, the response is dupe'd separately for CDP
and XHR.
2025-08-21 10:33:53 +08:00
Karl Seguin
557f8444b2 Merge pull request #955 from lightpanda-io/replace_deprecated_build
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Makes build.zig Zig 0.15 ready
2025-08-21 10:09:59 +08:00
Karl Seguin
65088b8644 swap unnecessary addModule with createModule 2025-08-21 09:59:42 +08:00
Karl Seguin
7cc9521cbb Merge pull request #958 from lightpanda-io/http_request_done_notification
Emits a http_request_done internal notification.
2025-08-21 09:23:41 +08:00
Karl Seguin
4ad19fc4d8 use merged v8 commit 2025-08-21 09:23:11 +08:00
Pierre Tachoire
ec71f8e2d9 handle text content type with HTML
For text content type (and application/json) we create a pseudo HTML
tree with the text value in a <pre> tag.

It allows CDP clients to interact with text content easily.
2025-08-20 15:27:15 +02:00
Pierre Tachoire
ff8a847795 Merge pull request #957 from lightpanda-io/remove_header_callback
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Remove the http/Client.zig header_callback.
2025-08-20 14:35:06 +02:00
Karl Seguin
6b001c50a4 Emits a http_request_done internal notification.
With networking enabled, CDP listens to this event and emits a
`Network.loadingFinished` event. This is event is used by puppeteer to know that
details about the response (i.e. the body) can be queries.

Added dummy handling for the Network.getResponseBody message. Returns an
empty body. Needed because we emit the loadingFinished event which signals
to drivers that they can ask for the body.
2025-08-20 19:32:19 +08:00
Karl Seguin
5759c88932 Remove the http/Client.zig header_callback.
The callback which was called on a per-header basis is removed. Only XHR was
using this, and it was created before the HeaderIterator existed (because I
didn't know we could iterate through the response headers in curl after the fact).

The header_done_callback remains, but is now called header_callback (a bit
confusing in the short term).

The only difficulty was with fulfilled requests, which do not have an easy
handle for our HeaderIterator. The existing code would segfault if
transfer.responseHeaderIterator() was called on a fulfilled requests.
The HeaderIterator is now a tagged union that abstracts whether the source of
the response header is a curl easy, or just an injected list from the fulfilled
requests.
2025-08-20 17:49:37 +08:00
Karl Seguin
00c11d9bd4 Merge pull request #956 from lightpanda-io/typo-fix
typo fix
2025-08-20 17:06:16 +08:00
Pierre Tachoire
ed99acebfe typo fix 2025-08-20 09:25:47 +02:00
Pierre Tachoire
bade412d30 Merge pull request #953 from lightpanda-io/is_navigation_and_arena
Use Transfer.arena in a few more places, correctly set is_navigation …
2025-08-20 08:59:32 +02:00
Pierre Tachoire
191e9ba073 Merge pull request #954 from lightpanda-io/remove_managed_arraylist
Replace all std.ArrayList with std.ArrayListUnmanaged
2025-08-20 08:55:03 +02:00
Karl Seguin
b21688a0ac Makes build.zig Zig 0.15 ready
Our build.zig is using a number of deprecated features, which are removed in
0.15.

This updates build.zig so that it still works in 0.14 and [hopefully] will work
in 0.15.

Related: https://github.com/lightpanda-io/zig-v8-fork/pull/87
2025-08-20 14:54:27 +08:00
Karl Seguin
a4d4da4d96 Replace all std.ArrayList with std.ArrayListUnmanaged
Very minor, but the more we can do upfront to align the code for Zig 0.15, the
better.
2025-08-20 12:30:39 +08:00
Karl Seguin
16c85c5b8a Use Transfer.arena in a few more places, correctly set is_navigation on redirect
Following up to Request Interception PR (1) and Cookie Redirect PR (2) which
both introduced features that were useful to the other. This PR closes that
loop.

(1) https://github.com/lightpanda-io/browser/pull/946
(2) https://github.com/lightpanda-io/browser/pull/948
2025-08-20 11:39:38 +08:00
Karl Seguin
2c7b39927a Merge pull request #952 from lightpanda-io/fix_compilation_error
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix compilation error
2025-08-20 10:28:54 +08:00
Karl Seguin
7f47692ad4 Fix compilation error
bad auto merge?
2025-08-20 10:04:15 +08:00
Karl Seguin
af4066da87 Merge pull request #946 from lightpanda-io/request_interception
Request Interception
2025-08-20 07:53:08 +08:00
Pierre Tachoire
4de4e7504d Merge pull request #951 from lightpanda-io/wpt_range
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Improve correctness of Node.compareDocumentPosition and Range api.
2025-08-19 17:23:39 +02:00
Karl Seguin
b46c181b07 zig fmt 2025-08-19 22:01:14 +08:00
Karl Seguin
e4f89092b3 add Range.intersectsNode and cover a few more edge-cases 2025-08-19 22:00:59 +08:00
Pierre Tachoire
4fbedf5b20 Merge pull request #948 from lightpanda-io/redirect-cookies
handle cookies on redirection manually
2025-08-19 14:48:37 +02:00
Karl Seguin
d51a03f1b6 Improve correctness of Node.compareDocumentPosition and Range api.
Should fix a good chunk (~20K I think) of the recently broken WPT tests.
2025-08-19 18:23:54 +08:00
Pierre Tachoire
f7eee0d461 http: add an arena to Transfer 2025-08-19 11:10:52 +02:00
Pierre Tachoire
39178d8d2b http: remove uselesss Client.arena 2025-08-19 11:10:25 +02:00
Pierre Tachoire
7795916c08 apply review comments 2025-08-19 10:01:35 +02:00
Pierre Tachoire
0e2a3d8009 handle cookies on redirection manually 2025-08-19 10:01:11 +02:00
Karl Seguin
38a0b6905e Merge pull request #949 from lightpanda-io/network_path_reference_stitch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix network-path reference stitching
2025-08-19 15:38:43 +08:00
Karl Seguin
8797549369 Fix network-path reference stitching 2025-08-18 23:05:11 +08:00
Karl Seguin
f5ec74252d Add fulfillRequest and more complete continueRequest 2025-08-18 18:29:10 +08:00
Karl Seguin
211012d367 move intercept_state and extra_headers from CDP instance to BrowserContext 2025-08-18 13:23:17 +08:00
Karl Seguin
c1319d1f27 add proper resourceType 2025-08-18 12:42:18 +08:00
Pierre Tachoire
d4d8773fd1 Merge pull request #927 from lightpanda-io/window-frames
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Partial window.frames implementation
2025-08-15 14:54:29 +02:00
Karl Seguin
01223601f2 Reduce allocations made during request interception
Stream (to json) the Transfer as a request and response object in the various
network interception-related events (e.g. Network.responseReceived).

Add a page.request_intercepted boolean flag for CDP to signal the page that
requests have been intercepted, allowing Page.wait to prioritize intercept
handling (or, at least, not block it).
2025-08-15 14:01:57 +08:00
Karl Seguin
d9ed4cfca8 Merge pull request #940 from lightpanda-io/redirect-cookies
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
enable curl cookie engine
2025-08-15 08:50:25 +08:00
Pierre Tachoire
7d0e4b6270 use CURLOPT_COOKIE to set cookies 2025-08-14 15:33:02 +02:00
Pierre Tachoire
b2f645a5ce enable curl cookie engine
Enabling Curl cookie engine brings advantage:
* handle cookies during a redirection: when a srv redirects including
  cookies, curl sends back the cookies correctly during the next request
2025-08-14 15:32:56 +02:00
Karl Seguin
6a29d6711c Merge pull request #945 from lightpanda-io/remove_unecessary_content_type_parse
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Remove unecessary content type parse
2025-08-14 19:07:41 +08:00
Karl Seguin
5b2806a784 expose response header amount 2025-08-14 18:57:57 +08:00
Karl Seguin
a2f15ce0b2 Remove unecessary content type parse
getResponseHeader takes header index
2025-08-14 18:26:01 +08:00
Karl Seguin
68400f3bcf Merge pull request #944 from lightpanda-io/fix_memory_leak
fix memory leak
2025-08-14 18:20:53 +08:00
Karl Seguin
31f3c2771a fix build error...sorry 2025-08-14 18:07:14 +08:00
Karl Seguin
f9352e26cb same memory leak, different place 2025-08-14 18:00:56 +08:00
Karl Seguin
4fa542bc38 fix memory leak 2025-08-14 17:51:40 +08:00
Pierre Tachoire
a707e10af7 Merge pull request #922 from lightpanda-io/nonblocking_libcurl
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Nonblocking libcurl
2025-08-14 11:43:19 +02:00
Pierre Tachoire
1e095fede5 zig fmt build.zig 2025-08-14 11:29:56 +02:00
Karl Seguin
96b10f4b85 Optimize Network.responseReceived
Add a header iterator to the transfer. This removes the need for NetworkState,
duping header name/values, and the http_header_received event.
2025-08-14 15:50:56 +08:00
Karl Seguin
5100e06f38 fix header done callback 2025-08-14 14:51:02 +08:00
Karl Seguin
35e2fa5058 Merge pull request #943 from lightpanda-io/integer-overflow
fix integer overflow for sleeping delay
2025-08-14 05:43:49 +08:00
Pierre Tachoire
8d2d4ffdd2 fix integer overflow for sleeping delay 2025-08-13 19:44:06 +02:00
sjorsdonkers
7d05712f40 setExtraHTTPHeaders 2025-08-13 14:54:59 +02:00
sjorsdonkers
c0106a238b http_headers_done_receiving 2025-08-13 14:29:23 +02:00
Karl Seguin
f6c68e4580 fix release build (constness via telemetry, not seen in debug) 2025-08-13 20:16:14 +08:00
Karl Seguin
3c8065fdee fix fmt 2025-08-13 20:12:39 +08:00
Karl Seguin
9bd8b2fc43 fix wpt runner 2025-08-13 19:39:49 +08:00
Karl Seguin
5a3d5f5512 improve elapsed display for larger numbers 2025-08-13 18:17:59 +08:00
Karl Seguin
ca9e850ac7 Create Client.Transfer earlier.
On client.request(req) we now immediately wrap the request into a Transfer. This
results in less copying of the Request object. It also makes the transfer.uri
available, so CDP no longer needs to std.Uri(request.url) anymore.

The main advantage is that it's easier to manage resources. There was a use-
after free before due to the sensitive nature of the tranfer's lifetime. There
were also corner cases where some resources might not be freed. This is
hopefully fixed with the lifetime of Transfer being extended.
2025-08-13 18:05:00 +08:00
Karl Seguin
2dc09c799f Merge pull request #930 from lightpanda-io/request_interception
request interception
2025-08-13 14:44:26 +08:00
sjorsdonkers
a49154acf4 http_request_fail 2025-08-12 15:20:48 +02:00
sjorsdonkers
77eee7f087 Cookies 2025-08-12 14:40:23 +02:00
sjorsdonkers
03694b54f0 3# This is a combination of 3 commits.
intercept continue and abort

feedback

First version of headers, no cookies yet
2025-08-12 13:49:20 +02:00
Karl Seguin
bed320204d Merge pull request #939 from lightpanda-io/raw-done
finalize document loading with non-HTML pages
2025-08-12 19:09:31 +08:00
Pierre Tachoire
971524fa3b finalize document loading with non-HTML pages
Avoid infinite the loop of loading non-HTML documents with CDP.
2025-08-12 12:55:44 +02:00
Karl Seguin
4758456069 Merge pull request #938 from lightpanda-io/node_isConnected
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Fix Node.isConnected
2025-08-12 18:17:28 +08:00
Karl Seguin
3ef4ba6b8b Fix Node.isConnected
The previous implementation just checked if a node had a parent. But it should
check the node has a document ancestor:

```
const d1 = document.createElement('div');
document.createElement('div').appendChild(d1);
d1.isConnected
```

should be `false`.

In addition to this fix, also added support for DocumentFragments which are
part of the ShadowRoot. This, like events, is one of those apis that DOES break
the ShadowRoot isolation.
2025-08-12 17:23:38 +08:00
Karl Seguin
a504f051e7 Merge pull request #937 from lightpanda-io/event_composedPath
Add ShadowRoot get/set innerHTML
2025-08-12 17:22:25 +08:00
Karl Seguin
ea0bbaf332 Revert "Treat pending requests as active"
This reverts commit 19c908035b.
2025-08-12 11:27:28 +08:00
Karl Seguin
19c908035b Treat pending requests as active
This ensures that page.wait won't unblock too early. As-is, this isn't an issue
since active can only be 0 if there are no active OR pending requests. However,
with request interception (https://github.com/lightpanda-io/browser/pull/930)
it's possible to have no active requests and no pending requests - from the
http client's point of view - but still have pending-on-intercept requests.

An alternative to this would be to undo these changes, and instead change
Page.wait to be intercept-aware. That is, Page.wait would continue to block on
http activity and scheduled tasks, as well as intercepted requests. However,
since the Page doesn't know anything about CDP right now, and it does know
about the http client, maybe doing this in the client is fine.
2025-08-12 11:13:19 +08:00
Muki Kiboigo
05192b6850 update flake 2025-08-11 12:09:22 -07:00
Karl Seguin
079ce5e9de whitelist application/ld+json 2025-08-11 21:38:36 +08:00
Karl Seguin
ff742c0169 don't allow concurrent blocking calls 2025-08-11 21:38:36 +08:00
Karl Seguin
332e264437 remove unimportant todos 2025-08-11 21:38:34 +08:00
Karl Seguin
3554634c1c cleanup optional request headers 2025-08-11 21:37:03 +08:00
Karl Seguin
c96fb3c2f2 support CDP proxy override 2025-08-11 21:37:03 +08:00
Karl Seguin
1e612e4166 Add command line options to control HTTP client
http_timeout_ms
http_connect_timeout_ms
http_max_host_open
http_max_concurrent
2025-08-11 21:37:03 +08:00
Karl Seguin
06984ace21 fix overflow and debug units 2025-08-11 21:37:03 +08:00
Karl Seguin
cabd4fa718 re-enable datauris 2025-08-11 21:37:03 +08:00
Karl Seguin
ddb549cb45 cookie support 2025-08-11 21:37:02 +08:00
Karl Seguin
c7484c69c0 Increase max concurrent request to 10
Improve wait analysis dump.

De-prioritize secondary schedules.

Don't log warning for application/json scripts

Change pretty log timer to display time from start.
2025-08-11 21:37:02 +08:00
Karl Seguin
9876d79680 Add Accept-Encoding
This is necessary because of CloudFront which will send gzip content even if
we don't ask for it.

Properly handle scripts that are both async and defer.

Add a helper to print state of page wait. This can be helpful in identifying
what's causing the page to hang on page.wait.
2025-08-11 21:37:02 +08:00
Karl Seguin
32566ccc80 Set window location on load
Set SUPPRESS_CONNECT_HEADERS option.
2025-08-11 21:37:02 +08:00
Karl Seguin
7f9e309ae8 Shutdown clean async scripts
Set parent current script
2025-08-11 21:37:02 +08:00
Karl Seguin
7831aabe5a connect proxy 2025-08-11 21:37:02 +08:00
Karl Seguin
74b40b97ec fix ScriptManager wrong order execution 2025-08-11 21:37:02 +08:00
Karl Seguin
f45726d61f ScriptManager & HttpClient support for JS modules
Improve cleanup/shutdown (*cough* memory leaks *cough*)
2025-08-11 21:37:01 +08:00
Karl Seguin
3c0d027306 dynamic script support 2025-08-11 21:37:01 +08:00
Karl Seguin
dc83765808 fix build 2025-08-11 21:37:01 +08:00
Karl Seguin
4244b572d1 Improve page.wait
Allow page.wait to transition page mode.

Optimize initial page load. No point running scheduler until the initial
page is loaded.

Support ISO-8859-1 charset
2025-08-11 21:37:01 +08:00
Karl Seguin
77475ca5e4 Re-enable --insecure_disable_tls_host_verification
Better error logs on http callback error

Fix wait timing
2025-08-11 21:37:01 +08:00
Karl Seguin
3555680335 Working navigation events (clicks, form submission) 2025-08-11 21:37:01 +08:00
Karl Seguin
f65a39a3e3 Re-enable telemetry
Start work on supporting navigation events (clicks, form submission).
2025-08-11 21:37:00 +08:00
Karl Seguin
94e8964f69 add custom scheduler 2025-08-11 21:37:00 +08:00
Karl Seguin
254d22e2cc don't poll libcurl if we have no running transfers 2025-08-11 21:37:00 +08:00
Karl Seguin
54ab1326e5 Switch XHR to new http client
get puppeteer/cdp.js working again

make test are all passing
2025-08-11 21:37:00 +08:00
Karl Seguin
b0fe5d60ab Initial work on integrating libcurl and making all http nonblocking 2025-08-11 21:36:56 +08:00
Karl Seguin
4b1eb2794f Add ShadowRoot get/set innerHTML
Adds event.composedPath()

This depends on https://github.com/lightpanda-io/libdom/pull/34
2025-08-11 16:32:08 +08:00
Karl Seguin
6a2dd1111c Merge pull request #928 from lightpanda-io/lit_compat
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
lit compatibility
2025-08-11 08:30:34 +08:00
Karl Seguin
f5da89b50b lit compatibility
Aims to improve compatibility for the lit framework (e.g. what Reddit is using).

1 - Adds support for adoptedStyleSheets to the Document and ShadowRoot
2 - Adds mock support for replace and replaceSync to the CSSStyleSheet
3 - Optionally include shadowroot in dump
4 - Special-case setting innerHTML on a TemplateElement
2025-08-09 07:43:27 +08:00
Karl Seguin
bede244598 Merge pull request #934 from lightpanda-io/with-base
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add a --with_base option to fetch
2025-08-09 07:37:32 +08:00
Karl Seguin
4df48c9695 Merge pull request #935 from lightpanda-io/mouse-event-log
use internal logger instead of std.log
2025-08-09 07:36:35 +08:00
Karl Seguin
05ad77ffbe Merge pull request #936 from lightpanda-io/runtime-empty-array
Fix crashes with empty array
2025-08-09 07:36:09 +08:00
Pierre Tachoire
dc23a74e7b add <base> in the DOM tree 2025-08-08 18:34:14 +02:00
Pierre Tachoire
f463cb16da runtime: handle empty array parameter 2025-08-08 17:50:18 +02:00
Pierre Tachoire
b785884cd8 runtime: fix returning an empty array crash 2025-08-08 17:26:39 +02:00
Pierre Tachoire
f09caec09a use internal logger instead of std.log 2025-08-08 16:21:23 +02:00
Pierre Tachoire
5e30a3997e typo fix
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2025-08-08 16:17:52 +02:00
Karl Seguin
8552a5797c Merge pull request #933 from lightpanda-io/document_fragment_get_element_by_id
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add DocumentFragment getElementById
2025-08-08 22:06:02 +08:00
Karl Seguin
a0d528981e Merge pull request #932 from lightpanda-io/libdom_element_attributes
Updates libdom
2025-08-08 22:05:42 +08:00
Pierre Tachoire
7ffdee0d7f node: add baseURI getter 2025-08-08 15:21:20 +02:00
Pierre Tachoire
3d0928a449 add a --with_base option to fetch
with_base option adds a <base> tag to the dump for better offline preview.
2025-08-08 15:18:11 +02:00
Pierre Tachoire
ea1bca05c7 fix no-script default value 2025-08-08 14:30:41 +02:00
Karl Seguin
df292a2103 Add DocumentFragment getElementById 2025-08-08 17:05:22 +08:00
Karl Seguin
7f2c360f33 Updates libdom
libdom's parsing is now less strict with respect to attribute names. See:
https://github.com/lightpanda-io/libdom/pull/33

However, the attribute name in setAttribute has stricter validation applied to
it, which we now handle directly.
2025-08-08 16:22:25 +08:00
Karl Seguin
fbd40a6514 Merge pull request #931 from lightpanda-io/element_getAttributeNames
add element.getAttributeNames()
2025-08-08 15:13:03 +08:00
Karl Seguin
9dd02ec67d add element.getAttributeNames() 2025-08-08 10:23:45 +08:00
Karl Seguin
8e55082d4e Merge pull request #929 from lightpanda-io/fix-webcomponents
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
webcomponent must be cast as HTMLElement
2025-08-07 19:04:31 +08:00
Pierre Tachoire
29378c57ea node: cast the libdom document depending its type 2025-08-07 12:54:18 +02:00
Pierre Tachoire
16c74cf3b4 element: fix toInterface for webcomponents
The webcomponents tag can be anything. But we must return them as
HTMLElement for HTML documents.
2025-08-07 12:47:02 +02:00
Pierre Tachoire
b199925f91 iframe: move HTMLIFrameElement in its own file 2025-08-07 10:35:04 +02:00
Pierre Tachoire
28397bf9d0 window: frame is obsolete, ignore them from frames list 2025-08-07 10:04:42 +02:00
Pierre Tachoire
1b7abf9972 window: partial implementation for indexed_get 2025-08-06 18:29:26 +02:00
Pierre Tachoire
b98bdeaae7 window.length dynamically 2025-08-06 18:29:25 +02:00
Pierre Tachoire
221274b473 first change to start support frames 2025-08-06 16:19:52 +02:00
Karl Seguin
cc6d443113 Merge pull request #926 from lightpanda-io/noscript_exclude_preload
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
When --noscript is specified, also exclude <link rel=preload as=script>
2025-08-06 19:45:52 +08:00
Karl Seguin
b3c81c9e55 When --noscript is specified, also exclude <link rel=preload as=script> 2025-08-06 18:04:57 +08:00
Pierre Tachoire
84d07f3f18 Merge pull request #919 from lightpanda-io/html_element-and-element
Create HTMLElement instead of pure Element
2025-08-06 10:55:46 +02:00
Pierre Tachoire
0fee2bbf28 upgrade netsurf/libdom 2025-08-06 10:42:54 +02:00
Pierre Tachoire
ea38845622 detect HTML document 2025-08-06 10:42:54 +02:00
Pierre Tachoire
81a0e95916 netsurf: remove inline for documentCreateHTMLElement* 2025-08-06 10:42:54 +02:00
Pierre Tachoire
2a9feee476 init default HTML doc and Image w/ HTML Elements 2025-08-06 10:42:53 +02:00
Pierre Tachoire
c38c1fa93a remove netsurf.elementHTMLGetTagType 2025-08-06 10:42:53 +02:00
Pierre Tachoire
8d7c35d034 refacto and use Element.toInterface 2025-08-06 10:42:53 +02:00
Pierre Tachoire
425f62607b add Tag.fromString to get element tag from tagname 2025-08-06 10:42:52 +02:00
Pierre Tachoire
c1752ae5eb document.documentElement returns a *parser.Element
For XML documents, the documentElement could be another element than
HTMLElement. So we don't want to pass to through the toInterface.
2025-08-06 10:42:52 +02:00
Pierre Tachoire
d61e91b949 Merge pull request #924 from lightpanda-io/fix-null-owner
node: check owner null before using it
2025-08-06 10:38:59 +02:00
Pierre Tachoire
090c0f8857 node: check owner null before using it 2025-08-05 18:23:41 +02:00
Pierre Tachoire
c453dd2b3c Merge pull request #923 from lightpanda-io/doc-owner-next
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
node: don't call owner twice in _insertBefore
2025-08-05 15:59:16 +02:00
Pierre Tachoire
b2b2e97edc zig fmt 2025-08-05 14:47:25 +02:00
Pierre Tachoire
bd9e4dbc79 node: don't call owner twice in _insertBefore
When the ref_node_ is null, call directly _appendChild w/o fixing the
node's owner.
2025-08-05 14:45:25 +02:00
Pierre Tachoire
0c19070800 Merge pull request #920 from SrikanthKumarC/main
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix: Properly handle node ownership when using appendChild and insertBefore
2025-08-05 14:45:18 +02:00
Karl Seguin
07e37b257f Merge pull request #921 from lightpanda-io/cdp-agent-commt
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
cdp: add comment for CDP_USER_AGENT
2025-08-05 07:38:43 +08:00
Srikanth
0a5f060d1b add tests and simplify walker traversal 2025-08-04 23:53:29 +05:30
muki
6fcfcb630d Merge pull request #916 from lightpanda-io/allow-nullable-listener
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
2025-08-04 06:15:39 -07:00
Pierre Tachoire
7aff90aec7 cdp: add comment for CDP_USER_AGENT 2025-08-04 14:40:44 +02:00
Srikanth
f1e513443b refactor: use walker to traverse the nodes 2025-08-04 14:27:39 +05:30
Srikanth
c533b10e19 fix: traverse all children correctly 2025-08-04 13:00:03 +05:30
Srikanth
b4014e8c24 Fix: Properly handle node ownership when using appendChild and insertBefore 2025-08-03 20:27:32 +05:30
sjorsdonkers
478f3a5308 simplify statusText
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
2025-07-29 09:53:54 +02:00
sjorsdonkers
b98edf3d76 CDP response statusText 2025-07-29 09:53:54 +02:00
Karl Seguin
02fe46de58 Merge pull request #915 from lightpanda-io/css_tweaks
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Tweak cssom
2025-07-24 19:28:21 +08:00
Karl Seguin
ab2fd0ad36 Merge pull request #911 from lightpanda-io/select_options
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Implement select.options
2025-07-24 07:48:12 +08:00
Muki Kiboigo
88655d877b handle null event listener 2025-07-23 06:53:44 -07:00
Karl Seguin
6e94affea6 Update src/browser/dom/html_collection.zig
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-07-23 21:34:42 +08:00
Karl Seguin
f7f382275a Merge pull request #908 from lightpanda-io/guard_against_double_script_execution
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Prevent double-execution of script tags.
2025-07-23 21:33:52 +08:00
Karl Seguin
23f3bf43c2 Merge pull request #910 from lightpanda-io/performance_getEntriesByX
Add placeholder performance getEntriesByName and Type
2025-07-23 21:19:01 +08:00
Karl Seguin
8a0c4909b9 fix file casing 2025-07-23 16:06:07 +08:00
Karl Seguin
2aeaf02d05 Tweak cssom
The only functionality change is adding a `named_set` to the CSSStyleDeclaration
so that styles can be set (`named_get` was already defined)

Combine the StringHashMapUnmanaged + ArrayListUnmanaged into a single
StringArrayHashMapUnmanaged.

Use file structs, because @import("css_style_declaration.zig").CSSStyleDeclaration
is a bit tedious.

Various micro-optimization around parsing CSS, e.g. ascii.eqlIgnoreCase in loops
replaced by 1 lowercase + N*mem.eql.
2025-07-23 15:34:32 +08:00
Karl Seguin
f4a6e34713 update libdom 2025-07-23 07:51:33 +08:00
Karl Seguin
3eb85da02c Implement select.options
Add HTMLOptionsCollection and enhance HTMLOptionElement API.

Amazon.
2025-07-23 07:39:53 +08:00
Karl Seguin
6533456472 Add placeholder performance getEntriesByName and Type 2025-07-22 08:05:52 +08:00
Karl Seguin
7969e047c7 Merge pull request #909 from lightpanda-io/zig_fmt
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig fmt
2025-07-22 08:05:50 +08:00
Karl Seguin
f0d6d9d177 zig fmt 2025-07-22 07:57:17 +08:00
Karl Seguin
ca574df3be Prevent double-execution of script tags.
Depends on https://github.com/lightpanda-io/libdom/pull/31
2025-07-22 07:54:39 +08:00
Karl Seguin
0b793d82fe Merge pull request #907 from lightpanda-io/array_buffer_as_u8_slice
Map ArrayBuffer and ArrayBufferView to u8.
2025-07-22 07:13:57 +08:00
Karl Seguin
f6d51462eb Merge pull request #906 from lightpanda-io/text_decoder
Add TextDecoder (utf8 support only)
2025-07-22 07:13:21 +08:00
Karl Seguin
5bdacbab61 Merge pull request #903 from lightpanda-io/MessageChannel
Add MessageChannel
2025-07-22 07:13:07 +08:00
Karl Seguin
e239cc962b Merge pull request #904 from lightpanda-io/minor-refactor-prep-for-tls
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Minor refactor prep for tls
2025-07-21 20:55:35 +08:00
sjorsdonkers
6ebd4fcf5b fix unencrypted keepalive 2025-07-21 14:28:53 +02:00
Karl Seguin
ef427fa966 Map ArrayBuffer and ArrayBufferView to u8.
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/86

Built ontop of https://github.com/lightpanda-io/browser/pull/906 just because
this is the feature that uses it.
2025-07-21 19:46:57 +08:00
Karl Seguin
f4383a11d7 Merge pull request #905 from lightpanda-io/scheme_only_url
Allow scheme-only URLs
2025-07-21 19:36:24 +08:00
Karl Seguin
77b6377473 Add TextDecoder (utf8 support only) 2025-07-21 16:29:42 +08:00
Karl Seguin
7bf3cf999f Allow scheme-only URLs
new URL('sveltekit-internal://') is valid. Used by amazon.
2025-07-21 15:46:23 +08:00
sjorsdonkers
4ab611de0c minor refactor prep for tls 2025-07-21 09:30:22 +02:00
Karl Seguin
d7745a418f Merge pull request #902 from lightpanda-io/window_DOMContentLoaded
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Trigger the DOMContentLoaded on the Window
2025-07-19 08:51:12 +08:00
Karl Seguin
058a5a43ba Add MessageChannel 2025-07-18 16:47:04 +08:00
Karl Seguin
878dbd81b1 Merge pull request #901 from lightpanda-io/url_stitch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Rework URL.stitch, handle ../ (for yahoo)
2025-07-17 21:44:24 +08:00
Karl Seguin
3c64ed1eb2 Merge pull request #899 from lightpanda-io/element_remove
Add element.remove()  (needed by reddit)
2025-07-17 21:44:08 +08:00
Karl Seguin
ee50f1238c Trigger the DOMContentLoaded on the Window
This is hacky, but it's inspired by how NetSurf does it. While the Window isn't
the parent of the Document, many events should bubble from the Document to the
Window. libdom simply doesn't handle this (it has no concept of a Window, and
the Document has no parent).

We potentially need to do this for multiple event types (NetSurf only does it
for the 'load' event as far as I can tell). It would be nice to find a generic
way to do this...maybe intercept any addEventListener on the body and
registering special events on the Window? For now, `DOMContentLoaded` is the
blocking (for finance.yahoo.com) and we can see if this is really an issue for
other event types.
2025-07-17 21:38:54 +08:00
Karl Seguin
1af2513fc0 zig fmt 2025-07-17 20:52:15 +08:00
Karl Seguin
9c0d26bc84 add note about incomplete removal 2025-07-17 20:51:05 +08:00
Karl Seguin
4d9053a83b Update src/url.zig
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-07-17 20:45:42 +08:00
Karl Seguin
3f7e98c277 Update src/url.zig
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-07-17 20:45:35 +08:00
Karl Seguin
aebc877e7b Merge pull request #900 from lightpanda-io/getDocument_depth
support `depth` parameter for DOM.getDocument
2025-07-17 20:44:58 +08:00
Karl Seguin
eef5f3fec2 support null params to CDP DOM.getDocument 2025-07-17 19:05:17 +08:00
Karl Seguin
16a1677fde Rework URL.stitch, handle ../ (for yahoo)
Also handle ./ anywhere in the path.
2025-07-17 17:54:00 +08:00
Karl Seguin
f199816fcd support depth parameter for DOM.getDocument 2025-07-17 14:17:33 +08:00
Karl Seguin
5e74e17b41 Merge pull request #888 from lightpanda-io/cdp_dom_requestChildNodes
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add support for CDP's DOM.requestChildNodes
2025-07-17 10:48:24 +08:00
Karl Seguin
98b041e84a requestChildNode cannot have a depth of 0 2025-07-17 10:36:20 +08:00
Karl Seguin
bba9c8f652 Add element.remove() (needed by reddit) 2025-07-17 10:00:38 +08:00
Karl Seguin
1a05fe6ae1 Merge pull request #887 from lightpanda-io/go_rod
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Noop CDP methods that go-rod requires
2025-07-16 20:01:03 +08:00
sjorsdonkers
16fcbf66ee http_proxy_before ?? comment 2025-07-16 11:20:00 +02:00
Karl Seguin
b7fd4e90e2 Merge pull request #894 from lightpanda-io/HTMLStyleElement_get_sheet
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add HTMLStyleElement.get_sheet
2025-07-16 10:34:37 +08:00
Karl Seguin
b6341c10cc Merge pull request #892 from lightpanda-io/set_timeout_params
Support params for setTimeout and setInterval
2025-07-16 08:17:11 +08:00
Karl Seguin
08487b0fcc Merge pull request #891 from lightpanda-io/reattach_shadowroot
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add childElementCount and children to DocumentFragment
2025-07-15 23:36:26 +08:00
muki
b084dde22a Merge pull request #872 from lightpanda-io/dynamic-import 2025-07-15 08:31:52 -07:00
Karl Seguin
5229a7c997 Merge pull request #897 from lightpanda-io/animate
Add Element.animate and Animation
2025-07-15 23:09:08 +08:00
Karl Seguin
e56c56e2fe Merge pull request #895 from lightpanda-io/performance_clear
dummy performance clearMarks and clearMeasures
2025-07-15 21:40:31 +08:00
Karl Seguin
7374f956cf Merge pull request #896 from lightpanda-io/dont_send_after_disconnect
Don't queue data to send after we've initiated a disconnect of the cl…
2025-07-15 21:29:01 +08:00
Muki Kiboigo
287df42994 log module specifier on dynamic import stages 2025-07-15 06:22:52 -07:00
Muki Kiboigo
06e514cc2e use resource_str for stitching url 2025-07-15 06:22:52 -07:00
Muki Kiboigo
dffd8b5fec use module() for dynamic imports 2025-07-15 06:22:52 -07:00
Muki Kiboigo
2a87337875 dynamicImportCallback in JsContext 2025-07-15 06:22:50 -07:00
Karl Seguin
a74f79118f Merge pull request #893 from lightpanda-io/dump_noscript
Add a --noscript option to "improve" --dump
2025-07-15 21:22:33 +08:00
Muki Kiboigo
a13ed0bec3 add dynamic import callback to isolate 2025-07-15 06:22:13 -07:00
Karl Seguin
f8ca45f0f2 Add Element.animate and Animation
These are dummy implementations, but they do expose the ready and finished
promise, and do resolve the finished promise, so it should unblock basic cases.
2025-07-15 18:58:58 +08:00
Karl Seguin
4bf92a34f6 Don't queue data to send after we've initiated a disconnect of the client 2025-07-15 17:58:57 +08:00
Karl Seguin
4f1c84004a dummy performance clearMarks and clearMeasures 2025-07-15 12:11:28 +08:00
Karl Seguin
1bd430598d add HTMLStyleElement.get_sheet 2025-07-15 10:59:59 +08:00
Karl Seguin
3bc654bf97 Merge pull request #890 from lightpanda-io/xhr_cant_block_sync_requests
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Always make sure we have 1 free http state available for synchronous …
2025-07-15 10:08:54 +08:00
Karl Seguin
3906acb83d fix test 2025-07-14 18:42:25 +08:00
Karl Seguin
cfd62ac137 Add a --noscript option to "improve" --dump
Currently, fetch --dump includes <script> tag (either inline or with src). I
don't know what use-case this is the desired behavior. Excluding them, via the
new --noscript option has benefit that if you --dump --noscript and open the
resulting page in the browser, you don't re-execute JavaScript, which is
likely to break the page.

For example, opening a --dump of github makes it look like the page is broken
because it re-executes JavaScript that isn't meant to be re-executed.

Similarly, opening a --dump in a browser might execute JavaScript that
lightpanda browser failed to execute, making it looks like it worked better
than it did.
2025-07-14 18:24:36 +08:00
Karl Seguin
cd540dfae9 Support params for setTimeout and setInterval 2025-07-14 17:42:53 +08:00
Karl Seguin
74ad9ec8bf Add childElementCount and children to DocumentFragment
Also, when shadowRoot is re-attached to an element, clear all existing children
(like we're supposed to)
2025-07-14 17:01:11 +08:00
Karl Seguin
4f8a3fe5b9 Always make sure we have 1 free http state available for synchronous requests
If it wasn't for the fact that the HTTP client is likely going to see a major
refactor, it would definitely be time to create a specific state instance for
synchronous requests.
2025-07-14 16:41:26 +08:00
Karl Seguin
09ca0e6ef0 Add support for CDP's DOM.requestChildNodes
https://github.com/lightpanda-io/browser/issues/866
2025-07-14 15:13:01 +08:00
Karl Seguin
fae2b5acfa Noop CDP methods that go-rod requires
go-rod appears to stop processing when it receives an error, such as
UnknownMethod. Added placeholder handlers for Network.setUserAgentOverride and
Page.stopLoading.

Setting a custom user agent is something still being discussed, so no-oping it
seems reasonable. And, due to the currently synchronous nature of the initial
page load, no-oping stopLoading also seems reasonable.

https://github.com/lightpanda-io/browser/issues/867
2025-07-14 11:21:02 +08:00
Karl Seguin
d35a3eab6c Merge pull request #880 from lightpanda-io/webcomponents
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add basic ShadowRoot implementation, polyfill webcomponents
2025-07-14 11:10:40 +08:00
Karl Seguin
7f7f47497a Merge pull request #886 from lightpanda-io/scriptcompiler-compile
use ScriptCompiler to compile script
2025-07-14 11:07:29 +08:00
Karl Seguin
eb14ac3741 update build.zig.zon v8 version 2025-07-14 11:00:01 +08:00
Karl Seguin
22334faba3 update zig-v8-fork lib version 2025-07-14 10:51:27 +08:00
Karl Seguin
d08fd297e8 Merge pull request #881 from lightpanda-io/window_queueMicrotask
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add window.queueMicrotask
2025-07-13 09:03:57 +08:00
Pierre Tachoire
0dd664bfbf use ScriptCompiler to compile script 2025-07-12 12:09:16 -07:00
Karl Seguin
1602932d72 Add a "pre" polyfill
This is always run, but only the full webcomponents polyfill, it's very
small and isn't intrusive. This introduces a layer of indirection so that,
if the full polyfill is loaded, its monkeypatched constructor will be called
2025-07-12 19:49:19 +08:00
Karl Seguin
98c8b7d2b0 Merge pull request #875 from lightpanda-io/async_forward_proxy_to_tls
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Fix async https requests over a http forward proxy
2025-07-11 17:53:53 +08:00
Karl Seguin
b9ae24c42d add window.queueMicrotask 2025-07-11 17:46:39 +08:00
Karl Seguin
b387fd2bd4 Update src/http/client.zig
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-07-11 17:38:31 +08:00
Karl Seguin
818f4540fd Add basic ShadowRoot implementation, polyfill webcomponents 2025-07-11 17:32:01 +08:00
sjorsdonkers
49a97dbb66 fix callback crash with Node.Union
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
2025-07-11 10:05:44 +02:00
sjorsdonkers
a8b72c1d5f Separate NodeIterator impl, fix _filter 2025-07-11 10:05:44 +02:00
sjorsdonkers
765b8dc97b NodeIterator 2025-07-11 10:05:44 +02:00
sjorsdonkers
5123697afe EventTarget internal type for all 2025-07-11 09:55:16 +02:00
sjorsdonkers
2a2a9d7941 EventTarget InternalType 2025-07-11 09:55:16 +02:00
sjorsdonkers
2873aa5f81 EventTarget constructor 2025-07-11 09:55:16 +02:00
Karl Seguin
795c925ba1 Revert "Update src/http/client.zig"
This reverts commit 4a12d045e4.
2025-07-11 09:49:40 +08:00
Karl Seguin
d6ace3f695 Merge pull request #863 from lightpanda-io/innerHTML_head
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Setting innerHTML now captures head elements
2025-07-11 08:03:14 +08:00
Karl Seguin
dd04759de7 Merge pull request #869 from lightpanda-io/performance_observer
more PerformnaceObserver placeholders
2025-07-11 08:03:01 +08:00
Pierre Tachoire
10fbde84ba Merge pull request #879 from lightpanda-io/css-parser-error
Fix parser identifier with escaped string
2025-07-10 16:10:14 -07:00
Pierre Tachoire
2b5652e1e4 wip 2025-07-10 16:01:36 -07:00
Pierre Tachoire
18796ae44e css: allow escaped first char in identifier name 2025-07-10 15:44:04 -07:00
Pierre Tachoire
a67692dc29 Merge pull request #877 from lightpanda-io/visible-pseudoclass
Visible Psuedoclass
2025-07-10 14:18:10 -07:00
Muki Kiboigo
1efd756a55 add visible pseudoclass 2025-07-10 12:40:44 -07:00
Pierre Tachoire
29671acdb6 Merge pull request #847 from lightpanda-io/name-property-handler
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
enable conditionnal loading for polyfill
2025-07-10 09:18:29 -07:00
Karl Seguin
e82240a60e Setting innerHTML now captures head elements
I couldn't find where the behavior is described. AND, browsers seem to behave
differently depending on the state of the page (blank document vs actual page).

Still, some sites use innerHTML to load <script> tags, and, in libdom at least,
these are created in the implicit head. We cannot just copy the body nodes. To
keep it simple, I now copy all head and body elements.
2025-07-10 22:19:53 +08:00
Karl Seguin
72083c8614 Merge pull request #868 from lightpanda-io/element_hasAttributes_fix
Fix element.hasAttributes
2025-07-10 21:46:33 +08:00
Karl Seguin
8c2c1e534c Merge pull request #865 from lightpanda-io/document_domain
Fix document.domain
2025-07-10 21:46:15 +08:00
Karl Seguin
bfc01d957b Merge pull request #874 from lightpanda-io/document_styleSheets
add dummy document.get_styleSheets
2025-07-10 21:46:00 +08:00
Karl Seguin
4a12d045e4 Update src/http/client.zig
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-07-10 17:10:58 +08:00
Karl Seguin
2d78b2c219 add TODO note for dummy implementation 2025-07-10 17:03:51 +08:00
Karl Seguin
3049bb0b9f Fix async https requests over a http forward proxy
XHR requests to https (which is most XHR requests) currently don't work with
the implementation proxy because of this.
2025-07-10 16:27:09 +08:00
Karl Seguin
34ab8152fb add dummy document.get_styleSheets 2025-07-10 13:45:49 +08:00
Karl Seguin
fb58c50fb7 Merge pull request #870 from lightpanda-io/popover_open_pseudo_selector
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Accept popover-over pseudo selector
2025-07-10 08:27:43 +08:00
Pierre Tachoire
955f917015 Merge pull request #873 from lightpanda-io/macos-build
ci: fix macos version for building
2025-07-09 15:35:09 -07:00
Pierre Tachoire
12c7df98e4 ci: fix macos version for building 2025-07-09 15:26:07 -07:00
Pierre Tachoire
889c29a163 Merge pull request #871 from lightpanda-io/ws-http-max
ws: increase max http message from 2kb to 4kb
2025-07-09 15:13:50 -07:00
Pierre Tachoire
886c1370e7 ws: increase max http message from 2kb to 4kb 2025-07-09 15:02:40 -07:00
Karl Seguin
febcc0a673 Merge pull request #864 from lightpanda-io/link_href
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add HTMLElementLink get/set href
2025-07-09 18:48:17 +08:00
Karl Seguin
98cad6bf8d Accept popover-over pseudo selector
Optimize pseudo-selector parsing. Make comparison case insensitive, bucket
comparisons by length, and process input as integers.
2025-07-09 18:45:28 +08:00
Karl Seguin
7e5daedc8c more PerformnaceObserver placeholders 2025-07-09 18:10:23 +08:00
Karl Seguin
da3fe6f7ea fix test 2025-07-09 17:41:05 +08:00
Karl Seguin
f612ce262f Update src/browser/html/elements.zig
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-07-09 16:16:45 +08:00
Karl Seguin
24ccfca279 Fix element.hasAttributes
libdom's hasAttributes is based on the type. Elements, according to libdom,
always have attributes, thus hasAttributes always return true, even when the
element in question has no attribute. Change our _hasAttributes to only return
true if the attribute count > 0.
2025-07-09 16:14:53 +08:00
Karl Seguin
34b3c3982b Fix document.domain
Currently seems to always return null. Doesn't seem to be a way in libdom to
change this. The property is deprecated, and MDN recommends using location.host
instead, so change document.get_domain to wrap location.host.
2025-07-09 14:29:05 +08:00
Karl Seguin
7f732c94da add HTMLElementLink get/set href 2025-07-09 13:28:32 +08:00
Karl Seguin
bdc49a65aa Merge pull request #859 from lightpanda-io/document_fragment_query_selector
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add querySelect and querySelectorAll to DocumentFragment
2025-07-09 10:25:35 +08:00
Karl Seguin
73d82dd0ba I guess we can't use the call_arena for querySelectorAll 2025-07-09 10:19:16 +08:00
Karl Seguin
dfa4403c8a arena -> call_arena for querySelectorAll 2025-07-09 10:11:26 +08:00
Karl Seguin
b8f3b19499 Merge pull request #857 from lightpanda-io/improved_native_proto
Improve prototype resolution for native types
2025-07-09 10:01:38 +08:00
Karl Seguin
448718d112 Merge pull request #858 from lightpanda-io/callback_with_new_this
Allow JS Callback to be called with a previously-unseen this.
2025-07-09 09:34:14 +08:00
Pierre Tachoire
6de55df4bc Merge pull request #856 from lightpanda-io/resize_observer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add dummy ResizeObserver
2025-07-08 15:50:08 -07:00
Pierre Tachoire
189fe26667 Merge pull request #862 from lightpanda-io/macos-14
ci: use macos-14 for nightly builds
2025-07-08 15:49:47 -07:00
Pierre Tachoire
7230884116 ci: use macos-14 for nightly builds 2025-07-08 08:27:45 -07:00
Karl Seguin
d7fba81f8f Add querySelect and querySelectorAll to DocumentFragment 2025-07-08 19:24:35 +08:00
Karl Seguin
29ac13185c Allow JS Callback to be called with a previously-unseen this. 2025-07-08 19:17:59 +08:00
Karl Seguin
3a49ee83ce Improve prototype resolution for native types
Prototype resolution of Zig types previously had 2 limitations (bug?). The first
was that the Zig prototype chain could only be 1 deep. You couldn't do A->B->C
where each of those was a Zig type (but you could do A->B->C->D->E ... so long
as every other type was a C opaque value).

The other limitation was that Zig prototypes only worked when the nested field
was directly embedded in the struct (i.e. not a pointer). So you could do:

```zig
const X = struct {
   proto: XParent,
};
```

But not:

```zig
const X = struct {
   proto: *XParent,
};
```

This addresses both limitations. The first issue is solved by keeping track
of the cumulative offset
2025-07-08 18:37:24 +08:00
Karl Seguin
95cbbc3b45 add dummy ResizeObserver 2025-07-08 18:35:25 +08:00
Karl Seguin
2a5c7d139f Merge pull request #855 from lightpanda-io/zig_fmt
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig fmt
2025-07-08 18:34:14 +08:00
Karl Seguin
b74863873b zig fmt 2025-07-08 18:28:21 +08:00
Karl Seguin
7b46fe9cc8 Merge pull request #848 from lightpanda-io/fix_insecure_forward_proxy
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix non-tls forward-proxy
2025-07-08 09:52:23 +08:00
Karl Seguin
afc8c69a82 Merge pull request #854 from lightpanda-io/remove_debug_log
remove std.debug.print
2025-07-08 09:39:19 +08:00
Karl Seguin
38bbad6e88 Revert "fix secure connection logic"
This reverts commit b6132f2497.
2025-07-08 09:33:53 +08:00
Karl Seguin
1df47fd415 remove std.debug.print 2025-07-08 09:33:19 +08:00
Pierre Tachoire
faf21c5fff Merge pull request #853 from lightpanda-io/typo-fix
typo fix
2025-07-07 17:24:28 -07:00
Karl Seguin
2aee580795 Merge pull request #849 from lightpanda-io/mutation_observer_loop
Rework MutationObserver callback.
2025-07-08 08:15:02 +08:00
Pierre Tachoire
404c027546 typo fix 2025-07-07 17:14:52 -07:00
Karl Seguin
04e59c6df2 Merge pull request #850 from lightpanda-io/set_attribute_value
Attribute.set_value uses element, if possible
2025-07-08 08:14:52 +08:00
Karl Seguin
835042b794 Merge pull request #851 from lightpanda-io/add_event_listener_signal
Add support for the signal option of addEventListener
2025-07-08 08:14:38 +08:00
Pierre Tachoire
907490e266 Merge pull request #852 from lightpanda-io/katie-lpd-patch-1
Some checks failed
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Update README.md
2025-07-07 17:01:09 -07:00
Pierre Tachoire
80fe167646 Update README.md 2025-07-07 17:00:54 -07:00
katie-lpd
d30631f991 Apply suggestions from code review
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-07-07 16:59:07 -07:00
katie-lpd
8956ab85f9 Update README.md 2025-07-07 16:50:32 -07:00
Pierre Tachoire
2cdc9e9f5f cdp: use a polyfill loader per isolate 2025-07-07 16:31:54 -07:00
Pierre Tachoire
13c623755c js: remove existing unknown property debug
Because it will be displayed only if the property is non-native.
So if your property is set in pureJS, you will still have the log...
2025-07-07 16:31:54 -07:00
Pierre Tachoire
bdfceec520 refacto a bit the missing callback into polyfill
Add a debug global unknown property
2025-07-07 16:31:53 -07:00
Pierre Tachoire
941dace7f9 enable conditionnal loading for polyfill 2025-07-07 16:31:53 -07:00
Karl Seguin
07693e54af Add support for the signal option of addEventListener 2025-07-07 20:56:19 +08:00
Karl Seguin
b6132f2497 fix secure connection logic 2025-07-07 19:56:21 +08:00
Karl Seguin
b3fe3d02c9 Attribute.set_value uses element, if possible
Only when setAttribute is called directly on the element, does libdom raise
a `DOMAttrModified` event (which MutationObserver uses).

From what I can tell, libdom's element set attribute _does_ rely on the
underlying attribute set value, so the behavior should be pretty close, it just
does extra things on top of that.
2025-07-07 19:47:17 +08:00
Karl Seguin
e880b18bb1 Rework MutationObserver callback.
Previously, MutationObserver callbacks where called using the `jsCallScopeEnd`
mechanism. This was slow and resulted in records split in a way that callers
might not expect. `jsCallScopeEnd` has been removed.

The new approach uses the loop.timeout mechanism, much like a window.setTimeout
and only registers a timeout when events have been handled. It should perform
much better.

Exactly how MutationRecords are supposed to be grouped is still a mystery to me.
This new grouping is still wrong in many cases (according to WPT), but appears
slightly less wrong; I'm pretty hopeful clients don't really have hard-coded
expectations for this though.

Also implement the attributeFilter option of MutationObserver. (Github)
2025-07-07 19:29:10 +08:00
Karl Seguin
74a299eef7 Fix non-tls forward-proxy 2025-07-07 11:03:04 +08:00
Karl Seguin
300428ddfb Merge pull request #840 from lightpanda-io/xhr_readystatechange
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add readystate change event to XHR
2025-07-06 08:59:19 +08:00
Pierre Tachoire
1c27f8251e Merge pull request #846 from lightpanda-io/e2e-draft
ci: don't run 2e2 on draft
2025-07-05 16:46:04 -07:00
Pierre Tachoire
92badd3722 ci: don't run 2e2 on draft 2025-07-05 14:22:21 -07:00
Karl Seguin
8a80f0b3dd Merge pull request #843 from lightpanda-io/empty_anchor_fix
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
An empty anchor should return empty strings for its getters
2025-07-04 23:43:10 +08:00
Karl Seguin
fcc74b63d3 correct comment 2025-07-04 23:17:48 +08:00
Karl Seguin
d7155e6662 An empty anchor should return empty strings for its getters
document.createElement('a').host  or .href or .. should return an empty string.

However, URL.constructor(document.createElement('a')) should fail.

Because HTMLAnchorElement uses URL.constructor, we have the wrong behavior.

This adds a guard for an empty anchor. This might not cover all of the cases
which are valid for an anchor but invalid for a URL.constructor, but it's
the most common.
2025-07-04 19:23:19 +08:00
Karl Seguin
42c3841639 Merge pull request #842 from lightpanda-io/fix_elementFromPoint_crash
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Rely on js.zig for float->int translation
2025-07-04 19:12:46 +08:00
Karl Seguin
c331713401 set correct state on xhr.abort and send correct events 2025-07-04 19:12:26 +08:00
Karl Seguin
002d9c1747 Merge pull request #841 from lightpanda-io/scroll_events
make window.scrollTo triggers scroll and scrollend events
2025-07-04 19:00:28 +08:00
Karl Seguin
2885ceceb1 document use of i32 2025-07-04 18:55:14 +08:00
sjorsdonkers
22a644ba01 rename tls_in_tls to tlsproxy 2025-07-04 10:00:22 +02:00
sjorsdonkers
bab120a75d secure changes 2025-07-04 10:00:22 +02:00
Francis Bouvier
7a07c82f06 https-proxy: update upstream tls.zig 2025-07-04 10:00:22 +02:00
sjorsdonkers
e881d2f6cf tls proxy tweaks 2025-07-04 10:00:22 +02:00
Francis Bouvier
c8d003a08f https-proxy: update tls.zig 2025-07-04 10:00:22 +02:00
Francis Bouvier
e2cc404571 Handle TLS proxy, both for HTTP and HTTPS (tls in tls) endpoints 2025-07-04 10:00:22 +02:00
sjorsdonkers
be71eaae47 TLS connect proxy WIP 2025-07-04 10:00:22 +02:00
Karl Seguin
ed31a452b2 Rely on js.zig for float->int translation
Not only does this ensure compatibility with browsers, it doesn't crash when
the value is NaN of Infinity.
2025-07-04 11:34:34 +08:00
Pierre Tachoire
f51ee7f3a0 Merge pull request #829 from lightpanda-io/pumpmessageloop
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add pump message loop calls
2025-07-03 10:11:26 -07:00
Pierre Tachoire
9d1dc97766 remove useless debug log 2025-07-03 09:49:01 -07:00
Pierre Tachoire
b78729f685 test: inject platform to the serveCDP app 2025-07-03 09:49:00 -07:00
Pierre Tachoire
44a76e59f9 run pumpmessageloop in its own loop 2025-07-03 09:49:00 -07:00
Pierre Tachoire
1504e36a68 use comptime test for platform existence 2025-07-03 09:49:00 -07:00
Pierre Tachoire
80348ef190 fix wpt tests with platform requirement 2025-07-03 09:48:59 -07:00
Pierre Tachoire
a3c14748d3 fix unit testing with platform deps requirement 2025-07-03 09:48:59 -07:00
Pierre Tachoire
3c0143af92 add runIdleTasks 2025-07-03 09:48:57 -07:00
Pierre Tachoire
22a93a9c39 add pump message loop calls 2025-07-03 09:47:50 -07:00
Karl Seguin
e8866a6431 Merge pull request #838 from lightpanda-io/improved_js_value_printing
Improve JS value printing
2025-07-04 00:30:35 +08:00
Karl Seguin
455ed79872 Remove HTTP client generic Loop parameter
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
I think we initially thought we might need different clients for different
parts of the system, each with a unique loop  (e.g. we thought telemetry might
need some isolation). But that never happened, so it's just needless now,
especially since the async connect uses the non-generic *Loop type directly.
2025-07-03 15:10:47 +02:00
Karl Seguin
3d17c531d7 make window.scrollTo triggers scroll and scrollend events 2025-07-03 19:37:07 +08:00
Karl Seguin
dfe90243d6 Add readystate change event to XHR
Deal with non-node current target crashing. Builds ontop of abort_signal, but
with the new event-target specific union, I think this will work in for all
future cases.
2025-07-03 19:32:24 +08:00
Karl Seguin
bf1db50667 Merge pull request #839 from lightpanda-io/build_time
improve build times (a little)
2025-07-03 15:34:53 +08:00
Pierre Tachoire
a2565a7c83 range: add detach function 2025-07-03 09:16:36 +02:00
Pierre Tachoire
947d01a3c0 range starts and ends with the global document by default 2025-07-03 09:16:36 +02:00
Karl Seguin
be11d82c9c improve build times (a little) 2025-07-03 13:56:01 +08:00
Karl Seguin
7a0e7fff13 Improve JS value printing
Don't error on JSON.stringify failure (likely caused by circular reference).

In debug mode, try to print [slightly] more meaningful value representation
when default serialization results in [object Object].
2025-07-03 10:35:09 +08:00
Karl Seguin
81fb71b7f7 Merge pull request #830 from lightpanda-io/SetHostInitializeImportMetaObjectCallback
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Implement ImportMeta callback
2025-07-03 09:17:47 +08:00
Karl Seguin
b10f5ec99f bump zig-v8-fork version 2025-07-02 13:38:01 +08:00
Karl Seguin
5abe7bdeef Merge pull request #831 from lightpanda-io/log_invalid_cookie_expiry
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
use logger for invalid cookie expiry
2025-07-02 10:11:20 +08:00
Karl Seguin
54be651415 Merge pull request #832 from lightpanda-io/range_selectNodeContents
range.selectNodeContents
2025-07-02 10:11:01 +08:00
Pierre Tachoire
cdbf6d7ae7 Merge pull request #834 from lightpanda-io/arm-generic
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
ARM generic
2025-07-01 12:08:30 -07:00
Pierre Tachoire
50349edf4d ci: use a generic target for arm build 2025-07-01 07:55:32 -07:00
sjorsdonkers
da307c1b40 range.selectNodeContents 2025-07-01 15:11:01 +02:00
Karl Seguin
b50b96bd1d Implement ImportMeta callback
The first time `import.meta` is called within a module, this callback is called
and we can populate it with whatever fields we want. For WebAPI, the important
field is `url`:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta

Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/80
2025-07-01 15:59:24 +08:00
Karl Seguin
92654fc5aa use logger for invalid cookie expiry 2025-07-01 12:25:25 +08:00
Pierre Tachoire
36b2de216b Merge pull request #828 from lightpanda-io/arm-compat
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Arm compat
2025-06-30 11:53:06 -07:00
Pierre Tachoire
8745c1016e ci: use cpu cortex_a72 for arm build 2025-06-30 10:58:15 -07:00
Pierre Tachoire
f5a58c1ff0 Merge pull request #826 from lightpanda-io/upgrade-v8
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Upgrade v8
2025-06-29 10:54:46 -07:00
Pierre Tachoire
d9e72049ae ci: use ubuntu 22.04 for arm64 build 2025-06-29 10:48:04 -07:00
Pierre Tachoire
927ca01161 upgrade zig v8 version 2025-06-29 10:47:03 -07:00
Pierre Tachoire
3ea8d0b01c Merge pull request #824 from lightpanda-io/dom-non-html
create a DOM tree for non-html files
2025-06-29 10:44:26 -07:00
Karl Seguin
c52d33e331 Merge pull request #822 from lightpanda-io/undefined_or
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add UndefinedOr(T) union
2025-06-28 09:09:45 +08:00
Karl Seguin
fd36606acc change field order 2025-06-28 09:02:12 +08:00
Karl Seguin
1c6f4a79e0 Merge pull request #821 from lightpanda-io/abort_controller
Abort controller
2025-06-28 09:00:07 +08:00
Pierre Tachoire
7896d274a3 create a DOM tree for non-html files too. 2025-06-27 12:17:03 -07:00
Pierre Tachoire
6937c8ecb4 Merge pull request #823 from lightpanda-io/atob_btoa
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add atob and btoa
2025-06-27 09:21:41 -07:00
Karl Seguin
f02b9566c5 add atob and btoa 2025-06-27 18:36:29 +08:00
Karl Seguin
c9936c2b7e Add UndefinedOr(T) union
Some apis want a value or undefined. For these, we can't use an Optional
return type, null maps to JS null. Adds an Env.UndefinedOr(T) generic
union for such return types.
2025-06-27 17:55:13 +08:00
Karl Seguin
bbd9e5e07c add AbortController API 2025-06-27 17:31:25 +08:00
sjorsdonkers
476fb7ec4e DOMException constructor
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
2025-06-27 08:13:43 +02:00
Karl Seguin
7435274be2 Merge pull request #819 from lightpanda-io/update_tls_lib
Upgrade tlz.zig to latest version
2025-06-27 13:45:28 +08:00
Karl Seguin
08d2ea6a10 abort controller 2025-06-27 13:14:35 +08:00
Karl Seguin
41b7ed6938 Upgrade tlz.zig to latest version
Was seeing pretty frequent TLS errors on reddit. I think I had the wrong max
TLS record size, but figured this was an opportunity to upgrade tls.zig, which
has seen quite a few changes since our last upgrade.

Specifically, the nonblocking TLS logic has been split into two structs: one
for handshaking, and then another to be used to encrypt/decrypt after the h
andshake is complete. The biggest impact here is with respect to keepalive,
since what we want to keepalive is the connection post-handshake, but we don't
have this object until much later.

There was also some general API changes, with respect to state and partially
encrypted/decrypted data which we must now maintain.
2025-06-27 13:14:12 +08:00
Pierre Tachoire
7a311a181b Merge pull request #820 from lightpanda-io/ci-e2e
ci: use hetzner for 2e2 regression perf
2025-06-26 22:12:34 -07:00
Pierre Tachoire
ddcb597710 ci: use hetzner for 2e2 regression perf 2025-06-26 17:37:35 -07:00
Pierre Tachoire
9c75f29875 ci: optimize 2e2 build 2025-06-26 17:04:05 -07:00
Pierre Tachoire
343f3885f7 Merge pull request #817 from lightpanda-io/script_tag_dump
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
dump script tag's text content as-is
2025-06-26 11:27:04 -07:00
Karl Seguin
ed7dfeab84 dump script tag's text content as-is 2025-06-26 12:41:22 +08:00
Karl Seguin
8de27b3674 Merge pull request #813 from lightpanda-io/crypto_get_random_values_fix
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Crypto.getRandomValues consistency
2025-06-26 11:43:39 +08:00
Karl Seguin
f56b0a5f6d Merge branch 'main' into crypto_get_random_values_fix 2025-06-26 10:25:53 +08:00
Karl Seguin
0a27e1254f Merge pull request #814 from lightpanda-io/root_module_nested_modules
Allow root modules to imported modules
2025-06-26 10:25:10 +08:00
Karl Seguin
3f9b256fcb Merge pull request #812 from lightpanda-io/identity_map_collision
We cannot have empty Zig structs mapping to JS instances
2025-06-26 10:24:09 +08:00
Karl Seguin
9ea9859150 Merge pull request #809 from lightpanda-io/html_element_dataset
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add element.dataset API
2025-06-26 09:18:19 +08:00
Pierre Tachoire
03e3f95d2e Merge pull request #810 from lightpanda-io/proxy-authentication
basic/bearer proxy authentication
2025-06-25 17:31:47 -07:00
Pierre Tachoire
e721b0af92 Merge pull request #816 from lightpanda-io/connect_proxy
Connect proxy
2025-06-25 17:31:27 -07:00
Karl Seguin
e18c589de3 Allow root modules to imported modules
Root modules (non-cacheable) should register their module_id -> URL so that,
if they load a nested module, we can get the full URL of the nested module.
2025-06-25 18:20:55 +08:00
sjorsdonkers
aea34264a9 basic/bearer testing 2025-06-25 12:04:38 +02:00
Karl Seguin
8d3a04235d Crypto.getRandomValues consistency
Crypto.getRandomValues should mutate the given parameter as well as return
the value. This return value must be the same (JsObject) as the input parameter.

There might be more magical ways to solve this, but I opted for both the
simplest and most flexible: adding a `toZig` function to JsObject which does
what js.zig does internally when mapping js values to Zig (and, of course, it
uses the same code).

This allows a caller to receive a JsObject (not too common, but we already do
that in a few places) and return that same JsObject (again, not too common, but
we do have support for returning JsObject directly already). With the main
addition that the JsObjet can now be turned into a Zig type by the caller.
2025-06-25 18:03:26 +08:00
Karl Seguin
9c4088b24c We cannot have empty Zig structs mapping to JS instances
An empty struct will share the same address as its sibling (1) which will cause
an collision in the identity map.

(1) - This depends on Zig's non-guaranteed layout, so the collision might not
be with its sibling, but rather some other [seemingly random] field.
2025-06-25 14:58:09 +08:00
Karl Seguin
1e7ee4e0a1 proxy_type 'simple' renamed to 'forward' 2025-06-25 12:21:44 +08:00
Karl Seguin
ec92f110b3 Change dataset to work directly off DOM element 2025-06-25 12:16:08 +08:00
Karl Seguin
2aa5eb85ad Add element.dataset API
Uses the State to store the dataset, but, on first load, loads the data
attributes from the DOM.
2025-06-25 12:16:08 +08:00
Karl Seguin
2815f02382 Merge pull request #811 from lightpanda-io/crypto-getrandomvalues-return
Some checks failed
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Return Random Array from crypto.GetRandomValues
2025-06-25 07:45:04 +08:00
Karl Seguin
8bd7c8dd41 Merge pull request #807 from lightpanda-io/css-util-iface
add CSS utility interface
2025-06-25 07:44:15 +08:00
Karl Seguin
269dcf071f Merge pull request #806 from lightpanda-io/document-range
add AbstractRange and Range
2025-06-25 07:41:52 +08:00
Karl Seguin
997ec7f0bc Merge pull request #805 from lightpanda-io/performance-mark
add PerformanceEntry and PerformanceMark
2025-06-25 07:41:19 +08:00
Muki Kiboigo
d9c26bb77f return array in crypto.getRandomValues 2025-06-24 15:01:32 -07:00
Muki Kiboigo
c0fc3a19c8 add CSS utility interface 2025-06-24 13:55:47 -07:00
Muki Kiboigo
ce638c39e3 add AbstractRange and Range 2025-06-24 12:06:50 -07:00
Muki Kiboigo
6b651cd5e4 add PerformanceEntry and PerformanceMark 2025-06-24 12:04:28 -07:00
sjorsdonkers
4560f31010 basic/bearer proxy authentication 2025-06-24 16:38:58 +02:00
Karl Seguin
c97a32e24b Initial work on CONNECT proxy.
Cannot currently connect to the proxy over TLS (though, once connected, it can
connect to the actual site over TLS). No support for authentication.
2025-06-24 15:10:20 +08:00
Karl Seguin
8a005bc5a1 Merge pull request #808 from lightpanda-io/accept-header
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
http: send an Accept: */* header
2025-06-24 09:42:14 +08:00
Pierre Tachoire
20aabee72e http: send an Accept: */* header 2025-06-23 18:18:04 -07:00
Karl Seguin
a00c2345ee Merge pull request #802 from lightpanda-io/endless_loop_fix_and_dot_slash_stitch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Don't allow timeouts to be registered when shutting down
2025-06-24 08:57:59 +08:00
Karl Seguin
cb35b3624a Merge pull request #803 from lightpanda-io/inline_module_no_cache
Fix module caching
2025-06-24 08:55:23 +08:00
Karl Seguin
c6f59a7aa6 Merge pull request #804 from lightpanda-io/add_error_event_web_api
add ErrorEvent webapi
2025-06-24 08:54:01 +08:00
Karl Seguin
bf296ad797 add ErrorEvent webapi 2025-06-23 19:04:59 +08:00
Karl Seguin
256540934b reject long timeouts as we're shutting down 2025-06-23 17:27:22 +08:00
Karl Seguin
3c07c0818d improve variable names 2025-06-23 17:19:30 +08:00
Karl Seguin
a01d18ace1 Fix module caching
In https://github.com/lightpanda-io/browser/pull/798 module caching was added.
This was necessary as the same module loaded multiple time should result in the
same v8 module instance.

To make this work, modules became cached by their full URL. The full URL of one
module was also used to determine the full URL of nested modules (full url +
specifier).

With inline scripts, the page URL was used as the full URL. While this is
correct when resolving nested modules, it's incorrect for caching the module
itself. Two inline modules on a page share the same URL, but they aren't the
same and should be cached.

To fix this, inline modules still inherit the page URL, in order to resolve the
correct URL for nested modules, but are themselves never cached.
2025-06-23 17:10:54 +08:00
Karl Seguin
55e02f01dc fix wpt runner 2025-06-23 16:47:31 +08:00
Karl Seguin
fe6ccad485 loop.run now takes a maximum wait time 2025-06-23 16:43:28 +08:00
Karl Seguin
11fe79312d Don't allow timeouts to be registered when shutting down
Currently, a timeout that sets a timeout can cause loop.run to block forever.

Also, cleanup URL stitch with leading './'. The resulting URL was valid, but
since we use the URL as the module cache key, it's important to resolve various
representations of the same URL in the same way.
2025-06-23 15:01:58 +08:00
Karl Seguin
bdb2338b5b Merge pull request #796 from lightpanda-io/docker
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
update dockerfile to multi-arch
2025-06-23 12:51:20 +08:00
Karl Seguin
bbafb048d0 Merge pull request #798 from lightpanda-io/module_loading
Fix module loading
2025-06-23 08:54:17 +08:00
Karl Seguin
9fc2fa51bd Merge pull request #797 from lightpanda-io/template_content
add HTML Template's content attribute
2025-06-23 08:54:00 +08:00
Karl Seguin
d8ec50345a Fix module loading
When V8 calls the ResolveModuleCallback that we give it, it passes the specifier
which is essentially the string given to `from`:

```
import {x} from './blah.js';
```

We were taking that specifier and giving it to the page. The page knew the
currently executing script, an thus could resolve the full URL. Given the full
URL, it could either return the JS content from its module cache or fetch
the source.

At best though, this isn't efficient. If two files import the same module, yes
we cache the src, but we still ask v8 to re-compile it. At worse, it crashes
due to resource exhaustion in the case of cyclical dependencies.

ResolveModuleCallback should instead detect that it has already loaded the
module and return the previously loaded module. Essentially, we shouldn't be
caching the JavaScript source, we should be caching the v8 module.

However, in order to do this, we need more than the specifier, which might only
be a relative path (and thus isn't unique). So, in addition to a module cache,
we now also maintain an module identifier lookup. Given a module, we can get
its full path. Thankfully ResolveModuleCallback gives us the referring module,
so we can look up that modules URL, stitch it to the specifier, and get the
full url (the unique identifier) within the JS runtime.

Need more real world testing, and a fully working example before I celebrate,
but for sites with many import, this appears to improve performance by many
orders of magnitude.
2025-06-20 19:17:55 +08:00
Karl Seguin
9f1cc09ca8 add HTML Template's content attribute 2025-06-20 14:36:56 +08:00
Karl Seguin
5dcc3db36b Merge pull request #795 from lightpanda-io/performance_observer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add dummy PerformanceObserver
2025-06-20 08:01:09 +08:00
Pierre Tachoire
898b73ffc8 update dockerfile to multi-arch 2025-06-19 10:10:14 -07:00
Karl Seguin
c5d49a9d34 Add dummy PerformanceObserver
Adds a dummy PerformanceObserver. Only the supportedEntryTypes static attribute
is supported, and it currently returns an empty array. This hopefully prevents
code from trying to use it. For example, before using it, reddit checks if
specific types are supported and, if not, doesn't use it.

This introduced complexity in the js runtime. Our current approach to
attributes only works with primitive types. Non-primitive types can't be
attached to a FunctionTemplate (v8 will crash saying only primitive types can
be set). Plus, all non primitive types require a context to create anyways.

We now detect "primitive" attributes and "complex" attributes. Primitive
attributes are setup as before. Complex attributes are setup per-context,
requiring another loop through our types to detect & setup on each context
creation.
2025-06-19 18:20:02 +08:00
Karl Seguin
ef9f828d35 Merge pull request #790 from lightpanda-io/css-stylesheet
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Minimal CSSStyleSheet
2025-06-19 10:32:13 +08:00
Karl Seguin
c691764205 Merge pull request #794 from lightpanda-io/window-screen
add Screen and ScreenOrientation
2025-06-19 10:29:52 +08:00
sjorsdonkers
2c940d4fd6 browser context proxyServer 2025-06-19 10:26:33 +08:00
Karl Seguin
54bd55d45d fix CSSStyleSheet prototype 2025-06-19 10:25:13 +08:00
Karl Seguin
0b846b15b1 Merge pull request #789 from lightpanda-io/browsercontext-proxyServer
browser context proxyServer
2025-06-19 10:22:17 +08:00
Muki Kiboigo
269eb7e154 add Screen and ScreenOrientation 2025-06-18 12:53:54 -07:00
Muki Kiboigo
97bc19e4ae clean up various imports in CSSOM 2025-06-18 11:32:28 -07:00
Muki Kiboigo
2656cc7842 Add basic tests for CSSStyleSheet 2025-06-18 11:32:28 -07:00
Muki Kiboigo
ba94818415 add CSSStyleSheet 2025-06-18 11:32:27 -07:00
Pierre Tachoire
ac759a6eed Merge pull request #793 from lightpanda-io/domrect-bottom
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add top, left, bottom, right to DOMRect
2025-06-18 09:54:08 -07:00
Pierre Tachoire
1839b346a6 Merge pull request #792 from lightpanda-io/fix_current_script_scope
Fixes the scoping of page.current_script
2025-06-18 09:51:41 -07:00
Pierre Tachoire
c1ffe7f8e6 Merge pull request #791 from lightpanda-io/zig_event_target_fix
Fix crash when event.currentTarget is used with EventTargetTBase
2025-06-18 08:26:25 -07:00
Pierre Tachoire
833b4d10bd add top, left, bottom, right to DOMRect 2025-06-18 08:21:33 -07:00
Pierre Tachoire
ce98c336c9 keep EventTargetTBase as the dom_event_target
Mimic a dom_node by adding the refcnt field right after the vtable
pointer.
2025-06-18 07:08:35 -07:00
Karl Seguin
d05619990a Fixes the scoping of page.current_script
This was previously being set back to null before it was actually needed.

Also, added a more logs / log details.
2025-06-18 18:36:00 +08:00
Karl Seguin
8033e41d4a Merge pull request #788 from lightpanda-io/dont_keepalive_unprocess_request
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Delay setting the requests' keepalive flag until the request is fully…
2025-06-18 16:46:45 +08:00
sjorsdonkers
60f4eab759 handle no params 2025-06-18 10:07:37 +02:00
sjorsdonkers
d7656ea985 expires dashes and f64 2025-06-18 10:07:37 +02:00
sjorsdonkers
e402998577 JS may not set/get HttpOnly cookies 2025-06-18 10:07:37 +02:00
sjorsdonkers
073f75efa3 CDP Network cookie tests 2025-06-18 10:07:37 +02:00
sjorsdonkers
da414f7eb3 CDP.Storage cookies tests 2025-06-18 10:07:37 +02:00
sjorsdonkers
270b89830a Cleaning up crumbles 2025-06-18 10:07:37 +02:00
sjorsdonkers
74ce7ca416 refactor path / domain parsing 2025-06-18 10:07:37 +02:00
sjorsdonkers
3f4338cb51 wip 2025-06-18 10:07:37 +02:00
sjorsdonkers
30ee41fd0e Network.getCookies 2025-06-18 10:07:37 +02:00
sjorsdonkers
4965fec55c storage cookies 2025-06-18 10:07:37 +02:00
sjorsdonkers
18dff8455c lower case domain 2025-06-18 10:07:37 +02:00
sjorsdonkers
fe16f06aee clearRetainingCapacity 2025-06-18 10:07:37 +02:00
sjorsdonkers
48c1c05a93 setCookie 2025-06-18 10:07:37 +02:00
sjorsdonkers
38dee1166d setCookies 2025-06-18 10:07:37 +02:00
sjorsdonkers
0c6fc68eae deleteCookies 2025-06-18 10:07:37 +02:00
Karl Seguin
223611d89e Fix crash when event.currentTarget is used with EventTargetTBase
When EventTargetTBase is used, we pass the container as the target to libdom.
This is not safe, as libdom is expecting an event_target. We see, for example
that when _dom_event_get_current_target is called, the refcnt is increased.
This works if the current_target is a valid event_target, but if it's a
Zig instance (like the Window) ... we're just altering some bits of the
window instance.

This attempts to add a dummy target to EventTargetTBase which can acts as a
real event_targt in place of the Zig instance.
2025-06-18 14:49:15 +08:00
sjorsdonkers
6f5141d5fb browser context proxyServer 2025-06-17 18:43:12 +02:00
Karl Seguin
a6ac7d9c4e Delay setting the requests' keepalive flag until the request is fully processed
We currently set request._keepalive prematurely. There are [error cases] where
the request could be abandoned before being fully drained. While we do try to
drain in some cases, it isn't always possible. For this reason,
request.keepalive is only set at the end of the request lifecycle, at which
point we know the connection is ready to be re-used.
2025-06-17 19:55:36 +08:00
Karl Seguin
9b35736be3 Merge pull request #786 from lightpanda-io/custom-elements
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add CustomElementRegistry
2025-06-17 15:53:34 +08:00
Karl Seguin
9f54cb35f4 remove unused import and debug stmt 2025-06-17 08:20:11 +08:00
Karl Seguin
329bffb127 Fix non-probing of union array failure
Probing a union match for an possible value should rarely hard-fail. Instead,
it should return an .{.invalid = {}} response to let the prober decide how to
proceed. This fixes a hard-fail when a JS value fails to probe as an array.

Also, add :modal pseudo-class.

(both issues came up looking at github integration)
2025-06-17 08:19:01 +08:00
Karl Seguin
e2542f41b5 Improve build and test speed
Test speed has been improved only slightly by tweaking a 2-second running tests.

Build has been improved by:
1 - moving logFunctionCallError out of js.Caller and to a standalone function
2 - removing some non-generic code from the generic portions of the logger

Caller.getter and Caller.setter have been removed in favor or calling
Caller.method. This wasn't previously possible - prior to our v8 upgrade, they
had different signatures.

Also removed a largely unused parser/str.zig file.
2025-06-17 08:19:01 +08:00
Karl Seguin
efc7b9d4a5 Add comment explaining why we're walking the form the way we are 2025-06-17 08:19:01 +08:00
Karl Seguin
72915760c4 Use css.querySelectorAll to find form elements
Libdom's formGetCollection doesn't work (like I would expect) for dynamically
added elements.

For example, given:

```
let el = document.createElement('input');
document.getElementsByTagName('form')[0].append(el);
```

(and assume the page has a form), I'd expect `el.form` to be equal to the form
the input was added to. Instead, it's null. This is a problem given that
`dom_html_form_element_get_elements` uses the element's `form` attribute to
"collect" the elements.

This uses our existing querySelector to find the form elements.
2025-06-17 08:19:01 +08:00
Karl Seguin
e9d7a946c5 Merge pull request #784 from lightpanda-io/union_param_array_fix
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix non-probing of union array failure
2025-06-17 08:08:04 +08:00
Karl Seguin
714e5e0456 Merge pull request #785 from lightpanda-io/build_and_test_speed
Improve build and test speed
2025-06-17 08:07:43 +08:00
Karl Seguin
26e8642aca Merge pull request #782 from lightpanda-io/form_support_dynamic_elements
Use css.querySelectorAll to find form elements
2025-06-17 08:07:30 +08:00
Karl Seguin
68dfb4ee86 fix custom elements when minified js is used 2025-06-16 07:47:53 -07:00
Karl Seguin
f1ff789334 implement custom elements - i think/hope 2025-06-16 07:45:49 -07:00
Muki Kiboigo
1f45d5b8e4 add CustomElementRegistry 2025-06-16 07:35:55 -07:00
Karl Seguin
c20052f314 Add comment explaining why we're walking the form the way we are 2025-06-16 19:56:19 +08:00
Karl Seguin
c28d87d59c Improve build and test speed
Test speed has been improved only slightly by tweaking a 2-second running tests.

Build has been improved by:
1 - moving logFunctionCallError out of js.Caller and to a standalone function
2 - removing some non-generic code from the generic portions of the logger

Caller.getter and Caller.setter have been removed in favor or calling
Caller.method. This wasn't previously possible - prior to our v8 upgrade, they
had different signatures.

Also removed a largely unused parser/str.zig file.
2025-06-16 19:50:13 +08:00
Karl Seguin
237ddcba9a Fix non-probing of union array failure
Probing a union match for an possible value should rarely hard-fail. Instead,
it should return an .{.invalid = {}} response to let the prober decide how to
proceed. This fixes a hard-fail when a JS value fails to probe as an array.

Also, add :modal pseudo-class.

(both issues came up looking at github integration)
2025-06-16 17:07:43 +08:00
Karl Seguin
eadb5b6461 Use css.querySelectorAll to find form elements
Libdom's formGetCollection doesn't work (like I would expect) for dynamically
added elements.

For example, given:

```
let el = document.createElement('input');
document.getElementsByTagName('form')[0].append(el);
```

(and assume the page has a form), I'd expect `el.form` to be equal to the form
the input was added to. Instead, it's null. This is a problem given that
`dom_html_form_element_get_elements` uses the element's `form` attribute to
"collect" the elements.

This uses our existing querySelector to find the form elements.
2025-06-16 13:49:34 +08:00
Karl Seguin
faebabe3c7 Merge pull request #781 from lightpanda-io/null-scriptname
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
handle null scriptname in stacktrace
2025-06-16 11:52:40 +08:00
Pierre Tachoire
02c510b07f upgrade zig-v8 2025-06-13 19:19:10 +02:00
Pierre Tachoire
63541970eb handle null scriptname in stack trace 2025-06-13 19:17:07 +02:00
Pierre Tachoire
a8a5605fe1 typo fix 2025-06-13 19:16:54 +02:00
sjorsdonkers
0c0ddc10ee rename scope jscontext
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
2025-06-13 10:30:50 +02:00
Karl Seguin
9bd5ff69ef Merge pull request #779 from lightpanda-io/waitForNavigation
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Internal navigation should change the CDP loader-id
2025-06-13 09:38:33 +08:00
Karl Seguin
eadf351e82 Merge pull request #778 from lightpanda-io/terminate_execution
Terminate execution on internal navigation
2025-06-13 09:38:21 +08:00
Karl Seguin
e3afa294af Merge pull request #776 from lightpanda-io/fix_internal_navigation_deadlocks
Rework internal navigation to prevent deadlocking
2025-06-13 09:38:09 +08:00
Pierre Tachoire
582894cdc3 Merge pull request #780 from lightpanda-io/fix_loop_run_wait
Fix loop run (Page.wait)
2025-06-12 17:26:05 +02:00
Karl Seguin
2788c36ca6 Fix loop run (Page.wait)
In https://github.com/lightpanda-io/browser/pull/767 I tried to call loop.run
from within a loop.run (spoiler, it didn't work), in order to make sure aborted
connections were properly cleaned up before starting a new navigation. That
resulted in having loop.run no longer wait for timeouts for fear of having to
wait on a long timeout. The ended up breaking page.wait (used in the fetch
command).

This commit brings back the original behavior where loop.run() waits for all
completions. Which is now safe to do since the nested loop.run() call has
been removed.
2025-06-12 23:01:07 +08:00
Karl Seguin
872a9d393d Internal navigation should change the CDP loader-id
This is one of the ways that puppeteer knows that navigation happened
and is needed to support `waitForNavigation` which compares the
existing loader-id with the new one, so it has to change.

Also, fix a crash that could happen if CDP disconnects while
connections are being aborted.
2025-06-12 19:11:26 +08:00
Karl Seguin
b1ca242d89 Terminate execution on internal navigation
Currently, if there's an internal navigation event, we continue to process the
page normally. This has negative performance implication, and can result in
user-scripts producing unexpected results.

For example, imagine a page with a script that does.

```js
if (x) {
   form.submit();
}

reloadProduct();
```

The call to `form.submit()` should stop the script from executing. And, if the
page has 10 other <script> tags after this, they shouldn't be loaded nor
executed.

This code terminates the execution of the current script on an internal
navigation event, and stops the rest of the page from load.

While I believe this creates a more "correct" behavior, it also introduces new
edge cases. There's no a period of time, between the termination being stopped
and then being resumed, where executing code is not safe.
2025-06-12 16:38:48 +08:00
Karl Seguin
97c769e805 Rework internal navigation to prevent deadlocking
The mix of sync and async HTTP requests requires care to avoid deadlocks.
Previously, it was possible for async requests to use up all available HTTP
state objects duration a navigation flow (either directly, or via an internal
redirect (e.g. click, submit, ...)). This would block the navigation, which,
because everything is single thread, would block the I/O loop, resulting in a
deadlock.

The correct solution seems to be to remove all synchronous I/O. And I tried to
do that, but I ran into a wall with module-loading, which is initiated from V8.
V8 says "give me the source for this module", and I don't see a great way to
tell it: wait a bit.

So I went back to trying to make this work with the hybrid model, despite last
weeks failures to get it to work. I changed two things:

1 - The http client will only directly initiate an async request if there's
    at least 2 free state objects available (1 for the request, and leaving 1
    free for any synchronous requests)

2 - Delayed navigation retries until there's at least 1 free http state object
    available.

Commits from last week did help with this. First, we're now guaranteed to have
a single sync-request at a time (previously, we could have had 2). Secondly,
the async connection is now async end-to-end (previously, it could have blocked
on an empty state pool).

We could probably make this a bit more obviously by reserving 1 state object
for synchronous requests. But, since the long term solution is probably having
no synchronous requests, I'm happy with anything that lets me move past this
issue.
2025-06-12 12:34:51 +08:00
Karl Seguin
0de33b36f8 Merge pull request #773 from lightpanda-io/keydown_handling
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Add basic support for key events
2025-06-12 12:32:09 +08:00
Karl Seguin
cf39bdc7f7 use inputGetType and add buttonGetType 2025-06-12 09:46:02 +08:00
Karl Seguin
34b49498c9 Making sure that the optional parameters have defaults
Co-authored-by: Sjors <72333389+sjorsdonkers@users.noreply.github.com>
2025-06-12 09:46:01 +08:00
Karl Seguin
3a4bd00020 Ignore dead keys
Submit form on "Enter" within text input.

Convert "Enter" to "\n" in textarea.
2025-06-12 09:46:01 +08:00
Karl Seguin
effd07d8c0 Add basic support for key events
Support CDP's Input.dispatchKeyEvent and DOM key events. Currently only
keydown is supported and expects every key to be a displayable character.

It turns out that manipulating the DOM via key events isn't great because the
behavior really depends on the cursor. So, to do this more accurately, we'd
have to introduce some concept of a cursor.

Personally, I don't think we'll run into many pages that are purposefully
using keyboard events. But driver (puppeteer/playwright) scripts might be
another issue.
2025-06-12 09:45:59 +08:00
sjorsdonkers
d9ce89ab31 import html_element
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
2025-06-10 11:28:28 +02:00
Karl Seguin
5483c52227 Merge pull request #771 from lightpanda-io/http_request_fail
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Emit http_request_fail notification
2025-06-09 15:22:46 +08:00
sjorsdonkers
f12e9b6a49 use js try for errors
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
2025-06-06 14:06:25 +02:00
Karl Seguin
2b48902f1b Emit http_request_fail notification
CDP translate this into a Network.loadingFailed. This is necessary to make sure
every Network.requestWillBeSent is paired with either a Network.loadingFailed
or a Network.responseReceived.
2025-06-06 19:15:47 +08:00
Karl Seguin
305460dedb Merge pull request #768 from lightpanda-io/setExtraHTTPHeaders
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
setExtraHTTPHeaders
2025-06-06 16:45:07 +08:00
sjorsdonkers
bacef41a3b extra header feedback 2025-06-06 10:33:15 +02:00
Karl Seguin
f789c84816 Merge pull request #767 from lightpanda-io/unblock_async_http_request
Unblock async http request
2025-06-06 13:22:29 +08:00
Karl Seguin
09466a2dff Merge pull request #764 from lightpanda-io/url_search_parmas_from_object
URLSearchParam constructor support for object initialization
2025-06-06 13:22:17 +08:00
Karl Seguin
e77d888aab Merge pull request #766 from lightpanda-io/slow_down_animation_frame
Delay requestAnimation
2025-06-06 13:22:04 +08:00
Karl Seguin
478d91928c Merge pull request #765 from lightpanda-io/http_client_optimization
Optimize the lifecycle of async requests
2025-06-06 13:21:54 +08:00
Karl Seguin
fdd1a778f3 Properly drain event loop when navigating between pages 2025-06-06 12:53:45 +08:00
Karl Seguin
a5d87ab948 Reduce duration of the main request
We currently keep the main request open during loadHTMLDoc and processHTMLDoc.
It _has_ to be open during loadHTMLDoc, since that streams the body. But it
does not have to be open during processHTMLDoc, which can be log and itself
could make use of that same connection if it was released. Reorganized the
navigate flow to limit the scope of the request.

Also, just like we track pending_write and pending_read, we now also track
pending_connect and only shutdown when all are not pending.
2025-06-05 23:41:21 +08:00
sjorsdonkers
f1672dd6d2 setExtraHTTPHeaders 2025-06-05 16:42:29 +02:00
Karl Seguin
48c25c380d Removing blocking code async HTTP request
The HTTP Client has a state pool. It blocks when we've exceeded max_concurrency.
This can block processing forever. A simple way to reproduce this is to go into
the demo cdp.js, and execute the XHR request 5 times (loading json/product.json)

To some degree, I think this is a result of weird / non-intuitive execution
flow. If you exec a JS with 100 XHR requests, it'll call our XHR _send function
but none of these will execute until the loop is run (after the script is done
being executed). This can result in poor utilization of our connection and
state pool.

For an async request, getting the *Request object is itself now asynchronous.
If no state is available, we use the Loop's timeout (at 20ms) to keep checking
for an available state.
2025-06-05 20:52:37 +08:00
Karl Seguin
3a5aa87853 Optimize the lifecycle of async requests
Async HTTP request work by emitting a "Progress" object to a callback. This
object has a "done" flag which, when `true`, indicates that all data has been
emitting and no future "Progress" objects will be sent.

Callers like XHR buffer the response and wait for "done = true" to then process
the request.

The HTTP client relies on two important object pools: the connection and the
state (with all the buffers for reading/writing).

In its current implementation, the async flow does not release these pooled
objects until the final callback has returned. At best, this is inefficient:
we're keeping the connection and state objects checked out for longer than they
have to be. At worse, it can lead to a deadlock. If the calling code issues a
new request when done == true, we'll eventually run out of state objects in the
pool.

This commit now releases the state objects before emit the final "done" Progress
message. For this to work, this final message will always have null data and
an empty header object.
2025-06-05 20:52:37 +08:00
Karl Seguin
f436744dd4 Delay requestAnimation
This is often called in a tight loop (the callback to requestAnimation typically
calls requestAnimation).

Instead, we can treat it like a setTimeout with a short delay (5ms ?). This has
the added benefit of making it cancelable, FWIW.
2025-06-05 20:35:46 +08:00
Karl Seguin
6df5e55807 Optimize the lifecycle of async requests
Async HTTP request work by emitting a "Progress" object to a callback. This
object has a "done" flag which, when `true`, indicates that all data has been
emitting and no future "Progress" objects will be sent.

Callers like XHR buffer the response and wait for "done = true" to then process
the request.

The HTTP client relies on two important object pools: the connection and the
state (with all the buffers for reading/writing).

In its current implementation, the async flow does not release these pooled
objects until the final callback has returned. At best, this is inefficient:
we're keeping the connection and state objects checked out for longer than they
have to be. At worse, it can lead to a deadlock. If the calling code issues a
new request when done == true, we'll eventually run out of state objects in the
pool.

This commit now releases the state objects before emit the final "done" Progress
message. For this to work, this final message will always have null data and
an empty header object.
2025-06-05 12:40:59 +08:00
Karl Seguin
c758054250 URLSearchParam constructor support for object initialization
This adds support for:

```
new URLSearchParams({over: 9000});
```

The spec says that any thing that produces/iterates a sequence of string pairs
is valid. By using the lower-level JsObject, this hopefully takes care of the
most common cases. But I don't think it's complete, and I don't think we
currently capture enough data to make this work. There's no way for the JS
runtime to know if a value (say, a netsurf instance, or even a Zig instance)
provides an string=>string iterator.
2025-06-05 09:44:36 +08:00
Karl Seguin
fff0a8a522 Merge pull request #757 from lightpanda-io/window_target_crash
Some checks failed
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Fix crash when event target is the window.
2025-06-05 07:55:59 +08:00
Karl Seguin
4ff978f318 Merge pull request #762 from lightpanda-io/url_constructor
Url constructor
2025-06-05 07:55:48 +08:00
Karl Seguin
b29e07faba expose URLSearchParams toString and URL.toString 2025-06-04 21:41:49 +08:00
Karl Seguin
b35107a966 URL stitch avoid double / 2025-06-04 21:41:49 +08:00
Karl Seguin
1090ff0175 URL constructor overload support
Allow URL constructor to be created with another URL or an HTML element.

Add URL set_search method.

Remove no-longer-used url/query.zig
2025-06-04 21:41:49 +08:00
Karl Seguin
8de57ec0e0 Merge pull request #761 from lightpanda-io/pozo_for_custom_state
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Improve usability of NodeWrapper
2025-06-04 21:38:50 +08:00
Karl Seguin
4165f47a64 merge all states 2025-06-04 19:52:23 +08:00
sjorsdonkers
f931026216 update libdom with embedder data fix 2025-06-04 12:38:26 +02:00
Karl Seguin
19df73729a Improve usability of NodeWrapper
The NodeWrapper pattern attaches a Zig instance to a libdom Node. That works in
isolation, but for 1 given node, we might want to attach different instances.

For example, for an HTMLScriptElement we want to attach an `onError`, but for
that same node viewed as an HTMLElement we want to a `CSSStyleDeclaration`. We
can only have one. Currently, this code will crash if, for example, we create
the embedded data as an HTMLScriptElement, then try to read the embedded data
as an HTMLElement.

This PR introduces dedicated state class. So if you want the onError property,
you no longer ask the NodeWrapper for an HTMLSCriptElement. Instead, you ask
for a storage/HTMLElement.

Nothing fancy here, just memory-inefficient optional fields. If it gets out of
hand, we'll think of something more clever.
2025-06-04 18:04:39 +08:00
Karl Seguin
9efc1a1c09 Merge pull request #752 from lightpanda-io/url_search_params
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Rework/fix URLSearchParams
2025-06-04 14:38:23 +08:00
Karl Seguin
234e7afb12 Merge pull request #721 from lightpanda-io/HTMLInputElement-properties
Input element properties
2025-06-04 14:22:45 +08:00
Karl Seguin
8904afaa74 Fix crash when event target is the window.
On page load, emitted by the page, the target is the window, but it's improperly
cast since the pointer is actually `window.base`. This is going to be a problem
in general for any Zig type dispatched as a target, but the Window one is the
most obvious and the easiest to fix. If this issue comes up with other types,
we'll need to come up with a more robust solution.
2025-06-04 11:17:57 +08:00
Karl Seguin
d95a18b6eb Merge pull request #756 from lightpanda-io/nix-2505
Bump Nixpkgs to 25.05
2025-06-04 08:40:51 +08:00
Karl Seguin
bcd4bdb4e0 Merge pull request #754 from lightpanda-io/fix-makebuilddev
fix makebuilddev
2025-06-04 08:34:42 +08:00
Karl Seguin
73df41b5b2 Merge pull request #753 from lightpanda-io/console_error_stack_trace
Make stacktraces available in debug via `page.stackTrace()`
2025-06-04 08:34:09 +08:00
Karl Seguin
d32fbfd634 Merge pull request #749 from lightpanda-io/functions
Poor support for functions/namespaces.
2025-06-04 08:33:51 +08:00
Karl Seguin
6b0c532f48 Merge pull request #742 from lightpanda-io/focus_and_active_element
Focus and active element
2025-06-04 08:33:20 +08:00
Muki Kiboigo
9f4ee7d6a8 update nixpkgs to 25.05 2025-06-03 10:44:03 -07:00
sjorsdonkers
7da83d2259 fix makebuilddev 2025-06-03 16:25:35 +02:00
sjorsdonkers
ceb9453006 Simplify testing 2025-06-03 16:04:31 +02:00
Karl Seguin
7091b37f3a Make stacktraces available in debug via page.stackTrace()
Automatically include the stack trace in a `console.error` output. This is
useful because code frequently does:

```
  try blah();
  catch (e) console.log(e);
```

Which we log, but, without this, don't get the stack.
2025-06-03 20:40:40 +08:00
Karl Seguin
18e6f9be71 Detached node can't have focus.
Refactor isNodeAttached because of the "law of three."
2025-06-03 20:25:15 +08:00
sjorsdonkers
19d40845a4 input prop testing 2025-06-03 14:11:35 +02:00
Karl Seguin
211ce20132 Add document.activeElement and HTMLElement.focus() 2025-06-03 20:10:33 +08:00
sjorsdonkers
275b97948b input element properties 2025-06-03 14:08:54 +02:00
Karl Seguin
13d602a9e0 Rework/fix URLSearchParams
Extracts the FormData logic, which is both more complete and more correct and
reuses it between FormData and URLSearchParams.

This includes the additional iterator behavior, `set` and URLSearchParams
constructor from FormData.
2025-06-03 20:01:01 +08:00
Francis Bouvier
69215e7d27 Merge pull request #751 from lightpanda-io/dummy_window_scroll_to
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add noop window.scrollTo
2025-06-03 13:49:29 +02:00
Karl Seguin
7e8df34681 add noop window.scrollTo 2025-06-03 18:43:35 +08:00
Karl Seguin
6451065c77 Poor support for functions/namespaces.
If you look at the specification for `console` [1], you'll note that it's a
namespace, not an interface (like most things). Furthermore, MDN lists its
methods as "static".

But it's a pretty weird namespace IMO, because some of its "functions", like
`count` can have state associated with them.

This causes some problems with our current implementation. Something like:

```
[1].forEach(console.log)
```

Fails, since `this` isn't our window-attached Console instance.

This commit introducing a new `static_XYZ` naming convention which does not
have the class/Self as a receiver:

```
pub fn static_log(values: []JsObject, page: *Page) !void {
```

This turns Console into a namespace for these specific functions, while still
being used normally for those functions that require state.

We could infer this behavior from the first parameter, but that seems more
error prone. For now, I prefer having the explicit `static_` prefix.

[1] https://console.spec.whatwg.org/#console-namespace
2025-06-03 14:40:10 +08:00
Karl Seguin
bde8c54e7e Merge pull request #748 from lightpanda-io/test_leak
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
fix leak in test
2025-06-03 10:58:53 +08:00
Karl Seguin
97b17af056 fix leak in test 2025-06-03 10:49:52 +08:00
Karl Seguin
9c2e3e2c76 Merge pull request #740 from lightpanda-io/fix_anchor_href
Fix anchor href
2025-06-03 10:47:25 +08:00
Karl Seguin
3c637872f2 Merge pull request #743 from lightpanda-io/default_timeout_10s
Increase default timeout from 3s to 10s.
2025-06-03 10:47:10 +08:00
Karl Seguin
4c8e2a1258 Setting anchor href should consider document.url 2025-06-03 09:58:26 +08:00
Karl Seguin
e5a76d737c Increase default timeout from 3s to 10s.
The wait_for_network_idle demo often times out for me. I don't see any reason
to have the default so low. More likely to cause user scripts to unnecessarily
fail.
2025-06-03 09:57:51 +08:00
Karl Seguin
a482d5998d Merge pull request #739 from lightpanda-io/url_constructor
Fix url constructor
2025-06-03 09:55:47 +08:00
Karl Seguin
12bc540ec9 Merge pull request #744 from lightpanda-io/dockerfile_zig_path_fix
Update zig filename for new pattern used in 0.14.1+
2025-06-03 09:52:25 +08:00
Karl Seguin
b6a37f6fb8 Merge pull request #747 from lightpanda-io/fix_crash_on_error_exit
fix a silly log crash on exit error
2025-06-03 09:52:08 +08:00
Karl Seguin
bbdb25420a Merge pull request #746 from lightpanda-io/null_object_guard
Guard against null object when trying to fetch a function
2025-06-03 09:51:54 +08:00
Karl Seguin
e3099a16d4 fix a silly log crash on exit error 2025-06-02 23:34:09 +08:00
Karl Seguin
167fe5f758 Guard against null object when trying to fetch a function 2025-06-02 23:27:29 +08:00
Karl Seguin
36f59da7cc Update zig filename for new pattern used in 0.14.1+
https://github.com/lightpanda-io/browser/issues/711
2025-06-02 21:59:09 +08:00
Karl Seguin
1ac23ce191 Merge pull request #735 from lightpanda-io/improved_logging
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Improved logging
2025-06-02 21:53:51 +08:00
Karl Seguin
a000dfe676 include stack trace in JS function call log errors 2025-06-02 21:43:24 +08:00
Karl Seguin
9e834e0db5 Revert "experiment with reducing retained arena size"
This reverts commit 2f6b4c04da3e4659a3ebe8bcb9195f4625feaa16.
2025-06-02 21:43:20 +08:00
Karl Seguin
021fc8fb59 experiment with reducing retained arena size 2025-06-02 21:41:53 +08:00
Karl Seguin
625fa03c22 fix tests 2025-06-02 21:38:57 +08:00
Karl Seguin
6e80b03faa Improve script logging
1 - Add a custom console.lp function to make our debug logs stand out from
    script logs.

2 - In some cases, significantly improve how JavaScript values are serialized
    in debug logs and in console.log.
2025-06-02 21:38:57 +08:00
Karl Seguin
c3f3eea7fb Improve logging
1 - Make log_level a runtime option (not a build-time)
2 - Make log_format a runtime option
3 - In Debug mode, allow for log scope filtering

Improve the general usability of scopes. Previously, the scope was more or less
based on the file that the log was in. Now they are more logically grouped.
Consider the case where you want to silence HTTP request information, previously
you'd have to filter out the `page`, `xhr` and `http_client` scopes, but that
would also elimiate other page, xhr and http_client logs. Now, you can just
filter out the `http` scope.
2025-06-02 21:38:56 +08:00
Karl Seguin
47da5e0338 Merge pull request #737 from lightpanda-io/release_fast
Run puppeteer-perf using ReleaseFast
2025-06-02 21:20:07 +08:00
Karl Seguin
2ef7ea6512 change stitch alloc default to .always 2025-06-02 19:24:08 +08:00
Karl Seguin
6b1f2c0ed2 Merge pull request #741 from lightpanda-io/2xx_status
Allow any 2xx status code for scripts
2025-06-02 19:20:59 +08:00
Karl Seguin
bb465ed1ed Allow any 2xx status code for scripts
DDG will sometimes return a 202 for its result javascript, meaning it isn't
ready and the rest of the JS will then handle that case. It's weird, but there's
no reason for us to abort on a 2xx code.
2025-06-02 17:20:28 +08:00
Karl Seguin
ac75f9bf57 Fix url constructor
url, base were being joined in the wrong order. Switch to using URL.stitch if
a base is given.
2025-06-02 16:43:01 +08:00
Karl Seguin
c80deeb5ec Merge pull request #738 from lightpanda-io/buttons_submit_form
Submit input and button submits can now submit forms
2025-06-02 16:30:51 +08:00
sjorsdonkers
1b87f9690c remove superflous text 2025-06-02 10:23:06 +02:00
sjorsdonkers
e799fcd48a xmlserializer for doctype 2025-06-02 10:23:06 +02:00
Karl Seguin
4644e55883 Do not reset transfer_arena if page navigation results in delayed navigation
We normally expect a navigation event to happen at some point after the page
loads, like a puppeteer script clicking on a link. But, it's also possible for
the main navigation event to result in a delayed navigation. For example, an
html page with this JS:

<script>top.location = '/';</script>

Would result in a delayed navigation being called from the main navigate
function. In these cases, we cannot clear the transfer_arena when navigate is
completed, as its memory is needed by the new "sub" delayed navigation.
2025-06-02 14:16:36 +08:00
Karl Seguin
747a8ad09c Submit input and button submits can now submit forms 2025-06-02 11:27:44 +08:00
Karl Seguin
32dc19cb1c Run puppeteer-perf using ReleaseFast 2025-06-01 19:30:33 +08:00
Karl Seguin
527579aef4 Merge pull request #720 from lightpanda-io/clean_xhr_shutdown
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Clean Http Request Shutdown
2025-05-31 07:51:11 +08:00
Karl Seguin
1869ef0c38 Merge pull request #734 from lightpanda-io/url_resolve_buffer_size
increase buffer size 1024->4096
2025-05-31 07:50:57 +08:00
Karl Seguin
e7007b4231 fix test 2025-05-31 07:31:05 +08:00
Karl Seguin
6ca57c1f8c Merge pull request #723 from lightpanda-io/form_submit
Form submit
2025-05-31 07:23:49 +08:00
Karl Seguin
f2f7a349ce Merge pull request #715 from lightpanda-io/location_change
Implement location.reload(), location.assign() and location setter
2025-05-31 07:23:36 +08:00
Karl Seguin
f696aa3748 Merge pull request #726 from lightpanda-io/fix_set_innerhtml_and_html_collection
Fix set_innerHTML, fix HTMLCollection fixed (postAttached) return type
2025-05-31 07:23:24 +08:00
Karl Seguin
f35e3ec78a Merge pull request #725 from lightpanda-io/dynamic_script_onload
Execute onload for dynamic script
2025-05-31 07:23:14 +08:00
Karl Seguin
e339ee3f0c Clean Http Request Shutdown
The Request object now exists on the heap, allowing it to outlive whatever is
making the request (e.g. the XHR object). We can now wait until all inflight IO
events are completed before clearing the memory.

This change fixes the crash observed in:
https://github.com/lightpanda-io/browser/issues/667
2025-05-31 07:22:01 +08:00
Karl Seguin
c30b424f36 increase buffer size 1024->4096 2025-05-31 07:19:30 +08:00
Pierre Tachoire
0b0b405974 Merge pull request #733 from lightpanda-io/e2e-bench
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
ci: disable telemetry for 2e2 tests
2025-05-30 16:33:25 +02:00
Karl Seguin
ef64fa3794 Execute onload for dynamic script
Add support for onerror for static and dynamic scripts.

Make script type checking case insensitive.
2025-05-30 22:24:44 +08:00
Pierre Tachoire
2531aed50b ci: disable telemetry for 2e2 tests 2025-05-30 16:22:59 +02:00
Karl Seguin
6adb46abd5 Merge pull request #727 from lightpanda-io/named_node_map_named_index_and_iteartor
Implement named_get and iterator on NamedNodeMap
2025-05-30 22:22:06 +08:00
Karl Seguin
3ef1d8b0b9 Merge pull request #729 from lightpanda-io/fix_node_insert_before_null_reference
support null referene node to Node.insertBefore
2025-05-30 22:21:29 +08:00
Karl Seguin
71b5dc2f81 Merge pull request #731 from lightpanda-io/minor_chores
Update zig-v8-fork + zig fmt fix
2025-05-30 22:21:18 +08:00
Karl Seguin
5909ab7641 Merge pull request #730 from lightpanda-io/fix_html_image
Fix HTMLImageElement
2025-05-30 22:21:06 +08:00
Pierre Tachoire
b7beb73a92 Merge pull request #728 from lightpanda-io/e2e-bench
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: switch lpd_bench_cdp
2025-05-30 15:41:55 +02:00
Karl Seguin
0acbb20c00 Merge pull request #732 from lightpanda-io/intersection_observer_threshold
IntersectionObserver's threshold option should be an union
2025-05-30 21:28:18 +08:00
Karl Seguin
9a2c0067f1 IntersectionObserver's threshold option should be an union 2025-05-30 20:48:10 +08:00
Karl Seguin
ab45b42382 Update zig-v8-fork + zig fmt fix
zig-v8-fork update simply removes a couple std.debug statements
2025-05-30 20:08:52 +08:00
Karl Seguin
4a6cee0611 Fix HTMLImageElement
HTMLImageElement is the correct class name. However, it has a "legacy factory":
Image (i.e. new Image()).
2025-05-30 20:05:51 +08:00
Karl Seguin
d39cada0c6 support null referene node to Node.insertBefore 2025-05-30 18:03:03 +08:00
Pierre Tachoire
b7b67681c7 ci: give time to start services 2025-05-30 11:27:35 +02:00
Pierre Tachoire
8551e05808 ci: switch lpd_bench_cdp 2025-05-30 11:02:28 +02:00
Karl Seguin
cfdbd418c1 Implement named_get and iterator on NamedNodeMap 2025-05-30 14:42:54 +08:00
Karl Seguin
2a4feb7bee Fix set_innerHTML, fix HTMLCollection fixed (postAttached) return type 2025-05-30 13:32:29 +08:00
Karl Seguin
7202d758a2 Merge pull request #714 from lightpanda-io/live_scripts
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Load dynamically added <script> tags
2025-05-29 18:06:56 +08:00
Karl Seguin
dab59aded3 Merge pull request #707 from lightpanda-io/skip_large_headers
Skip large header lines that don't fit into the header buffer.
2025-05-29 18:06:40 +08:00
Karl Seguin
20d0b4ad16 update libdom dep 2025-05-29 16:00:41 +08:00
Karl Seguin
eed4fc7844 Load dynamically added <script> tags
Add a callback to libdom which triggers whenever a script tag is added. Page
registers the callback AFTER the HTML is parsed, but before any JS is processed
and loads the script tags.
2025-05-29 16:00:40 +08:00
Karl Seguin
0ccd9e0579 Merge pull request #716 from lightpanda-io/skip_long_timeouts
Skip long setTimeout/setInterval
2025-05-29 15:59:52 +08:00
Karl Seguin
74b36d6d32 support form.submit()
Only supports application/x-www-form-urlencoded
2025-05-29 14:10:07 +08:00
Karl Seguin
58215a470b Implement location.reload(), location.assign() and location setter
I'm not sure that _any_ location instance should be able to change the page URL.
But you can't create a new location (i.e. new Location() isn't valid), and the
only two ways I know of are via `window.location` and `document.location` both
of which _should_ alter the location of the window/document.
2025-05-29 13:59:15 +08:00
Karl Seguin
608e0a0122 Skip long setTimeout/setInterval
I guess this should eventually become a configuration option - what time is too
long and should they be skipped or just be run sooner?

But for now, this unblocks from fetching a site like DDG which does a setTimeout
of 2 minutes.
2025-05-29 13:58:31 +08:00
Karl Seguin
bddb3f0542 Merge pull request #724 from lightpanda-io/apt_update
run apt-get update before trying to install
2025-05-29 13:57:02 +08:00
Karl Seguin
83da81839b run apt-get update before trying to install 2025-05-29 13:50:22 +08:00
Karl Seguin
73d63293d9 Merge pull request #722 from lightpanda-io/nix
Update flake.nix for Zig 0.14.1
2025-05-29 08:10:15 +08:00
Muki Kiboigo
f49710f361 update flake.nix for Zig 0.14.1 2025-05-28 13:05:03 -07:00
Karl Seguin
dffbce1934 Merge pull request #712 from lightpanda-io/tweak_http_logs
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Reduce info logs of HTTP event
2025-05-28 23:04:29 +08:00
Karl Seguin
06a33b0c8b Merge pull request #717 from lightpanda-io/missing-t
Missing T
2025-05-28 23:02:40 +08:00
Karl Seguin
a1f140acf7 Merge pull request #718 from lightpanda-io/max_memory_30
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
increase max memory threshold to 30
2025-05-28 17:21:18 +08:00
Karl Seguin
fed37bcc48 increase max memory threshold to 30 2025-05-28 17:07:28 +08:00
sjorsdonkers
88df9f0134 missing t 2025-05-28 10:42:33 +02:00
Karl Seguin
79d1425530 Reduce info logs of HTTP event
In normal cases, only log a single info event HTTP request. In an error case or
when log-level=debug, more may be logged.
2025-05-28 11:18:38 +08:00
Karl Seguin
f9144378ae Re-enable microtask loop
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Must have gotten disabled in a merge?
2025-05-27 21:05:24 +02:00
Muki Kiboigo
d13d28e6f4 use single pointer to parser.MouseEvent 2025-05-27 20:55:54 +02:00
Muki Kiboigo
c438bb2fbe fix style of MouseEvent interface 2025-05-27 20:55:54 +02:00
Muki Kiboigo
5f4dd43124 support int enums in jsValueToZig 2025-05-27 20:55:54 +02:00
Muki Kiboigo
e7f16f371c add MouseEvent 2025-05-27 20:55:54 +02:00
Karl Seguin
30ff17df28 Skip large header lines that don't fit into the header buffer.
https://github.com/lightpanda-io/browser/issues/672
2025-05-28 00:14:51 +08:00
Karl Seguin
d7a3e2f450 Merge pull request #694 from lightpanda-io/add_event_listener_object
AddEventListener object listener
2025-05-27 21:05:52 +08:00
Karl Seguin
9ce3fc9f8e Refactor events
Removes some duplication between xhr/event_target and dom/event_target.

Implement 'once' option of addEventListener.
2025-05-27 21:03:43 +08:00
Karl Seguin
f0017c3e92 No-op eventHandler's passive option
This is a hint to the brower that the listener won't call preventDefault. In
theory, we should enforce this. But in practice, ignoring it should be ok.
2025-05-27 20:59:16 +08:00
Karl Seguin
99b7508c7a support object listener on removeEventListener also 2025-05-27 20:59:16 +08:00
Karl Seguin
cff8857a36 AddEventListener object listener
Instead of taking a callback function, addEventListener can take an object
that exposes a `handleEvent` function. When used this way, `this` is
automatically bound. I don't think the current behavior is correct when
`handleEvent` is defined as a property (getter), but I couldn't figure out how
to make it work the way WPT expects, and it hopefully isn't a common usage
pattern.

Also added option support to removeEventListener.
2025-05-27 20:59:14 +08:00
Karl Seguin
60395852d5 Merge pull request #706 from lightpanda-io/cookie-domain-localhost
cookies: accept localhost domain
2025-05-27 20:55:21 +08:00
Karl Seguin
edf125b4ba Merge pull request #705 from lightpanda-io/page_as_state
Replace SessionState directly with the Page.
2025-05-27 20:55:01 +08:00
Pierre Tachoire
b731fa4b78 cookie: ignore case when comparing with localhost domain
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2025-05-27 14:31:59 +02:00
Karl Seguin
676e6ecec1 fix/revert debug code 2025-05-27 20:31:37 +08:00
Karl Seguin
7d9951aa3c Replace SessionState directly with the Page. 2025-05-27 20:31:34 +08:00
Karl Seguin
1d0876af4d Merge pull request #691 from lightpanda-io/logger
Replace std.log with a structured logger
2025-05-27 20:24:07 +08:00
Pierre Tachoire
c6f23eee77 cookies: accept localhost domain 2025-05-27 14:11:32 +02:00
Karl Seguin
8d3cf04324 re-enable log tests 2025-05-27 19:57:58 +08:00
Karl Seguin
fe9344ce57 Try stateless logger (to save memory) 2025-05-27 19:57:58 +08:00
Karl Seguin
d7c4824633 remove unused init, and remove magic pre-alloc 2025-05-27 19:57:58 +08:00
Karl Seguin
2feba3182a Replace std.log with a structured logger
Outputs in logfmt in release and a "pretty" print in debug mode. The format
along with the log level will become arguments to the binary at some point in
the future.
2025-05-27 19:57:58 +08:00
Pierre Tachoire
e9920caa69 Merge pull request #703 from lightpanda-io/window_top
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Simple window.top implementation (not frame-aware)
2025-05-27 09:48:52 +02:00
Karl Seguin
9bcaaab9d7 Simple window.top implementation (not frame-aware) 2025-05-27 15:25:27 +08:00
Pierre Tachoire
d47db317fb Merge pull request #702 from lightpanda-io/ci-bench
ci: execute cdp bench on main only
2025-05-27 06:48:31 +02:00
Pierre Tachoire
287d0fad85 Merge pull request #701 from lightpanda-io/s3-nightly
ci: use GLACIER IR class storage for release
2025-05-27 06:40:35 +02:00
Pierre Tachoire
7c19de3d61 ci: execute cdp bench on main only 2025-05-27 06:38:14 +02:00
Pierre Tachoire
a76cdf7514 ci: use GLACIER IR class storage for release 2025-05-27 06:34:24 +02:00
Karl Seguin
9abead7c49 Merge pull request #690 from lightpanda-io/zig_0_14_1
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Upgrade to Zig 0.14.1
2025-05-27 08:11:33 +08:00
Pierre Tachoire
5ff3f71f83 Merge pull request #700 from lightpanda-io/s3-nightly
S3 nightly
2025-05-26 21:57:31 +02:00
Pierre Tachoire
e2f9ca66b6 ci: upload artifact on s3 2025-05-26 21:43:14 +02:00
Pierre Tachoire
e90048e5a8 Merge pull request #696 from lightpanda-io/cli_argument_name_fix
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Fix insecure_disable_tls_host_verification in serve more
2025-05-26 20:35:21 +02:00
Pierre Tachoire
eb1795aff9 Merge pull request #699 from lightpanda-io/bench-runner
ci: add missing AWS creds
2025-05-26 18:59:11 +02:00
Pierre Tachoire
3a92f93e6f ci: add missing AWS creds 2025-05-26 18:58:41 +02:00
Pierre Tachoire
d1bd358785 Merge pull request #698 from lightpanda-io/bench-runner
ci: refacto e2e http start/stop
2025-05-26 18:53:53 +02:00
Pierre Tachoire
f63ea62f2d ci: refacto e2e http start/stop 2025-05-26 18:30:51 +02:00
Pierre Tachoire
3fd5ed4feb Merge pull request #697 from lightpanda-io/bench-runner
ci:fix deps
2025-05-26 18:22:25 +02:00
Pierre Tachoire
ba7df8b9cf ci:fix deps 2025-05-26 18:21:59 +02:00
Pierre Tachoire
18b97df619 Merge pull request #693 from lightpanda-io/bench-runner
ci: add cdp-bench
2025-05-26 18:20:30 +02:00
Pierre Tachoire
087d23269b ci: add hyperfine test 2025-05-26 18:07:05 +02:00
Karl Seguin
c77fb98b1f Fix insecure_disable_tls_host_verification in serve more
It's currently using `--insecure_tls_verify_host` which is inconsistent with
fetch-mode and not what the help text says.
2025-05-26 22:42:42 +08:00
Pierre Tachoire
8c1f38f74d ci: e2e: build release w/ -Dcpu=x86_64 option 2025-05-26 13:16:36 +02:00
Pierre Tachoire
13091e0de4 ci: add cdp-bench
The cdp bench is run on self host machine.
2025-05-26 13:16:36 +02:00
Karl Seguin
1a72bf5962 Merge pull request #692 from lightpanda-io/get_computed_style
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
make getComptedStyle return an empty CSSStyleDeclaration
2025-05-26 17:24:31 +08:00
Karl Seguin
b8cd0c1a77 remove debug statement 2025-05-26 15:43:21 +08:00
Karl Seguin
ecd593fb53 Add dummy Element.checkVisibility
`checkVisibility` currently always return true. Also, when the visibility CSS
property is checked, always return 'visible'. This allows the playwright click
test to pass with a working getComputedStyle. It's also probably more accurate -
by default, most elements are probably visible. But it still isn't great.

Add named_get to CSSStyleDeclaration (allowing things like `style.display`).
2025-05-26 15:08:25 +08:00
Karl Seguin
b17f20e2c5 make getComptedStyle return an empty CSSStyleDeclaration 2025-05-26 11:16:51 +08:00
Karl Seguin
eae9f9ceee Merge pull request #664 from lightpanda-io/treewalker
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add TreeWalker
2025-05-26 11:06:56 +08:00
Karl Seguin
d2c13ed32b Merge pull request #680 from lightpanda-io/css_style_declaration
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
CSSStyleDeclaration implementation
2025-05-25 19:34:20 +08:00
Karl Seguin
6fb78a99bf update mlugg/setup-zig action 2025-05-25 09:10:42 +08:00
Karl Seguin
bcc4980189 Upgrade to Zig 0.14.1 2025-05-24 19:55:50 +08:00
Karl Seguin
bed394db80 Prefix tests (easier to filter, i.e. make test F="CSSValue")
Don't dupe value if it doesn't need to be quoted.
2025-05-24 11:45:42 +08:00
Karl Seguin
1fe2bf5dd5 Use fetchRemove and getOrPut to streamline map manipulation 2025-05-24 10:24:32 +08:00
Karl Seguin
7cc332a96e Merge pull request #675 from lightpanda-io/http_request_notifications
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
HTTP request notification
2025-05-24 10:10:16 +08:00
Karl Seguin
6ce24b3443 Rename allocator to arena to make the intent more clear
Use expectEqual where possible

deduplicate finalize and finishDeclaration
2025-05-24 10:08:26 +08:00
Karl Seguin
1dc6e91ec4 increase max memory threshold 2025-05-24 09:53:45 +08:00
Karl Seguin
f59e3cd4da Maybe retry on TlsAlertCloseNotify error
This might not be specific to network notification, but the issue happens all
the time testing scenarios that rely on network notification, so it's hard
to ignore.
2025-05-24 09:01:13 +08:00
Karl Seguin
94a30b2167 HTTP request notification
- Add 2 internal notifications
  1 - http_request_start
  2 - http_request_complete

- When Network.enable CDP message is received, browser context registers for
  these 2 events (when Network.disable is called, it unregisters)

- On http_request_start, CDP will emit a Network.requestWillBeSent message.
  This _does not_ include all the fields, but what we have appears to be enough
  for puppeteer.waitForNetworkIdle.

- On http_request_complete, CDP will emit a Network.responseReceived message.
  This _does not_ include all the fields, bu what we have appears to be enough
  for puppeteer.waitForNetworkIdle.

We currently don't emit any other new events, including any network-specific
lifecycleEvent (i.e. Chrome will emit an networkIdle and networkAlmostIdle).

To support this, the following other things were done:
- CDP now has a `notification_arena` which is re-used between browser contexts.
  Normally, CDP code runs based on a "cmd" which has its own message_arena, but
  these notifications happen out-of-band, so we needed a new arena which is
  valid for handling 1 notification.

- HTTP Client is notification-aware. The SessionState no longer includes the
  *http.Client directly. It instead includes an http.RequestFactory which is
  the combination fo the client + a specific configuration (i.e. *Notification).
  This ensures that all requests made from that factory have the same settings.

- However, despite the above, _some_ requests do not appear to emit CDP events,
  such as loading a <script src="X">. So the page still deals directly with the
  *http.Client.

- Playwright and Puppeteer (but Playwright in particular) are very sensitive to
  event ordering. These new events have introduced additional sensitivity.
  The result sent to Page.navigate had to be moved to inside the navigate event
  handler, which meant passing some cdp-specific data (the input.id) into the
  NavigateOpts. This is the only way I found to keep both happy - the sequence
  of events is closer (but still pretty far) from what Chrome does.
2025-05-24 09:01:12 +08:00
Raph
bd0fa1487f Merge branch 'main' into css_style_declaration 2025-05-24 03:00:18 +02:00
Karl Seguin
d262f017c5 Merge pull request #689 from lightpanda-io/image
new Image constructor
2025-05-24 08:51:08 +08:00
Karl Seguin
a98c08c06c Merge pull request #688 from lightpanda-io/connection_cleanup
Fix connection memory leak
2025-05-24 08:38:44 +08:00
Raph
a2e0fd28e0 added basic style test to HTMLElement 2025-05-24 02:20:15 +02:00
Raph
5dbdf8321a removed unnecessary call to free 2025-05-24 02:13:08 +02:00
Raph
9d122bd181 Merge branch 'main' into css_style_declaration 2025-05-24 02:00:33 +02:00
Raph
09727101c1 various fixes according to PR review 2025-05-24 01:59:28 +02:00
sjorsdonkers
5fc9cd7d48 non deprecated netsurf image properties 2025-05-23 15:25:41 +02:00
sjorsdonkers
7adaa53f42 image constructor 2025-05-23 11:37:46 +02:00
Karl Seguin
cc82b1ae25 Fix connection memory leak
When the idle pool is full and the oldest connection is freed, free the
connection instance.
2025-05-23 17:11:14 +08:00
Karl Seguin
0df531a646 Merge pull request #687 from lightpanda-io/always_gc_hints
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Remove --gc_hints option, apply the --gc_hints behavior by default
2025-05-23 14:47:03 +08:00
Karl Seguin
b1d0368479 Remove --gc_hints option, apply the --gc_hints behavior by default 2025-05-23 14:15:55 +08:00
Karl Seguin
46c6a0b4ff Merge pull request #683 from lightpanda-io/libc_v8_out_path_include_os
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
include OS in libc_v8 lib path
2025-05-23 08:40:44 +08:00
Muki Kiboigo
97d414aa00 Fixing TreeWalker Filtering 2025-05-22 12:23:00 -07:00
Pierre Tachoire
ab8da3965b Merge pull request #685 from lightpanda-io/rsync-v8
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
README: rsync is used to get v8 sources
2025-05-22 14:45:44 +02:00
Pierre Tachoire
589fa4c9de README: rsync is used to get v8 sources 2025-05-22 14:45:10 +02:00
Karl Seguin
f4a27af37e zig fmt build.zig 2025-05-22 16:58:29 +08:00
Karl Seguin
ca0f407b7b include OS in libc_v8 lib path 2025-05-22 16:45:06 +08:00
Karl Seguin
4810a5643e Merge pull request #682 from lightpanda-io/make_debug_and_formdata_wpt
Add debug log level to make build-dev and add new make run-debug
2025-05-22 15:56:22 +08:00
Karl Seguin
72a983f6d8 Apply suggestions from code review
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-05-22 15:36:55 +08:00
Karl Seguin
a720333c0f Add debug log level to make build-dev and add new make run-debug
Update WPT submodule, now includes xhr/formdata tests.
2025-05-22 15:28:07 +08:00
Karl Seguin
38c6fa9c76 Don't error when failing to convert type to function.
Because jsValueToStruct is now used in union probing, it shouldn't fail on a
mismatch, but rather return null. It's up to the caller to decide whether that's
an error or not.
2025-05-22 13:02:08 +08:00
Karl Seguin
eed3d27665 Merge pull request #678 from lightpanda-io/ExecutionWorld
Rename to ExecutionWorld
2025-05-22 08:56:06 +08:00
Raph
450e345b28 fixed self fetching for HTMLElement 2025-05-22 02:01:11 +02:00
Raph
913568aba2 Added support for CSSStyleDeclaration API 2025-05-22 01:51:03 +02:00
Muki Kiboigo
3c3de9d325 use Env.Function instead of Env.Callback 2025-05-21 16:29:48 -07:00
Muki Kiboigo
fada732b33 add NodeFilter 2025-05-21 09:46:43 -07:00
Muki Kiboigo
152d0fdda7 add TreeWalker 2025-05-21 09:46:43 -07:00
Pierre Tachoire
6506fa792d Merge pull request #679 from lightpanda-io/increase-MAX_MESSAGE_SIZE
Increase MAX_MESSAGE_SIZE
2025-05-21 18:03:16 +02:00
Pierre Tachoire
867c72ba90 fix comment 2025-05-21 18:02:33 +02:00
sjorsdonkers
3f6b095da4 Increase MAX_MESSAGE_SIZE 2025-05-21 17:51:25 +02:00
Karl Seguin
f1d6d386c5 Merge pull request #669 from lightpanda-io/form_data_from_form
FormData constructor form & submitter parameter
2025-05-21 23:36:12 +08:00
Karl Seguin
72944a4e5e Support submit button submitters and check for disabled option on select 2025-05-21 21:47:33 +08:00
sjorsdonkers
193e012aa6 Rename to ExecutionWorlds 2025-05-21 14:34:23 +02:00
Karl Seguin
3ee17e01e1 Merge pull request #677 from lightpanda-io/move_jsValueToZig
Move jsValueToZig from Caller to the Scope
2025-05-21 20:21:48 +08:00
sjorsdonkers
7421fa0a33 dom.getBoxModel 2025-05-21 13:28:31 +02:00
sjorsdonkers
2cdfc3f4c3 setChildNodes checks 2025-05-21 12:36:31 +02:00
sjorsdonkers
4322d8e494 dom.querySelector 2025-05-21 12:36:31 +02:00
Karl Seguin
73a59dcd7d Move jsValueToZig from Caller to the Scope
Caller is a transient object that exists only for calling Zig functions from
JS. But jsValueToZig is more generally useful and can be used outside of an
explicit JS call. The scope is a better place for these as it's generally
referenced already by any code that would need to map values (i.e. a Callback).
2025-05-21 18:32:50 +08:00
Karl Seguin
3a15790847 Merge pull request #671 from lightpanda-io/webapi_destructor
Allow webapis to register a destructor to do cleanup on scope (page) end
2025-05-21 18:09:42 +08:00
sjorsdonkers
3f31573bcb No need to navigate to about:blank 2025-05-21 09:43:15 +02:00
sjorsdonkers
967ab18d53 default:blank as default document 2025-05-21 09:43:15 +02:00
sjorsdonkers
0929bd217d load aboutblank doc 2025-05-21 09:43:15 +02:00
Karl Seguin
ce832a8063 Rollback XHR/HTTP.client change
This PR will be only for having the destructor hook. XHR/http.client changes to
leverage this will be done in a subsequent PR.
2025-05-21 11:38:26 +08:00
Karl Seguin
fc0281b563 Merge pull request #665 from lightpanda-io/log_debug
Tweak debug logging
2025-05-21 09:03:06 +08:00
Karl Seguin
f42bd02cfc Don't crash on success
Keep request around, as the http/client needs it for cleanup. Calling abort
on an already deinit'd request is safe.
2025-05-20 19:22:43 +08:00
Karl Seguin
52634ddeb3 Allow webapis to register a destructor to do cleanup on scope (page) end
Add destructor to XHR to abort any inflight requests.
2025-05-20 18:56:22 +08:00
Karl Seguin
ed79b4ebd8 FormData constructor form & submitter parameter
FormData takes two optional parameters: a form and a submitter.

Building the FormData from these is a first step in supporting form submission.

Basic extension of the HTMLForm element. There was more work done on the Select
web api, because the netsurf implementation isn't great. But all of the input
elements will need to have their web api extended.
2025-05-20 18:18:03 +08:00
Pierre Tachoire
36ca7839d6 Merge pull request #666 from lightpanda-io/playwright-support-disclaimer
Playwright support disclaimer
2025-05-20 10:20:13 +02:00
Pierre Tachoire
fa5d583657 fix space 2025-05-20 10:19:56 +02:00
Sjors
5e67f09583 Disclaimer feedback 2025-05-20 09:48:08 +02:00
Sjors
8b74d96f12 Playwright support disclaimer 2025-05-20 09:26:51 +02:00
Karl Seguin
769d99e7bd Tweak debug logging
1 - Add a log_level build option to control the default log level from
    the build (e.g. -Dlog_level=debug). Defaults to info

2 - Add a new boolean log_unknown_properties build option to enable
    logging unknown properties. Defautls to false.

3 - Remove the log debug for script eval - this can be a huge value
    (i.e. hundreds of KB), which makes the debug log unusable IMO.
2025-05-20 11:29:14 +08:00
Karl Seguin
812f4d2699 Merge pull request #650 from lightpanda-io/http_client_async_gzip
Add support for gzip responses in AsyncHandler
2025-05-20 11:26:58 +08:00
sjorsdonkers
f95defe82f Do not getComputedStyle 2025-05-19 17:52:00 +02:00
sjorsdonkers
226dafa9e3 refix rebase regressions 2025-05-19 16:53:59 +02:00
sjorsdonkers
6c92d50c68 elementsFromPoint cleanup 2025-05-19 16:53:59 +02:00
sjorsdonkers
384e74fe7e Also return body and html elements 2025-05-19 16:53:59 +02:00
sjorsdonkers
216f6cc8e8 handle detached elements 2025-05-19 16:53:59 +02:00
sjorsdonkers
333c377bc7 make elementFromPoint more robust against future changes 2025-05-19 16:53:59 +02:00
sjorsdonkers
59b33faf61 confirm data is retained in elementFromPoint 2025-05-19 16:53:59 +02:00
sjorsdonkers
b87003427c fix unset heap_ptr 2025-05-19 16:53:59 +02:00
sjorsdonkers
cb48000df7 elementsFromPoint 2025-05-19 16:53:59 +02:00
Pierre Tachoire
58cc5d8d1a Merge pull request #660 from lightpanda-io/implementation-update
implementation: remove the setTitle method call
2025-05-19 16:14:46 +02:00
Karl Seguin
39799d3006 Merge pull request #662 from lightpanda-io/fix_broken_test_build
fix broken test build
2025-05-19 22:14:16 +08:00
Karl Seguin
73bf4479b5 fix broken test build 2025-05-19 22:03:34 +08:00
Pierre Tachoire
9f0f84bbee Merge pull request #658 from lightpanda-io/ready_state
Add document.readyState
2025-05-19 15:49:31 +02:00
Karl Seguin
1ff422a29c Merge pull request #659 from lightpanda-io/dedup-document
Deduplicate document
2025-05-19 19:07:16 +08:00
Pierre Tachoire
8daa525cc1 implementation: remove the setTitle method call
Libdom uses the doc's body and title attributes by default.
But it fallback to the DOM tree if the attributes are NULL.

I think it's better to have only the DOM tree set on document creation.
2025-05-19 12:16:07 +02:00
sjorsdonkers
76f1fcb634 dedup document 2025-05-19 11:35:29 +02:00
Karl Seguin
2b6cf95752 Add document.readyState
To support this, add the ability to embedded data into libdom nodes, so that
we can extend libdom without having to alter it.
2025-05-19 16:48:11 +08:00
Pierre Tachoire
a99d193b12 Merge pull request #653 from lightpanda-io/document_default_view
add defaultView getter to HTMLDocument
2025-05-19 10:19:54 +02:00
Pierre Tachoire
a3b576abd8 Merge pull request #656 from lightpanda-io/module-exception
module: report module's evaluation error
2025-05-17 11:17:28 +02:00
Pierre Tachoire
2261eac288 expection: fix non-nullable return 2025-05-17 11:02:37 +02:00
Karl Seguin
9366729d7a Merge pull request #655 from lightpanda-io/dom-parser
Some checks failed
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / browser fetch (push) Blocked by required conditions
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
add DOMParser
2025-05-17 09:56:32 +08:00
Karl Seguin
ad1a4fe450 Merge pull request #652 from lightpanda-io/transfer_arena
Introduce a "transfer_arena"
2025-05-17 09:44:21 +08:00
Pierre Tachoire
9f97725894 module: report module's evaluation error 2025-05-16 20:27:41 +02:00
Muki Kiboigo
bff3d27518 add DOMParser 2025-05-16 09:56:18 -07:00
Karl Seguin
2bc1192ad3 reduce lifetime of transfer_arena 2025-05-16 22:04:13 +08:00
Karl Seguin
f165131da8 add defaultView getter to HTMLDocument 2025-05-16 20:33:28 +08:00
Karl Seguin
afd29fef81 Merge pull request #651 from lightpanda-io/html_all_collection
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Rework HTMLAllCollection
2025-05-16 17:26:26 +08:00
Karl Seguin
071a4f97e5 Introduce a "transfer_arena"
Some data has to exist specifically for the navigation of one page to another.
For example, if a hyperlink is clicked, the URL begins its life with the
original page, but is transferred to the new page. The page_arena cannot be used
for such data.

It's possible to use the session_arena, but it's lifetime is much longer and,
given enough navigation, could accumulate a lot of memory.

The new transfer_arena exists within the session, but only exists until the
next navigation.

While currently only used for the navigation URL, the main goal here is to have
a place to put the request body on form submission, which has a lifetime similar
to a click url.

While I'm at it, I promoted the existing session arena and the new transfer
arena to the browser, allowing better memory re-use between sessions.
2025-05-16 15:53:25 +08:00
Karl Seguin
04c990de89 Merge pull request #649 from lightpanda-io/html_collection_named_properties
Fix HTMLCollection named property issues
2025-05-16 14:47:02 +08:00
Karl Seguin
b08ffcc437 Rework HTMLAllCollection
Capture its unique properties:
1- instances are falsy, and
2- instance can be called as a function

The behavior is used for browser detection (i.e. duckduckgo treats us as a
legacy browser because we document.all != false)
2025-05-16 13:39:27 +08:00
Karl Seguin
7156df8d9a Add support for gzip responses in AsyncHandler
Compliments https://github.com/lightpanda-io/browser/pull/601 which added this
behavior to the SyncHandler.
2025-05-16 12:51:53 +08:00
Karl Seguin
1a83e69669 Fix HTMLCollection named property issues
1 - Named properties should not be enumerable
2 - Empty key should always result in a null/undefined (depending on the API)
    even if there's an element with an empty id/name

To address the first issue, we now require PropertyAttributes to be specified
when setting an object's value.
2025-05-16 11:31:52 +08:00
Karl Seguin
210d4f6aa1 Merge pull request #620 from lightpanda-io/upgrade_v8
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Upgrade v8
2025-05-16 08:17:15 +08:00
Karl Seguin
b559506d4e remove unecessary space 2025-05-16 08:10:16 +08:00
Karl Seguin
3fec6ff5bc Merge pull request #643 from lightpanda-io/add_event_listener_options
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Support the capture field of the addEventListener option
2025-05-15 22:48:55 +08:00
Karl Seguin
ce74307172 Merge pull request #646 from lightpanda-io/split_browser_file
Move Session, Page and Renderer into their own respective files
2025-05-15 22:48:35 +08:00
Karl Seguin
e44e68f8fc Move Session, Page and Renderer into their own respective files 2025-05-15 22:43:50 +08:00
Karl Seguin
eff1341088 Merge pull request #647 from lightpanda-io/form_data
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add FormData web API
2025-05-15 17:34:02 +08:00
Karl Seguin
ddd35e3d80 Merge pull request #641 from lightpanda-io/js_debug_helpers
Make Callback.printFunc public
2025-05-15 16:44:31 +08:00
Karl Seguin
265272b9d3 move FormData to xhr folder 2025-05-15 16:39:49 +08:00
Karl Seguin
206e34ac80 Explicit error if an AddEventListenerOption flag is set that we dont' support 2025-05-15 13:32:40 +08:00
Karl Seguin
ea556ff201 Merge pull request #635 from lightpanda-io/http_proxy
add direct http proxy support
2025-05-15 12:58:54 +08:00
Karl Seguin
110dc751a4 add FormData web API 2025-05-15 12:44:24 +08:00
Karl Seguin
46546def28 Merge pull request #638 from lightpanda-io/DOM-scoll-and-quads
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
scrollIntoViewIfNeeded, getContentQuads, innerWidth/Heigh
2025-05-15 09:12:00 +08:00
sjorsdonkers
48de14ade3 null JS tests where we are not checking the output 2025-05-14 17:17:58 +02:00
sjorsdonkers
f74647ccfc Allign error detection 2025-05-14 17:13:56 +02:00
sjorsdonkers
b92a85f0a9 Cleanup and inner dimensions 2025-05-14 17:13:55 +02:00
sjorsdonkers
853965e7a9 scollifneeded and contentQuads wip 2025-05-14 17:13:55 +02:00
Karl Seguin
6f9dd8d7cd Make expected runtime runner value optional to skip assertion
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
2025-05-14 16:18:53 +02:00
Karl Seguin
905eb1a93f Make expected runner value optional to skip assertion 2025-05-14 16:18:53 +02:00
Karl Seguin
7862fc7cb7 Merge pull request #640 from lightpanda-io/script_nomodule
Don't load script tags with the nomodule attribute
2025-05-14 18:30:26 +08:00
Karl Seguin
903168b3a6 Support the capture field of the addEventListener option
addEventListener can take a boolean (capture, already supported) or an object
of options. This adds union support for the two, but only supports the `capture`
field of the options object.

The other fields are not supported by netsurf.
2025-05-14 18:24:41 +08:00
Karl Seguin
5e8fcb579f print value using toDetailString 2025-05-14 17:56:00 +08:00
Karl Seguin
fae018b4ea Make Callback.printFunc public
When calling a Zig function from JS fails due to a parameter type error,
log.debug information about the function and parameters.
2025-05-14 17:37:46 +08:00
Karl Seguin
dc0e278a24 Don't load script tags with the nomodule attribute
These tags should not be loaded as we support ES modules.
2025-05-14 16:50:34 +08:00
Karl Seguin
aaa34ab860 link libc_v8.a in correct directory 2025-05-14 15:19:47 +08:00
Karl Seguin
66638cab33 update v8 revision 2025-05-14 15:02:55 +08:00
Karl Seguin
a729a61100 zig fmt build.zig 2025-05-14 11:27:49 +08:00
Karl Seguin
23b39c6a63 return explicit intercept state from named/indexed getters 2025-05-14 11:27:48 +08:00
Karl Seguin
37467d3753 remove debug statement 2025-05-14 11:27:39 +08:00
Karl Seguin
8d3a378761 remove unused import, add debug statement 2025-05-14 11:27:39 +08:00
Karl Seguin
3993f9c2bb update to latest zig-v8-fork add unzip to apt get install sample 2025-05-14 11:27:37 +08:00
Karl Seguin
b542762dce update zig-v8 dep for proper named property masking flag 2025-05-14 11:27:25 +08:00
Karl Seguin
35b2ea870d use zig-v8-fork v8_upgrade branch 2025-05-14 11:26:48 +08:00
sjorsdonkers
b2605dd30c cancelAnimationFrame and test 2025-05-13 18:24:10 +02:00
sjorsdonkers
18b04e2999 requestAnimationFrame 2025-05-13 18:24:10 +02:00
sjorsdonkers
54c2dedac0 expectEqual and naming 2025-05-13 17:42:35 +02:00
sjorsdonkers
0efa6661b8 fix units of reslution to microsec 2025-05-13 17:42:35 +02:00
sjorsdonkers
42d0532580 cleanup 2025-05-13 17:42:35 +02:00
sjorsdonkers
8d5f7c8d3e performance now 2025-05-13 17:42:35 +02:00
Karl Seguin
04214200b8 Merge pull request #626 from lightpanda-io/text-encoder
WIP implement TextEncoder
2025-05-13 22:49:10 +08:00
Pierre Tachoire
99229513ba implement TextEncoder 2025-05-13 16:41:59 +02:00
Karl Seguin
c3a992e6d4 Merge pull request #632 from lightpanda-io/module-map
Page Module Map
2025-05-13 21:46:11 +08:00
Muki Kiboigo
e15c80927b check page module_map before fetching 2025-05-13 06:39:45 -07:00
Karl Seguin
e918a0bf26 add direct http proxy support 2025-05-13 18:21:27 +08:00
Karl Seguin
35bff8cc67 Merge pull request #631 from lightpanda-io/element_closest
Element closest
2025-05-13 16:40:25 +08:00
sjorsdonkers
0998ae753c use log an brievity 2025-05-13 10:31:40 +02:00
Karl Seguin
7bb6506709 Merge pull request #623 from lightpanda-io/http_client_keepalive
add keepalive to http client
2025-05-13 10:51:07 +08:00
Karl Seguin
64f80312de fix formatting 2025-05-13 10:42:51 +08:00
Karl Seguin
ce2eed28c1 Fix memory leaks 2025-05-13 10:42:16 +08:00
Karl Seguin
505fa91d7d add keepalive to http client 2025-05-13 10:42:16 +08:00
sjorsdonkers
dd7e6d3831 Element closest 2025-05-12 17:20:43 +02:00
Pierre Tachoire
b086337dbe Merge pull request #629 from lightpanda-io/return_typed_arrays
Ability to return typed arrays
2025-05-12 14:31:04 +02:00
Karl Seguin
49562f50f2 update zig-v8-fork build version 2025-05-12 18:43:40 +08:00
Karl Seguin
884ec05a1e Merge pull request #628 from lightpanda-io/init_netsurf_at_page_creation
Init netsurf at page creation
2025-05-12 17:57:59 +08:00
Karl Seguin
212d7f1865 Ability to return typed arrays 2025-05-12 17:47:05 +08:00
sjorsdonkers
9ab8a2cbd2 remove manual test parser.init 2025-05-12 11:34:57 +02:00
Pierre Tachoire
f633eddd73 Merge pull request #624 from lightpanda-io/document_fragment_apis
Add prepend, append and relpaceChildren to DocumentFragment
2025-05-12 11:31:23 +02:00
sjorsdonkers
f5761ee69d Init netsurf at page creation 2025-05-12 11:19:19 +02:00
Karl Seguin
b8cdc0f145 Add prepend, append and relpaceChildren to DocumentFragment 2025-05-12 09:35:33 +08:00
Karl Seguin
b5eea2136b Merge pull request #612 from lightpanda-io/fix-url-resolving
Fix URL Resolving
2025-05-11 08:38:57 +08:00
Pierre Tachoire
deded47da2 Merge pull request #622 from lightpanda-io/wpt-fix
ci: fix workflows dependency for wpt tests
2025-05-10 08:36:05 +02:00
Pierre Tachoire
fdc0e2597d Merge pull request #621 from lightpanda-io/attributes
A few attribute fixes
2025-05-10 08:30:29 +02:00
Pierre Tachoire
da5b0260f2 ci: fix workflows dependency for wpt tests 2025-05-10 08:28:07 +02:00
Karl Seguin
beb960b753 A few attribute fixes
Driven by dom/nodes/attributes.html. However, many issues remain and seem
complicated to fix. Some of the remaining issues are documented in
https://github.com/lightpanda-io/project/discussions/124
2025-05-10 14:17:15 +08:00
Karl Seguin
5cc338dedc Merge pull request #609 from lightpanda-io/ddg_compat
Work on DDG support (but still not working)
2025-05-10 10:32:50 +08:00
Muki Kiboigo
15be42340d handling relative base URLs 2025-05-09 07:00:57 -07:00
Muki Kiboigo
f10bee8cb0 fix single slash url resolving issue 2025-05-09 06:59:12 -07:00
Pierre Tachoire
eadf18821f Merge pull request #616 from lightpanda-io/wpt-nightly
ci: run wpt test nightly
2025-05-09 13:39:15 +02:00
Pierre Tachoire
56b1c7b11a ci: run wpt test nightly 2025-05-09 13:28:34 +02:00
Pierre Tachoire
e4513976f7 Merge pull request #617 from lightpanda-io/before_and_after
Add before and after functions
2025-05-09 12:00:39 +02:00
Pierre Tachoire
b71ea3852e Merge pull request #618 from lightpanda-io/action-timeouts
Timouts for all GH actions
2025-05-09 11:55:57 +02:00
sjorsdonkers
ae6c29ccff Timouts for all GH actions 2025-05-09 11:20:51 +02:00
sjorsdonkers
1820e79617 Renable microtask_node that was fixed in main 2025-05-09 10:44:07 +02:00
sjorsdonkers
2a95b7a37c Reduce url buffer 2025-05-09 10:44:07 +02:00
sjorsdonkers
fb95df66c9 redisable microtask_node 2025-05-09 10:44:07 +02:00
sjorsdonkers
3c76284d89 Print error on navigation failure 2025-05-09 10:44:07 +02:00
sjorsdonkers
29967fde50 delay navigate on click 2025-05-09 10:44:07 +02:00
sjorsdonkers
bd65e4084c renderer fix & url buffer 2025-05-09 10:44:07 +02:00
Karl Seguin
a2a9977af6 Merge pull request #614 from lightpanda-io/browser_controlled_gc_hints
Move call/control of gc_hint to browser.
2025-05-09 12:26:14 +08:00
Karl Seguin
0369b490b8 Add before and after functions
Element and CharData both have a before & after function.

Also, changed the existing functions that took a Node but should take
a Node or Text, i.e append, prepend and replaceChildren. These functions, along
with the new before/after all take a new NodeOrText union.
2025-05-09 12:23:31 +08:00
Pierre Tachoire
d9e5821d31 Merge pull request #613 from lightpanda-io/css_selector_parsing_tweaks
"Improve" css selector parsing
2025-05-08 14:43:43 +02:00
Karl Seguin
54a7df8d40 Move call/control of gc_hint to browser.
It has more context than the env about when this should be called. Specifically
it can be called once per session, whereas, in the env, we can only call it
once per context - which could be too often.
2025-05-08 18:31:46 +08:00
Karl Seguin
17ed502123 Merge pull request #601 from lightpanda-io/http_gzip
Support gzip compressed content for the synchronous http client
2025-05-08 13:52:05 +08:00
Karl Seguin
56eef2ec94 "Improve" css selector parsing
This is driven by dom/nodes/ParentNode-querySelector-escapes.html

It seems like invalid unicode and null terminating characters should be replaced
with the replacement character (i.e. �).

Also, allow escape sequence to be at the end of the string.

For the functions I changed, I added:

```zig
const sel = p.s;
const sel_len = sel.len
```

To improve readability (`selector` can't be used because it shadows the import).

Tests went from 39/68 -> 66/68.
2025-05-08 11:34:04 +08:00
Karl Seguin
200036efc9 undo microtask change, do it in a separate PR 2025-05-08 07:46:05 +08:00
Karl Seguin
7fa7f4ed8a Work on DDG support (but still not working)
- Add dummy MediaQueryList and window.matchMedia
- Execute deferred scripts after non-deferred
  I realize this doesn't change much, given how we currently load all scripts
  after the document is parsed, but scripts _could_ depend on execution order.
- Add support for executing the `onload` attribute of <scripts>

I also cleaned up some of the Script code, i.e. removimg `unknown` kind and
simply returning a null script, and removing the EmptyBody error and returning
a null body string.

Finally, I re-enabled the microtask loop which I must have previously disabled.
2025-05-08 07:46:04 +08:00
Karl Seguin
3466325d4d Merge pull request #610 from lightpanda-io/loop_interval_cleanup
Optimize intervals, and make sure they're probably cleaned up.
2025-05-08 07:44:59 +08:00
muki
1613345dec Merge pull request #611 from lightpanda-io/nix
Add Nix Development Support
2025-05-07 06:19:22 -07:00
Muki Kiboigo
759accef07 add README entry about Nix 2025-05-07 06:16:32 -07:00
Muki Kiboigo
6d02669fc3 add flake.nix 2025-05-07 06:16:32 -07:00
Karl Seguin
6d8d688063 Optimize intervals, and make sure they're probably cleaned up.
A loop interval will no longer stop the loop from returning from `run`, and
no longer requires mutating event_nb on each iteration.

Re-enable microtask loop, which I accidentally stopped in a previous commit.
2025-05-07 19:20:26 +08:00
Karl Seguin
5207bdfd85 Merge pull request #608 from lightpanda-io/wpt-opts-url
wpt: use local url for wpt tests
2025-05-07 16:07:24 +08:00
Karl Seguin
690d4238e8 Merge pull request #607 from lightpanda-io/fix_int_overflow
fix overflow when setting timeout/interval
2025-05-07 16:05:37 +08:00
Pierre Tachoire
95ee78b1bd wpt: use local url for wpt tests 2025-05-07 09:43:18 +02:00
Karl Seguin
25eadc2263 fix overflow when setting timeout/interval 2025-05-07 15:37:47 +08:00
Pierre Tachoire
28e4065890 Merge pull request #606 from lightpanda-io/no_owned_slice
remove unecessary toOwnedSlice
2025-05-07 08:47:37 +02:00
Karl Seguin
e44388b506 remove unecessary toOwnedSlice 2025-05-07 14:40:03 +08:00
Pierre Tachoire
540dea9fc3 Merge pull request #604 from lightpanda-io/non_generic_named_function
Change NamedFunction from a generic to a normal struct.
2025-05-07 08:32:14 +02:00
Karl Seguin
c31290b794 Change NamedFunction from a generic to a normal struct.
NamedFunction is important for displaying good error messages when there's
something wrong with the Zig structs we're trying to bind to JS. By making it
a normal struct, it's easier and cheaper to pass wherever an @compileError
might be needed.
2025-05-07 13:50:25 +08:00
Pierre Tachoire
f1fe4c0c70 Merge pull request #600 from lightpanda-io/timeouts_and_intervals
Make intervals easier and faster, add window.setInterval and clearInt…
2025-05-06 15:18:15 +02:00
Pierre Tachoire
921ac18876 Merge pull request #602 from lightpanda-io/Subpixel-mouse-events
Subpixel mouse events
2025-05-06 15:11:40 +02:00
sjorsdonkers
505ad0380e typo 2025-05-06 12:52:08 +02:00
sjorsdonkers
2b7a7c0054 floor the pixels 2025-05-06 12:45:18 +02:00
sjorsdonkers
0dea4c51b7 Subpixel mouse events 2025-05-06 12:45:17 +02:00
Karl Seguin
3095f2110e Merge pull request #599 from lightpanda-io/NativeIntersectionObserver
Native IntersectionObserver
2025-05-06 17:36:56 +08:00
sjorsdonkers
e32d35b156 no reobserve rootbounds for Window 2025-05-06 11:28:08 +02:00
sjorsdonkers
db28336e5d Support options in observer and tests 2025-05-06 11:28:07 +02:00
sjorsdonkers
c5c5accaa8 Native IntersectionObserver 2025-05-06 11:28:06 +02:00
Karl Seguin
78bfdd4515 Support gzip compressed content for the synchronous http client 2025-05-06 16:23:44 +08:00
Karl Seguin
01aa826a24 Make intervals easier and faster, add window.setInterval and clearInterval
When the browser microtask was added, zig-specific timeout functions were
added to the loop. This was necessary for two reasons:
1 - The existing functions were JS specific
2 - We wanted a different reset counter for JS and Zig

Like we did in https://github.com/lightpanda-io/browser/pull/577, the loop is
now JS-agnostic. It gets a Zig callback, and the Zig callback can execute JS
(or do whatever). An intrusive node, like with events, is used to minimize
allocations.

Also, because the microtask was recently moved to the page, there is no longer
a need for separate event counters. All timeouts are scoped to the page.

The new timeout callback can now be used to efficiently reschedule a task. This
reuses the IO.completion and Context, avoiding 2 allocations. More importantly
it makes the internal timer_id static for the lifetime of an "interval". This
is important for window.setInterval, where the callback can itself clear the
interval, which we would need to detect in the callback handler to avoid
re-scheduling. With the stable timer_id, the existing cancel mechanism works
as expected.

The loop no longer has a cbk_error. Callback code is expected to try/catch
callbacks (or use callback.tryCall) and handle errors accordingly.
2025-05-05 19:03:45 +08:00
Pierre Tachoire
7f2506d8a6 Merge pull request #598 from lightpanda-io/unused_imports
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
remove unused code, mostly imports
2025-05-05 12:07:29 +02:00
Karl Seguin
7c2f7b6338 Merge pull request #595 from lightpanda-io/env_debug_ergonomics
Improve the debug ergonomics of the Env generic.
2025-05-05 16:22:05 +08:00
Karl Seguin
5f05de30a6 Improve the debug ergonomics of the Env generic.
Previously, we were passing our WebAPIs directly as an anonymous tuple. This
resulted in Env(T) having an _awful_ name - a name composed of hundreds of
classes.

By wrapping the anonymous tuple into a normal struct, the Env now gets a sane
name which helps improve stack traces (and profiling, and debugging, ...)
2025-05-05 16:03:55 +08:00
Pierre Tachoire
7741de7ae0 Merge pull request #597 from lightpanda-io/fix_undefined_access
Remove undefined that causes crash
2025-05-05 09:54:54 +02:00
Karl Seguin
d4c8e8c50e Merge pull request #592 from lightpanda-io/isolated-polyfill-+-create-when-needed
Isolated polyfill & create world when needed
2025-05-05 15:03:05 +08:00
Pierre Tachoire
bf36ff9cb9 Merge pull request #593 from lightpanda-io/crypto_web_api
add crypto web api
2025-05-05 08:56:27 +02:00
Pierre Tachoire
8eadccdee2 Merge pull request #587 from lightpanda-io/dom-setchildnodes
cdp: dispatch DOM.setChildNodes event for search results
2025-05-05 08:56:04 +02:00
Kilari Teja
b32839292c Support Data URI in scripts tags (#596)
* Support text/javascript mime type

* Support base64 encoded scripts

Related to https://github.com/lightpanda-io/browser/issues/412
2025-05-05 14:48:21 +08:00
Pierre Tachoire
2402443dcc cdp: add comments on setChildNodes event
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2025-05-05 08:48:04 +02:00
sjorsdonkers
9f72c98967 Error on null page/scope 2025-05-05 08:46:33 +02:00
sjorsdonkers
f6f744aea1 Fix gc_hints not being send 2025-05-05 08:46:33 +02:00
sjorsdonkers
cddc55694a load polyfills on creation 2025-05-05 08:46:32 +02:00
sjorsdonkers
8930e2f06e isolated polyfill + create when needed 2025-05-05 08:46:32 +02:00
Karl Seguin
b8e5e130b9 remove unused code, mostly imports 2025-05-05 13:29:41 +08:00
Karl Seguin
a8c5087a38 Remove undefined that causes crash
These values are set to undefined, and used (in the item function) before ever
being set. Causes crashes in release mode.
2025-05-04 21:18:30 +08:00
Karl Seguin
d9f21e0475 add empty cases to empty test suite (#594)
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
2025-05-03 14:11:39 +08:00
Karl Seguin
ca3fa3dc40 Rework WPT runner (#589)
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
* Rework WPT runner

We have no crashing tests, remove safe mode. Allows better re-use of arenas,
and if we do introduce a crash, it won't be easy to ignore. Could allow for
re-using the environment across tests to further improve performance.

Remove console now that we have a working console api.

* Update workflows, add summary

Remove --safe option from WPT workflows (it's no longer valid)

Include a total test/case summary when --summary or --text (default) is used.

* remove wpt --safe flag from Makefile

* handle tests in the root of the test folder

* Fix a couple possible segfaults base on strange usage (WPT stuff)

* generate proper JSON

* generate proper JSON (for real this time?)

* fix tag type check
2025-05-03 07:53:02 +08:00
Karl Seguin
ddd0a42b26 add crypto web api 2025-05-03 07:52:12 +08:00
Pierre Tachoire
f884627927 cdp: sent setchildnodes once per node 2025-05-02 22:10:26 +02:00
Pierre Tachoire
9373cf9cf6 cdp: refacto sendChildNodes 2025-05-02 21:55:14 +02:00
Pierre Tachoire
f04030904e cdp: fix tests for setchildnodes 2025-05-02 15:55:49 +02:00
Pierre Tachoire
271b2a0417 Merge pull request #591 from lightpanda-io/element_matches
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
add Element.matches web api
2025-05-02 11:57:55 +02:00
Karl Seguin
a4f7393fc8 Merge pull request #590 from lightpanda-io/zig_fmt
zig fmt
2025-05-02 16:40:24 +08:00
Karl Seguin
8f851beda1 add Element.matches web api 2025-05-02 16:30:49 +08:00
Karl Seguin
4489efa8d9 zig fmt 2025-05-02 16:03:13 +08:00
Pierre Tachoire
8b9084cb73 cdp: dispatch the correct dom hierarchy wit setChildNodes 2025-05-01 19:42:04 +02:00
Pierre Tachoire
1146453dc2 cdp: add session to setChildNodes event 2025-05-01 16:51:02 +02:00
Pierre Tachoire
bd54395948 Merge pull request #588 from lightpanda-io/custom_events
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add CustomEvent api
2025-05-01 16:29:57 +02:00
Karl Seguin
89ac27ba97 Add CustomEvent api 2025-05-01 19:33:22 +08:00
Karl Seguin
74eaee53a4 Merge pull request #585 from lightpanda-io/union_params
Support union parameters
2025-05-01 19:20:21 +08:00
Karl Seguin
20e4261aa7 Support union parameters
There's ambiguity in mapping due to the flexible nature of JavaScript. Hopefully
most types are unambiguous, like a string or am *parser.Node.

We need to "probe" each field to see if it's a possible candidate for the JS
value. On a perfect match, we stop probing and set the appropriate union field.
There are 2 levels of possible matches: candidate and coerce. A "candidate"
match has higher precedence. This is necessary because, in JavaScript, a lot
of things can be coerced to a lot of other, seemingly wrong, things.

For example, say we have this union:

a: i32,
b: bool,

Field `a` is a perfect match for the value 123. And field b is a coerce match
(because, yes, 123 can be coerced to a boolean). So we map it to `a`.

Field `a` is a candidate match for the value 34.2, because float -> int are both
"Numbers" in JavaScript. And field b is a coerce match. So we map it to `a`.

Both field `a` and field `b` are coerce matches for "hello". So we map it to `a`
because it's declared first (this relies on how Zig currently works, but I don't
think the ordering of type declarations is guaranteed, so that's an issue).
2025-05-01 18:31:55 +08:00
Pierre Tachoire
312189fbde Merge pull request #586 from lightpanda-io/cancel_via_lookup
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Change the Linux cancel implementation to match MacOS'
2025-05-01 10:18:35 +02:00
Karl Seguin
d05063ec61 Merge pull request #579 from lightpanda-io/console
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add console web api
2025-05-01 09:50:37 +08:00
Karl Seguin
47c14db54c Merge pull request #577 from lightpanda-io/unified_intrusive_events
Unify the Zig and JS events using an intrusive node.
2025-05-01 09:50:19 +08:00
Karl Seguin
f0e0650244 Merge pull request #568 from lightpanda-io/notifications
Introduce more general notification capabilities
2025-05-01 09:50:06 +08:00
Pierre Tachoire
d2a68e62e9 cdp: add attributes to the node's writer 2025-04-30 15:56:06 +02:00
Pierre Tachoire
09fbbc1e17 netsurf: node's attributes can be null 2025-04-30 15:55:34 +02:00
Karl Seguin
8971822247 Change the Linux cancel implementation to match MacOS'
cancel on linux was a "real" cancel, but the implementation was unsafe. It took
whatever `id` it was given and @ptrFromInt'd it. This is problematic since the
`id` is user-supplied with virtually no validation.

Using the existing MacOS canceled lookup seems both easier and safer than trying
to validate the cancellation id.
2025-04-30 21:41:52 +08:00
Karl Seguin
1f0d1920bf Merge branch 'main' into unified_intrusive_events 2025-04-30 21:32:34 +08:00
Karl Seguin
cb7c8502b0 add console web api 2025-04-30 20:50:31 +08:00
Karl Seguin
27d1f79839 Merge pull request #583 from lightpanda-io/share-state-and-global-with-the-isolated
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Share underlying DOM global Window with the isolated World
2025-04-30 19:55:14 +08:00
sjorsdonkers
83ef21e699 page handlescope clarification 2025-04-30 12:01:56 +02:00
Karl Seguin
6c592669da Introduce more general notification capabilities
Replaces the existing, very specialized Notification with something more
general.

Currently, the existing page_navigate and page_navigated have been migrated.

Telemetry's page navigation event now also hooks into these events to generate
the telemetry record.
2025-04-30 17:33:51 +08:00
Pierre Tachoire
88f7687646 cdp: dispatch DOM.setChildNodes on performSearch 2025-04-30 09:19:59 +02:00
Pierre Tachoire
f12a527ae3 cdp: add ParentId to Node.Writer 2025-04-30 09:01:56 +02:00
sjorsdonkers
7dde0be043 share sessionstate and underlying DOM global with the isolated 2025-04-29 23:17:39 +02:00
Pierre Tachoire
2910f4f527 Merge pull request #581 from lightpanda-io/svgelement_dummy2
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Svgelement dummy2
2025-04-29 18:50:16 +02:00
Pierre Tachoire
93c0df33c2 Merge pull request #578 from lightpanda-io/scope_tightening
Reorganize v8 contexts and scope
2025-04-29 18:46:31 +02:00
sjorsdonkers
7d9f6eef27 instanceof svgelement test 2025-04-29 18:11:47 +02:00
sjorsdonkers
7d742d62b8 SVGElement dummy 2025-04-29 18:11:47 +02:00
sjorsdonkers
4db80cb9e7 Adopt state into the isolated world 2025-04-29 18:10:55 +02:00
Pierre Tachoire
addfbcb68f Merge pull request #582 from lightpanda-io/remove_main_shell
Remove unused main_shell
2025-04-29 17:31:42 +02:00
sjorsdonkers
fac46d9d0b Redo resolveNode 2025-04-29 16:56:50 +02:00
sjorsdonkers
e38ff08de2 Remove unused main_shell 2025-04-29 14:43:14 +02:00
sjorsdonkers
c31e2d91dd Remove global scope 2025-04-29 11:59:14 +02:00
Karl Seguin
7309fec51d Fully fake contextCreated
emit contextCreated when it's needed, not when it actually happens.

I thought we could make this sync-up, but we'd need to create 3 contexts to
satisfy both puppeteer and chromedp. So rather than having it partially
driven by notifications from Browser, I rather just fake it all for now.
2025-04-29 13:29:42 +08:00
Karl Seguin
2e01fa738a Make undefined->null safer, and apply the same trick to BrowserContext 2025-04-29 11:28:43 +08:00
Karl Seguin
9044925f32 emit context created on createTarget event for chromedp 2025-04-29 10:58:23 +08:00
Karl Seguin
2d5ff8252c Reorganize v8 contexts and scope
- Pages within the same session have proper isolation
  - they have their own window
  - they have their own SessionState
  - they have their own v8.Context

- Move inspector to CDP browser context
  - Browser now knows nothing about the inspector

- Use notification to emit a context-created message
  - This is still a bit hacky, but again, it decouples browser from CDP
2025-04-29 10:22:08 +08:00
Karl Seguin
072110481f Unify the Zig and JS events using an intrusive node.
The approach borrows heavily from Zig's new LinkedList API.

The main benefit is that it unifies how event callbacks are done. When the
Page.windowClick event was added, the Event structure was changed to a union,
supporting a distinct Zig and JS event.

This new approach more or less treats everything like a Zig event. A JS event
is just a Zig struct that has a Env.Callback which it can invoke in its handle
method.

The intrusive nature of the EventNode means that what used to be 1 or 2
allocations is now 0 or 1.

It also has the benefit of making netsurf completely unaware of Env.Callbacks.
2025-04-26 22:22:34 +08:00
Pierre Tachoire
0fb0532875 Merge pull request #562 from lightpanda-io/mutation_observer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Improve MutationObserver
2025-04-25 10:53:48 +02:00
Pierre Tachoire
d8dd94c679 Merge pull request #569 from lightpanda-io/make_cdp_less_generic
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Make CDP less generic.
2025-04-25 09:50:17 +02:00
Karl Seguin
f3d7736acf Update src/browser/dom/mutation_observer.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2025-04-25 15:48:46 +08:00
Pierre Tachoire
8fbf5590f8 Merge pull request #573 from lightpanda-io/typed_arrays
add support for mapping integer typed arrays into zig slices
2025-04-25 09:30:44 +02:00
Pierre Tachoire
b8ac93045e Merge pull request #574 from lightpanda-io/enable_icu
initialize ICU
2025-04-25 09:29:34 +02:00
Karl Seguin
89fea9b4df initialize ICU
This makes functions like new Intl.DateTimeFormat() not crash.
2025-04-25 13:15:38 +08:00
Karl Seguin
a3323dc8a7 add support for mapping integer typed arrays into zig slices 2025-04-25 13:01:43 +08:00
Karl Seguin
ba0505c13c Merge pull request #571 from lightpanda-io/remove-log.zig
Remove log.zig
2025-04-25 08:47:30 +08:00
Karl Seguin
dd8432e8fd Merge pull request #572 from lightpanda-io/framenavigated-order
cdp: dispatch Page.frameNavigated before DOM.documentUpdated
2025-04-25 08:47:07 +08:00
Pierre Tachoire
11c7f57745 cdp: dispatch Page.frameNavigated before DOM.documentUpdated
chromedp client expects to receive Page.frameNavigated before
DOM.documentUpdated.
2025-04-24 18:28:43 +02:00
sjorsdonkers
89a3fac316 log.zig does not appear to be used 2025-04-24 15:17:16 +02:00
Karl Seguin
b0b3e92600 remove Browser.EnvType 2025-04-24 19:48:27 +08:00
Karl Seguin
1fca035cfe Make CDP less generic.
It's still generic over the client - we need to assert messages written to and
be able to send specific commands, but it's no longer generic over Browser/
Session/Page/etc..
2025-04-24 18:06:55 +08:00
Karl Seguin
4c89bb0e0a Improve MutationObserver
- Fix get_removedNodes (it was returning addedNodes)
- get_removedNodes and get addedNodes now return references
- used enum for dispatching and clean up dispatching in general
- Remove MutationRecords and simply return an array
  - this allows the returned records to be iterable (as they should be)
  - jsruntime ZigToJs will now map a Zig array to a JS array
-Rely on default initialize of NodeList
-Batch observed records
 - Callback only executed when call_depth == 0
 - Fixes crashes when a MutationObserver callback mutated the nodes being
   observes.
 - Fixes some WPT issues, but Netsurf's mutationEventRelatedNode does not
   appear to be to spec, so most tests fail.
 - Allow zig methods to execute arbitrary code when call_depth == 0
   - This is a preview of how I hope to make XHR not crash if the CDP session
     ends while there's still network activity
2025-04-24 17:40:37 +08:00
Pierre Tachoire
332508f563 Merge pull request #567 from lightpanda-io/kind_before_deinit
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
access the executor kind before it becomes invalid
2025-04-24 10:43:37 +02:00
Karl Seguin
158d11e93c access the executor kind before it becomes invalid 2025-04-24 16:36:04 +08:00
Pierre Tachoire
18a49601a0 Merge pull request #566 from lightpanda-io/null_prefix_namespace
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
pass null namespace/prefix to libdom
2025-04-24 09:27:03 +02:00
Karl Seguin
b971b4754f update libdom to latest version, fixing null ptr usage and stack overflow 2025-04-24 15:18:22 +08:00
Pierre Tachoire
cfef22257e Merge pull request #560 from lightpanda-io/remove_arena_frees
Remove unnecessary cleanup when we know we have an arena
2025-04-24 09:04:46 +02:00
Karl Seguin
3153d8ee8c pass null namespace/prefix to libdom 2025-04-24 11:52:01 +08:00
sjorsdonkers
b2a975fe4a remove executionContextCreated
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
2025-04-23 17:00:22 +02:00
sjorsdonkers
b2ba505954 Check if it works with v8 97bcfb6 2025-04-23 17:00:22 +02:00
sjorsdonkers
a1b673175a errdefer in the right scope 2025-04-23 17:00:22 +02:00
sjorsdonkers
d666de07a7 test scaffolding 2025-04-23 17:00:22 +02:00
sjorsdonkers
64508cec61 Executor World kind 2025-04-23 17:00:22 +02:00
sjorsdonkers
e0bcb625c2 browsercontext arena 2025-04-23 17:00:22 +02:00
sjorsdonkers
9534e765e5 refix page.contextCreated 2025-04-23 17:00:22 +02:00
sjorsdonkers
39124d2878 text fix 2025-04-23 17:00:22 +02:00
sjorsdonkers
9ae4d66194 page cleanup 2025-04-23 17:00:22 +02:00
sjorsdonkers
09850d7500 Fix executor used in resolveNode 2025-04-23 17:00:22 +02:00
sjorsdonkers
8897d9179c isolated world 2025-04-23 17:00:22 +02:00
Karl Seguin
2d1b9d64bd Remove unnecessary cleanup when we know we have an arena
Change a few old alloc+memcpy to dupe

Some other smaller cleanups.
2025-04-23 17:52:13 +08:00
Pierre Tachoire
e603a1707c Merge pull request #559 from lightpanda-io/processing_instruction_clone
Manually "clone" processing_instruction
2025-04-23 09:51:33 +02:00
Pierre Tachoire
6b1e7a1c5d Merge pull request #558 from lightpanda-io/node_is_equal
Implement custom isEqualNode
2025-04-23 09:50:43 +02:00
Pierre Tachoire
5acd4b5fd4 Revert "browser: temporary ignore mime sniff"
This reverts commit 0b2c4679fd.
2025-04-23 09:26:10 +02:00
Pierre Tachoire
9e88adb0da Merge pull request #557 from lightpanda-io/fix_peek
peek must check existing data first
2025-04-23 09:25:59 +02:00
Karl Seguin
69eaf0d338 Manually "clone" processing_instruction
When you clone a processing_node via the node_clone_node, or directly via the
processing_node copy, you end up in _dom_pi_copy:
da8b967905/src/core/pi.c (L104)

For whatever, reason, the node created here gets a vtable that doesn't seem
compatible with how we cast vtables in netsurf.zig. For now, a simple fix is
to create a new new and copy the attributes over.

Fixes https://github.com/lightpanda-io/browser/issues/123 and a WPT crash.
2025-04-23 13:31:05 +08:00
Karl Seguin
680de2dca1 Implement custom isEqualNode
Netsurf's dom_node_is_equal appears to be both unsafe and incorrect. It's unsafe
because various node types don't have the dom_node_get_attributes implementation
so they default to setting the node attributes to null:
https://github.com/lightpanda-io/libdom/blob/master/src/core/node.c#L658

However, it doesn't do a NULL check when comparing them, so it crashes:
da8b967905/src/core/namednodemap.c (L312)

Furthermore, specific nodes need to be compared using specific attributes/values.

This PR fixes a WPT crash.
2025-04-23 09:59:57 +08:00
Karl Seguin
837188f8d1 peek must check existing data first 2025-04-23 08:28:20 +08:00
Pierre Tachoire
4a696b4053 Merge pull request #556 from lightpanda-io/mime-sniff-skip
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
browser: temporary ignore mime sniff
2025-04-22 18:55:57 +02:00
Pierre Tachoire
0b2c4679fd browser: temporary ignore mime sniff 2025-04-22 18:47:52 +02:00
Pierre Tachoire
5a08c92d02 Merge pull request #553 from lightpanda-io/mime_sniffing
Try to sniff the mime type based on the body content
2025-04-22 17:25:29 +02:00
Pierre Tachoire
faf93441f6 Merge pull request #555 from lightpanda-io/wpt_filter_non_tests
Don't [try] to run non-tests
2025-04-22 17:00:19 +02:00
Karl Seguin
8aa3608a3c Don't [try] to run non-tests
Currently, we treat every .html file in tests/wpt as-if it's a test. That
isn't always the case. wpt has a manifest tool to generate a manifest (in
JSON) of the tests, we should probably use that (but it's quite large).

This PR filters out two groups. First, everything in resources/ appears to
be things to _run_ the tests, not actual tests.

Second, any file without a "testharness.js" doesn't appear to be a test
either. Note that WPT's own manifest generator looks at this:
43a0615361/tools/manifest/sourcefile.py (L676)
(which is used later in the file to determine the type of file).
2025-04-22 21:05:12 +08:00
Pierre Tachoire
9727a9d000 Merge pull request #547 from lightpanda-io/jsruntime_arenas
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Re-introduce call_arena
2025-04-22 13:58:16 +02:00
Pierre Tachoire
1b74289c43 Merge pull request #543 from lightpanda-io/describeNode2
descibeNode for new js runtime
2025-04-22 13:57:18 +02:00
sjorsdonkers
a698ff8309 describeNode feedback 2025-04-22 13:49:01 +02:00
sjorsdonkers
5026c48805 Update zig-v8 to v0.1.18 2025-04-22 13:49:00 +02:00
sjorsdonkers
2ac63b6985 describeNode 2025-04-22 13:49:00 +02:00
Karl Seguin
114e11f52a Partially revert some changes.
The call_arena is still re-added, and the call_depth is still used, but
the call_arena and scope_arenas are no longer part of the Env - they remain on
the Executor.

This is to accommodate upcoming changes where multiple executors will exist at
once. While the shared allocators _might_ have been safe in some cases, the
performance gains don't justify the risk of having 2 executors interacting in a
way where sharing the allocators would cause issues.
2025-04-22 16:56:26 +08:00
Karl Seguin
3277d1baac Re-introduce call_arena
Because of callbacks, calls into Zig can be nested. Previously, the call_arena
was reset after _every_ call. When calls are nested, this doesn't work - the
nested call resets the arena, which the caller might still need. A `call_depth`
integer was added to the Executor. Each call starts by incrementing the
call_depth and, on deinit, decrements the call_depth. The call_arena is only
reset when the call_depth == 0. This could result in lower memory use.

Also promoted the call_arena and scope_arena to the Env. Practically speaking,
nothing has changed, since they're still reset under the exact same conditions.
However, when an executor ends and a new one is started, it can now reuse the
retained_capacity of the previous arenas. This should result in fewer
allocations.
2025-04-22 15:20:37 +08:00
Pierre Tachoire
f3d8ec040c Merge pull request #549 from lightpanda-io/type_error_on_non_zig_values
Return TypeError if trying to turn an unknown v8.Object into a toa
2025-04-22 09:14:03 +02:00
Pierre Tachoire
0a29e9b3cf Merge pull request #548 from lightpanda-io/namednodemap_indexed_get
add indexed_get to namednodemap
2025-04-22 09:13:05 +02:00
Pierre Tachoire
4b7c17ac03 Merge pull request #546 from lightpanda-io/jsruntime_js_to_null_terminated_string
Support binding JS strings to [:0]const u8
2025-04-22 09:11:36 +02:00
Pierre Tachoire
1849f4c11d Merge pull request #544 from lightpanda-io/token_list_iterators
Add missing TokenList APIs
2025-04-22 09:03:19 +02:00
Karl Seguin
b9f61466ba Try to sniff the mime type based on the body content
Synchronous body reader now exposes a peek() function to get the first few bytes
from the response body. This will be no less than 100 bytes (assuming the body
is that big), but could be more. Streaming API, via res.next() continues to work
as-is even if peek() is called.

Introduce Mime.sniff() that detects a few common types - the ones that we care
about right now - from the body content.
2025-04-22 10:58:26 +08:00
Karl Seguin
d8fa9b8c4f Return TypeError if trying to turn an unknown v8.Object into a toa 2025-04-20 12:47:28 +08:00
Karl Seguin
42bc80e5b5 add indexed_get to namednodemap 2025-04-19 21:28:16 +08:00
Karl Seguin
9f7446ba56 use allocSentinel (which i didn't know about) 2025-04-19 17:25:01 +08:00
Karl Seguin
7bdea1befa Support binding JS strings to [:0]const u8
Some APIs need a null-terminated string. Currently, they have to ask for a
`[]const u8` and then convert it to a `[:0]const u8`. This is 2 allocations: 1
for jsruntime to get the `[]const u8` from v8, and then one to get the [:0]. By
supporting `[:0]const u8` directly, this is now a single allocation.
2025-04-19 16:19:46 +08:00
Pierre Tachoire
66ec087416 Merge pull request #516 from karlseguin/javascript_anchor_click
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add zig listener support to netsurf event handler
2025-04-18 15:17:37 +02:00
Karl Seguin
9b4d1d442e Allow this argument to TokenList forEach
JsObject can now be used as a normal parameter. It'll receive the opaque value.
This is largely useful when a Zig function takes an argument which it needs
to pass back into a callback.

JsThis is now a thin wrapper around JsObject for functions that was the JsObject
of the receiver. This is for advanced usage where the Zig function wants to
manipulate the v8.Object that represents the zig value. postAttach is an example
of such usage.
2025-04-18 20:38:52 +08:00
sjorsdonkers
16a30fa3b7 enum as subtype
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
2025-04-18 13:46:54 +02:00
sjorsdonkers
1cd3ebfc3f remove tnames 2025-04-18 13:46:54 +02:00
sjorsdonkers
fd170df98f node subtypes 2025-04-18 13:46:54 +02:00
Karl Seguin
a2291b0713 Add missing TokenList APIs
Add value setter, keys(), values(), entries() and forEach().

Like nodelist, forEach still doesn't support `this` arg (gotta think about
how to do this).

I think these iterable methods are missing in a few places, so I added a
generic Entries iterator and a generic Iterable.

jsruntime will now map a Zig tuple to a JS array.
2025-04-18 19:10:37 +08:00
Karl Seguin
3134ff81f4 JS clicks and MouseInput clicks trigger page navigation 2025-04-18 16:24:04 +08:00
Karl Seguin
072bc514f4 Add zig listener support to netsurf event handler
Add _click handler to HTMLElement

Register zig click listener on document.

Largely waiting on https://github.com/lightpanda-io/browser/pull/501/files to
finalize the placeholders.
2025-04-18 16:20:46 +08:00
Pierre Tachoire
581a79f3fc Merge pull request #540 from lightpanda-io/make-e2e
make: add end2end target
2025-04-18 09:49:08 +02:00
Pierre Tachoire
cccb8e9645 Merge pull request #538 from lightpanda-io/node_class_attributes
Add Node.$NODE_TYPE class attributes
2025-04-18 09:48:11 +02:00
Pierre Tachoire
646fcafa62 Merge pull request #541 from lightpanda-io/subtype_fix
Change TypeLookup values from simple index (usize) to a TypeMeta
2025-04-18 09:47:09 +02:00
Karl Seguin
615453a687 Change TypeLookup values from simple index (usize) to a TypeMeta
TypeMeta constains the index and the subtype. This allows retrieving the subtype
based on the actual value being bound, as opposed to the struct type. (I.e. it
returns the correct subtype when a Zig class is a proxy for another using the
Self declaration).

Internally store the subtype as an enum. Reduces @sizeOf(TaggedAnyOpaque) from
32 to 16.

Finally, sub_type renamed to subtype for consistency with v8.
2025-04-18 09:56:08 +08:00
Karl Seguin
361a1a21ac zig fmt :| 2025-04-18 00:10:40 +08:00
Karl Seguin
e3e3311dd0 add deprecated node types (both Chrome and FF have them) 2025-04-18 00:10:03 +08:00
Pierre Tachoire
74fa9a6b2b ci: use the demo go runner 2025-04-17 17:57:19 +02:00
Pierre Tachoire
b62faef520 make: add end2end target 2025-04-17 17:41:29 +02:00
Karl Seguin
74391d59a5 Add Node.$NODE_TYPE class attributes 2025-04-17 21:41:28 +08:00
Pierre Tachoire
1c08b3e5e4 Merge pull request #534 from lightpanda-io/mutable_response_header_value
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Make HTTP Response header values mutable
2025-04-17 13:10:33 +02:00
Pierre Tachoire
8c489c2131 Merge pull request #506 from lightpanda-io/jsruntime
replace zig-js-runtime
2025-04-17 13:09:44 +02:00
Pierre Tachoire
19976939b7 readme: fix build-v8 target 2025-04-17 13:01:01 +02:00
Karl Seguin
4e1659b98d Disable the call arena (for now)
The call arena doesn't consider nested calls (like, from callbacks). Currently
when a "call" ends, the arena is cleared. But in a callback, if we do that,
the memory for the containing code is no longer valid, even though it's still
executing.

For now, use the existing scope_arena, instead of the call_arena. In the future
we need to track the call-depth, and only reset the call_arena when we're done
with a top-level statement.

Also:
-Properly handle callback errors
-Increase wpt file size
-Merge latest loop.zig from zig-js-runtime.
2025-04-17 18:38:47 +08:00
Pierre Tachoire
26ef8deca5 Merge pull request #535 from lightpanda-io/wsl-support-note
WSL support note
2025-04-17 11:01:09 +02:00
Karl Seguin
4e5fe5ae1a Merge pull request #536 from lightpanda-io/jsruntime-imp
test: re-introduce js source name
2025-04-17 16:18:48 +08:00
Pierre Tachoire
7f308f59b4 test: re-introduce js source name
Having a js source name is useful to detect where the error comes from.

Using `null` generates messages with `<anonymous>` source name.
eg. `ReferenceError: report is not defined\n    at <anonymous>:1:1`
vs. `ReferenceError: report is not defined\n    at teststatus:1:1`
2025-04-17 10:08:13 +02:00
Karl Seguin
f4e8bb6c66 Re-introduce postAttach
index_get seems to be ~1000x slower than setting the value directly on the
v8.Object. There's a lot of information on "v8 fast properties", and values
set directly on objects seem to be heavily optimized. Still, I can't imagine
indexed properties are always _that_ slow, so I must be doing something wrong.
Still, for now, this brings back the original functionality / behavior / perf.

Introduce the ability for Zig functions to take a Env.JsObject parameter. While
this isn't currently being used, it aligns with bringing back the postAttach /
JSObject functionality in main.

Moved function *State to the end of the function list (making it consistent with
getters and setters). The optional Env.JsObject parameter comes after the
optional state.

Removed some uncessary arena deinits from a few webapis.
2025-04-17 09:26:37 +08:00
Karl Seguin
e3638053d0 better error messages in WPT report (in line with what main branch is doing) 2025-04-16 19:34:36 +08:00
Karl Seguin
d688d8812d add missing try/catch around loop wait for wpt tests 2025-04-16 19:20:35 +08:00
Karl Seguin
4a6bf38666 ResponseHeader.get should return mutable slice 2025-04-16 16:54:28 +08:00
Sjors
f532b62913 missing space 2025-04-16 10:41:20 +02:00
sjorsdonkers
0080c8457f WSL support note 2025-04-16 10:05:52 +02:00
Karl Seguin
613904e3a4 Make HTTP Response header values mutable
The HTTP response values _are_ mutable, but because we're using std.http.Header
the type is a `[]const u8`. This introduce a custom `Header` type where the
value is `[]u8`.

The goal is largely to allow more efficient value-comparison, by allowing
calling code to lower-case in-place. I specifically have the Mime parser in
mind:

25dcae7648/src/browser/mime.zig (L134)
2025-04-16 14:05:21 +08:00
Karl Seguin
753a093689 zig fmt :| 2025-04-15 21:16:20 +08:00
Karl Seguin
ea6f8ce4d9 Add more tests
Remove index and named setters, since they aren't working, and they aren't
currently needed.
2025-04-15 20:37:59 +08:00
Karl Seguin
180359e148 zig build test --json to get duration/memory stats 2025-04-15 18:49:39 +08:00
Karl Seguin
5816443ad3 improve XHR test reliability 2025-04-15 18:24:43 +08:00
Karl Seguin
e9fce9223e add some debug lines to see if we can fix the github action 2025-04-15 15:42:55 +08:00
Karl Seguin
f6c43eaf9c Fix dockerfile (hopefully)
Add dummy --json stats output to tests

Comment typos fixed

Add make get-v8, build-v8 and build-v8-dev make targets
2025-04-15 15:18:06 +08:00
Karl Seguin
8af71be551 Import some of the zig-js-runtime env tests
- Fix passing nothing into variadic (i.e. slice) parameter
- Optimize @sizeOf(T) == 0 types by avoiding uncessary allocations
  (something zig-js-runtime is doing)
2025-04-15 15:18:06 +08:00
Karl Seguin
9e36702eb2 Improve documentation
Remove Env from caller, and don't store Env in isolate. We don't need it, we
can execute all runtime code from the Executor (which we store a pointer to
in the v8.Context)
2025-04-15 15:18:06 +08:00
Karl Seguin
cda6f89dba work on fixing github workflows 2025-04-15 15:18:06 +08:00
Karl Seguin
b8d7744563 replace zig-js-runtime 2025-04-15 15:18:04 +08:00
Karl Seguin
25dcae7648 Merge pull request #529 from lightpanda-io/document-cookie
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Document cookie
2025-04-11 23:34:44 +08:00
Pierre Tachoire
ee6382ef03 dom: use cookie jar's allocator to parse cookie 2025-04-11 16:23:03 +02:00
Pierre Tachoire
0310192660 dom: assume we are using an arena for cookie 2025-04-11 14:27:08 +02:00
Pierre Tachoire
c88bc65379 cookie: use a ; w/o space for cookie separator in requests 2025-04-11 12:40:16 +02:00
Pierre Tachoire
37340dc549 dom: implement document.cookie 2025-04-11 12:19:11 +02:00
Pierre Tachoire
9b6764a852 Merge pull request #527 from lightpanda-io/update-cla
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add bornlex to cla whitelist
2025-04-11 11:14:46 +02:00
Pierre Tachoire
b176857b8d add bornlex to cla whitelist 2025-04-11 11:14:01 +02:00
Pierre Tachoire
f034065247 Merge pull request #520 from lightpanda-io/navigate_notifications
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Communicate page navigation state via notifications
2025-04-10 13:42:30 +02:00
Karl Seguin
64bd4dee38 Merge pull request #523 from lightpanda-io/url-about-blank
url: accept about:blank
2025-04-10 19:15:04 +08:00
Pierre Tachoire
22307239ae url: accept about:blank 2025-04-10 13:00:14 +02:00
Karl Seguin
3fc7ffadbf rename ts => timestamp, ctx => notify_ctx 2025-04-10 18:27:14 +08:00
Pierre Tachoire
b87a80a32d Merge pull request #521 from lightpanda-io/remove_wpt_env_wait
Remove the WPT js_env.wait() on error.
2025-04-10 11:40:33 +02:00
Karl Seguin
c775de260a Remove the WPT js_env.wait() on error.
40c0c7d421

Makes it unecessary as wait is now always called on deinit.
2025-04-10 16:30:44 +08:00
Karl Seguin
30fd358286 improve playwright pafe lifecycle message compatibility 2025-04-10 16:07:31 +08:00
Karl Seguin
71c3d484a9 Communicate page navigation state via notifications
In order to support click handling on anchors from JavaScript, we need some hook
from the page/session to the CDP instance. This first phase adds notifications
in page.navigate, as well as a primitive notification hook to the session.

CDP's existing Page.navigate uses this new notifiation system.
2025-04-10 14:25:19 +08:00
Pierre Tachoire
66bac32e33 Merge pull request #519 from lightpanda-io/url
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add URL struct
2025-04-09 16:43:44 +02:00
Pierre Tachoire
4f0ea888ef Merge pull request #513 from lightpanda-io/resolveNode
Resolve node
2025-04-09 15:00:35 +02:00
Pierre Tachoire
bc1a83d04a Update src/cdp/domains/dom.zig 2025-04-09 14:46:53 +02:00
sjorsdonkers
32d9fc0d32 Pass objectGroup as groupName 2025-04-09 13:40:00 +02:00
Karl Seguin
41bd3704ef update lightpanda and wpt URL usage 2025-04-09 19:21:59 +08:00
Karl Seguin
be75b5b237 Add URL struct
Combine uri + rawuri into single struct.

Try to improve ownership around URIs and URI-like things.
 - cookie & request can take *const std.Uri
   (TODO: make them aware of the new URL struct?)
 - Location (web api) should own its URL (web api URL)
 - Window should own its Location

Most of these changes result in (a) a cleaner Page and (b) not having to carry
around 2 nullable objects (URI and rawuri).
2025-04-09 18:19:07 +08:00
sjorsdonkers
3a7da6665f unittest scaffolding 2025-04-09 11:33:44 +02:00
sjorsdonkers
2f47e04de7 Use findOrAddValue for precise JsValue 2025-04-09 11:33:41 +02:00
sjorsdonkers
7dc3add5fd reolveNode WIP 2025-04-09 11:32:23 +02:00
Pierre Tachoire
8b296534a4 Merge pull request #517 from lightpanda-io/wpt-fix
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Wpt fix
2025-04-09 08:39:18 +02:00
Karl Seguin
f9c4cefe59 Update zig-js-runtime, wait for loop on wpt error
Updates zig-js-runtime to latest, reverting the loop reset change. This solves
the introduced memory leak.

On WPT error, call js_env.wait() to ensure all pending events are completed.
Without this, on error, the code is likely to crash as the timeout callback
executes AFTER env.deinit() is called. This is now possible to do safely on
Mac now that cancel is pseudo-implemented.
2025-04-09 09:01:04 +08:00
Pierre Tachoire
d772eaf4a2 upgrade zig-jsruntime 2025-04-08 17:34:56 +02:00
Pierre Tachoire
27ec1a13da wpt: add missing renderer 2025-04-08 17:34:18 +02:00
Pierre Tachoire
07e8dfa257 Merge pull request #501 from karlseguin/renderer
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add a dumb renderer to get coordinates
2025-04-08 17:12:49 +02:00
Karl Seguin
0fbf48ab4d actually dispatch click 2025-04-08 22:51:19 +08:00
Karl Seguin
f38a0d2d67 Remove BrowserContext URL
Add BrowserContext.getURL which gets the URL from the session.page.
2025-04-08 22:51:17 +08:00
Karl Seguin
b76875bf5d use netsurf's mousevent 2025-04-08 22:43:53 +08:00
Karl Seguin
0253de80de Add a dumb renderer to get coordinates
FlatRenderer positions items on a single row, giving each a height and width of
1.

Added getBoundingClientRect to the DOMelement which, when requested for the
first time, will place the item in with the renderer.

The goal here is to give elements a fixed position and to make it easy to map
x,y coordinates onto an element. This should work, at least with puppeteer,
since it first requests the boundingClientRect before issuing a click.
2025-04-08 22:43:53 +08:00
Pierre Tachoire
647575261e Merge pull request #515 from karlseguin/html_document_subtype
add 'node' subtype to htmldocument
2025-04-08 15:45:40 +02:00
Pierre Tachoire
3c2b348ce5 Merge pull request #502 from lightpanda-io/cdp_node_children
Cdp node children
2025-04-08 15:45:16 +02:00
Karl Seguin
8aef6ca372 add 'node' subtype to htmldocument 2025-04-08 10:40:52 +08:00
Karl Seguin
0139437c3d Wrap getDocument response in a root object 2025-04-08 10:05:32 +08:00
Pierre Tachoire
a7b91ee57d Merge pull request #514 from lightpanda-io/js-kind
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
browser: script with type text/javascript are js
2025-04-07 22:22:35 +02:00
Pierre Tachoire
ad0117e060 browser: script with type text/javascript are js 2025-04-07 22:06:39 +02:00
Pierre Tachoire
309d70c142 Merge pull request #509 from lightpanda-io/browser-no-content-type
browser: assume no-content type is html
2025-04-07 17:39:54 +02:00
Pierre Tachoire
c9ff59a433 Merge pull request #511 from lightpanda-io/http_chunk_reader_fix
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Don't emit incorrect empty chunk
2025-04-07 17:34:53 +02:00
Karl Seguin
ec9a1416a1 Don't emit incorrect empty chunk
When we only have 1 or 2 bytes missing from a chunk (i.e. the tailing \n or
\r\n), don't emit an empty chunk if we have more data available to process.
2025-04-07 22:40:02 +08:00
Pierre Tachoire
dac622fc46 browser: assume no-content type is html 2025-04-07 14:07:55 +02:00
Pierre Tachoire
92e2daf056 Merge pull request #508 from lightpanda-io/readme_iconv_install
add libiconv install direction
2025-04-07 12:09:39 +02:00
Karl Seguin
08e68a1cff add libiconv install direction 2025-04-07 18:01:04 +08:00
Karl Seguin
8f4be9b76f break when child node list fails 2025-04-07 10:40:46 +08:00
Karl Seguin
fab6ec94fa Merge pull request #504 from lightpanda-io/redirect-url
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
browser: update urls after redirection
2025-04-04 16:06:48 +08:00
Pierre Tachoire
5cbcb901f1 browser: fix buffer url usage w/ the arena 2025-04-04 09:53:47 +02:00
Karl Seguin
4d075818f6 Lazily load nodes
Node registry now only tracks the node id (which we need to be consistent) and
the underlying parser.Node. All other data is loaded on-demand (i.e. when we
serialize the node). This allows us to serialize node values as they appear
when they are serialized, as opposed to when they are registered.
2025-04-04 11:24:34 +08:00
Pierre Tachoire
4302be5619 browser: update urls after redirection 2025-04-03 18:27:49 +02:00
Karl Seguin
68d1be3b94 Add children node to CDP Node representation
Add Node writer. Different CDP messages want different child depths. For now,
only support immediate children, but the new writer should make it easy to
support variable.
2025-04-03 21:28:57 +08:00
Karl Seguin
af68b10c5d Better CDP node serialization
Include direct descendant, with hooks for other serialization options.

Don't include parentId if null.
2025-04-03 21:18:18 +08:00
Pierre Tachoire
8b16d0e7ed Merge pull request #495 from lightpanda-io/cdp_node
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add CDP Node Registry
2025-04-01 17:25:25 +02:00
katie-lpd
2d5c24d8b5 Update README.md
Typo
2025-04-01 11:54:04 +02:00
Pierre Tachoire
0110ac62bf Merge pull request #490 from karlseguin/cookies
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Add Cookie support to browser & xhr requests
2025-03-31 14:35:28 +02:00
Pierre Tachoire
5bfa44b1b4 Merge pull request #497 from lightpanda-io/upgrade-jsruntime
ci: add a browser fetch test
2025-03-31 14:17:58 +02:00
Karl Seguin
d21821a0fb add cookie_jar to wpt script 2025-03-31 18:44:09 +08:00
Karl Seguin
84dfde2e51 add cookies to XHR requests 2025-03-31 18:44:09 +08:00
Karl Seguin
22d33fa286 Add cookie support to browser (not XHR yet) requests 2025-03-31 18:44:09 +08:00
Pierre Tachoire
f6f83e2114 upgrade zig-jsruntime 2025-03-31 12:36:23 +02:00
Pierre Tachoire
c6ad734de0 ci: run wpt classic only on PR 2025-03-31 12:35:34 +02:00
Pierre Tachoire
cf015b2ce7 main: exit 1 on memory leak detection 2025-03-31 12:35:33 +02:00
Pierre Tachoire
fbe8086c98 ci: add a browser fetch test 2025-03-31 12:35:29 +02:00
Pierre Tachoire
95cae6e7de Merge pull request #498 from karlseguin/pool_polish
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Accommodate zig-js-runtime loop changes
2025-03-31 12:35:09 +02:00
Pierre Tachoire
d12fd78ef0 Merge pull request #499 from karlseguin/http_req_connection_close
On a non websocket upgrade connection, close the connection
2025-03-31 12:25:53 +02:00
Karl Seguin
b2d9f835bf Zig fmt 2025-03-31 15:29:54 +08:00
Karl Seguin
735772f43a On a non websocket upgrade connection, close the connection
Solves slow startup time with chromedp
2025-03-31 15:26:37 +08:00
Karl Seguin
75f66a6cb2 Accommodate zig-js-runtime loop changes 2025-03-31 14:59:40 +08:00
Pierre Tachoire
24d5dfe3c6 Merge pull request #371 from lightpanda-io/ci-wpt-split
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
ci: split wpt wnd wpt-json jobs
2025-03-28 13:39:32 +01:00
Karl Seguin
be9e953971 Add CDP Node Registry
This expands on the existing CDP node work used in  DOM.search. It introduces
a node registry to track all nodes returned to the client and give lookups to
get a node from a Id or a *parser.node.

Eventually, the goal is to have the Registry emit the DOM.setChildNodes event
whenever necessary, as well as support many of the missing DOM actions.

Added tests to existing search handlers. Reworked search a little bit to avoid
some unnecessary allocations and to hook it into the registry.

The generated Node is currently incomplete. The parentId is missing, the
children are missing. Also, we still need to associate the v8 ObjectId to the
node.

Finally, I moved all action handlers into a nested "domain" folder.
2025-03-28 19:00:29 +08:00
Pierre Tachoire
82e67b7550 Merge pull request #489 from lightpanda-io/microtasks
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
run v8 micro tasks
2025-03-27 17:15:50 +01:00
Pierre Tachoire
791549fda8 Merge pull request #494 from karlseguin/insecure_disable_tls_host_verification
Add an `insecure_disable_tls_host_verification` command line option
2025-03-27 15:57:39 +01:00
Pierre Tachoire
c763783d53 upgrade vendor/zig-js-runtime 2025-03-27 15:49:48 +01:00
Pierre Tachoire
e347e7e5fb browser: use loop.resetJS 2025-03-27 15:49:48 +01:00
Pierre Tachoire
3f1d0df7f9 cdp: run microtasks after send inspector 2025-03-27 15:49:48 +01:00
Pierre Tachoire
c6cb6d5eeb Merge pull request #493 from lightpanda-io/upgrade-jsruntime
upgrade vendor/zig-js-runtime
2025-03-27 14:40:28 +01:00
Pierre Tachoire
57025f8173 upgrade vendor/zig-js-runtime 2025-03-27 14:28:00 +01:00
Karl Seguin
3e7f07374c Pass HttpClient options in wpt 2025-03-27 18:18:29 +08:00
Karl Seguin
fba9cb071d zig fmt :| 2025-03-27 18:15:27 +08:00
Karl Seguin
c6538e1038 Add an insecure_disable_tls_host_verification command line option
When set, this disables the host verification of all HTTP requests. Available
for both the fetch and serve mode.

Also introduced an App.Config, for future command line options which need to
be passed more deeply into the code.
2025-03-27 18:02:30 +08:00
Pierre Tachoire
3a1a582013 Merge pull request #482 from karlseguin/http_client
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Replace zig-async-io with a custom HTTP client
2025-03-27 08:52:21 +01:00
Karl Seguin
531a484cb0 Fix a few comments
Switch generic http_client error level from warn to err
2025-03-27 08:11:48 +08:00
Pierre Tachoire
16c477229a Merge pull request #491 from lightpanda-io/cla-add-sjors
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: add Sjors to the cla allow list
2025-03-25 14:49:19 +01:00
Pierre Tachoire
f2565049b8 ci: add Sjors to the cla allow list 2025-03-25 14:40:36 +01:00
Karl Seguin
afdb5d7233 reset read_pos after handshake is established 2025-03-23 20:08:12 +08:00
Karl Seguin
18be1202db Prevent double in-flight recvs
Retry on test timeout for slower machines (i.e. CI build), while also reducing
wait time for faster builds.
2025-03-23 19:05:37 +08:00
Karl Seguin
14cc87e1a5 Use latest tls.zig (with new allocation-free API)
Add more fuzz tests around async tls.
2025-03-23 19:05:37 +08:00
Karl Seguin
2a0d1b0a48 Switch to nonblocking socket
Improve test server handshake performance, allowing for a few more fuzz
iterations without making tests unbearably slow.
2025-03-23 19:05:37 +08:00
Karl Seguin
22aa126b29 Cleaner merge
Switch to non-blocking sockets.

Fix TLS handshake/receive/send ordering
2025-03-23 19:05:35 +08:00
Karl Seguin
feb2046549 add TLS integration test for sync client 2025-03-23 19:01:40 +08:00
Karl Seguin
2f362f2aa2 handle redirects on asynchronous calls 2025-03-23 19:01:40 +08:00
Karl Seguin
de160d9170 Cleanup synchronous connection for tls and non-tls.
Drain response prior to redirect.
2025-03-23 19:01:40 +08:00
Karl Seguin
226c18cb56 handle redirects on synchronous calls 2025-03-23 19:01:40 +08:00
Karl Seguin
314aea4e1e fix double dereference 2025-03-23 19:01:40 +08:00
Karl Seguin
807d3a600c Support transfer-encoding: chunked, fix async+tls integration 2025-03-23 19:01:40 +08:00
Karl Seguin
fa8ea1ef43 use latest tls.zig 2025-03-23 19:01:40 +08:00
Karl Seguin
2017d4785b replace zig-async-io and std.http.Client with a custom HTTP client 2025-03-23 19:01:40 +08:00
Karl Seguin
fd35724aa8 zig 0.14 fmt 2025-03-23 19:01:40 +08:00
Karl Seguin
e1a85d97e3 Zig 0.14 compatibility 2025-03-23 19:01:40 +08:00
Pierre Tachoire
b972c9fe30 Merge pull request #484 from lightpanda-io/telemetry_batch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
send telemetry events in batches (up to 20)
2025-03-23 11:21:56 +01:00
Pierre Tachoire
4c68150dec Merge pull request #487 from lightpanda-io/mkdir_p_app_path
Use makePath to create any missing intermediate directories for app dir
2025-03-23 10:48:55 +01:00
Karl Seguin
3d6dd06b99 Generate non-persisted iid if app_path is null 2025-03-22 23:58:57 +08:00
Karl Seguin
81759fa57a Use makePath to create any missing intermediate directories for app dir
https://github.com/lightpanda-io/browser/issues/486
2025-03-22 23:53:46 +08:00
Pierre Tachoire
20160cb071 Merge pull request #485 from lightpanda-io/ubuntu-22-04
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
ci: use ubuntu 22.04 for x86_64 build
2025-03-22 10:48:57 +01:00
Pierre Tachoire
8931506657 ci: use ubuntu 22.04 for x86_64 build 2025-03-22 10:40:50 +01:00
Karl Seguin
2aee346299 send telemetry events in batches (up to 20) 2025-03-21 22:40:10 +08:00
Pierre Tachoire
f89efd84d3 Merge pull request #481 from lightpanda-io/auto-attach
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
cdp: implement target.setAutoAttach and target.detachFromTarget
2025-03-21 12:14:01 +01:00
Pierre Tachoire
7607ab2c84 cdp: target: implement detach from target 2025-03-20 09:36:00 +01:00
Pierre Tachoire
fe7f6bee1c cdp: create a cdp state for target_auto_attach 2025-03-20 09:35:59 +01:00
Pierre Tachoire
b43658eb3f cdp: target: add test for #474
Can't attach to just created target
2025-03-20 09:35:59 +01:00
Pierre Tachoire
85caa09e63 Merge pull request #477 from lightpanda-io/ci-v8-version
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add macos x86_64 bin to nightly build
2025-03-19 17:35:20 +01:00
Pierre Tachoire
c32853bfd6 docker: update zig-v8 version 2025-03-19 17:12:53 +01:00
Pierre Tachoire
e79cd58c8f ci: add macos x86_64 nightly build 2025-03-19 17:12:10 +01:00
Pierre Tachoire
0d291f1a36 ci: upgrade zig v8 version 2025-03-19 17:12:05 +01:00
Pierre Tachoire
24aa8e2a07 Merge pull request #480 from lightpanda-io/zig-0.14
Zig 0.14
2025-03-19 17:06:57 +01:00
Pierre Tachoire
0a0c155292 upgrade vendor after zig 0.14 merge 2025-03-19 16:55:26 +01:00
Pierre Tachoire
55a942aa22 wpt: fix zig-0.14 compat 2025-03-19 16:48:22 +01:00
Karl Seguin
b51499e87b update to latest zig-js-runtime 2025-03-19 16:28:21 +01:00
Karl Seguin
936048d478 upgrade telemetry to zig 0.14 2025-03-19 16:28:21 +01:00
Karl Seguin
bd6497743c zig 0.14 fmt 2025-03-19 16:28:21 +01:00
Karl Seguin
6873d8d445 update tls.zig dep 2025-03-19 16:28:20 +01:00
Karl Seguin
21c9dde858 Zig 0.14 compatibility 2025-03-19 16:28:15 +01:00
Pierre Tachoire
17d3d620ff Merge pull request #478 from lightpanda-io/global_http_client
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Share the HTTP client globally
2025-03-19 11:31:37 +01:00
Karl Seguin
705603a088 remove explicit thread stack size.
The real win is having a global http_client, so the thread only needs a pointer.
2025-03-19 16:17:41 +08:00
Karl Seguin
ba8a0179d5 Share the HTTP client globally 2025-03-19 11:09:58 +08:00
Pierre Tachoire
9fe10747ce Merge pull request #476 from karlseguin/implicit_browser_context
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Implicitly create BrowserContext on createTarget if one doesn't exist
2025-03-18 09:21:50 +01:00
Pierre Tachoire
4a4d9a9377 Merge pull request #446 from karlseguin/telemetry
Add Usage Telemetry
2025-03-18 09:10:31 +01:00
Karl Seguin
2e7342a59c add driver field to navigate telemetry 2025-03-18 10:40:04 +08:00
Karl Seguin
c9bc5be42b add additition navigate fields 2025-03-18 09:56:57 +08:00
Karl Seguin
b75b36dc61 zig fmt 2025-03-18 08:27:47 +08:00
Karl Seguin
1e6a1bd3af store iid in application data directory 2025-03-18 08:27:47 +08:00
Karl Seguin
b0a2087015 fix unit test 2025-03-18 08:27:47 +08:00
Karl Seguin
a5ee34a2db send telemetry synchronously in a background thread 2025-03-18 08:27:47 +08:00
Karl Seguin
a6a8130234 update telemetry URL (but not vendored dependency this time) 2025-03-18 08:27:34 +08:00
Karl Seguin
288761632f Revert "update telemetry URL"
This reverts commit 88850bcdd38026720f03087be8ef7e9869072ac6.
2025-03-18 08:27:34 +08:00
Karl Seguin
25bf4fa738 update telemetry URL 2025-03-18 08:27:34 +08:00
Karl Seguin
3b4de6a405 remove [incorrect] data version 2025-03-18 08:27:32 +08:00
Karl Seguin
75512602c3 Add log to display telemetry state 2025-03-18 08:27:02 +08:00
Karl Seguin
cd33a089d1 flatten events, include aarch + os, remove eid 2025-03-18 08:26:58 +08:00
Karl Seguin
6b83281539 Add navigate telemetry 2025-03-18 08:25:44 +08:00
Karl Seguin
2609671982 don't try (and fail) to get userData after clearing context 2025-03-18 08:02:09 +08:00
Karl Seguin
accf2c0e5e use async-client for telemetry 2025-03-18 08:02:09 +08:00
Karl Seguin
53f6e66c23 Remove plausible, leave a dummy provider for now
Add batching, add install optional id (persisted) and execution id (per run)
2025-03-18 08:02:09 +08:00
Karl Seguin
56ddcc8e29 Initial usage telemetry 2025-03-18 08:02:09 +08:00
Karl Seguin
430779979e Implicitly create BrowserContext on createTarget if one doesn't exist 2025-03-17 20:45:57 +08:00
Pierre Tachoire
671dbcfd55 Merge pull request #470 from lightpanda-io/resove-module
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
browser: fix module URL resolution
2025-03-17 11:33:59 +01:00
Pierre Tachoire
087a7b5f3c browser: use *const Page with fetchModule 2025-03-17 09:58:31 +01:00
Pierre Tachoire
229844d399 browser: use *const Script with evalScript 2025-03-17 09:51:01 +01:00
Pierre Tachoire
36081653b0 Merge pull request #472 from lightpanda-io/linux_aarch64
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: use ubuntu 24.04
2025-03-15 10:40:09 +01:00
Pierre Tachoire
9811c5d577 ci: use ubuntu 24.04 2025-03-15 10:24:34 +01:00
Pierre Tachoire
4394186dc3 Merge pull request #469 from lightpanda-io/linux_aarch64
Linux aarch64 build
2025-03-15 10:17:42 +01:00
Pierre Tachoire
725b48d8aa ci: fix install params for linux 2025-03-15 10:01:46 +01:00
Pierre Tachoire
3fd8347943 browser: fix module URL resolution 2025-03-14 19:02:33 +01:00
Pierre Tachoire
5e7c26c34b dockerfile: add ARCH parameter 2025-03-14 17:27:17 +01:00
Pierre Tachoire
d7019264a2 docker: upgrade ubuntu 2025-03-14 14:51:05 +01:00
Pierre Tachoire
ade9fa5d0e ci: add linux aarch64 to the nightly build 2025-03-14 14:38:05 +01:00
Pierre Tachoire
f84c4393b9 ci: upgrade zig-v8 version 2025-03-14 14:37:38 +01:00
Pierre Tachoire
48d01c0ab5 Merge pull request #465 from lightpanda-io/inspector-cache-debug
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
don't generate debug js file on release
2025-03-14 11:52:30 +01:00
Pierre Tachoire
aca01d81d6 cdp: use .zig-cache to save js script debug files 2025-03-14 11:41:21 +01:00
Pierre Tachoire
6a0b154d67 cdp: dump runtime js only in debug mode 2025-03-14 11:41:20 +01:00
Pierre Tachoire
7ce69987d5 Merge pull request #463 from karlseguin/page_arena
Optimize memory usage
2025-03-14 10:17:41 +01:00
Karl Seguin
3fe28d5441 Optimize memory usage
The two bigger changes here are:

1- The http_client has been moved from the Session to the Browser, allowing
   its connection pool to be re-used across multiple sessions

2- The browser now has a page_arena which is used for all page-level allocation
   and which can be re-used between pages (currently retains 1MB of memory).
   Previously, pages uses an arena that was tied to the lifetime of the page,
   thus it could not be re-used.

Using the Bench allocator for zig-js-runtime, allocated bytes went from
1347037879 to 834932438 (in a RUNS=1000 of puppeteer demo).

Various other changes to try to simplify the API and remove the possibility
of invalid states. For example, session.newPage() now includes the logic for
page.start() so that there should now never be a page that wasn't started.
2025-03-12 13:38:22 +08:00
Pierre Tachoire
43f42f9ca0 Merge pull request #461 from lightpanda-io/ci-playwright
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: add e2e test w/ playwright connection
2025-03-11 10:11:08 +01:00
Pierre Tachoire
3e288f1fcf Merge pull request #462 from lightpanda-io/upgrade-jsruntime
upgrade vendor/zig-js-runtime
2025-03-11 10:10:06 +01:00
Pierre Tachoire
8ccd75fdfb upgrade vendor/zig-js-runtime 2025-03-11 09:53:33 +01:00
Pierre Tachoire
fd6aa6e54e ci: add e2e test w/ playwright connection 2025-03-11 09:52:11 +01:00
Pierre Tachoire
4802a2ce82 Merge pull request #460 from karlseguin/playwright
Remove CDP FrameId
2025-03-11 08:41:39 +01:00
Karl Seguin
e3409a27e7 fix test 2025-03-11 10:51:40 +08:00
Karl Seguin
5182edce6f Remove CDP FrameId
I don't know if FrameId is related to an <iframe>, and whether each Page has
1 implicit "frame". But, playwright seems to treat frameId and targetId as
interchangeable, and chrome seems to agree (at leas to some degree); chrome will
return a targetId and reuse that value for the frameId.

So the simplest solution is just to remove our concept of a frameId and use
targetId exclusively. This doesn't seem to cause any issues with puppeteer.
2025-03-11 10:37:43 +08:00
Pierre Tachoire
763d8d025e Merge pull request #453 from lightpanda-io/loop-reset
Some checks failed
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer-perf (push) Blocked by required conditions
e2e-test / demo-scripts (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
Reset loop event after page stop.
2025-03-10 16:07:34 +01:00
Pierre Tachoire
a3045c9808 ci: run demo's puppeteer scripts 2025-03-10 15:59:46 +01:00
Pierre Tachoire
6b78b011b7 upgrade zig-jsruntime 2025-03-10 15:59:46 +01:00
Pierre Tachoire
bd7b84e136 loop: reset the loop after page end 2025-03-10 15:59:46 +01:00
Pierre Tachoire
2a9bab3f13 Merge pull request #450 from lightpanda-io/cdp-playwright
cdp: improve playwright support
2025-03-10 15:56:41 +01:00
Pierre Tachoire
6ca1e6c6dd cdp: let the inspector return the response
When a command is forwarded to the inspector, it handles directly the
reponse to the message.
2025-03-10 14:57:10 +01:00
Pierre Tachoire
f3a1a6a191 cdp: add a Page.getFrameTree unit test 2025-03-10 14:57:10 +01:00
Pierre Tachoire
675932c65b cdp: improve playwright support
The getTargetInfo result must return a `targetInfo` key.

Here is an example returned by Chrome:
```json
{
  "id": 16,
  "result": {
    "targetInfo": {
      "targetId": "d93a1bbc-f906-4bbb-bb4d-a2285234b091",
      "type": "browser",
      "title": "",
      "url": "",
      "attached": true,
      "canAccessOpener": false
    }
  }
}
```
2025-03-10 14:57:05 +01:00
Pierre Tachoire
708abb0e30 Merge pull request #459 from lightpanda-io/browser_context
Make CDP server more authoritative with respect to IDs
2025-03-10 14:49:53 +01:00
Karl Seguin
9de84aee2e Don't send CDP result when message is forward to inspector.
Rely on inspector to send the result, otherwise we'll send 2 responses to the
same message (one ourselves and one from the inspector), which Playwright does
not like.
2025-03-10 14:34:32 +01:00
Karl Seguin
adb8779d00 allow Target.getTargetInfo to be called without parameters 2025-03-10 14:34:32 +01:00
Karl Seguin
fbb0e675f5 send attach events before result 2025-03-10 14:34:32 +01:00
Karl Seguin
a3e2b5246e Make CDP server more authoritative with respect to IDs
The TL;DR is that this commit enforces the use of correct IDs, introduces a
BrowserContext, and adds some CDP tests.

These are the ids we need to be aware of when talking about CDP:
- id
- browserContextId
- targetId
- sessionId
- loaderId
- frameId

The `id` is the only one that _should_ originate from the driver. It's attached
to most messages and it's how we maintain a request -> response flow: when
the server responds to a specific message, it echo's back the id from the
requested message. (As opposed to out-of-band events sent from the server which
won't have an `id`). When I say "id" from this point forward, I mean every id
except for this req->res id.

Every other id is created by the browser.

Prior to this commit, we didn't really check incoming ids from the driver. If
the driver said "attachToTarget" and included a targetId, we just assumed that
this was the current targetId. This was aided by the fact that we only used
hard-coded IDS. If _we_ only "create" a frameId of "FRAME-1", then it's tempting
to think the driver will only ever send a frameId of "FRAME-1".

The issue with this approach is that _if_ the browser and driver fall out of sync
and there's only ever 1 browserContextId, 1 sessionId and 1 frameId, it's not
impossible to imagine cases where we behave on the thing.

Imagine this flow:
- Driver asks for a new BrowserContext
- Browser says OK, your browserContextId is 1
- Driver, for whatever reason, says close browserContextId 2
- Browser says, OK, but it doesn't check the id and just closes the only
  BrowserContext it knows about (which is 1)

By both re-using the same hard-coded ids, and not verifying that the ids sent
from the client correspond to the correct ids, any issues are going to be hard
to debug.

Currently LOADER_ID and FRAEM_ID are still hard-coded. Baby steps.
2025-03-10 14:34:32 +01:00
Pierre Tachoire
ccacac0597 Merge pull request #458 from karlseguin/serialized_writes
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Serialize socket writes + consider client pending completions when sh…
2025-03-10 10:24:57 +01:00
Karl Seguin
ca230aa230 Serialize socket writes + consider client pending completions when shutting down
Previously, we could have multiple in-flight messages from the server to a
single client. This isn't safe and can lead to message interleaving. While
write / send are atomic, they are only atomic for the N bytes which they write,
which may not be the entire buffer. Consider this writeAll function:

```
pub fn writeAll(socket: socket_t, bytes: []const u8) !void {
    var index: usize = 0;
    while (index < bytes.len) {
        index += try posix.write(socket, bytes[index..]);
    }
}
```

If we're trying to send "abc123", this could take anywhere from 1 to 6 calls
to posix.write (it would take 6 calls, for example, if every call to
posix.write only wrote a single byte). Now if you're trying to write other data
to this same socket at the same time, messages _will_ get interleaved.

In order for this to work, the client now has a send_queue (doubly linked list).
When one message is sent, it sends the next.

In addition to the above change, the Client is now self-contained with respect
to its lifetime. This is necessary so that completions which come in AFTER our
concept of its lifetime ends, can still be processed. I think all types that
receive completions need to follow this model. This relies on the fact that
kqueue (which I know for a fact) and io_uring (which people seem to imply) handle
socket shutdown properly. It's still a bit messy because of timeout and not
wanting to wait until timeout to accept new connections, but needing to wait
until timeout to cleanup the client.

The self-contained nature of Client makes it difficult to test as a generic. I
removed Client(T). Tests now use real sockets. Some tests had to be removed
because they're too difficult to test over a real connection :(
2025-03-07 20:29:57 +08:00
Pierre Tachoire
7b775d2ad7 Merge pull request #452 from lightpanda-io/katie-lpd-patch-1
Update README.md
2025-03-04 17:16:34 +01:00
Pierre Tachoire
c5397bfbe2 Merge pull request #448 from karlseguin/set_cookie
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add Set-Cookie parsing
2025-03-04 13:20:33 +01:00
Karl Seguin
9fec6ebc66 fix typo, improve comment, add 1 test case 2025-03-04 19:46:36 +08:00
Pierre Tachoire
6bc38c5782 Merge pull request #455 from lightpanda-io/upgrade-zig-azync-io
upgrade vendor/zig-async-io
2025-03-04 11:37:30 +01:00
Pierre Tachoire
7f9d585d7f upgrade vendor/zig-async-io 2025-03-04 11:29:17 +01:00
Pierre Tachoire
0b14d36c95 Merge pull request #454 from lightpanda-io/upgrade-zig-jsruntime
upgrade vendor/zig-js-runtime
2025-03-04 11:07:26 +01:00
Pierre Tachoire
e22ca2d082 upgrade vendor/zig-js-runtime 2025-03-03 15:37:43 +01:00
katie-lpd
52a70cb7f5 Update README.md
A really important visual change in the readme :)
2025-03-01 19:43:28 +01:00
Karl Seguin
a00d1d068a Cookie with SameSite=None is only valid when Secure 2025-02-27 16:47:39 +08:00
Pierre Tachoire
6ae4ed9fc3 Merge pull request #449 from karlseguin/longer_timeout
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
allow longer timeouts (u8 -> u16)
2025-02-27 09:11:25 +01:00
Karl Seguin
6f5028612a add cookie jar 2025-02-27 16:09:10 +08:00
Karl Seguin
c31c12d31a add test for Storage shed, use map.getOrPut 2025-02-27 11:57:46 +08:00
Karl Seguin
28008d835e allow longer timeouts (u8 -> u16) 2025-02-27 11:00:37 +08:00
Pierre Tachoire
08e99a32cb Merge pull request #445 from karlseguin/capture_git_commit
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Make the the short git SHA available within the program
2025-02-26 14:10:24 +01:00
Karl Seguin
68fc87bc01 Add Set-Cookie parsing 2025-02-26 21:00:43 +08:00
Karl Seguin
d0ba06c44b Add git_commit to build and build-dev target
Add "version" command to cli.
2025-02-26 20:44:44 +08:00
Karl Seguin
d501cbf765 Make the the short git SHA available within the program 2025-02-26 20:44:44 +08:00
Pierre Tachoire
488c7e6c27 Merge pull request #447 from lightpanda-io/mem-regression
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
ci: increase the max memory value to detect regression
2025-02-26 11:38:54 +01:00
Pierre Tachoire
155559c2c4 ci: increase the max memory value to detect regression 2025-02-26 10:55:19 +01:00
Pierre Tachoire
a22e1bc5e5 Merge pull request #442 from karlseguin/cli_commands
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Add explicit commands to binary
2025-02-25 09:17:45 +01:00
Karl Seguin
9519d3f7ce use an arena for the args 2025-02-22 20:25:01 +08:00
Pierre Tachoire
3f23e07c02 Merge pull request #443 from karlseguin/logging
Some checks failed
e2e-test / zig build release (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
e2e-test / puppeteer (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Add a structured logger
2025-02-22 12:28:12 +01:00
Pierre Tachoire
6c75177edc Merge pull request #444 from karlseguin/id
Add an id generator
2025-02-22 12:25:54 +01:00
Karl Seguin
85df280447 When explicit mode (serve/fetch/help) isn't given, infer it from the options 2025-02-22 13:54:05 +08:00
Karl Seguin
734cf243f6 update workflow to launch lightpanda in serve mode 2025-02-22 12:40:47 +08:00
Karl Seguin
d8f7817eeb Add explicit commands to binary
./lightpanda serve --host ...
./lightpanda fetch https://...

Makes it easier to communicate / document which command has which options.

Internally added a "usage" command for displaying the usage - removing the need
for error.NoError :|
2025-02-22 12:40:47 +08:00
Karl Seguin
94b6b2636a Add an id generator
Create UUID v4.

Create prefixed ids. To support more of the CDP protocol, we need to remove the
hard-coded IDs (session, browser context, frame, loader, ...) and be able to
dynamically create them, i.e. creating a new BrowserContextId when
Target.createBrowserContext is called.

var frame_id = id.Incremental(u16, "FRM"){};
frame_id.next() == "FRM-1"
frame_id.next() == "FRM-2"

Generation is allocation-free (the returned string is only valid until the
next call to next()). This is not thread safe, each CDP instance will have its
own generator (for each id it needs to generate).

The generated IDs are different than what Chrome uses, i.e.
BROWSERSESSIONID597D9875C664CAC0. I looked at various drivers and none have
any expectations beyond a string. Shorter IDs will be more efficient. Also, the
ID can cheeply be converted to and from an integer, allowing for lookups via
AutoHashMap(u16) instead of StringHashMap.
2025-02-22 09:11:40 +08:00
Karl Seguin
1036f7580f Add a structured logger
In debug mode, it has a more user-friendly output:

level | the log messge | ms since last message | key=value key=value

In release mode, it logs using logfmt, which is supported by most log
ingestion frameworks.

Not being used anywhere right now, keeping this PR small with no impact on
existing code.
2025-02-22 09:10:40 +08:00
Pierre Tachoire
908febb363 Merge pull request #441 from karlseguin/cdp_tests
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Turn CDP into a generic so that mocks can be injected for testing
2025-02-21 17:49:47 +01:00
Pierre Tachoire
aefd091b44 Merge pull request #440 from karlseguin/managed_completions
Ensure completions are executed on the currently connected client
2025-02-21 17:39:22 +01:00
Karl Seguin
99fb82e244 Turn CDP into a generic so that mocks can be injected for testing
ADD CDP testing helpers (mock Browser, Session, Page and Client). These are
placeholders until tests are added which use them.

Added a couple CDP tests.
2025-02-21 13:17:35 +08:00
Karl Seguin
756d6620cc Ensure completions are executed on the currently connected client
For the time being, given that we only allow 1 client at a time, I took a
shortcut to implement this. The server has an incrementing "current_client_id"
which is part of every completion. On completion callback, we just check if
its client_id is still equal to the server's current_client_id.
2025-02-21 09:35:51 +08:00
Pierre Tachoire
09505dba09 Merge pull request #436 from lightpanda-io/ci-unittest
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
ci: add unittest execution
2025-02-20 17:45:41 +01:00
Pierre Tachoire
9401eff297 ci: add unittest execution 2025-02-20 17:10:10 +01:00
Pierre Tachoire
adbec3d272 Merge pull request #439 from karlseguin/dont_share_timeout_completion
Don't share or reuse timeout_completion
2025-02-20 17:09:44 +01:00
Karl Seguin
e301ba0cdb Don't share or reuse timeout_completion
Results in undefined behavior when a client disconnects and another reconnects
while the timeout is being monitored:

https://github.com/lightpanda-io/browser/pull/436#issuecomment-2670455216
2025-02-20 23:56:55 +08:00
Pierre Tachoire
b12eef218a Merge pull request #422 from karlseguin/cdp_struct
Refactor CDP
2025-02-20 15:26:37 +01:00
Karl Seguin
bc4560877a zig fmt 2025-02-20 22:08:56 +08:00
Karl Seguin
521a740d3a Merge branch 'main' into cdp_struct 2025-02-20 22:08:37 +08:00
Pierre Tachoire
be12b724cc Merge pull request #438 from karlseguin/xhr_state_as_enum
Use an enum for XHR's state.
2025-02-20 14:57:37 +01:00
Pierre Tachoire
073873a3e9 Merge pull request #437 from karlseguin/make_zig_path
Use $(ZIG) variable when building netsurf
2025-02-20 14:56:55 +01:00
Pierre Tachoire
fcdcb50b8b Merge pull request #426 from karlseguin/c_allocator
In release mode, switch from page_allocator to c_allocator
2025-02-20 14:37:54 +01:00
Karl Seguin
61a7848fd9 Use an enum for XHR's state. 2025-02-20 14:06:38 +08:00
Karl Seguin
6d6b840cf6 Use $(ZIG) variable when building netsurf 2025-02-20 08:42:45 +08:00
Karl Seguin
4dbba103d4 In release mode, switch from page_allocator to c_allocator 2025-02-20 08:09:53 +08:00
Pierre Tachoire
a2932f05f4 Merge pull request #435 from karlseguin/server_tests
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Fix server hang on client disconnect
2025-02-19 17:45:42 +01:00
Pierre Tachoire
5d4efb7692 Merge pull request #434 from lightpanda-io/chore/readme
Chore: update readme images
2025-02-19 16:41:15 +01:00
Karl Seguin
39a9efb73b Fix server hang on client disconnect
https://github.com/lightpanda-io/browser/issues/425

Add a few integration tests for the TCP server which are fast enough to be run
as part of the unit tests (one of the new tests covers the above issue).
2025-02-19 15:01:12 +08:00
Nicolas Rigaudiere
5037bd07d5 chore: update readme images 2025-02-18 15:43:49 +01:00
Pierre Tachoire
73a2fa3f9c Merge pull request #428 from lightpanda-io/ci-rgression
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
ci: add puppeteer regression test
2025-02-18 15:07:17 +01:00
Pierre Tachoire
79387f469b Merge pull request #433 from lightpanda-io/adjust-readme
readme: adjust image width
2025-02-18 13:57:51 +01:00
Pierre Tachoire
f986cfecff readme: adjust image width 2025-02-18 13:51:10 +01:00
Pierre Tachoire
4d51a9123b Merge pull request #432 from lightpanda-io/adjust-readme
readme: move status up
2025-02-18 13:43:24 +01:00
Pierre Tachoire
7602f15544 readme: move status up 2025-02-18 13:41:45 +01:00
Pierre Tachoire
3180ba7de9 Merge pull request #431 from lightpanda-io/adjust-readme
readme: update benchmark image
2025-02-18 11:55:56 +01:00
Pierre Tachoire
3e01cf19b0 readme: add benchmark details 2025-02-18 11:55:21 +01:00
Pierre Tachoire
14eebfe39e readme: update benchmark image 2025-02-18 11:55:21 +01:00
Pierre Tachoire
9176599b29 Merge pull request #430 from lightpanda-io/adjust-readme
readme: fix badges
2025-02-18 11:37:52 +01:00
Pierre Tachoire
d6575faa9f readme: fix badges 2025-02-18 11:37:08 +01:00
Pierre Tachoire
24c5bf9ff4 Merge pull request #429 from lightpanda-io/adjust-readme
readme: update download instructions + improve CDP example
2025-02-18 11:35:22 +01:00
Pierre Tachoire
cdcc5e106f readme: use curl to download binary 2025-02-18 11:32:53 +01:00
Pierre Tachoire
1a8cc2d019 readme: adjust text 2025-02-18 11:32:30 +01:00
Pierre Tachoire
27e907491b readme: remove text duplication 2025-02-18 11:25:04 +01:00
Pierre Tachoire
0a1e6623c8 readme: allow examples copy/paste 2025-02-18 11:20:56 +01:00
Pierre Tachoire
689dddd11a readme: allow copy/paste install instruction 2025-02-18 11:19:02 +01:00
Pierre Tachoire
f8d01e1596 readme: update exemple t odump links 2025-02-18 11:15:06 +01:00
Pierre Tachoire
cd429f5935 readme: fix binary name 2025-02-18 11:06:51 +01:00
Pierre Tachoire
03355f6a4a readme: remove useless badges 2025-02-18 11:01:52 +01:00
Pierre Tachoire
dc1d593019 ci: adjust memory regression max values 2025-02-18 10:57:36 +01:00
Pierre Tachoire
9894cceeaa ci: extract end-to-end test on its own file 2025-02-18 10:52:08 +01:00
Pierre Tachoire
bcedbc845e ci: add puppeteer regression test 2025-02-17 16:39:15 +01:00
Karl Seguin
f508288ce3 Fix segfault when multiple inflight Send completions fail 2025-02-17 18:43:41 +08:00
Karl Seguin
18080cef9f fix test 2025-02-17 12:14:11 +08:00
Karl Seguin
c4eeef2a86 On CDP process error, let client decide how to close
Fixes issue where CDP closes the client, but client still registers a recv
operation.
2025-02-17 12:05:25 +08:00
Karl Seguin
b60a91f53c fix memory leak 2025-02-17 11:45:19 +08:00
Karl Seguin
b1c3de6518 zig fmt 2025-02-13 17:32:01 +08:00
Karl Seguin
a43a6a299c Merge branch 'main' into cdp_struct 2025-02-13 17:30:15 +08:00
Pierre Tachoire
d8fae5bc41 Merge pull request #408 from karlseguin/websocket_server
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
Make TCP server websocket-aware
2025-02-13 09:04:23 +01:00
Karl Seguin
fa9b6f58e5 trying to fix submodule version 2025-02-13 09:42:26 +08:00
Karl Seguin
89ff1411e9 Fix memory leak on invalid websocket continuation frames 2025-02-13 09:34:25 +08:00
Karl Seguin
701e8277d6 support continuation frames 2025-02-13 08:51:21 +08:00
Karl Seguin
4a11f80c45 Make websocket client reader stateful
Move more logic into the reader. Avoid copying partial messages in
cases where we know that the buffer is large enough.

This is mostly groundwork for trying to add support for continuation
frames.
2025-02-13 08:51:21 +08:00
Karl Seguin
f1b275d5d0 Increase fuzz count. Add test for [too] large HTTP requests 2025-02-13 08:51:21 +08:00
Karl Seguin
68e0ffc95c "fix" test compilation 2025-02-13 08:51:21 +08:00
Karl Seguin
0753eb7691 zig fmt 2025-02-13 08:51:21 +08:00
Karl Seguin
92afcd174d remove websocket.zig dependency from build 2025-02-13 08:51:21 +08:00
Karl Seguin
94be7a0e79 Make TCP server websocket-aware
Adding HTTP & websocket awareness to the TCP server.

HTTP server handles `GET /json/version` and websocket upgrade requests.

Conceptually, websocket handling is the same code as before, but receiving
data will parse the websocket frames and writing data will wrap it in
a websocket frame.

The previous `Ctx` was split into a `Server` and a `Client`. This was
largely done to make it easy to write unit tests, since the `Client` is
a generic, all its dependencies (i.e. the server) can be mocked out. This
also makes it a bit nicer to know if there is or isn't a client (via the
server's client optional).

Added a MemoryPool for the Send object (I thought that was a nice touch!)

Removed MacOS hack on accept/conn completion usage.

Known issues:
- When framing an outgoing message, the entire message has to be duped. This
is no worse than how it was before, but it should be possible to eliminate
this in the future. Probably not part of this PR.

- Websocket parsing will reject continuation frames. I don't know of a single
client that will send a fragmented message (websocket has its own
message fragmentation), but we should probably still support this just in
case.

- I don't think the receive, timeout and close completions can safely be
re-used like we're doing. I believe they need to be associated with a specific
client socket.

- A new connection creates a new browser session. I think this is right (??),
but for the very first, we're throwing out a perfectly usable session. I'm
thinking this might be a change to how Browser/Sessions work.

- zig build test won't compile. This branch reproduces the issue with none
of these changes:
https://github.com/karlseguin/browser/tree/broken_test_build

(or, as a diff to main):
https://github.com/lightpanda-io/browser/compare/main...karlseguin:broken_test_build
2025-02-13 08:51:19 +08:00
Pierre Tachoire
0814daf99d Merge pull request #421 from lightpanda-io/upgrade-tigerbeetle
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
zig-test / demo-puppeteer (push) Blocked by required conditions
upgrade tigerbeetle
2025-02-12 14:46:35 +01:00
Pierre Tachoire
b2e3419bff upgrade tigerbeetle 2025-02-12 14:37:39 +01:00
Karl Seguin
1846d0bc21 drats, zig fmt again 2025-02-12 18:32:33 +08:00
Karl Seguin
d282055e10 Merge branch 'main' into cdp_struct 2025-02-12 17:56:47 +08:00
Karl Seguin
6ab64d155b Refactor CDP
CDP is now an struct which contains its own state a browser and a session.

When a client connection is made and successfully upgrades, the client creates
the CDP instance. There is now a cleaner separation betwen Server, Client and
CDP.

Removed a number of allocations, especially when writing results/events from
CDP to the client. Improved input message parsing. Tried to remove some usage
of undefined.
2025-02-12 16:47:37 +08:00
Pierre Tachoire
6ba3e57f5f Merge pull request #404 from lightpanda-io/cdp-documentUpdated
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
zig-test / demo-puppeteer (push) Blocked by required conditions
cdp: dispatch a DOM.documentUpdated event
2025-02-11 14:38:39 +01:00
Karl Seguin
14fe4f65e1 support continuation frames 2025-02-11 11:16:39 +08:00
Karl Seguin
bdb70444d6 Make websocket client reader stateful
Move more logic into the reader. Avoid copying partial messages in
cases where we know that the buffer is large enough.

This is mostly groundwork for trying to add support for continuation
frames.
2025-02-11 11:16:39 +08:00
Karl Seguin
4d9cc55a87 Increase fuzz count. Add test for [too] large HTTP requests 2025-02-11 11:16:39 +08:00
Karl Seguin
f41c1cbfd0 "fix" test compilation 2025-02-11 11:16:39 +08:00
Karl Seguin
72eaab68be zig fmt 2025-02-11 11:16:39 +08:00
Karl Seguin
733c6b4c17 remove websocket.zig dependency from build 2025-02-11 11:16:39 +08:00
Karl Seguin
c0c0694fcc Make TCP server websocket-aware
Adding HTTP & websocket awareness to the TCP server.

HTTP server handles `GET /json/version` and websocket upgrade requests.

Conceptually, websocket handling is the same code as before, but receiving
data will parse the websocket frames and writing data will wrap it in
a websocket frame.

The previous `Ctx` was split into a `Server` and a `Client`. This was
largely done to make it easy to write unit tests, since the `Client` is
a generic, all its dependencies (i.e. the server) can be mocked out. This
also makes it a bit nicer to know if there is or isn't a client (via the
server's client optional).

Added a MemoryPool for the Send object (I thought that was a nice touch!)

Removed MacOS hack on accept/conn completion usage.

Known issues:
- When framing an outgoing message, the entire message has to be duped. This
is no worse than how it was before, but it should be possible to eliminate
this in the future. Probably not part of this PR.

- Websocket parsing will reject continuation frames. I don't know of a single
client that will send a fragmented message (websocket has its own
message fragmentation), but we should probably still support this just in
case.

- I don't think the receive, timeout and close completions can safely be
re-used like we're doing. I believe they need to be associated with a specific
client socket.

- A new connection creates a new browser session. I think this is right (??),
but for the very first, we're throwing out a perfectly usable session. I'm
thinking this might be a change to how Browser/Sessions work.

- zig build test won't compile. This branch reproduces the issue with none
of these changes:
https://github.com/karlseguin/browser/tree/broken_test_build

(or, as a diff to main):
https://github.com/lightpanda-io/browser/compare/main...karlseguin:broken_test_build
2025-02-11 11:16:39 +08:00
Pierre Tachoire
055530c8c6 cdp: send dom node children 2025-02-10 12:19:35 +01:00
Pierre Tachoire
fb3b38aec7 cdp: implement getSearchResults and discardSearchResults 2025-02-10 09:31:10 +01:00
Pierre Tachoire
4e4a8f1bab cdp: implement DOM.performSearch 2025-02-10 09:31:09 +01:00
Pierre Tachoire
39b3786776 cdp: ctx state has init and deinit now 2025-02-10 09:31:09 +01:00
Pierre Tachoire
8b22313ca1 netsurf: return empty string on null for node name 2025-02-10 09:31:09 +01:00
Pierre Tachoire
402f72cfa8 cdp: adjust page deinit 2025-02-10 09:31:08 +01:00
Pierre Tachoire
e7dcb8a605 cdp: introduce current page
avoid page struct copy
2025-02-10 09:31:08 +01:00
Pierre Tachoire
8f8a1fda85 cdp: implement DOM.getDocument 2025-02-10 09:31:08 +01:00
Pierre Tachoire
26be25c3d5 cdp: dispatch a DOM.documentUpdated event 2025-02-10 09:31:04 +01:00
Pierre Tachoire
50b53b00e0 Merge pull request #414 from karlseguin/url_query
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
Improve memory and performance of url.Query
2025-02-10 09:30:37 +01:00
Karl Seguin
94531cb3d0 Improve memory and performance of url.Query
1 - Use getOrPut to avoid making 2 map lookups where possible.

2 - Use an arena allocator for Values, which makes memory management simpler.

3 - Because of #2, we no longer need to allocate key or values which don't need
    to be unescaped. The downside is that the input string has to outlive the
    query.Values (but I think this is currently always the case)

4 - Optimize unescape logic & allocations

5 - Improve test coverage
2025-02-10 16:11:44 +08:00
Pierre Tachoire
842760255b Merge pull request #413 from karlseguin/mime
Improve performance & compliance of MIME parsing
2025-02-10 08:59:00 +01:00
Pierre Tachoire
c78b582d71 Merge pull request #409 from karlseguin/unittest_build
Add a new unittest build step
2025-02-10 08:45:24 +01:00
Karl Seguin
4ab02fab1c Fix build.
zig build test can pass, but zig build run won't even compile. // TODO: fix.
2025-02-10 11:18:16 +08:00
Karl Seguin
6863f3227f Improve performance & compliance of MIME parsing
Common cases, text/html, text/xml and text/plain parse about 2x faster. Other
cases are about 30% faster.

Support quoted attributes, i.e. charset="utf-8" & valid escape sequences. This
potentially requires allocation, thus Mime.parse now takes an allocator.

Stricter validation around type/subtype based on RFC.

More tests.

Replace Mime.eql with isHTML(). Equality is complicated and was previously
incorrect (it was case sensitive, it should not be). Since we currently only
use isHTML-like behavior, built a (faster) method specifically for that.
2025-02-10 11:07:55 +08:00
Karl Seguin
d01d43eccb Remove setup/teardown functionality. YAGNI 2025-02-09 13:16:27 +08:00
Karl Seguin
2aa5f4fc82 small iterator tweak 2025-02-09 11:04:21 +08:00
Karl Seguin
3af0531111 zig fmt + add U32Iterator tests 2025-02-09 11:04:21 +08:00
Karl Seguin
6e58b98b3d Startup local HTTP server for testing
Change loader tests to use local HTTP server.

Add missing test scripts (i.e. storage) to unittest runs.
2025-02-09 11:04:21 +08:00
Karl Seguin
62805cdf1d add license to file 2025-02-09 11:04:21 +08:00
Karl Seguin
4229b1d2a4 Report memory leaks when using std.testing.allocator
Fix leaks in storage bottle
2025-02-09 11:04:21 +08:00
Karl Seguin
2c4661a250 Add a new unittest build step
Preserves all existing behavior (i.e. make test and zig build test are not
changed in any way).

The new 'unittest' only runs unit tests and is fast to build. It takes ~1.7 to
build unittest, vs ~11.09 to build test. This is really the main goal, and
hopefully any unit test which are (a) fast and (b) don't impact build times
will be run here.

The test runner is based on:
https://gist.github.com/karlseguin/c6bea5b35e4e8d26af6f81c22cb5d76b

It allow filtering, i.e. `make unittest F="parse query dup"`.

'unittest' does memory leak detection when tests use std.testing.allocator.

Fixed a memory leak in url/query which was detected/reported with by the new
'unittest'.

In order to avoid having 3 src/test_xyx.zig files, I merged the existing
test_runner.zig and run_tests.zig into a single main_tests.zig. (this change
is superfluous, but I thought it was cleaner this way. Happy to revert this).
2025-02-09 11:04:21 +08:00
Pierre Tachoire
0c1a486ed9 Merge pull request #411 from karlseguin/reader_tweak
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
Minor Reader tweaks
2025-02-08 11:33:49 +01:00
Karl Seguin
688cb55c2b Minor Reader tweaks
1- Remove `parser.trim`, it was only being used in 1 place. All other places
   are using `std.mem.trim(u8, X, &std.ascii.whitespace)`, so i updated MIME to
   use this as well

2- Use slightly more meaningful field name, i => pos, s = data

3- Leverage std.mem.indexOfScalarPos which can be more efficient for longer
   inputs (since it leverages SIMD)
2025-02-08 15:57:32 +08:00
Francis Bouvier
1594f148f8 Merge pull request #399 from karlseguin/generate
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
Tweak generate.Tuple and generate.Union
2025-02-06 08:53:52 +01:00
Karl Seguin
fafd8c4af1 Improve format and re-add docstrings
Implements RP suggestions.
2025-02-06 09:57:04 +08:00
Karl Seguin
3d66758507 zig fmt 2025-02-01 15:38:08 +08:00
Karl Seguin
fc0ec860b0 Tweak generate.Tuple and generate.Union
Leverage comptime fields to give generated Tuple a default value, allowing
TupleT and Tuple to be merged.

Only call generate.Tuple on the final output. This eliminates redundant
deduplication, and results in a simpler API (nested types just need to expose
a natural Zig tuple).

generate.Union leverages the new Tuple and removes unused features.
2025-02-01 14:53:00 +08:00
Pierre Tachoire
00d332cd16 Merge pull request #396 from karlseguin/xmlserializer
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
Add HTML encoding to text node and HTML attribute values
2025-01-31 12:22:52 +01:00
Pierre Tachoire
4c8c0f8738 Merge pull request #394 from lightpanda-io/xmlserializer
implement XMLSerializer
2025-01-31 09:09:47 +01:00
Karl Seguin
54978132bb Add HTML encoding to text node and HTML attribute values 2025-01-31 16:01:32 +08:00
Pierre Tachoire
018abe0188 dom: implement outerHTML 2025-01-30 16:09:47 +01:00
Pierre Tachoire
b186497fb0 implement XMLSerializer 2025-01-30 16:09:47 +01:00
Pierre Tachoire
27f9963ccb Merge pull request #391 from lightpanda-io/cdp-ctx-sessionid
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
zig-test / demo-puppeteer (push) Blocked by required conditions
cdp: use an enum for SessionID
2025-01-30 14:02:47 +01:00
Pierre Tachoire
a4e3f03bf5 Merge pull request #393 from karlseguin/submodules_over_https
Use https:// instead of git@ for submodules
2025-01-30 14:01:43 +01:00
Pierre Tachoire
27a6be4ce0 Merge pull request #392 from karlseguin/readme_typo
Fix small install typo in readme
2025-01-30 08:59:39 +01:00
Karl Seguin
76a2520e56 Use https:// instead of git@ for submodules
Allows easily building the project, following the steps in readme, without
a github account or having some special git configuration.
2025-01-30 11:56:37 +08:00
Karl Seguin
0a472681af Fix small install typo in readme 2025-01-30 11:50:12 +08:00
Pierre Tachoire
6d530691f3 cdp: use an enum for SessionID 2025-01-29 18:38:05 +01:00
Pierre Tachoire
a74c9e8481 Merge pull request #389 from lightpanda-io/cdp-empty-params
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
chromedp: msg missing params or result
2025-01-28 17:11:03 +01:00
Pierre Tachoire
8aac26a331 cdp: check parameter's type on sendEvent
Disallow void type.
2025-01-28 16:01:47 +01:00
Pierre Tachoire
fc59a0f6ab cdp: send empty param instead of void
Sending void parameters generated unmarshal errors with chromedp client.
Empty struct is required.
2025-01-28 15:46:13 +01:00
Pierre Tachoire
3fb16774b7 Merge pull request #356 from lightpanda-io/location
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
dom: first draft for location
2025-01-27 13:16:05 +01:00
Pierre Tachoire
7b35bb4c0f dom: improve location impl 2025-01-27 12:33:06 +01:00
Pierre Tachoire
318e2bd1c6 dom: expose document.location 2025-01-27 12:33:05 +01:00
Pierre Tachoire
09ba4bcf43 upgrade libdom 2025-01-27 12:33:04 +01:00
Pierre Tachoire
0c89fa7b1e Merge pull request #383 from lightpanda-io/katie-lpd-patch-1
Update README.md
2025-01-27 10:26:42 +01:00
katie-lpd
7eedb3320d Update README.md 2025-01-27 09:59:30 +01:00
Pierre Tachoire
cfac75ea49 Merge pull request #380 from eltociear/patch-1
docs: update README.md
2025-01-26 20:59:27 +01:00
Ikko Eltociear Ashimine
f00a6c396f docs: update README.md
dependancies -> dependencies
dependancy -> dependency
2025-01-27 03:20:00 +09:00
Pierre Tachoire
e74a9711ca Merge pull request #378 from spidy0x0/patch-1
fix typo
2025-01-25 10:34:41 +01:00
Pierre Tachoire
636d3cdf90 Merge pull request #377 from arilotter/patch-1
fix typo in readme
2025-01-25 10:34:16 +01:00
spidy0x0
71966affa1 fix typo 2025-01-24 19:47:12 +00:00
Ari Lotter
bf4dc195ec fix typo in readme 2025-01-24 12:53:19 -05:00
Pierre Tachoire
dccca17e09 Merge pull request #376 from lightpanda-io/katie-lpd-patch-1
Update README.md
2025-01-24 12:03:09 +01:00
Pierre Tachoire
5381a4354c add badges 2025-01-24 12:02:42 +01:00
katie-lpd
c70425fbf7 Update README.md 2025-01-24 11:53:05 +01:00
Pierre Tachoire
341f5725a4 netsurf: implement location setter/getter 2025-01-23 15:22:03 +01:00
Pierre Tachoire
d7069df80d dom: first draft for location 2025-01-23 11:51:05 +01:00
Pierre Tachoire
579714a60b Merge pull request #374 from lightpanda-io/reuseport-unixsocket
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
server: REUSEPORT is not allowed on unix socket
2025-01-22 16:23:58 +01:00
Pierre Tachoire
bbdf63635a server: REUSEPORT is not allowed on unix socket
see 5b0af621c3
2025-01-22 16:08:22 +01:00
Pierre Tachoire
fd7db18221 ci: split wpt and wpt-json jobs 2025-01-22 13:59:14 +01:00
Pierre Tachoire
482ed8d958 Merge pull request #370 from lightpanda-io/kernel-version
upgrade vendor/zig-js-runtime
2025-01-22 13:58:22 +01:00
Pierre Tachoire
673e16878d ci: run tests on vendor changes 2025-01-22 13:48:52 +01:00
Pierre Tachoire
e11ceab029 upgrade vendor/zig-js-runtime 2025-01-22 13:46:54 +01:00
Pierre Tachoire
7fe719f43c Merge pull request #361 from lightpanda-io/docker-updage-zig-v8
docker: update zig v8
2025-01-17 12:36:53 +01:00
Pierre Tachoire
3fd3ac1de1 docker: update zig v8 2025-01-17 12:30:28 +01:00
Pierre Tachoire
0e90a675af Merge pull request #357 from katie-lpd/patch-2
Update README.md
2025-01-16 14:09:53 +01:00
katie-lpd
ee861c1f91 Update README.md 2025-01-16 14:06:00 +01:00
Pierre Tachoire
40c9355088 Merge pull request #355 from lightpanda-io/history
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
dom: history placeholder
2025-01-15 11:55:50 +01:00
Pierre Tachoire
8f1557254a typo fix 2025-01-15 11:45:30 +01:00
Pierre Tachoire
11d28b0bc3 dom: add placeholder for history interface 2025-01-15 11:45:30 +01:00
Pierre Tachoire
974cf780c0 dom: clean history file 2025-01-14 15:04:40 +01:00
Pierre Tachoire
73bb14e4a9 Merge pull request #285 from lightpanda-io/cdp-cdpcli
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
cdp: cdpcli compatibility
2025-01-13 18:16:40 +01:00
Pierre Tachoire
daf4236023 runtime: fix sessionid 2025-01-13 18:08:09 +01:00
Pierre Tachoire
4c9a24c64e start inspector when the js env starts 2025-01-13 10:53:38 +01:00
Pierre Tachoire
c149f65158 cdp: remove event dispateched by inspector 2025-01-13 10:53:36 +01:00
Pierre Tachoire
c5688c1bd3 cdp: display last message on cdp error 2025-01-13 10:53:35 +01:00
Pierre Tachoire
b276a15786 cdp: add target.detachFromTarget noop 2025-01-13 10:53:33 +01:00
Pierre Tachoire
2fed239ece browser: split page start from page navigate 2025-01-13 10:53:29 +01:00
Pierre Tachoire
8e2cb36597 cdp: fix some id inconsitency accross runtime messages 2025-01-13 10:49:48 +01:00
Pierre Tachoire
bcaace1c91 cdp: use identifiable hard coded ids 2025-01-13 10:47:51 +01:00
Pierre Tachoire
d664d07141 cdp: dispatch executionContextCreated on Runtime.enable 2025-01-13 10:47:42 +01:00
Pierre Tachoire
cb8b80c856 Merge pull request #345 from lightpanda-io/modules
browser: support for modules
2025-01-13 10:45:31 +01:00
Pierre Tachoire
d777d77b06 ci: update sig-v8 version 2025-01-13 10:36:52 +01:00
Pierre Tachoire
43678f8dc0 upgrade zig-js-runtime 2025-01-13 10:36:34 +01:00
Pierre Tachoire
5811577824 Merge pull request #354 from lightpanda-io/navigator-fix
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
zig-test / demo-puppeteer (push) Blocked by required conditions
navigator: remove useless import
2025-01-13 09:37:14 +01:00
Pierre Tachoire
1587122efa navigator: remove useless import 2025-01-10 17:42:20 +01:00
Pierre Tachoire
48e7c8ad0f browser: implement fetch module 2025-01-10 16:48:45 +01:00
Pierre Tachoire
766f9798f6 browser: load module 2025-01-09 11:48:39 +01:00
Pierre Tachoire
680d634725 update zig-js-runtime 2025-01-09 11:48:39 +01:00
Pierre Tachoire
7ac945bf88 browser: refacto script 2025-01-09 11:48:34 +01:00
Pierre Tachoire
188d7a8558 Merge pull request #352 from utay/fix-shell-main
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
Fix shell main
2025-01-08 17:15:18 +01:00
Pierre Tachoire
ee561b0d1e Merge pull request #353 from lightpanda-io/cdp-enable-security
Cdp enable security
2025-01-08 16:55:31 +01:00
Pierre Tachoire
82bbe78e95 cdp: return correct result on Runtime 2025-01-08 16:46:05 +01:00
Pierre Tachoire
c761cd059b cdp: log errors on sendMessageToTarget 2025-01-08 16:17:29 +01:00
Pierre Tachoire
03e87155ca cdp: add security.enable 2025-01-08 16:17:20 +01:00
Yannick Utard
ea39cc52b1 Fix shell main 2025-01-08 15:28:19 +01:00
Pierre Tachoire
90fb90b186 Merge pull request #351 from lightpanda-io/ignore-blank
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
zig-test / demo-puppeteer (push) Blocked by required conditions
browser: ignore about:blank navigation
2025-01-08 13:54:00 +01:00
Pierre Tachoire
5f8327eaf7 browser: ignore about:blank navigation 2025-01-08 13:44:41 +01:00
Pierre Tachoire
07869f3c48 Merge pull request #350 from lightpanda-io/cdp-enable
Cdp enable
2025-01-08 13:42:42 +01:00
Pierre Tachoire
8377eb02a5 cdp: add CSS.enable 2025-01-08 12:01:18 +01:00
Pierre Tachoire
3738e8eb44 cdp: add DOM.enable 2025-01-08 12:01:18 +01:00
Pierre Tachoire
4b000e44b3 cdp: add Inspector.enable 2025-01-08 12:01:18 +01:00
Pierre Tachoire
d6021d1702 Merge pull request #348 from lightpanda-io/dom-navigator
Dom navigator
2025-01-08 11:47:44 +01:00
Pierre Tachoire
b391eafc38 wpt: update tests 2025-01-08 11:38:11 +01:00
Pierre Tachoire
2af4545e10 dom: implement more navigator API 2025-01-08 11:03:26 +01:00
Pierre Tachoire
b96644d893 dom: implement navigatorLanguage 2025-01-08 11:03:26 +01:00
Pierre Tachoire
3cb77c0a32 dom: implement navigatorID 2025-01-08 11:03:25 +01:00
Pierre Tachoire
b3f7fb7be3 dom: implement navigator.userAgent 2025-01-08 11:03:11 +01:00
Pierre Tachoire
9fb51a1f29 Merge pull request #346 from lightpanda-io/target-created
cdp: add TargetCreated event on createTarget message
2025-01-08 10:10:18 +01:00
Pierre Tachoire
d78e8a725d cdp: remove useless parameter 2025-01-08 09:57:28 +01:00
Pierre Tachoire
7829bdbc95 Merge pull request #347 from lightpanda-io/send-message-to-target
cdp: add Target.sendMessageToTarget support
2025-01-07 16:21:41 +01:00
Pierre Tachoire
90ba6deba2 cdp: add Target.sendMessageToTarget support
see https://chromedevtools.github.io/devtools-protocol/tot/Target/#method-sendMessageToTarget
2025-01-07 16:00:44 +01:00
Pierre Tachoire
5fc763a738 cdp: add TargetCreated event on createTarget message 2025-01-07 10:34:47 +01:00
Pierre Tachoire
84614e903c Merge pull request #344 from lightpanda-io/test-e2e
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
zig-test / demo-puppeteer (push) Has been cancelled
Add end to end tests
2025-01-06 10:13:51 +01:00
Pierre Tachoire
c95d739347 ci: add test end to end
using puppeteer test from https://github.com/lightpanda-io/demo
2024-12-31 12:01:27 +01:00
Pierre Tachoire
c55849cef5 Merge pull request #343 from lightpanda-io/upgrade-zig-js
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
upgrade zig-js-runtime deps
2024-12-31 10:37:22 +01:00
Pierre Tachoire
88adb09417 upgrade zig-js-runtime deps 2024-12-31 10:09:48 +01:00
Pierre Tachoire
cb356ffca9 Merge pull request #341 from lightpanda-io/docker
fix docker port
2024-12-26 10:02:54 +01:00
Pierre Tachoire
2ba3f252ee fix docker port 2024-12-26 10:00:59 +01:00
Pierre Tachoire
ebe2c8e3dd Merge pull request #332 from lightpanda-io/verbose-option
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
add --verbose option
2024-12-17 09:41:58 +01:00
Francis Bouvier
ca2d63edcf Merge pull request #330 from lightpanda-io/contributing
add a CONTRIBUTING file
2024-12-16 15:54:17 +01:00
Francis Bouvier
489a1a1c37 Merge pull request #338 from lightpanda-io/README
README: be more precise on current status
2024-12-16 13:43:45 +01:00
Francis Bouvier
5cad13eea3 README: be more precise on current status
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-16 13:35:24 +01:00
Pierre Tachoire
a3e09e015c Merge pull request #329 from lightpanda-io/readme-getting-started
readme: add a quick start section
2024-12-16 09:34:10 +01:00
Pierre Tachoire
22a6accac1 readme: add a quick start section 2024-12-13 17:45:30 +01:00
Pierre Tachoire
277c97a959 Merge pull request #331 from lightpanda-io/continue-on-error
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
browser: don't stop processing page on error status code
2024-12-13 17:41:34 +01:00
Pierre Tachoire
c08bc3239a Merge pull request #334 from lightpanda-io/patch-1
Update README.md
2024-12-13 15:05:34 +01:00
katie-lpd
f798c7e0fb Update README.md 2024-12-13 14:45:02 +01:00
Pierre Tachoire
193780a88f cla: add katie-lpd to the allow list
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
2024-12-13 14:33:57 +01:00
Pierre Tachoire
ab4973ab6c add --verbose option 2024-12-13 12:29:05 +01:00
Pierre Tachoire
f9da815e8f browser: don't stop processing page on error status code 2024-12-13 11:56:18 +01:00
Pierre Tachoire
59e187d59a add a CONTRIBUTING file 2024-12-13 11:28:14 +01:00
Francis Bouvier
3b018e2a6d Merge pull request #325 from lightpanda-io/await_promise
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
cdp: runtime, replace `"awaitPromise":true` only if present
2024-12-09 11:19:45 +01:00
Francis Bouvier
d4939c8260 Merge pull request #322 from lightpanda-io/cdp_msg_nullable_params
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
cdp: handle nullable Type for params
2024-12-08 19:08:27 +01:00
Francis Bouvier
485352594c Merge pull request #324 from lightpanda-io/json_parse_unknown_field
cdp: do not throw error on json parse for unknown fields
2024-12-08 19:05:11 +01:00
Francis Bouvier
bcdcfee467 Merge pull request #323 from lightpanda-io/cdp_msg_size
Cdp msg size
2024-12-08 18:58:14 +01:00
Francis Bouvier
0217e3fcae cdp: runtime, replace "awaitPromise":true only if present
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-08 15:33:32 +01:00
Francis Bouvier
6e7d6421d5 cdp: do not throw error on json parse for unknown fields
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-08 15:27:12 +01:00
Francis Bouvier
913d3af938 cdp: increase msg size 16KB -> 256KB
And move header size encoding from 2 bytes -> 2 bytes

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-04 23:06:55 +01:00
Francis Bouvier
53dd0a5e4c cdp: handle nullable Type for params
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-04 22:39:54 +01:00
Francis Bouvier
4b8c3cb188 Merge pull request #321 from lightpanda-io/README
Some checks failed
wpt / web platform tests (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig build release (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
README: more updates
2024-12-04 17:16:01 +01:00
Francis Bouvier
c9ca170d57 README: more updates
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-04 16:18:15 +01:00
Francis Bouvier
c6090dbc16 Update README
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-04 16:03:01 +01:00
Francis Bouvier
c787b6a1a5 Merge pull request #312 from lightpanda-io/fetch-polyfill
Fetch polyfill
2024-12-04 15:57:45 +01:00
Francis Bouvier
766aa0f60a Merge pull request #320 from lightpanda-io/http_json_version
websockets: add addr server info in Stream
2024-12-04 15:56:06 +01:00
Pierre Tachoire
fbe8835626 polyfill: use @embedfile to embed polyfill 2024-12-04 14:26:51 +01:00
Pierre Tachoire
4e2b35b585 tests: remove useless test file 2024-12-04 14:26:21 +01:00
Francis Bouvier
8ef79e348c websockets: add addr server info in Stream
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-12-04 12:07:52 +01:00
Pierre Tachoire
47eef392d1 add missing license header 2024-12-03 14:34:04 +01:00
Francis Bouvier
b846541ff6 Merge pull request #311 from lightpanda-io/msg_size_encode
Some checks are pending
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig build release (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Msg size encode
2024-12-03 14:33:33 +01:00
Pierre Tachoire
adfffd2b08 polyfill: fetch: disable Arraybuffer usage 2024-12-02 18:00:32 +01:00
Pierre Tachoire
4138c6fe95 polyfill: first draft to polyfill fetch api 2024-12-02 18:00:32 +01:00
Francis Bouvier
3088c7a632 Merge pull request #318 from lightpanda-io/README
Update README
2024-11-29 18:41:10 +01:00
Francis Bouvier
21e7638ae1 Update README
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-29 18:40:26 +01:00
Francis Bouvier
ca0d90dbcf Merge pull request #310 from lightpanda-io/websockets
websocket: first implementation
2024-11-29 18:22:05 +01:00
Pierre Tachoire
8870045b0f Merge pull request #317 from lightpanda-io/upgrade-zig-async-io
upgrade zig-async-io
2024-11-29 15:22:13 +01:00
Pierre Tachoire
cbc8b2edf9 upgrade zig-async-io 2024-11-29 15:12:13 +01:00
Francis Bouvier
8f297b83c1 msg: rename MsgBuffer -> Buffer
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-29 15:07:52 +01:00
Francis Bouvier
b800d0eeb8 msg: fix len for msg.Buffer and encode msg size as binary header
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-29 15:07:52 +01:00
Francis Bouvier
d95462073a websockets: fix port default in help
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-29 15:06:22 +01:00
Francis Bouvier
95ac92b343 server: fix cancel
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-29 14:59:54 +01:00
Pierre Tachoire
4cfb317af6 Merge pull request #316 from lightpanda-io/innerText
dom: implement innerText
2024-11-28 17:55:14 +01:00
Pierre Tachoire
487aaffa94 dom: implement innerText 2024-11-28 17:47:41 +01:00
Pierre Tachoire
24fd7c7286 Merge pull request #314 from lightpanda-io/fix-xhr
xhr: fix invalid response with empty type
2024-11-28 16:34:30 +01:00
Pierre Tachoire
50e62d44ff xhr: fix invalid response with empty type 2024-11-28 16:29:09 +01:00
Francis Bouvier
760c082757 cli: wording mode -> opts
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-27 21:24:09 +01:00
Francis Bouvier
8449d5ab22 websocket: use Unix socket for internal server
And add an option for TCP only server

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-27 21:24:09 +01:00
Francis Bouvier
27b50c46c3 Update websokets dep
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-27 21:24:09 +01:00
Francis Bouvier
325ecedf0b websocket: first implementation
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-27 21:24:09 +01:00
Francis Bouvier
8f7a8c0ee1 Merge pull request #309 from lightpanda-io/update_deps
update zig-js-runtime
2024-11-26 12:59:19 +01:00
Francis Bouvier
7dbb55da06 update zig-js-runtime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-26 12:58:45 +01:00
Pierre Tachoire
4b90b70534 Merge pull request #308 from lightpanda-io/licensing
add licensing file to be more explicit w/ licenses
2024-11-26 12:52:16 +01:00
Pierre Tachoire
d6f1843ef3 add licensing file to be more explicit w/ licenses 2024-11-26 12:30:50 +01:00
Pierre Tachoire
5cf176927a Merge pull request #303 from lcoffe-botify/fix-makefile-download-zig
fix Makefile download-zig: url changed
2024-11-26 11:03:31 +01:00
Pierre Tachoire
eae21a034e Merge pull request #306 from lightpanda-io/cla
contrib: add CLA signature process
2024-11-26 10:31:17 +01:00
Pierre Tachoire
06855cdfa3 Merge pull request #307 from lightpanda-io/clean-ci
ci: remove useless token
2024-11-26 09:52:05 +01:00
Pierre Tachoire
45c7af9769 ci: remove useless token
The repos are public now, we don't need the token anymore to fetch.
2024-11-26 09:37:40 +01:00
Pierre Tachoire
f9ddd5c368 contrib: add CLA signature process 2024-11-26 09:35:14 +01:00
Pierre Tachoire
6bf4dc887e Merge pull request #305 from lightpanda-io/docker-ca-cert
docker: keep ca certs
2024-11-25 11:51:10 +01:00
Pierre Tachoire
4cc31bb41f docker: keep ca certs 2024-11-25 09:03:52 +01:00
Lucien Coffe
88e32505a3 fix Makefile download-zig: url changed 2024-11-22 18:50:11 +01:00
Francis Bouvier
5b5d28f7c1 Merge pull request #302 from lightpanda-io/zig-async-io
Use zig-async-io for xhr requests
2024-11-21 16:52:36 +01:00
Francis Bouvier
1a2fd9a584 Update dependencies
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-21 16:46:09 +01:00
Francis Bouvier
de286dd78e async: use zig-async-io
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-21 16:42:48 +01:00
Pierre Tachoire
70752027f1 async: remove @This from SigleThreaded 2024-11-19 15:55:26 +01:00
Pierre Tachoire
395eb3e8ad async: add missing tests execution 2024-11-19 15:54:04 +01:00
Pierre Tachoire
e1137274fb async: remove dead code 2024-11-19 15:51:36 +01:00
Francis Bouvier
bc716ef0ad Merge pull request #299 from lightpanda-io/cli_refacto
cli: code refacto
2024-11-19 15:31:19 +01:00
Pierre Tachoire
d2d2e851b0 async: fix assync call pop error 2024-11-18 17:39:38 +01:00
Pierre Tachoire
9149b60136 async: remove dead code 2024-11-18 17:39:38 +01:00
Pierre Tachoire
18ab0c8199 cdp: replace tick by run_for_ns 2024-11-18 17:39:38 +01:00
Pierre Tachoire
7fed1f3015 async: remove pseudo-async http client 2024-11-18 17:39:37 +01:00
Pierre Tachoire
6809bb5393 async: adapt async cli 2024-11-18 17:39:37 +01:00
Pierre Tachoire
fadf3f609a http: add full async client 2024-11-18 17:39:37 +01:00
Pierre Tachoire
8d2d089803 Merge pull request #301 from lightpanda-io/better-log
Better log
2024-11-18 14:39:40 +01:00
Pierre Tachoire
f6ad95c647 improve event's log result 2024-11-18 13:13:52 +01:00
Pierre Tachoire
0788e22dd5 typo fix 2024-11-18 13:13:23 +01:00
Francis Bouvier
de260693dc Merge pull request #296 from lightpanda-io/memory_leak
cdp: fix memory leak in msg parsing of the JSON
2024-11-15 02:17:01 +01:00
Francis Bouvier
f60fcbec04 Merge pull request #286 from lightpanda-io/cdp-refacto-input
cdp: refacto message JSON read
2024-11-15 01:03:53 +01:00
Francis Bouvier
4f99407462 Merge pull request #288 from lightpanda-io/cdp-create-target
cdp: browserContextId is optional in Target.createTarget
2024-11-15 00:53:22 +01:00
Pierre Tachoire
38813a20a7 Merge pull request #298 from lightpanda-io/docker-warning
docker: use absolute path with WORKDIR
2024-11-14 08:56:34 +01:00
Pierre Tachoire
2cd1e927f7 docker: use absolute path with WORKDIR
remove following the warning
```
 1 warning found (use docker --debug to expand):
 - WorkdirRelativePath: Relative workdir "browser" can have unexpected results if the base image changes (line 48)
 ```
2024-11-14 08:46:01 +01:00
Pierre Tachoire
5094942560 cdp: add msg tests into zig build test 2024-11-12 12:56:30 +01:00
Pierre Tachoire
82c37fc71b cdp: refacto message JSON read 2024-11-12 12:56:29 +01:00
Pierre Tachoire
8ba911c8dd cdp: return provided browser context id if any 2024-11-12 10:56:06 +01:00
Francis Bouvier
ac77453139 cli: code refacto
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-10 13:53:07 +01:00
Francis Bouvier
8a25545cac memory: use a GPA in Debug mode and a page allocator in Release
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-09 13:34:15 +01:00
Francis Bouvier
ed3a464843 cdp: fix memory leak in msg parsing of the JSON
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-09 03:25:42 +01:00
Francis Bouvier
1854074f64 Merge pull request #293 from lightpanda-io/cdp-contextid
cdp: use a u32 for context id
2024-11-07 15:49:40 +01:00
Francis Bouvier
ec5de2fce0 Merge pull request #287 from lightpanda-io/cdp-attach-to-target
cdp: add Target.attachToTarget noop
2024-11-07 15:49:15 +01:00
Francis Bouvier
3af34d11ca Merge pull request #291 from lightpanda-io/multi_build
Multi build
2024-11-06 18:17:24 +01:00
Francis Bouvier
eed7b7186d Merge pull request #284 from lightpanda-io/server-sync-deinit
server: ensure Send is always deinit in callback
2024-11-06 18:17:10 +01:00
Francis Bouvier
d5e7ebdc63 Merge pull request #295 from lightpanda-io/fix_cdp_full_async
Fix cdp full async
2024-11-06 18:14:43 +01:00
Francis Bouvier
3ecfa6aca8 Dockerfile: add install-libiconv
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-06 18:10:08 +01:00
Francis Bouvier
625c1741c6 Update zig-js-runtime (tigerbeetle)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-06 18:07:43 +01:00
Francis Bouvier
f6f5ec5eb3 server: add cancel current recv before accepting new connection
Only on Linux. On MacOS cancel is not supported for now and
we do not have any problem with the current recv operation
on a closed socket.

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-06 18:07:43 +01:00
Francis Bouvier
c74feb9c3a server: add log on I/O errors
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-05 17:16:39 +01:00
Pierre Tachoire
0d76f80223 cdp: use a u32 for context id 2024-11-04 10:08:36 +01:00
Pierre Tachoire
1e64513c16 Merge pull request #292 from lightpanda-io/tcp_nodelay
server: set TCP.NODELAY on linux to avoid latency issues
2024-11-04 10:04:25 +01:00
Francis Bouvier
64779acf32 Merge pull request #278 from lightpanda-io/cdp_full_async
Cdp full async
2024-11-01 18:14:21 +01:00
Francis Bouvier
c3a3ac19f4 server: set TCP.NODELAY on linux to avoid latency issues
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-11-01 17:54:49 +01:00
Francis Bouvier
b9bae3f66d build: update gitignore
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-30 13:41:48 +01:00
Francis Bouvier
2a2486cbe0 build: fix clean-libiconv
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-30 13:41:34 +01:00
Pierre Tachoire
0813d99b44 Merge pull request #290 from lightpanda-io/dockerfile
dockerfile: adjust binary name after merge
2024-10-30 10:42:15 +01:00
Pierre Tachoire
491e89d102 dockerfile: adjust binary name after merge 2024-10-30 10:40:59 +01:00
Francis Bouvier
f01558251c Merge pull request #277 from lightpanda-io/merge_bin
Merge get and server binaires
2024-10-29 22:27:09 +01:00
Francis Bouvier
8665d0420b Merge pull request #282 from lightpanda-io/docker-build
add a Dockerfile to build the project
2024-10-29 22:20:49 +01:00
Francis Bouvier
cf0636ca63 Update src/main.zig usage
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2024-10-29 22:19:44 +01:00
Francis Bouvier
46d0aa6f9e Remove all references to the name 'browsercore'
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-29 22:16:56 +01:00
Francis Bouvier
b9e2be2052 build: support multi os/arch conf for netsurf
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-29 20:06:29 +01:00
Pierre Tachoire
b3054d68bf cdp: browserContextId is optional in Target.createTarget
https://chromedevtools.github.io/devtools-protocol/tot/Target/#method-createTarget
2024-10-29 10:37:23 +01:00
Pierre Tachoire
60adf0a9c3 cdp: add Target.attachToTarget noop 2024-10-29 10:34:36 +01:00
Francis Bouvier
be5d7022cc build: support multi os/arch conf for libiconv
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-28 21:08:46 +01:00
Francis Bouvier
d1951b286c build: support multi os/arch conf for mimalloc
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-28 16:06:41 +01:00
Pierre Tachoire
dcdef2f640 server: ensure Send is always deinit in callback 2024-10-25 09:51:37 +02:00
Pierre Tachoire
7afe74310f add a Dockerfile to build the project 2024-10-23 15:22:12 +02:00
Pierre Tachoire
826f82610e Merge pull request #280 from lightpanda-io/cdpdump-close-dir
cdp: close dir in dumpFile
2024-10-23 10:15:31 +02:00
Pierre Tachoire
5d7796b95d cdp: close dir in dumpFile
and avoid error.ProcessFdQuotaExceeded error
2024-10-23 10:02:34 +02:00
Pierre Tachoire
b3ac313cc7 Merge pull request #279 from lightpanda-io/ci-ubuntu
ci: use ubuntu 22.04 for nightly build
2024-10-22 15:43:39 +02:00
Pierre Tachoire
b281ba7754 ci: use zig-v8 0.1.9 2024-10-22 15:03:01 +02:00
Pierre Tachoire
10994b202b ci: use ubuntu latest for all expect nightly build 2024-10-22 14:27:47 +02:00
Pierre Tachoire
2aeac1bdeb ci: force ubuntu 22.04 for nightly build
To ensure a better compatibility.
2024-10-22 14:27:10 +02:00
Francis Bouvier
8508c21080 cdp: remove send sync
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-21 18:29:10 +02:00
Francis Bouvier
20dd140c31 cdp: send I/O next read before executing current cmd
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-21 18:21:43 +02:00
Francis Bouvier
486c19079a Merge get and server binaires
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-18 16:06:23 +02:00
Pierre Tachoire
f30501ca3c Merge pull request #276 from lightpanda-io/compare-position
node: implement node.compareDocumentPosition
2024-10-17 15:06:47 +02:00
Pierre Tachoire
e67e6e267b Merge pull request #275 from lightpanda-io/fake-css-properties
html: implement empty style property
2024-10-17 15:06:40 +02:00
Pierre Tachoire
8dc757ddf3 node: implement getRootNode 2024-10-17 14:44:34 +02:00
Pierre Tachoire
b64f7d013d node: implement node.compareDocumentPosition 2024-10-17 14:44:33 +02:00
Francis Bouvier
62ec936f1e Merge pull request #215 from lightpanda-io/cdp_basic
CDP basic
2024-10-17 10:52:09 +02:00
Francis Bouvier
8d83dfad45 ci: force ubuntu version (24.04)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-17 10:24:00 +02:00
Pierre Tachoire
e450072f45 ci: add zig v8 version into the cache key 2024-10-16 21:00:44 +02:00
Francis Bouvier
7f08d08a78 Update zig-v8 again
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-16 17:54:41 +02:00
Francis Bouvier
b0634cd871 Adapt wpt and shell to zig-js-runtime changes
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-16 15:21:03 +02:00
Francis Bouvier
462485bfcb Update zig-v8 and zig-js-runtime deps
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-16 14:56:04 +02:00
Francis Bouvier
2311765289 Remove some dead code
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-16 14:53:50 +02:00
Francis Bouvier
7bc7da5499 browser: back on createPage returning a Page (pointer)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-16 14:53:11 +02:00
Pierre Tachoire
b712a4771e html: implement empty style property 2024-10-16 10:22:23 +02:00
Francis Bouvier
8e05f09fc8 server, cdp: improve logging
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-15 22:57:56 +02:00
Francis Bouvier
84c49fbe34 cdp: ensure there is an ID on each request
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-15 17:28:18 +02:00
Francis Bouvier
7750956c7b msg: Add a more complex test case with 2 multipart messages combined
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-15 16:07:46 +02:00
Francis Bouvier
ea9af210f9 Remove heap allocation for Session
And adapt to similar changes on zig-js-runtime for Env

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-15 15:52:48 +02:00
Francis Bouvier
efca71510a browser: put back VM is an arg for browser init
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-12 10:41:59 +02:00
Francis Bouvier
cbf6348055 server: panic if sendInspector without an inspector
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-12 10:38:53 +02:00
Francis Bouvier
ec680593b0 msg: set a hard limit max size
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-11 18:13:20 +02:00
Francis Bouvier
fd6c25daaa msg: improve comments on reallocation
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-11 18:05:04 +02:00
Francis Bouvier
4b495f213f cdp: add comment on hard coded ID for page.createIsolatedWorld
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 15:21:09 +02:00
Francis Bouvier
7ad03fb548 cdp: fix a comment on page.navigate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 15:18:55 +02:00
Francis Bouvier
17c641845e msg: return error if input does not have "size:"
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 15:13:06 +02:00
Francis Bouvier
e53b9d984b browser: add comment for auxData param in page.navigate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 15:10:30 +02:00
Francis Bouvier
28593d93ff browser: panic if callInspector without Inspector
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:47:14 +02:00
Francis Bouvier
fa4920bd94 browser: rename setInspector -> initInspector
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:45:28 +02:00
Francis Bouvier
eaf5c6f86f cdp: ensure method action is present
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:42:20 +02:00
Francis Bouvier
0d89b98bad cdp: ensure token is a string when needed in parser
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:35:56 +02:00
Francis Bouvier
bf56345e48 msg: comments typos
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:19:35 +02:00
Francis Bouvier
2bc58bebce server: rename public -> jsruntime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:11:43 +02:00
Francis Bouvier
c564702eac server: formatting
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:10:54 +02:00
Francis Bouvier
9400dd799e Add cli options for server (host, port, timeout)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 12:06:39 +02:00
Francis Bouvier
ff0bbc3f96 server: simplify Send I/O
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 01:21:24 +02:00
Francis Bouvier
15414f5ee4 server: remove unused sendLater
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 01:00:12 +02:00
Francis Bouvier
f9b097794f Simplify browser session.setInspector
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 00:58:13 +02:00
Francis Bouvier
a2f65eb540 server: simplify onInspector methods
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 00:56:15 +02:00
Francis Bouvier
cea38a10e9 server: rename buf in read_buf
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 00:56:11 +02:00
Francis Bouvier
c8a91d4cf6 server: merge Cmd and Accept in Ctx
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-09 00:55:29 +02:00
Francis Bouvier
b0ff325125 server: move to TCP conn
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-08 23:44:47 +02:00
Francis Bouvier
c35c09db60 server: timeout mechanism
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-08 23:40:50 +02:00
Francis Bouvier
49adb61146 server: handle close and re-open connection
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-08 16:22:24 +02:00
Francis Bouvier
76a9034668 server: newSession on disposeBrowserContext
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-07 21:14:55 +02:00
Francis Bouvier
4c225e515d server: let the caller of sendSync free the string
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-07 16:04:29 +02:00
Francis Bouvier
9c913b2e6c Move loop outside Browser
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-07 15:57:16 +02:00
Francis Bouvier
5ab1d2a8a5 Add License in new cdp files
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-01 18:02:21 +02:00
Francis Bouvier
2f3a581859 Add TODOs and comments
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-01 17:48:54 +02:00
Francis Bouvier
8bdd2a14e8 Add Target.disposeBrowserContext
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-01 17:13:47 +02:00
Francis Bouvier
1675f69582 Add Target.closeTarget
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-01 17:13:29 +02:00
Francis Bouvier
94d2d28806 Redirect Runtime domain to JS engine Inspector
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-10-01 17:12:08 +02:00
Pierre Tachoire
82a5e50056 Merge pull request #274 from lightpanda-io/upgrade-libdom
upgrade libdom
2024-09-25 11:53:28 +02:00
Pierre Tachoire
46062e185a upgrade libdom 2024-09-25 11:47:34 +02:00
Pierre Tachoire
6929141210 Merge pull request #273 from lightpanda-io/nodelist-iterator
nodelist: remove debug log
2024-09-25 09:51:00 +02:00
Pierre Tachoire
cce36c5fbd nodelist: remove debug log 2024-09-25 09:50:31 +02:00
Pierre Tachoire
2518287326 Merge pull request #272 from lightpanda-io/nodelist-iterator
nodelist: implement iterators
2024-09-25 09:42:59 +02:00
Pierre Tachoire
aefab86501 nodelist: implement iterators 2024-09-25 09:37:14 +02:00
Pierre Tachoire
30679d18ee Merge pull request #271 from lightpanda-io/currentscript
implement DOM document.currentscript
2024-09-24 10:14:21 +02:00
Pierre Tachoire
95c0ff6f39 dom: implement currentScript 2024-09-24 10:01:13 +02:00
Pierre Tachoire
4d6f59ecb8 upgrade libdom 2024-09-24 10:01:12 +02:00
Pierre Tachoire
4b5668f4fd Merge pull request #270 from lightpanda-io/nodelist-foreach
DOM: implement nodelist.foreach
2024-09-20 18:37:22 +02:00
Pierre Tachoire
44a5fa011a dom: implement nodelist.foreach 2024-09-20 18:32:23 +02:00
Pierre Tachoire
c3fd0dbf7a Merge pull request #223 from lightpanda-io/settimout
dom: first draft for window setTimeout
2024-09-19 16:49:55 +02:00
Pierre Tachoire
aeaa745600 clearTimeout: ignore invalid timeout ids 2024-09-19 16:37:42 +02:00
Pierre Tachoire
be27359109 dom: implement clearTimeout 2024-09-19 16:37:42 +02:00
Pierre Tachoire
e8a2ce3614 upgrade zig-js-runtime lib 2024-09-19 16:37:40 +02:00
Pierre Tachoire
0559fb9365 dom: first draft for window setTimeout 2024-09-19 16:36:34 +02:00
Pierre Tachoire
89f898cfa9 Merge pull request #268 from lightpanda-io/HttpHeadersOversize
use 64KB for header buffer
2024-09-19 15:36:25 +02:00
Pierre Tachoire
183abc4610 use 64KB for header buffer 2024-09-18 09:24:57 +02:00
Pierre Tachoire
5dfdedea0e Merge pull request #266 from lightpanda-io/ci-node20
ci: upgrade cache action to node20
2024-09-13 15:04:11 +02:00
Pierre Tachoire
8d89c6053e ci: upgrade cache action to node20 2024-09-13 14:52:06 +02:00
Pierre Tachoire
58d184dba1 Merge pull request #265 from lightpanda-io/ci-cpu
ci: target cpu x86_64 to improve CPU compat
2024-09-13 14:47:58 +02:00
Pierre Tachoire
fd0813fead ci: target cpu x86_64 to improve CPU compat
The `-Dcpu` option force compiling using less CPU's capabilities.
This will improve the binary compatibility with older CPUs.
2024-09-13 14:36:44 +02:00
Pierre Tachoire
c9fae67649 Merge pull request #258 from lightpanda-io/tls.zig
use tls.zig with async client
2024-07-22 10:44:38 +02:00
Pierre Tachoire
3e7f9aaa82 Merge pull request #262 from lightpanda-io/browser-deinit
browser: fix invalid deinit order
2024-07-19 17:20:28 +02:00
Pierre Tachoire
22b03bf7df browser: fix invalid deinit order
http client depends on io loop and must be deinit before.
2024-07-19 17:15:53 +02:00
Pierre Tachoire
6a4d64ed00 use tls.zig with async client
see https://github.com/ziglang/zig/compare/master...ianic:zig:tls23 for
http.std.Client integration
2024-07-19 14:39:50 +02:00
Pierre Tachoire
df27ce09ca Merge pull request #260 from lightpanda-io/zig0.13
upgrade to zig 0.13
2024-07-19 14:36:13 +02:00
Pierre Tachoire
6da2954e0b upgrade to zig 0.13 2024-07-19 14:32:47 +02:00
Pierre Tachoire
95245229b0 upgrade zig-js-runtime 2024-07-19 14:32:47 +02:00
Pierre Tachoire
65c4b471af browser: handle wait with unstarted env 2024-07-19 11:14:50 +02:00
Pierre Tachoire
d6e0559efd upgrade to zig 0.13 2024-07-18 16:57:16 +02:00
Pierre Tachoire
5f5e01a2cf Merge pull request #253 from lightpanda-io/refacto_exec
Adapt to js_exec changes in zig-js-runtime
2024-07-18 16:56:41 +02:00
Pierre Tachoire
8c3939b842 wpt: remove useless Suite.stack 2024-07-18 16:52:32 +02:00
Pierre Tachoire
b537e52a6d browser: use log instead of std.log 2024-07-18 16:46:12 +02:00
Pierre Tachoire
4434e11bdd wpt: restore the test results 2024-07-18 16:40:44 +02:00
Francis Bouvier
b8ec53f708 Adapt to js_exec changes in zig-js-runtime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-07-18 12:10:06 +02:00
Pierre Tachoire
f8395fec5c Merge pull request #257 from lightpanda-io/build-x86
ci: target cpu x86_64_v3+aes for compatibility
2024-07-17 17:04:20 +02:00
Pierre Tachoire
28155cb8d3 ci: target cpu x86_64_v3+aes for compatibility 2024-07-17 17:00:09 +02:00
Pierre Tachoire
f793278dfe Merge pull request #255 from lightpanda-io/ci
ci: remove container usage and download v8 from release
2024-07-17 15:16:41 +02:00
Pierre Tachoire
cdbbc71b0a ci: add nightly build for macos aarch64 2024-07-17 10:28:24 +02:00
Pierre Tachoire
bfa6f55551 ci: remove container usage and download v8 from release 2024-07-17 09:56:34 +02:00
Pierre Tachoire
5fb25cf015 Merge pull request #254 from lightpanda-io/nightly
ci: add linux amd64 nightly build
2024-07-16 14:29:36 +02:00
Pierre Tachoire
5cad450e5a ci: add linux amd64 nightly build 2024-07-16 12:47:26 +02:00
Francis Bouvier
14a3a662fd Fix response of runtime.Evaluate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-07-09 16:10:25 +02:00
Francis Bouvier
41409031fd Adapt to refacto in js_exec from zig-js-runtime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-07-08 22:51:41 +02:00
Pierre Tachoire
8ec22ca6b8 Merge pull request #246 from lightpanda-io/mutation-observer
dom: implement MutationObserver
2024-06-24 16:03:58 +02:00
Pierre Tachoire
d8fe029f80 mutation: very partial childList implementation 2024-06-24 15:19:39 +02:00
Pierre Tachoire
c9cfb1ecba dom: add attributeNS funcs 2024-06-24 15:19:39 +02:00
Pierre Tachoire
94e47f4f35 mutation: implement attribute and cdata observer 2024-06-24 15:19:38 +02:00
Pierre Tachoire
12111d4cdf dom: first draft for MutationObserver 2024-06-24 15:19:30 +02:00
Pierre Tachoire
00e8f13f62 Merge pull request #251 from lightpanda-io/eventlistener-data
events: create an EventHandlerData struct
2024-06-24 15:19:09 +02:00
Pierre Tachoire
e1e501e14a Merge pull request #252 from lightpanda-io/wpt-subtest
Wpt subtest
2024-06-21 16:32:36 +02:00
Pierre Tachoire
32015eae3c upgrade tests/wpt 2024-06-21 16:20:14 +02:00
Pierre Tachoire
ab31cc0a18 wpt: if no test case found, the suite fails 2024-06-21 16:19:37 +02:00
Pierre Tachoire
1924f136c6 events: create an EventHandlerData struct
It simplifies the EventHandlerFunc creation and allows to insert user's
data.
2024-06-21 09:20:03 +02:00
Francis Bouvier
ea410c8ced Fix changes in Zig 0.12 std lib
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-20 00:32:06 +02:00
Francis Bouvier
0481676be5 Merge pull request #250 from lightpanda-io/deps
Upgrade zig-js-runtime
2024-06-19 22:31:05 +02:00
Francis Bouvier
0da1955b52 Upgrade zig-js-runtime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-19 22:30:30 +02:00
Francis Bouvier
aca64eedca Uniformize calling name conventions
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-19 15:56:44 +02:00
Francis Bouvier
0f8b47b598 Move MsgBuffer in it's own file for unit test purpose
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-19 15:48:20 +02:00
Pierre Tachoire
522b293149 Merge pull request #249 from lightpanda-io/upgrade-jsruntime
upgrade zig-js-runtime
2024-06-19 15:32:44 +02:00
Francis Bouvier
5eae15889d Add some optional fields in Runtime.evaluate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-19 15:23:09 +02:00
Pierre Tachoire
0a50b586fd upgrade zig-js-runtime 2024-06-19 15:11:40 +02:00
Pierre Tachoire
11a63b776e Merge pull request #245 from lightpanda-io/hundred-types
generate: handle more than 100 in itoa
2024-06-19 13:56:06 +02:00
Pierre Tachoire
78467ff209 generate: handle more than 100 in itoa 2024-06-19 11:42:19 +02:00
Pierre Tachoire
6e5f67cd66 Merge pull request #243 from lightpanda-io/http-buf
http: use 16KB for the client header buffer
2024-06-18 17:21:59 +02:00
Pierre Tachoire
17a86cc1a6 http: use 16KB for the client header buffer 2024-06-18 17:19:05 +02:00
Pierre Tachoire
f6e080bdfd Merge pull request #203 from lightpanda-io/upgrade-zig
Upgrade zig 0.12
2024-06-18 16:16:44 +02:00
Pierre Tachoire
3744dc1e58 upgrade zig-js-runtime 2024-06-18 16:13:34 +02:00
Pierre Tachoire
9cdf1f5762 xhr: fix unit tests for 0.12.1 2024-06-18 16:13:34 +02:00
Pierre Tachoire
25ee34e65d jsruntime upgrade 2024-06-18 16:13:29 +02:00
Pierre Tachoire
720d3f4df9 test: fix comptime var 2024-06-18 16:13:28 +02:00
Pierre Tachoire
dcca5e60e3 upgrade jsruntime deps 2024-06-18 16:13:28 +02:00
Pierre Tachoire
f2a406d224 move netsurf and mimalloc into modules 2024-06-18 16:13:27 +02:00
Pierre Tachoire
68c8372493 build: use path() func 2024-06-18 16:13:27 +02:00
Pierre Tachoire
ef364f83c8 upgrade to zig 0.12.1 2024-06-18 16:13:27 +02:00
Pierre Tachoire
33c92776f0 build.zig: upgrade to zig 0.12.1 2024-06-18 16:13:26 +02:00
Pierre Tachoire
f5a2c8d303 upgrade to zig 0.12 2024-06-18 16:13:26 +02:00
Pierre Tachoire
c555c325e9 upgrade to zig 0.12
0.12.0-dev.3439+31a7f22b8
2024-06-18 16:13:26 +02:00
Pierre Tachoire
9310b91ad5 README: upgrade zig version 2024-06-18 16:13:25 +02:00
Pierre Tachoire
a708bc7d0f CI: upgrade zig version 0.12.1 2024-06-18 16:13:25 +02:00
Pierre Tachoire
4ba4ce0f7c build: upgrade build.zig to 0.12 2024-06-18 16:13:25 +02:00
Pierre Tachoire
49f20adbab upgrade jsruntime-lib 2024-06-18 16:13:22 +02:00
Pierre Tachoire
6724004a49 Merge pull request #242 from lightpanda-io/ci-EPERM
ci: add --security-opt seccomp=unconfined docker option
2024-06-18 16:08:34 +02:00
Pierre Tachoire
33ec300947 ci: run ci on .github changes 2024-06-18 16:05:21 +02:00
Pierre Tachoire
0c2f0b78aa ci: add --security-opt seccomp=unconfined docker option
It seems docker blocks io_uring by default using seccomp.

see tigerbeetle/tigerbeetle#1995 and
moby/moby#46762
2024-06-18 16:03:44 +02:00
Francis Bouvier
9319e4a7f1 Handle Runtime.callFunctionOn
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-17 16:35:22 +02:00
Francis Bouvier
4d756b5bfc Add a dumpFile utility function
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-17 16:34:47 +02:00
Francis Bouvier
409969621d Add Runtime.addBinding
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-12 17:56:54 +02:00
Francis Bouvier
7abb7277c9 Fix call to Runtime.executionContextCreated in Page.navigate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-12 17:56:07 +02:00
Francis Bouvier
152a4e5e7f Merge pull request #241 from lightpanda-io/README
Add a status section in the README
2024-06-11 16:55:28 +02:00
Francis Bouvier
e6f4b9cc66 Update README.md
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2024-06-11 16:55:18 +02:00
Francis Bouvier
67c8647e25 Add a status section in the README
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-11 16:35:00 +02:00
Francis Bouvier
9120b9c1de Add emulation.setTouchEmulationEnabled
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:19:08 +02:00
Francis Bouvier
08c11ac41f Add performance.enable
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:16:15 +02:00
Francis Bouvier
cecc03e1ed Add fetch.disable
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:12:31 +02:00
Francis Bouvier
7d67d131c2 Add network.setCacheDisabled
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:08:49 +02:00
Francis Bouvier
1929eed8ac Add contextID in state
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:03:12 +02:00
Francis Bouvier
ad8c9fac2b Add target.setDiscoverTargets
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:02:37 +02:00
Francis Bouvier
fa82160265 Add target.getBrowserContexts
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 16:02:02 +02:00
Francis Bouvier
dc1456f4e8 Handle CDP messages with different order
The 'method' still needs to be the first or the second key
(in this case after the 'id').

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-06-07 15:59:57 +02:00
Francis Bouvier
3ad19dffa1 Handle CDP msg with order <id, method> and <method, id>
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-30 17:43:01 +02:00
Francis Bouvier
bfb9db235e Basic Runtime.evaluate run
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-30 16:21:18 +02:00
Francis Bouvier
c57e50c5b9 Handle Runtime.evaluate (no-op)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-27 16:02:14 +02:00
Pierre Tachoire
9fe56c5889 Merge pull request #240 from lightpanda-io/wpt-wait
Wpt wait
2024-05-23 15:24:38 +02:00
Pierre Tachoire
77bf332f13 wpt: split script exec and wait loop 2024-05-23 15:11:24 +02:00
Pierre Tachoire
b4f445183c wpt: add log.debug method in pure JS 2024-05-23 15:11:24 +02:00
Pierre Tachoire
38d48d7515 upgrade zig-js-runtime 2024-05-23 15:11:24 +02:00
Francis Bouvier
bafdca3ffa MsgBuffer to handle both combined and multipart read
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-22 16:24:39 +02:00
Pierre Tachoire
c5776f0ae6 Merge pull request #231 from lightpanda-io/browser-wait
browser: extract loop wait
2024-05-22 15:18:00 +02:00
Pierre Tachoire
7a6d929f08 browser: add wait to run the loop 2024-05-22 15:13:23 +02:00
Pierre Tachoire
11f7fc4550 Merge pull request #227 from lightpanda-io/listener-error
event: log listeners errors and continue execution
2024-05-22 15:11:34 +02:00
Pierre Tachoire
d50f761c8f event: log listeners errors and continue execution 2024-05-22 15:07:49 +02:00
Pierre Tachoire
23dafb0f12 Merge pull request #216 from lightpanda-io/usrctx
Add user context
2024-05-22 15:07:02 +02:00
Pierre Tachoire
9ac46ea0cb upgrade zig-js-runtime 2024-05-22 14:59:36 +02:00
Pierre Tachoire
00d75584db usrctx: use ctx http client with xhr 2024-05-22 14:58:48 +02:00
Pierre Tachoire
c2e64c131a userctx: document is not opational anymore 2024-05-22 14:56:41 +02:00
Pierre Tachoire
840aea9013 dom: fix document creation process 2024-05-22 14:56:41 +02:00
Pierre Tachoire
bf522937e1 shell: add missing try 2024-05-22 14:56:41 +02:00
Pierre Tachoire
7d91f7992c dom: first draft for hierachy check in nodes 2024-05-22 14:56:40 +02:00
Pierre Tachoire
b2df0c1541 dom: implement document fragment constructor 2024-05-22 14:56:40 +02:00
Pierre Tachoire
d823eebce5 dom: remove useless TODO 2024-05-22 14:56:40 +02:00
Pierre Tachoire
55b80ecd15 dom: implement text constructor 2024-05-22 14:56:39 +02:00
Pierre Tachoire
14e1c44eb0 dom: implement comment constructor 2024-05-22 14:56:29 +02:00
Pierre Tachoire
eef2fa94d0 text: return error on constructor
Blocked by #102
2024-05-22 14:45:40 +02:00
Pierre Tachoire
49ee5e4e68 comment: return error on constructor
Blocked by https://github.com/lightpanda-io/browsercore/issues/102
2024-05-22 14:45:39 +02:00
Pierre Tachoire
b7f589ee1a dom: fix document constructor 2024-05-22 14:45:39 +02:00
Pierre Tachoire
e18d04a799 userctx: inject user context 2024-05-22 14:45:34 +02:00
Francis Bouvier
c1b73dfdc2 Merge pull request #239 from lightpanda-io/update_readme
Add benchmark graph in the README
2024-05-17 18:36:48 +02:00
Francis Bouvier
63e3f8d48a Add benchmark graph in the README
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-17 18:29:58 +02:00
Pierre Tachoire
194328f2de Merge pull request #238 from lightpanda-io/update_readme
Update README
2024-05-16 07:41:22 +02:00
Francis Bouvier
0636240a58 Update README
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-15 18:10:05 +02:00
Pierre Tachoire
7ec5a8d15b Merge pull request #234 from lightpanda-io/html-anchor
dom: add target and href accessors to anchor
2024-05-14 16:23:37 +02:00
Pierre Tachoire
ead08557bf script: implement interface 2024-05-14 16:19:22 +02:00
Pierre Tachoire
6d808d89b0 anchor: implement set_host 2024-05-14 13:44:27 +02:00
Pierre Tachoire
6b42b5abdd anchor: implement HTMLHyperlinkElementUtils interface
https://html.spec.whatwg.org/#htmlhyperlinkelementutils
2024-05-14 13:44:27 +02:00
Pierre Tachoire
e12d6e85f0 url: add origin getter 2024-05-14 13:44:26 +02:00
Pierre Tachoire
7da440e9d3 anchro: implement url manipulation 2024-05-14 13:44:26 +02:00
Pierre Tachoire
d93a065db9 url: improve url format 2024-05-14 13:44:26 +02:00
Pierre Tachoire
8d6ee42096 anchor: follow up 2024-05-14 13:44:25 +02:00
Pierre Tachoire
df6a905683 dom: add target and href accessors to anchor 2024-05-14 13:44:16 +02:00
Francis Bouvier
88c9875664 Merge pull request #237 from lightpanda-io/zig-js-runtime_rename
Update dependancy jsruntime-lib -> zig-js-runtime
2024-05-14 13:14:05 +02:00
Pierre Tachoire
5fbbf1b59f readme: fix zig-js-runtime step 2024-05-14 12:22:13 +02:00
Pierre Tachoire
f040f422e4 ci: rename jsruntime-lib into zig-js-runtime 2024-05-14 12:18:24 +02:00
Francis Bouvier
986e69f45d Update dependancy jsruntime-lib -> zig-js-runtime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-14 11:35:23 +02:00
Pierre Tachoire
deed0546cc Merge pull request #236 from lightpanda-io/license-source
add AGPL license header in zig files
2024-05-14 08:45:44 +02:00
Pierre Tachoire
2a3a243d1c add AGPL license header in zig files 2024-05-13 20:51:36 +02:00
Pierre Tachoire
eba1a715b1 Merge pull request #235 from lightpanda-io/open
add AGPL license
2024-05-13 14:46:47 +02:00
Pierre Tachoire
973ebcb7b5 add AGPL license 2024-05-13 12:10:15 +02:00
Pierre Tachoire
e1aec8bc07 Merge pull request #230 from lightpanda-io/dom-url
URL api
2024-05-13 11:26:55 +02:00
Pierre Tachoire
806b6c0c1e Merge pull request #233 from lightpanda-io/fix-window-global
wpt: dispatch native event
2024-05-07 16:18:10 +02:00
Pierre Tachoire
c6754d6a6e wpt: dispatch native event 2024-05-07 16:09:20 +02:00
Pierre Tachoire
1be9470942 Merge pull request #232 from lightpanda-io/fix-window-global
browser: fix global object window
2024-05-07 16:08:45 +02:00
Pierre Tachoire
edd0c7d0a3 browser: fix global object window 2024-05-07 15:59:00 +02:00
Pierre Tachoire
d0c741f3bb url: search query dynamic and encoded 2024-05-06 16:32:20 +02:00
Pierre Tachoire
a9842fd790 url: decode query 2024-05-06 15:06:03 +02:00
Pierre Tachoire
f7040153cd url: implement query parsing 2024-05-06 12:45:14 +02:00
Pierre Tachoire
e42b03acd8 mime: extract string parser 2024-05-06 12:44:45 +02:00
Pierre Tachoire
28a87c2a47 url: first draft 2024-05-03 16:18:11 +02:00
Pierre Tachoire
e2cd983851 Merge pull request #220 from lightpanda-io/session_page
Keep reference of current Page in Session
2024-05-02 18:15:53 +02:00
Francis Bouvier
df82d25e91 Return error if a Page already exists in Session
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-02 17:52:23 +02:00
Francis Bouvier
bcf4083f9c Keep reference of current Page in Session
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-05-02 17:52:14 +02:00
Pierre Tachoire
4444d8a008 Merge pull request #229 from lightpanda-io/upgrade-libdom
upgrade libdom
2024-05-02 17:42:27 +02:00
Pierre Tachoire
d193b73ee3 upgrade libdom 2024-05-02 17:36:44 +02:00
Pierre Tachoire
89bfc8ccdc Merge pull request #222 from lightpanda-io/webstorage
storage: first implementation of webstorage API
2024-05-02 15:35:22 +02:00
Pierre Tachoire
7c06067991 Merge pull request #214 from lightpanda-io/event-callback
Event callback
2024-05-02 15:30:08 +02:00
Pierre Tachoire
8b47d72079 event: set this arg on callback 2024-05-02 15:25:41 +02:00
Pierre Tachoire
a2a0db7bc4 upgrade jsruntime 2024-05-02 15:25:41 +02:00
Pierre Tachoire
5f6e5d57c0 upgrade tests/wpt 2024-05-02 15:23:15 +02:00
Pierre Tachoire
61357ee7e0 storage: update comment about dispatch event 2024-05-02 15:21:21 +02:00
Pierre Tachoire
88106f8449 Merge pull request #226 from lightpanda-io/upgrade-jsruntime
upgrade jsruntime-lib
2024-04-30 08:54:59 +02:00
Pierre Tachoire
6f2f0af0ef upgrade jsruntime-lib 2024-04-30 08:38:53 +02:00
Pierre Tachoire
eb829f4d36 Merge pull request #225 from lightpanda-io/netsurf-empty-event-type
dom: an event type can be null
2024-04-29 17:51:59 +02:00
Pierre Tachoire
d155421a40 netsurf: add missing netsurf DOM errors 2024-04-29 17:42:38 +02:00
Pierre Tachoire
9f2bad7498 dom: an event type can be null
In this case we return empty string
2024-04-29 16:31:37 +02:00
Pierre Tachoire
3c5d601622 storage: first implementation of webstorage API 2024-04-24 11:54:41 +02:00
Francis Bouvier
ba12945e5b Move read input from Cmd callback to allow unit tests
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-24 11:17:55 +02:00
Francis Bouvier
96906df64b Implement own protocol to handle msg size
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-23 12:48:35 +02:00
Francis Bouvier
3396c70b67 Send Runtime.executionContextCreated events in Page.navigate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-23 10:44:33 +02:00
Francis Bouvier
28d5c682cd Use sendEvent in Runtime.executionContextCreated and expose it
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-23 10:44:03 +02:00
Francis Bouvier
7a03562a33 Typo fix Page.LifecycleEvent
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-23 10:43:11 +02:00
Francis Bouvier
4a31dd8aa3 Let Page.navigate do actually navigation
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 17:13:32 +02:00
Francis Bouvier
1b1b7cdfb0 Add page_life_cycle_events in CDP state
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 17:12:37 +02:00
Francis Bouvier
9e13ffb8ff Add sendEvent utility function
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 17:11:31 +02:00
Pierre Tachoire
2a94e5a69e Merge pull request #199 from lightpanda-io/c_alloc
setCAllocator
2024-04-19 12:05:56 +02:00
Francis Bouvier
ed38705efd Basic version using Browser
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 11:57:44 +02:00
Pierre Tachoire
8e96ee337d wpt: skip tests/wpt/dom/events/remove-all-listeners.html 2024-04-19 11:55:55 +02:00
Pierre Tachoire
304a28a79d mimalloc: add strdup and strndup overrride 2024-04-19 11:48:03 +02:00
Pierre Tachoire
a3e91debea deps: upgrade netsurf deps 2024-04-19 11:48:03 +02:00
Pierre Tachoire
545bcc403a ci: rebuild mimalloc if it has changed 2024-04-19 11:48:01 +02:00
Pierre Tachoire
69b5a3db15 readme: add mimalloc info 2024-04-19 11:47:04 +02:00
Pierre Tachoire
53a5326248 mimalloc: avoid mimalloc override
By default mimalloc is built to override default allocation functions.
So it is used also by v8.

This change avoid the mimalloc override to keep the native stdlib
functions.
2024-04-19 11:47:03 +02:00
Pierre Tachoire
3834ebcfa4 replace calloc with mimalloc 2024-04-19 11:46:42 +02:00
Pierre Tachoire
9363acf4ec glue mimalloc with netsurf C libs 2024-04-19 11:46:42 +02:00
Pierre Tachoire
dad51a4179 upgrade libwapcaplet deps 2024-04-19 11:46:42 +02:00
Pierre Tachoire
59b2954ff4 deps: add mimalloc dependency 2024-04-19 11:46:41 +02:00
Pierre Tachoire
5e9d31b053 deps: use our fork for all netsurf deps 2024-04-19 11:46:41 +02:00
Francis Bouvier
76c88d049f setCAllocator
Replace custom malloc functions in netsurf libs with a global Zig allocator.

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 11:46:41 +02:00
Pierre Tachoire
f0773a3ca2 Merge pull request #219 from lightpanda-io/ci-deps
ci: force netsurf build deps each time
2024-04-19 11:45:48 +02:00
Pierre Tachoire
f9cff763d8 ci: disable build release on PR 2024-04-19 11:41:38 +02:00
Francis Bouvier
1a1cd0353c Add dummy Page.navigate
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 11:36:02 +02:00
Francis Bouvier
4f0b071c59 Fix getContent algo
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 11:35:47 +02:00
Pierre Tachoire
8d606d5dc5 ci: force netsurf build deps each time 2024-04-19 10:51:20 +02:00
Francis Bouvier
9ce574a1f0 Add Page.createIsolatedWorld
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 21:57:31 +02:00
Francis Bouvier
c54b50eb0c Add Browser.setWindowBounds
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 21:52:06 +02:00
Francis Bouvier
aec7455151 Add Emulation.setDeviceMetricsOverride
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 21:46:43 +02:00
Francis Bouvier
c7ba567d7f Handle non-empty void params in getContent
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 21:45:46 +02:00
Francis Bouvier
fc1b3d5397 Contextual frameTree
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 20:54:30 +02:00
Francis Bouvier
508741c367 Add Browser.getWindowForTarget
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 20:53:18 +02:00
Francis Bouvier
f02de77295 Add getContent
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 20:38:27 +02:00
Francis Bouvier
9974b56607 Add Target.createTarget
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 16:43:19 +02:00
Pierre Tachoire
7347e1d414 Merge pull request #218 from lightpanda-io/upgrade-wpt
upgrade wpt
2024-04-18 16:33:49 +02:00
Pierre Tachoire
b65e0e8d77 upgrade wpt 2024-04-18 16:32:59 +02:00
Francis Bouvier
0506a7bb53 Add Browser.createBrowserContext
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 16:14:19 +02:00
Francis Bouvier
06f161c423 Add Target.getTargetInfo
+ do not send attachedToTarget if sessionId

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 13:20:23 +02:00
Francis Bouvier
69f5bb9ed3 Add sessionId in Runime.runIfWaitingForDebugger response
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 13:18:51 +02:00
Francis Bouvier
490eb40028 Add method cdp function
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 13:18:16 +02:00
Francis Bouvier
43a558f5ae Make getParams return nullable
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 12:10:20 +02:00
Francis Bouvier
e4ae2df1a4 Add some optional params in methods
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 11:57:39 +02:00
Francis Bouvier
1620138421 Return sessionId in Emulation.setFocusEmulationEnabled
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 11:32:11 +02:00
Francis Bouvier
e59fc903f2 Return a result in Page.getFrameTree
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-18 10:20:47 +02:00
Francis Bouvier
4d8cdc6dc8 Handle sessionId in result
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-17 14:18:18 +02:00
Francis Bouvier
21afa1f4b3 Do not emit optional null value in JSON output
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-17 14:04:34 +02:00
Francis Bouvier
05c5d06df5 Change Page.addScriptToEvaluateOnNewDocument
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 17:28:28 +02:00
Francis Bouvier
9e8b765f7a Allow method with sessionId and use it when appropriate (*.enable)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 17:19:50 +02:00
Francis Bouvier
36dbc28bde Add Runtime.runIfWaitingForDebugger
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 16:40:50 +02:00
Francis Bouvier
26eda90f7e Add setFocusEmulationEnabled
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 16:38:47 +02:00
Francis Bouvier
211fa3d947 Handle several JSON msg in 1 read
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 16:38:15 +02:00
Pierre Tachoire
4da25fafd4 Merge pull request #217 from lightpanda-io/ci-refacto
CI refacto
2024-04-16 16:04:43 +02:00
Pierre Tachoire
d8f21e3c67 ci: ugrade GH actions versions 2024-04-16 15:55:41 +02:00
Pierre Tachoire
fe8b6e3060 ci: add missing permissions for wpt 2024-04-16 15:55:41 +02:00
Pierre Tachoire
8b03c0c651 ci: force ci on YAML changes 2024-04-16 15:55:41 +02:00
Pierre Tachoire
ffbcfc18f1 ci: extract install steps in its own action 2024-04-16 15:55:40 +02:00
Pierre Tachoire
e3f487a7f1 ci: force netsurf rebuild on change 2024-04-16 15:38:50 +02:00
Francis Bouvier
67bbd9957d Add Network domain
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 01:03:04 +02:00
Francis Bouvier
aff2250504 Add Emulation domain
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 01:02:44 +02:00
Francis Bouvier
86b1c851c0 Add Page.addScriptToEvaluateOnNewDocument
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:57:25 +02:00
Francis Bouvier
0a03dcb465 Add Page.setLifecycleEventsEnabled
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:54:40 +02:00
Francis Bouvier
e073e3388d Add Runtime domain
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:50:17 +02:00
Francis Bouvier
626fae0da0 Add Log domain
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:48:40 +02:00
Francis Bouvier
a708a7f387 Add Page.getFrameTree
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:48:17 +02:00
Francis Bouvier
b1242207a9 Add Page domain
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:41:26 +02:00
Francis Bouvier
980571073d Big refacto
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-16 00:38:06 +02:00
Francis Bouvier
5e1fe656e8 send Target.attachedToTarget after Target.setAutoAttach
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 21:34:00 +02:00
Francis Bouvier
ffbfd36502 Add stringify function in cdp
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 21:33:32 +02:00
Francis Bouvier
e908cb0ec4 Use send as normal behavior in cmdCallback
+ add nanoseconds param in sendLater

Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 21:32:14 +02:00
Francis Bouvier
95a64b7696 Handle concurrent calls to sendLater
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 17:57:33 +02:00
Francis Bouvier
cfd6fc9532 Working sendLater (I/O timeout)
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 17:36:22 +02:00
Francis Bouvier
defab0c774 Free msg at the right place
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 15:52:13 +02:00
Francis Bouvier
babac692d5 Remove alloc from CmdContext struct
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 15:47:19 +02:00
Francis Bouvier
c57bb9ef72 WIP: CDP
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-15 12:14:33 +02:00
Pierre Tachoire
309b6370f7 Merge pull request #213 from lightpanda-io/ci-build
ci: fix build dev command
2024-04-10 11:46:36 +02:00
Pierre Tachoire
6a560fd20c ci: fix build dev command 2024-04-10 11:46:08 +02:00
Pierre Tachoire
c8abbf411b Merge pull request #212 from lightpanda-io/ci-build
ci: split build dev and build release
2024-04-10 11:34:30 +02:00
Pierre Tachoire
c2f17cb216 ci: add missing s3 credentials for test 2024-04-10 11:32:51 +02:00
Pierre Tachoire
25332fd095 ci: split build dev and build release 2024-04-10 11:29:54 +02:00
Pierre Tachoire
e2a8a74906 Merge pull request #209 from lightpanda-io/bench
Add benchmark output when running js tests
2024-04-10 11:20:41 +02:00
Pierre Tachoire
cca6e363c7 ci: split zig test and zig build steps 2024-04-10 09:50:53 +02:00
Pierre Tachoire
cb2b488d27 bench: prepare v8, libdom and main metrics 2024-04-10 09:50:36 +02:00
Pierre Tachoire
a9e2569a1b bench: display duration in ms 2024-04-10 09:42:15 +02:00
Pierre Tachoire
44271cac1a Merge pull request #211 from lightpanda-io/upgrade-jsruntime
upgrade jsruntime-lib
2024-04-09 09:10:10 +02:00
Pierre Tachoire
762dfe8f31 upgrade jsruntime-lib 2024-04-09 09:00:16 +02:00
Pierre Tachoire
37350b0701 ci: save and export browser bench 2024-04-08 18:08:03 +02:00
Pierre Tachoire
e3f7504572 test: rename js bench into browser 2024-04-08 18:01:38 +02:00
Pierre Tachoire
deb8490991 Merge pull request #210 from lightpanda-io/upgrade-libdom
upgrade libdom
2024-04-08 15:43:09 +02:00
Pierre Tachoire
82c5019a44 Merge pull request #208 from lightpanda-io/pi-clone
dom: fix processing instruction clone
2024-04-08 15:22:53 +02:00
Pierre Tachoire
55c747ad45 upgrade libdom 2024-04-08 15:22:00 +02:00
Pierre Tachoire
d080dde361 test: bench: use pretty for console output 2024-04-08 14:54:55 +02:00
Pierre Tachoire
32349e472c test: add test arguments and expose json benchmark result 2024-04-08 14:40:52 +02:00
Pierre Tachoire
49e3d569de dom: fix processing instruction clone 2024-04-05 16:34:22 +02:00
Pierre Tachoire
d58045c330 Merge pull request #196 from lightpanda-io/css
css: implement css query
2024-04-05 10:57:41 +02:00
Pierre Tachoire
c80ef7ca96 Merge pull request #206 from lightpanda-io/upgrade-jsruntime
upgrade jsruntime
2024-04-03 15:16:41 +02:00
Pierre Tachoire
9db39e4165 Merge pull request #205 from lightpanda-io/upgrade-libdom
upgrade libdom
2024-04-03 15:15:54 +02:00
Pierre Tachoire
1e263cfc1b Merge pull request #204 from lightpanda-io/small-build-improvements
Small build improvements
2024-04-03 15:15:42 +02:00
Pierre Tachoire
5c804f2c3d upgrade jsruntime 2024-04-03 15:05:33 +02:00
Pierre Tachoire
29ce31f2fd upgrade libdom 2024-04-03 15:03:37 +02:00
Pierre Tachoire
6e8398be96 ci: track build.zig changes 2024-04-03 15:02:07 +02:00
Pierre Tachoire
0af69fee6d build: remove deprecated usage 2024-04-03 14:58:11 +02:00
Pierre Tachoire
20f25fc352 build: remove useless getInstallStep deps
the dependance of getInstallStep is useful only if we need a previous
binary to exists before using running the step.
2024-04-03 14:55:38 +02:00
Pierre Tachoire
a2eee9a278 README: upgrade zig version 2024-04-03 14:55:30 +02:00
Pierre Tachoire
b59618120f build: remove shell installation 2024-04-03 14:55:10 +02:00
Pierre Tachoire
ff0b7ed6bf build: fix path error 2024-04-03 14:55:01 +02:00
Pierre Tachoire
18d14f8c0c Merge pull request #201 from lightpanda-io/remove-lexbor
deps: remove lexbor
2024-04-03 14:43:18 +02:00
Pierre Tachoire
22459edccc CI: remove lexbor 2024-03-28 14:56:36 +01:00
Pierre Tachoire
52d3f3e966 deps: remove lexbor 2024-03-28 11:13:17 +01:00
Pierre Tachoire
17b20e1ad0 Merge pull request #200 from lightpanda-io/upgrade-wpt-dom
upgrade wpt deps
2024-03-26 11:58:17 +01:00
Pierre Tachoire
6b621fe5ab upgrade wpt deps 2024-03-26 11:40:07 +01:00
Pierre Tachoire
8eb4de9ccb css: ensure node is an element before accessing to attr 2024-03-26 11:08:25 +01:00
Pierre Tachoire
4d5f6d42fa dom: use the css matcher for DOM 2024-03-26 10:25:50 +01:00
Pierre Tachoire
0fa49b99bf css: add README 2024-03-25 18:35:28 +01:00
Pierre Tachoire
4c50b2af1a css: implement legend siblings check for :disabled 2024-03-25 17:56:28 +01:00
Pierre Tachoire
4e61a50946 css: add isEmptyText in node interface 2024-03-25 17:56:28 +01:00
Pierre Tachoire
2c7650cdb1 css: add isDocument, isText and isComment 2024-03-25 17:38:21 +01:00
Pierre Tachoire
8a91840783 css: comment :contains test 2024-03-25 17:09:55 +01:00
Pierre Tachoire
dcc7e51556 css: implement ~, + and > combinators 2024-03-25 17:09:11 +01:00
Pierre Tachoire
565d612abb css: trim attribute op value 2024-03-25 15:40:23 +01:00
Pierre Tachoire
e7738744cb css: add libdom tests 2024-03-25 15:39:59 +01:00
Pierre Tachoire
de9d253dc9 css: implement missing pseudo classes
:input :empty :root :link :enabled :disabled :checked
2024-03-25 14:48:08 +01:00
Pierre Tachoire
2671cda98f css: implement :lang match 2024-03-25 11:43:32 +01:00
Pierre Tachoire
bd899111d5 css: implement :only-child and :only-of-type 2024-03-25 10:25:46 +01:00
Pierre Tachoire
db5d933285 css: add nth- pseudo class 2024-03-25 08:50:57 +01:00
Pierre Tachoire
9c997ec86d css: add pseudo class relative match 2024-03-19 09:25:52 +01:00
Pierre Tachoire
75e80a47e6 css: implement group, compound and start combined match 2024-03-18 21:23:37 +01:00
Pierre Tachoire
d0dbbacd69 css: enable all css tests in zig build test 2024-03-18 21:22:45 +01:00
Pierre Tachoire
a2e747002b css: use parseSelectorGroup() with parse() 2024-03-18 21:22:45 +01:00
Pierre Tachoire
5e8ec4532d css: add attribute matcher 2024-03-18 16:01:46 +01:00
Pierre Tachoire
d64fffc5b3 css: implement id and class match selector 2024-03-18 12:48:03 +01:00
Pierre Tachoire
4629e8a9eb css: check if node is an html element 2024-03-18 11:36:06 +01:00
Pierre Tachoire
7839f466ea css: refacto test 2024-03-18 11:35:47 +01:00
Pierre Tachoire
954a693586 css: add matcher test w/ libdom 2024-03-18 09:51:05 +01:00
Pierre Tachoire
b59fd9b1fb css: matcher draft 2024-03-15 16:09:16 +01:00
Pierre Tachoire
a131e96ed5 css: lower case parse function 2024-03-15 15:03:55 +01:00
Pierre Tachoire
d9c76aa13e css: extract public api on its own file 2024-03-15 09:06:59 +01:00
Pierre Tachoire
6cf805360d css: extract selector in its own file 2024-03-15 08:59:41 +01:00
Pierre Tachoire
97c8053010 css: implement css query parser 2024-03-14 16:40:35 +01:00
Pierre Tachoire
621ffc5db7 Merge pull request #195 from lightpanda-io/browser-jstrace
browser: display js err trace on debug mode
2024-03-11 16:07:47 +01:00
Pierre Tachoire
a7efadabf5 browser: display js err trace on debug mode 2024-03-08 17:42:55 +01:00
Pierre Tachoire
a81e10f093 Merge pull request #184 from lightpanda-io/window-global
window: use window as global object
2024-03-08 12:43:24 +01:00
Pierre Tachoire
886c9daa47 window: inject DocumentHTML instead of Document 2024-03-08 12:24:24 +01:00
Pierre Tachoire
500da5bfd8 test: run JSRuntime test func directly
Instead of calling the bultin test functions
Indeed, it causes issue with type comparison.
See https://github.com/lightpanda-io/browsercore/pull/184#issuecomment-1964369066
2024-03-08 12:24:24 +01:00
Pierre Tachoire
fec212ab94 window: use window as global object 2024-03-08 12:24:23 +01:00
Pierre Tachoire
9221c810a6 Merge pull request #193 from lightpanda-io/build-test
build: use test step option struct
2024-03-07 11:27:33 +01:00
Pierre Tachoire
a1af89b6a0 build: use test step option struct 2024-03-06 15:59:12 +01:00
Pierre Tachoire
b8bf09c8e5 Merge pull request #192 from lightpanda-io/upgrade-jsruntime
upgrade jsruntime
2024-02-29 16:20:57 +01:00
Pierre Tachoire
026a6c0caf upgrade jsruntime 2024-02-29 16:05:51 +01:00
Pierre Tachoire
da763bf17d Merge pull request #191 from lightpanda-io/void-elements
dump: handle void HTML elements
2024-02-29 16:04:42 +01:00
Pierre Tachoire
6777ab9f3d dump: handle void HTML elements 2024-02-29 15:50:03 +01:00
Pierre Tachoire
45172461c7 Merge pull request #182 from lightpanda-io/innerHTML
dom: innerHTML
2024-02-29 15:47:48 +01:00
Pierre Tachoire
b4da2abff2 Merge pull request #189 from lightpanda-io/browser-set-document-uri
browser: inject document URL
2024-02-29 15:47:06 +01:00
Pierre Tachoire
63e19c7704 netsurf: factorize document parsing 2024-02-29 14:14:13 +01:00
Pierre Tachoire
399c7def51 browser: inject document URL 2024-02-29 13:37:08 +01:00
Pierre Tachoire
25bc2d5e75 DOM: improve innerHTML setter test 2024-02-28 14:44:40 +01:00
Pierre Tachoire
1c77d998c6 test: refacto dump test units 2024-02-28 14:40:31 +01:00
Pierre Tachoire
810bd11a5b dump: rename HTML dump funcs 2024-02-28 14:39:22 +01:00
Pierre Tachoire
08e2365d75 Merge pull request #181 from lightpanda-io/xhr-event-delay
xhr: respect 50ms min delay between two progress events
2024-02-27 17:54:50 +01:00
Pierre Tachoire
c0e2377e16 dom: implement innerHTML setter 2024-02-27 16:11:11 +01:00
Pierre Tachoire
f7c0bcceae dom: fix replace child 2024-02-27 16:11:11 +01:00
Pierre Tachoire
37f4a9c72c dom: add innerHTML getter 2024-02-27 16:11:10 +01:00
Pierre Tachoire
64ce07340b browser: expose nodeFile and accept a io.Writer 2024-02-27 16:11:07 +01:00
Pierre Tachoire
5a70db1322 Merge pull request #183 from lightpanda-io/xhr-json
xhr: use std.json.Value to parse JSON response
2024-02-26 18:13:15 +01:00
Pierre Tachoire
d4104883ef xhr: use std.json.Value to parse JSON response 2024-02-26 18:01:07 +01:00
Pierre Tachoire
4f51f28734 upgrade jsruntime 2024-02-26 18:01:06 +01:00
Pierre Tachoire
65e8b56db4 Merge pull request #177 from lightpanda-io/upgrade-wpt
upgrade tests/wpt
2024-02-26 16:01:38 +01:00
Pierre Tachoire
5439a37d25 xhr: respect 50ms min delay between two progress events 2024-02-15 17:53:54 +01:00
Pierre Tachoire
e222d72b46 upgrade tests/wpt 2024-02-12 16:02:28 +01:00
656 changed files with 110778 additions and 12661 deletions

63
.github/actions/install/action.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: "Browsercore install"
description: "Install deps for the project browsercore"
inputs:
arch:
description: 'CPU arch used to select the v8 lib'
required: false
default: 'x86_64'
os:
description: 'OS used to select the v8 lib'
required: false
default: 'linux'
zig-v8:
description: 'zig v8 version to install'
required: false
default: 'v0.2.4'
v8:
description: 'v8 version to install'
required: false
default: '14.0.365.4'
cache-dir:
description: 'cache dir to use'
required: false
default: '~/.cache'
runs:
using: "composite"
steps:
- name: Install apt deps
if: ${{ inputs.os == 'linux' }}
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y wget xz-utils python3 ca-certificates git pkg-config libglib2.0-dev gperf libexpat1-dev cmake clang
# Zig version used from the `minimum_zig_version` field in build.zig.zon
- uses: mlugg/setup-zig@v2
# Rust Toolchain for html5ever
- uses: dtolnay/rust-toolchain@stable
- name: Cache v8
id: cache-v8
uses: actions/cache@v4
env:
cache-name: cache-v8
with:
path: ${{ inputs.cache-dir }}/v8
key: libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}_${{ inputs.zig-v8 }}.a
- if: ${{ steps.cache-v8.outputs.cache-hit != 'true' }}
shell: bash
run: |
mkdir -p ${{ inputs.cache-dir }}/v8
wget -O ${{ inputs.cache-dir }}/v8/libc_v8.a https://github.com/lightpanda-io/zig-v8-fork/releases/download/${{ inputs.zig-v8 }}/libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}.a
- name: install v8
shell: bash
run: |
mkdir -p v8
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a v8/libc_v8.a

View File

@@ -1,60 +0,0 @@
name: build-deps
on:
push:
branches:
- "main"
paths:
- "vendor/lexbor-src"
- "vendor/netsurf/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: lightpanda-io/browsercore-deps
ZIG_DOCKER_VERSION: 0.12.0-dev.1773-8a8fd47d2
jobs:
build-deps:
strategy:
matrix:
include:
- os: linux
build_arch: amd64
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
submodules: true
- name: Docker connect
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Docker build
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: Dockerfile.deps
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{env.ZIG_DOCKER_VERSION}}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
ZIG_DOCKER_VERSION=${{ env.ZIG_DOCKER_VERSION }}
platforms: ${{matrix.os}}/${{matrix.build_arch}}

196
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,196 @@
name: nightly build
env:
AWS_ACCESS_KEY_ID: ${{ vars.NIGHTLY_BUILD_AWS_ACCESS_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.NIGHTLY_BUILD_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.NIGHTLY_BUILD_AWS_BUCKET }}
AWS_REGION: ${{ vars.NIGHTLY_BUILD_AWS_REGION }}
RELEASE: ${{ github.ref_type == 'tag' && github.ref_name || 'nightly' }}
on:
push:
tags:
- '*'
schedule:
- cron: "2 2 * * *"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
permissions:
contents: write
jobs:
build-linux-x86_64:
env:
ARCH: x86_64
OS: linux
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
with:
os: ${{env.OS}}
arch: ${{env.ARCH}}
mode: 'release'
- name: v8 snapshot
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
- name: zig build
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: Rename binary
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: upload on s3
run: |
export DIR=`git show --no-patch --no-notes --pretty='%cs_%h'`
aws s3 cp --storage-class=GLACIER_IR lightpanda-${{ env.ARCH }}-${{ env.OS }} s3://lpd-nightly-build/${DIR}/lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
tag: ${{ env.RELEASE }}
makeLatest: true
build-linux-aarch64:
env:
ARCH: aarch64
OS: linux
runs-on: ubuntu-22.04-arm
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
with:
os: ${{env.OS}}
arch: ${{env.ARCH}}
mode: 'release'
- name: v8 snapshot
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
- name: zig build
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=generic -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: Rename binary
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: upload on s3
run: |
export DIR=`git show --no-patch --no-notes --pretty='%cs_%h'`
aws s3 cp --storage-class=GLACIER_IR lightpanda-${{ env.ARCH }}-${{ env.OS }} s3://lpd-nightly-build/${DIR}/lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
tag: ${{ env.RELEASE }}
makeLatest: true
build-macos-aarch64:
env:
ARCH: aarch64
OS: macos
# macos-14 runs on arm CPU. see
# https://github.com/actions/runner-images?tab=readme-ov-file
runs-on: macos-14
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
with:
os: ${{env.OS}}
arch: ${{env.ARCH}}
mode: 'release'
- name: v8 snapshot
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
- name: zig build
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: Rename binary
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: upload on s3
run: |
export DIR=`git show --no-patch --no-notes --pretty='%cs_%h'`
aws s3 cp --storage-class=GLACIER_IR lightpanda-${{ env.ARCH }}-${{ env.OS }} s3://lpd-nightly-build/${DIR}/lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
tag: ${{ env.RELEASE }}
makeLatest: true
build-macos-x86_64:
env:
ARCH: x86_64
OS: macos
runs-on: macos-14-large
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
with:
os: ${{env.OS}}
arch: ${{env.ARCH}}
mode: 'release'
- name: v8 snapshot
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
- name: zig build
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: Rename binary
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: upload on s3
run: |
export DIR=`git show --no-patch --no-notes --pretty='%cs_%h'`
aws s3 cp --storage-class=GLACIER_IR lightpanda-${{ env.ARCH }}-${{ env.OS }} s3://lpd-nightly-build/${DIR}/lightpanda-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
tag: ${{ env.RELEASE }}
makeLatest: true

34
.github/workflows/cla.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: "CLA Assistant"
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened,closed,synchronize]
permissions:
actions: write
contents: read
pull-requests: write
statuses: write
jobs:
CLAAssistant:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_GH_PAT }}
with:
path-to-signatures: 'signatures/browser/version1/cla.json'
path-to-document: 'https://github.com/lightpanda-io/browser/blob/main/CLA.md'
# branch should not be protected
branch: 'main'
allowlist: krichprollsch,francisbouvier,katie-lpd,sjorsdonkers,bornlex
remote-organization-name: lightpanda-io
remote-repository-name: cla

View File

@@ -0,0 +1,68 @@
name: e2e-integration-test
env:
LIGHTPANDA_DISABLE_TELEMETRY: true
on:
schedule:
- cron: "4 4 * * *"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
zig-build-release:
name: zig build release
runs-on: ubuntu-latest
timeout-minutes: 15
# Don't run the CI with draft PR.
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- name: zig build release
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: upload artifact
uses: actions/upload-artifact@v4
with:
name: lightpanda-build-release
path: |
zig-out/bin/lightpanda
retention-days: 1
demo-scripts:
name: demo-integration-scripts
needs: zig-build-release
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
repository: 'lightpanda-io/demo'
fetch-depth: 0
- run: npm install
- name: download artifact
uses: actions/download-artifact@v4
with:
name: lightpanda-build-release
- run: chmod a+x ./lightpanda
- name: run end to end integration tests
run: |
./lightpanda serve & echo $! > LPD.pid
go run integration/main.go
kill `cat LPD.pid`

250
.github/workflows/e2e-test.yml vendored Normal file
View File

@@ -0,0 +1,250 @@
name: e2e-test
env:
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
AWS_REGION: ${{ vars.LPD_PERF_AWS_REGION }}
LIGHTPANDA_DISABLE_TELEMETRY: true
on:
push:
branches:
- main
paths:
- "build.zig"
- "src/**/*.zig"
- "src/*.zig"
- "vendor/zig-js-runtime"
- ".github/**"
- "vendor/**"
pull_request:
# By default GH trigger on types opened, synchronize and reopened.
# see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
# Since we skip the job when the PR is in draft state, we want to force CI
# running when the PR is marked ready_for_review w/o other change.
# see https://github.com/orgs/community/discussions/25722#discussioncomment-3248917
types: [opened, synchronize, reopened, ready_for_review]
paths:
- ".github/**"
- "build.zig"
- "src/**/*.zig"
- "src/*.zig"
- "vendor/**"
- ".github/**"
- "vendor/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
zig-build-release:
name: zig build release
runs-on: ubuntu-latest
timeout-minutes: 15
# Don't run the CI with draft PR.
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
with:
mode: 'release'
- name: zig build release
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: upload artifact
uses: actions/upload-artifact@v4
with:
name: lightpanda-build-release
path: |
zig-out/bin/lightpanda
retention-days: 1
demo-scripts:
name: demo-scripts
needs: zig-build-release
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
repository: 'lightpanda-io/demo'
fetch-depth: 0
- run: npm install
- name: download artifact
uses: actions/download-artifact@v4
with:
name: lightpanda-build-release
- run: chmod a+x ./lightpanda
- name: run end to end tests
run: |
./lightpanda serve & echo $! > LPD.pid
go run runner/main.go
kill `cat LPD.pid`
- name: build proxy
run: |
cd proxy
go build
- name: run end to end tests through proxy
run: |
./proxy/proxy & echo $! > PROXY.id
./lightpanda serve --http_proxy 'http://127.0.0.1:3000' & echo $! > LPD.pid
go run runner/main.go
kill `cat LPD.pid` `cat PROXY.id`
- name: run request interception through proxy
run: |
export PROXY_USERNAME=username PROXY_PASSWORD=password
./proxy/proxy & echo $! > PROXY.id
./lightpanda serve & echo $! > LPD.pid
URL=https://demo-browser.lightpanda.io/campfire-commerce/ node puppeteer/proxy_auth.js
BASE_URL=https://demo-browser.lightpanda.io/ node playwright/proxy_auth.js
kill `cat LPD.pid` `cat PROXY.id`
cdp-and-hyperfine-bench:
name: cdp-and-hyperfine-bench
needs: zig-build-release
env:
MAX_MEMORY: 28000
MAX_AVG_DURATION: 23
LIGHTPANDA_DISABLE_TELEMETRY: true
# use a self host runner.
runs-on: lpd-bench-hetzner
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
repository: 'lightpanda-io/demo'
fetch-depth: 0
- run: npm install
- name: download artifact
uses: actions/download-artifact@v4
with:
name: lightpanda-build-release
- run: chmod a+x ./lightpanda
- name: start http
run: |
go run ws/main.go & echo $! > WS.pid
sleep 2
- name: run puppeteer
run: |
./lightpanda serve & echo $! > LPD.pid
sleep 2
RUNS=100 npm run bench-puppeteer-cdp > puppeteer.out || exit 1
cat /proc/`cat LPD.pid`/status |grep VmHWM|grep -oP '\d+' > LPD.VmHWM
kill `cat LPD.pid`
- name: puppeteer result
run: cat puppeteer.out
- name: memory regression
run: |
export LPD_VmHWM=`cat LPD.VmHWM`
echo "Peak resident set size: $LPD_VmHWM"
test "$LPD_VmHWM" -le "$MAX_MEMORY"
- name: duration regression
run: |
export PUPPETEER_AVG_DURATION=`cat puppeteer.out|grep 'avg run'|sed 's/avg run duration (ms) //'`
echo "puppeteer avg duration: $PUPPETEER_AVG_DURATION"
test "$PUPPETEER_AVG_DURATION" -le "$MAX_AVG_DURATION"
- name: json output
run: |
export AVG_DURATION=`cat puppeteer.out|grep 'avg run'|sed 's/avg run duration (ms) //'`
export TOTAL_DURATION=`cat puppeteer.out|grep 'total duration'|sed 's/total duration (ms) //'`
export LPD_VmHWM=`cat LPD.VmHWM`
echo "{\"duration_total\":${TOTAL_DURATION},\"duration_avg\":${AVG_DURATION},\"mem_peak\":${LPD_VmHWM}}" > bench.json
cat bench.json
- name: run hyperfine
run: |
hyperfine --export-json=hyperfine.json --warmup 3 --runs 20 --shell=none "./lightpanda --dump http://127.0.0.1:1234/campfire-commerce/"
- name: stop http
run: kill `cat WS.pid`
- name: write commit
run: |
echo "${{github.sha}}" > commit.txt
- name: upload artifact
uses: actions/upload-artifact@v4
with:
name: bench-results
path: |
bench.json
hyperfine.json
commit.txt
retention-days: 10
perf-fmt:
name: perf-fmt
needs: cdp-and-hyperfine-bench
# Don't execute on PR
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: ghcr.io/lightpanda-io/perf-fmt:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: download artifact
uses: actions/download-artifact@v4
with:
name: bench-results
- name: format and send json result
run: /perf-fmt cdp ${{ github.sha }} bench.json
- name: format and send json result
run: /perf-fmt hyperfine ${{ github.sha }} hyperfine.json
browser-fetch:
name: browser fetch
needs: zig-build-release
runs-on: ubuntu-latest
steps:
- name: download artifact
uses: actions/download-artifact@v4
with:
name: lightpanda-build-release
- run: chmod a+x ./lightpanda
- run: ./lightpanda fetch https://demo-browser.lightpanda.io/campfire-commerce/

View File

@@ -1,94 +1,47 @@
name: wpt
env:
ARCH: x86_64-linux
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
AWS_REGION: ${{ vars.LPD_PERF_AWS_REGION }}
LIGHTPANDA_DISABLE_TELEMETRY: true
on:
push:
branches:
- main
paths:
- "src/**/*.zig"
- "src/*.zig"
- "tests/wpt/**"
- "vendor/**"
pull_request:
schedule:
- cron: "23 2 * * *"
# By default GH trigger on types opened, synchronize and reopened.
# see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
# Since we skip the job when the PR is in draft state, we want to force CI
# running when the PR is marked ready_for_review w/o other change.
# see https://github.com/orgs/community/discussions/25722#discussioncomment-3248917
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "src/**/*.zig"
- "src/*.zig"
- "tests/wpt/**"
- "vendor/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
wpt:
name: web platform tests
# Don't run the CI with draft PR.
if: github.event.pull_request.draft == false
name: web platform tests json output
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.0-dev.1773-8a8fd47d2
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get jsruntime-lib submodules also.
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- name: install v8
run: |
mkdir -p vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/debug
ln -s /usr/local/lib/libc_v8.a vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/debug/libc_v8.a
- uses: ./.github/actions/install
mkdir -p vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/release
ln -s /usr/local/lib/libc_v8.a vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/release/libc_v8.a
- name: build wpt
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -- version
- name: install deps
run: |
ln -s /usr/local/lib/lexbor vendor/lexbor
ln -s /usr/local/lib/libiconv vendor/libiconv
ln -s /usr/local/lib/netsurf/build vendor/netsurf/build
ln -s /usr/local/lib/netsurf/lib vendor/netsurf/lib
ln -s /usr/local/lib/netsurf/include vendor/netsurf/include
- run: zig build wpt -Dengine=v8 -- --safe --summary
# For now WPT tests doesn't pass at all.
# We accept then to continue the job on failure.
# TODO remove the continue-on-error when tests will pass.
continue-on-error: true
- name: json output
run: zig build wpt -Dengine=v8 -- --safe --json > wpt.json
- name: run test with json output
run: zig-out/bin/lightpanda-wpt --json > wpt.json
- name: write commit
run: |
echo "${{github.sha}}" > commit.txt
- name: upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wpt-results
path: |
@@ -100,10 +53,9 @@ jobs:
name: perf-fmt
needs: wpt
# Don't execute on PR
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: ghcr.io/lightpanda-io/perf-fmt:latest
credentials:
@@ -112,7 +64,7 @@ jobs:
steps:
- name: download artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: wpt-results

View File

@@ -11,6 +11,8 @@ on:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- ".github/**"
- "build.zig"
- "src/**/*.zig"
- "src/*.zig"
# Allows you to run this workflow manually from the Actions tab
@@ -24,19 +26,16 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig:0.12.0-dev.1773-8a8fd47d2
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
outputs:
zig_fmt_errs: ${{ steps.fmt.outputs.zig_fmt_errs }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0
# Zig version used from the `minimum_zig_version` field in build.zig.zon
- uses: mlugg/setup-zig@v2
- name: Run zig fmt
id: fmt
run: |
@@ -55,6 +54,7 @@ jobs:
fi
echo "${delimiter}" >> "${GITHUB_OUTPUT}"
- name: Fail the job
if: steps.fmt.outputs.zig_fmt_errs != ''
run: exit 1

View File

@@ -1,16 +1,22 @@
name: zig-test
env:
ARCH: x86_64-linux
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
AWS_REGION: ${{ vars.LPD_PERF_AWS_REGION }}
on:
push:
branches:
- main
paths:
- "build.zig"
- "src/**/*.zig"
- "src/*.zig"
- "vendor/jsruntime-lib"
- "vendor/zig-js-runtime"
- ".github/**"
- "vendor/**"
pull_request:
# By default GH trigger on types opened, synchronize and reopened.
@@ -21,57 +27,72 @@ on:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- ".github/**"
- "build.zig"
- "src/**/*.zig"
- "src/*.zig"
- "vendor/**"
- ".github/**"
- "vendor/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
zig-test:
name: zig test
timeout-minutes: 15
# Don't run the CI with draft PR.
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- name: zig build test
run: METRICS=true zig build -Dprebuilt_v8_path=v8/libc_v8.a test > bench.json
- name: write commit
run: |
echo "${{github.sha}}" > commit.txt
- name: upload artifact
uses: actions/upload-artifact@v4
with:
name: bench-results
path: |
bench.json
commit.txt
retention-days: 10
bench-fmt:
name: perf-fmt
needs: zig-test
# Don't execute on PR
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.0-dev.1773-8a8fd47d2
image: ghcr.io/lightpanda-io/perf-fmt:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: download artifact
uses: actions/download-artifact@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get jsruntime-lib submodules also.
submodules: recursive
name: bench-results
- name: install v8
run: |
mkdir -p vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/debug
ln -s /usr/local/lib/libc_v8.a vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/debug/libc_v8.a
mkdir -p vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/release
ln -s /usr/local/lib/libc_v8.a vendor/jsruntime-lib/vendor/v8/${{env.ARCH}}/release/libc_v8.a
- name: install deps
run: |
ln -s /usr/local/lib/lexbor vendor/lexbor
ln -s /usr/local/lib/libiconv vendor/libiconv
ln -s /usr/local/lib/netsurf/build vendor/netsurf/build
ln -s /usr/local/lib/netsurf/lib vendor/netsurf/lib
ln -s /usr/local/lib/netsurf/include vendor/netsurf/include
- name: zig build debug
run: zig build -Dengine=v8
- name: zig build test
run: zig build test -Dengine=v8
- name: zig build release
run: zig build -Doptimize=ReleaseSafe -Dengine=v8
- name: format and send json result
run: /perf-fmt bench-browser ${{ github.sha }} bench.json

12
.gitignore vendored
View File

@@ -1,7 +1,11 @@
zig-cache
/.zig-cache/
/.lp-cache/
zig-out
/vendor/lexbor/
/vendor/netsurf/build/
/vendor/netsurf/lib/
/vendor/netsurf/include/
/vendor/netsurf/out
/vendor/libiconv/
lightpanda.id
/v8/
/build/
/src/html5ever/target/
src/snapshot.bin

33
.gitmodules vendored
View File

@@ -1,24 +1,15 @@
[submodule "vendor/jsruntime-lib"]
path = vendor/jsruntime-lib
url = git@github.com:lightpanda-io/jsruntime-lib.git
[submodule "vendor/lexbor-src"]
path = vendor/lexbor-src
url = https://github.com/lexbor/lexbor
[submodule "vendor/netsurf/libwapcaplet"]
path = vendor/netsurf/libwapcaplet
url = https://source.netsurf-browser.org/libwapcaplet.git
[submodule "vendor/netsurf/libparserutils"]
path = vendor/netsurf/libparserutils
url = https://source.netsurf-browser.org/libparserutils.git
[submodule "vendor/netsurf/libdom"]
path = vendor/netsurf/libdom
url = git@github.com:lightpanda-io/libdom.git
[submodule "vendor/netsurf/share/netsurf-buildsystem"]
path = vendor/netsurf/share/netsurf-buildsystem
url = https://source.netsurf-browser.org/buildsystem.git
[submodule "vendor/netsurf/libhubbub"]
path = vendor/netsurf/libhubbub
url = https://source.netsurf-browser.org/libhubbub.git
[submodule "tests/wpt"]
path = tests/wpt
url = https://github.com/lightpanda-io/wpt
[submodule "vendor/nghttp2"]
path = vendor/nghttp2
url = https://github.com/nghttp2/nghttp2.git
[submodule "vendor/zlib"]
path = vendor/zlib
url = https://github.com/madler/zlib.git
[submodule "vendor/curl"]
path = vendor/curl
url = https://github.com/curl/curl.git
[submodule "vendor/brotli"]
path = vendor/brotli
url = https://github.com/google/brotli

93
CLA.md Normal file
View File

@@ -0,0 +1,93 @@
# Lightpanda (Selecy SAS) Grant and Contributor License Agreement (“Agreement”)
This agreement is based on the Apache Software Foundation Contributor License
Agreement. (v r190612)
Thank you for your interest in software projects stewarded by Lightpanda
(Selecy SAS) (“Lightpanda”). In order to clarify the intellectual property
license granted with Contributions from any person or entity, Lightpanda must
have a Contributor License Agreement (CLA) on file that has been agreed to by
each Contributor, indicating agreement to the license terms below. This license
is for your protection as a Contributor as well as the protection of Lightpanda
and its users; it does not change your rights to use your own Contributions for
any other purpose. This Agreement allows an individual to contribute to
Lightpanda on that individuals own behalf, or an entity (the “Corporation”) to
submit Contributions to Lightpanda, to authorize Contributions submitted by its
designated employees to Lightpanda, and to grant copyright and patent licenses
thereto.
You accept and agree to the following terms and conditions for Your present and
future Contributions submitted to Lightpanda. Except for the license granted
herein to Lightpanda and recipients of software distributed by Lightpanda, You
reserve all right, title, and interest in and to Your Contributions.
1. Definitions. “You” (or “Your”) shall mean the copyright owner or legal
entity authorized by the copyright owner that is making this Agreement with
Lightpanda. For legal entities, the entity making a Contribution and all
other entities that control, are controlled by, or are under common control
with that entity are considered to be a single Contributor. For the purposes
of this definition, “control” means (i) the power, direct or indirect, to
cause the direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
“Contribution” shall mean any work, as well as any modifications or
additions to an existing work, that is intentionally submitted by You to
Lightpanda for inclusion in, or documentation of, any of the products owned
or managed by Lightpanda (the “Work”). For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication
sent to Lightpanda or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems (such
as GitHub), and issue tracking systems that are managed by, or on behalf of,
Lightpanda for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise designated
in writing by You as “Not a Contribution.”
2. Grant of Copyright License. Subject to the terms and conditions of this
Agreement, You hereby grant to Lightpanda and to recipients of software
distributed by Lightpanda a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable copyright license to reproduce, prepare derivative
works of, publicly display, publicly perform, sublicense, and distribute
Your Contributions and such derivative works.
3. Grant of Patent License. Subject to the terms and conditions of this
Agreement, You hereby grant to Lightpanda and to recipients of software
distributed by Lightpanda a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this section) patent license
to make, have made, use, offer to sell, sell, import, and otherwise transfer
the Work, where such license applies only to those patent claims licensable
by You that are necessarily infringed by Your Contribution(s) alone or by
combination of Your Contribution(s) with the Work to which such
Contribution(s) were submitted. If any entity institutes patent litigation
against You or any other entity (including a cross-claim or counterclaim in
a lawsuit) alleging that your Contribution, or the Work to which you have
contributed, constitutes direct or contributory patent infringement, then
any patent licenses granted to that entity under this Agreement for that
Contribution or Work shall terminate as of the date such litigation is
filed.
4. You represent that You are legally entitled to grant the above license. If
You are an individual, and if Your employer(s) has rights to intellectual
property that you create that includes Your Contributions, you represent
that You have received permission to make Contributions on behalf of that
employer, or that Your employer has waived such rights for your
Contributions to Lightpanda. If You are a Corporation, any individual who
makes a contribution from an account associated with You will be considered
authorized to Contribute on Your behalf.
5. You represent that each of Your Contributions is Your original creation (see
section 7 for submissions on behalf of others).
6. You are not expected to provide support for Your Contributions,except to the
extent You desire to provide support. You may provide support for free, for
a fee, or not at all. Unless required by applicable law or agreed to in
writing, You provide Your Contributions on an “AS IS” BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
7. Should You wish to submit work that is not Your original creation, You may
submit it to Lightpanda separately from any Contribution, identifying the
complete details of its source and of any license or other restriction
(including, but not limited to, related patents, trademarks, and license
agreements) of which you are personally aware, and conspicuously marking the
work as “Submitted on behalf of a third-party: [named here]”.

10
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,10 @@
# Contributing
Lightpanda accepts pull requests through GitHub.
You have to sign our [CLA](CLA.md) during your first pull request process
otherwise we're not able to accept your contributions.
The process signature uses the [CLA assistant
lite](https://github.com/marketplace/actions/cla-assistant-lite). You can see
an example of the process in [#303](https://github.com/lightpanda-io/browser/pull/303).

81
Dockerfile Normal file
View File

@@ -0,0 +1,81 @@
FROM debian:stable-slim
ARG MINISIG=0.12
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
ARG V8=14.0.365.4
ARG ZIG_V8=v0.2.4
ARG TARGETPLATFORM
RUN apt-get update -yq && \
apt-get install -yq xz-utils ca-certificates \
clang make curl git
# Get Rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- --profile minimal -y
ENV PATH="/root/.cargo/bin:${PATH}"
# install minisig
RUN curl --fail -L -O https://github.com/jedisct1/minisign/releases/download/${MINISIG}/minisign-${MINISIG}-linux.tar.gz && \
tar xvzf minisign-${MINISIG}-linux.tar.gz -C /
# clone lightpanda
RUN git clone https://github.com/lightpanda-io/browser.git
WORKDIR /browser
# install zig
RUN ZIG=$(grep '\.minimum_zig_version = "' "build.zig.zon" | cut -d'"' -f2) && \
case $TARGETPLATFORM in \
"linux/arm64") ARCH="aarch64" ;; \
*) ARCH="x86_64" ;; \
esac && \
curl --fail -L -O https://ziglang.org/download/${ZIG}/zig-${ARCH}-linux-${ZIG}.tar.xz && \
curl --fail -L -O https://ziglang.org/download/${ZIG}/zig-${ARCH}-linux-${ZIG}.tar.xz.minisig && \
/minisign-linux/${ARCH}/minisign -Vm zig-${ARCH}-linux-${ZIG}.tar.xz -P ${ZIG_MINISIG} && \
tar xvf zig-${ARCH}-linux-${ZIG}.tar.xz && \
mv zig-${ARCH}-linux-${ZIG} /usr/local/lib && \
ln -s /usr/local/lib/zig-${ARCH}-linux-${ZIG}/zig /usr/local/bin/zig
# install deps
RUN git submodule init && \
git submodule update --recursive
# download and install v8
RUN case $TARGETPLATFORM in \
"linux/arm64") ARCH="aarch64" ;; \
*) ARCH="x86_64" ;; \
esac && \
curl --fail -L -o libc_v8.a https://github.com/lightpanda-io/zig-v8-fork/releases/download/${ZIG_V8}/libc_v8_${V8}_linux_${ARCH}.a && \
mkdir -p v8/ && \
mv libc_v8.a v8/libc_v8.a
# build v8 snapshot
RUN zig build -Doptimize=ReleaseFast \
-Dprebuilt_v8_path=v8/libc_v8.a \
snapshot_creator -- src/snapshot.bin
# build release
RUN zig build -Doptimize=ReleaseFast \
-Dsnapshot_path=../../snapshot.bin \
-Dprebuilt_v8_path=v8/libc_v8.a \
-Dgit_commit=$(git rev-parse --short HEAD)
FROM debian:stable-slim
RUN apt-get update -yq && \
apt-get install -yq tini
FROM debian:stable-slim
# copy ca certificates
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=0 /browser/zig-out/bin/lightpanda /bin/lightpanda
COPY --from=1 /usr/bin/tini /usr/bin/tini
EXPOSE 9222/tcp
# Lightpanda install only some signal handlers, and PID 1 doesn't have a default SIGTERM signal handler.
# Using "tini" as PID1 ensures that signals work as expected, so e.g. "docker stop" will not hang.
# (See https://github.com/krallin/tini#why-tini).
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/bin/lightpanda", "serve", "--host", "0.0.0.0", "--port", "9222", "--log_level", "info"]

View File

@@ -1,34 +0,0 @@
# This dockerfile is used to build browsercore vendor dependencies except
# jsruntime-lib v8.
# jsruntime-lib v8 is built via zig-v8-fork/Dockerfile.
ARG ZIG_DOCKER_VERSION=0.11.0
FROM ghcr.io/lightpanda-io/zig:${ZIG_DOCKER_VERSION} as build
# Install required dependencies
RUN apt update && \
apt install -y git curl bash xz-utils python3 ca-certificates pkg-config \
libglib2.0-dev gperf libexpat1-dev cmake build-essential
COPY ./Makefile /src/
WORKDIR /src
# build lexbor
ADD ./vendor/lexbor-src /src/vendor/lexbor-src
RUN make install-lexbor
# build libiconv
RUN make install-libiconv
# build netsurf
ADD ./vendor/netsurf /src/vendor/netsurf
RUN make install-netsurf
FROM scratch as artifact
COPY --from=build /src/vendor/libiconv /usr/local/lib/libiconv
COPY --from=build /src/vendor/lexbor /usr/local/lib/lexbor
COPY --from=build /src/vendor/netsurf/build /usr/local/lib/netsurf/build
COPY --from=build /src/vendor/netsurf/lib /usr/local/lib/netsurf/lib
COPY --from=build /src/vendor/netsurf/include /usr/local/lib/netsurf/include

661
LICENSE Normal file
View File

@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

14
LICENSING.md Normal file
View File

@@ -0,0 +1,14 @@
# Licensing
License names used in this document are as per [SPDX License
List](https://spdx.org/licenses/).
The default license for this project is [AGPL-3.0-only](LICENSE).
The following directories and their subdirectories are licensed under their
original upstream licenses:
```
vendor/
tests/wpt/
```

220
Makefile
View File

@@ -3,6 +3,30 @@
ZIG := zig
BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
# option test filter make test F="server"
F=
# OS and ARCH
kernel = $(shell uname -ms)
ifeq ($(kernel), Darwin arm64)
OS := macos
ARCH := aarch64
else ifeq ($(kernel), Darwin x86_64)
OS := macos
ARCH := x86_64
else ifeq ($(kernel), Linux aarch64)
OS := linux
ARCH := aarch64
else ifeq ($(kernel), Linux arm64)
OS := linux
ARCH := aarch64
else ifeq ($(kernel), Linux x86_64)
OS := linux
ARCH := x86_64
else
$(error "Unhandled kernel: $(kernel)")
endif
# Infos
# -----
@@ -10,7 +34,7 @@ BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
## Display this help screen
help:
@printf "\e[36m%-35s %s\e[0m\n" "Command" "Usage"
@printf "\033[36m%-35s %s\033[0m\n" "Command" "Usage"
@sed -n -e '/^## /{'\
-e 's/## //g;'\
-e 'h;'\
@@ -23,173 +47,75 @@ help:
# $(ZIG) commands
# ------------
.PHONY: build build-release run run-release shell test bench download-zig wpt
.PHONY: build build-v8-snapshot build-dev run run-release shell test bench wpt data end2end
zig_version = $(shell grep 'recommended_zig_version = "' "vendor/jsruntime-lib/build.zig" | cut -d'"' -f2)
kernel = $(shell uname -ms)
## Build v8 snapshot
build-v8-snapshot:
@printf "\033[36mBuilding v8 snapshot (release safe)...\033[0m\n"
@$(ZIG) build -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
@printf "\033[33mBuild OK\033[0m\n"
## Download the zig recommended version
download-zig:
ifeq ($(kernel), Darwin x86_64)
$(eval target="macos")
$(eval arch="x86_64")
else ifeq ($(kernel), Darwin arm64)
$(eval target="macos")
$(eval arch="aarch64")
else ifeq ($(kernel), Linux aarch64)
$(eval target="linux")
$(eval arch="aarch64")
else ifeq ($(kernel), Linux arm64)
$(eval target="linux")
$(eval arch="aarch64")
else ifeq ($(kernel), Linux x86_64)
$(eval target="linux")
$(eval arch="x86_64")
else
$(error "Unhandled kernel: $(kernel)")
endif
$(eval url = "https://ziglang.org/builds/zig-$(target)-$(arch)-$(zig_version).tar.xz")
$(eval dest = "/tmp/zig-$(target)-$(arch)-$(zig_version).tar.xz")
@printf "\e[36mDownload zig version $(zig_version)...\e[0m\n"
@curl -o "$(dest)" -L "$(url)" || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\e[33mDownloaded $(dest)\e[0m\n"
## Build in release-fast mode
build: build-v8-snapshot
@printf "\033[36mBuilding (release safe)...\033[0m\n"
@$(ZIG) build -Doptimize=ReleaseFast -Dsnapshot_path=../../snapshot.bin -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
@printf "\033[33mBuild OK\033[0m\n"
## Build in debug mode
build:
@printf "\e[36mBuilding (debug)...\e[0m\n"
@$(ZIG) build -Dengine=v8 || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\e[33mBuild OK\e[0m\n"
build-dev:
@printf "\033[36mBuilding (debug)...\033[0m\n"
@$(ZIG) build -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
@printf "\033[33mBuild OK\033[0m\n"
build-release:
@printf "\e[36mBuilding (release safe)...\e[0m\n"
@$(ZIG) build -Doptimize=ReleaseSafe -Dengine=v8 || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\e[33mBuild OK\e[0m\n"
## Run the server
## Run the server in release mode
run: build
@printf "\e[36mRunning...\e[0m\n"
@./zig-out/bin/browsercore || (printf "\e[33mRun ERROR\e[0m\n"; exit 1;)
@printf "\033[36mRunning...\033[0m\n"
@./zig-out/bin/lightpanda || (printf "\033[33mRun ERROR\033[0m\n"; exit 1;)
## Run a JS shell in release-safe mode
## Run the server in debug mode
run-debug: build-dev
@printf "\033[36mRunning...\033[0m\n"
@./zig-out/bin/lightpanda || (printf "\033[33mRun ERROR\033[0m\n"; exit 1;)
## Run a JS shell in debug mode
shell:
@printf "\e[36mBuilding shell...\e[0m\n"
@$(ZIG) build shell -Dengine=v8 || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\033[36mBuilding shell...\033[0m\n"
@$(ZIG) build shell || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
## Run WPT tests
wpt:
@printf "\e[36mBuilding wpt...\e[0m\n"
@$(ZIG) build wpt -Dengine=v8 -- --safe $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\033[36mBuilding wpt...\033[0m\n"
@$(ZIG) build wpt -- $(filter-out $@,$(MAKECMDGOALS)) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
wpt-summary:
@printf "\e[36mBuilding wpt...\e[0m\n"
@$(ZIG) build wpt -Dengine=v8 -- --safe --summary $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\033[36mBuilding wpt...\033[0m\n"
@$(ZIG) build wpt -- --summary $(filter-out $@,$(MAKECMDGOALS)) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
## Test
## Test - `grep` is used to filter out the huge compile command on build
ifeq ($(OS), macos)
test:
@printf "\e[36mTesting...\e[0m\n"
@$(ZIG) build test -Dengine=v8 || (printf "\e[33mTest ERROR\e[0m\n"; exit 1;)
@printf "\e[33mTest OK\e[0m\n"
@script -q /dev/null sh -c 'TEST_FILTER="${F}" $(ZIG) build test -freference-trace' 2>&1 \
| grep --line-buffered -v "^/.*zig test -freference-trace"
else
test:
@script -qec 'TEST_FILTER="${F}" $(ZIG) build test -freference-trace' /dev/null 2>&1 \
| grep --line-buffered -v "^/.*zig test -freference-trace"
endif
## Run demo/runner end to end tests
end2end:
@test -d ../demo
cd ../demo && go run runner/main.go
# Install and build required dependencies commands
# ------------
.PHONY: install-submodule
.PHONY: install-lexbor install-jsruntime install-jsruntime-dev install-libiconv
.PHONY: _install-netsurf install-netsurf clean-netsurf test-netsurf install-netsurf-dev
.PHONY: install-dev install
.PHONY: install
## Install and build dependencies for release
install: install-submodule install-lexbor install-jsruntime install-netsurf
install: install-submodule
## Install and build dependencies for dev
install-dev: install-submodule install-lexbor install-jsruntime-dev install-netsurf-dev
install-netsurf-dev: _install-netsurf
install-netsurf-dev: OPTCFLAGS := -O0 -g -DNDEBUG
install-netsurf: _install-netsurf
install-netsurf: OPTCFLAGS := -DNDEBUG
BC_NS := $(BC)vendor/netsurf
ICONV := $(BC)vendor/libiconv
# TODO: add Linux iconv path (I guess it depends on the distro)
# TODO: this way of linking libiconv is not ideal. We should have a more generic way
# and stick to a specif version. Maybe build from source. Anyway not now.
_install-netsurf: install-libiconv
@printf "\e[36mInstalling NetSurf...\e[0m\n" && \
ls $(ICONV) 1> /dev/null || (printf "\e[33mERROR: you need to install libiconv in your system (on MacOS on with Homebrew)\e[0m\n"; exit 1;) && \
export PREFIX=$(BC_NS) && \
export OPTLDFLAGS="-L$(ICONV)/lib" && \
export OPTCFLAGS="$(OPTCFLAGS) -I$(ICONV)/include" && \
printf "\e[33mInstalling libwapcaplet...\e[0m\n" && \
cd vendor/netsurf/libwapcaplet && \
BUILDDIR=$(BC_NS)/build/libwapcaplet make install && \
cd ../libparserutils && \
printf "\e[33mInstalling libparserutils...\e[0m\n" && \
BUILDDIR=$(BC_NS)/build/libparserutils make install && \
cd ../libhubbub && \
printf "\e[33mInstalling libhubbub...\e[0m\n" && \
BUILDDIR=$(BC_NS)/build/libhubbub make install && \
rm src/treebuilder/autogenerated-element-type.c && \
cd ../libdom && \
printf "\e[33mInstalling libdom...\e[0m\n" && \
BUILDDIR=$(BC_NS)/build/libdom make install && \
printf "\e[33mRunning libdom example...\e[0m\n" && \
cd examples && \
zig cc \
-I$(ICONV)/include \
-I$(BC_NS)/include \
-L$(ICONV)/lib \
-L$(BC_NS)/lib \
-liconv \
-ldom \
-lhubbub \
-lparserutils \
-lwapcaplet \
-o a.out \
dom-structure-dump.c \
$(ICONV)/lib/libiconv.a && \
./a.out > /dev/null && \
rm a.out && \
printf "\e[36mDone NetSurf $(OS)\e[0m\n"
clean-netsurf:
@printf "\e[36mCleaning NetSurf build...\e[0m\n" && \
cd vendor/netsurf && \
rm -R build && \
rm -R lib && \
rm -R include
test-netsurf:
@printf "\e[36mTesting NetSurf...\e[0m\n" && \
export PREFIX=$(BC_NS) && \
export LDFLAGS="-L$(ICONV)/lib -L$(BC_NS)/lib" && \
export CFLAGS="-I$(ICONV)/include -I$(BC_NS)/include" && \
cd vendor/netsurf/libdom && \
BUILDDIR=$(BC_NS)/build/libdom make test
install-libiconv:
ifeq ("$(wildcard vendor/libiconv/lib/libiconv.a)","")
@mkdir -p vendor/libiconv
@cd vendor/libiconv && \
curl https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz | tar -xvzf -
@cd vendor/libiconv/libiconv-1.17 && \
./configure --prefix=$(BC)vendor/libiconv --enable-static && \
make && make install
endif
install-lexbor:
@mkdir -p vendor/lexbor
@cd vendor/lexbor && \
cmake ../lexbor-src -DLEXBOR_BUILD_SHARED=OFF && \
make
install-jsruntime-dev:
@cd vendor/jsruntime-lib && \
make install-dev
install-jsruntime:
@cd vendor/jsruntime-lib && \
make install
data:
cd src/data && go run public_suffix_list_gen.go > public_suffix_list.zig
## Init and update git submodule
install-submodule:

327
README.md
View File

@@ -1,115 +1,316 @@
# Browsercore
<p align="center">
<a href="https://lightpanda.io"><img src="https://cdn.lightpanda.io/assets/images/logo/lpd-logo.png" alt="Logo" height=170></a>
</p>
## Build
<h1 align="center">Lightpanda Browser</h1>
<p align="center"><a href="https://lightpanda.io/">lightpanda.io</a></p>
<div align="center">
[![License](https://img.shields.io/github/license/lightpanda-io/browser)](https://github.com/lightpanda-io/browser/blob/main/LICENSE)
[![Twitter Follow](https://img.shields.io/twitter/follow/lightpanda_io)](https://twitter.com/lightpanda_io)
[![GitHub stars](https://img.shields.io/github/stars/lightpanda-io/browser)](https://github.com/lightpanda-io/browser)
</div>
Lightpanda is the open-source browser made for headless usage:
- Javascript execution
- Support of Web APIs (partial, WIP)
- Compatible with Playwright[^1], Puppeteer, chromedp through [CDP](https://chromedevtools.github.io/devtools-protocol/)
Fast web automation for AI agents, LLM training, scraping and testing:
- Ultra-low memory footprint (9x less than Chrome)
- Exceptionally fast execution (11x faster than Chrome)
- Instant startup
[<img width="350px" src="https://cdn.lightpanda.io/assets/images/github/execution-time.svg">
](https://github.com/lightpanda-io/demo)
&emsp;
[<img width="350px" src="https://cdn.lightpanda.io/assets/images/github/memory-frame.svg">
](https://github.com/lightpanda-io/demo)
</div>
_Puppeteer requesting 100 pages from a local website on a AWS EC2 m5.large instance.
See [benchmark details](https://github.com/lightpanda-io/demo)._
[^1]: **Playwright support disclaimer:**
Due to the nature of Playwright, a script that works with the current version of the browser may not function correctly with a future version. Playwright uses an intermediate JavaScript layer that selects an execution strategy based on the browser's available features. If Lightpanda adds a new [Web API](https://developer.mozilla.org/en-US/docs/Web/API), Playwright may choose to execute different code for the same script. This new code path could attempt to use features that are not yet implemented. Lightpanda makes an effort to add compatibility tests, but we can't cover all scenarios. If you encounter an issue, please create a [GitHub issue](https://github.com/lightpanda-io/browser/issues) and include the last known working version of the script.
## Quick start
### Install
**Install from the nightly builds**
You can download the last binary from the [nightly
builds](https://github.com/lightpanda-io/browser/releases/tag/nightly) for
Linux x86_64 and MacOS aarch64.
*For Linux*
```console
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux && \
chmod a+x ./lightpanda
```
*For MacOS*
```console
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-aarch64-macos && \
chmod a+x ./lightpanda
```
*For Windows + WSL2*
The Lightpanda browser is compatible to run on windows inside WSL. Follow the Linux instruction for installation from a WSL terminal.
It is recommended to install clients like Puppeteer on the Windows host.
**Install from Docker**
Lightpanda provides [official Docker
images](https://hub.docker.com/r/lightpanda/browser) for both Linux amd64 and
arm64 architectures.
The following command fetches the Docker image and starts a new container exposing Lightpanda's CDP server on port `9222`.
```console
docker run -d --name lightpanda -p 9222:9222 lightpanda/browser:nightly
```
### Dump a URL
```console
./lightpanda fetch --dump https://lightpanda.io
```
```console
info(browser): GET https://lightpanda.io/ http.Status.ok
info(browser): fetch script https://api.website.lightpanda.io/js/script.js: http.Status.ok
info(browser): eval remote https://api.website.lightpanda.io/js/script.js: TypeError: Cannot read properties of undefined (reading 'pushState')
<!DOCTYPE html>
```
### Start a CDP server
```console
./lightpanda serve --host 127.0.0.1 --port 9222
```
```console
info(websocket): starting blocking worker to listen on 127.0.0.1:9222
info(server): accepting new conn...
```
Once the CDP server started, you can run a Puppeteer script by configuring the
`browserWSEndpoint`.
```js
'use strict'
import puppeteer from 'puppeteer-core';
// use browserWSEndpoint to pass the Lightpanda's CDP server address.
const browser = await puppeteer.connect({
browserWSEndpoint: "ws://127.0.0.1:9222",
});
// The rest of your script remains the same.
const context = await browser.createBrowserContext();
const page = await context.newPage();
// Dump all the links from the page.
await page.goto('https://wikipedia.com/');
const links = await page.evaluate(() => {
return Array.from(document.querySelectorAll('a')).map(row => {
return row.getAttribute('href');
});
});
console.log(links);
await page.close();
await context.close();
await browser.disconnect();
```
### Telemetry
By default, Lightpanda collects and sends usage telemetry. This can be disabled by setting an environment variable `LIGHTPANDA_DISABLE_TELEMETRY=true`. You can read Lightpanda's privacy policy at: [https://lightpanda.io/privacy-policy](https://lightpanda.io/privacy-policy).
## Status
Lightpanda is in Beta and currently a work in progress. Stability and coverage are improving and many websites now work.
You may still encounter errors or crashes. Please open an issue with specifics if so.
Here are the key features we have implemented:
- [x] HTTP loader ([Libcurl](https://curl.se/libcurl/))
- [x] HTML parser ([html5ever](https://github.com/servo/html5ever))
- [x] DOM tree
- [x] Javascript support ([v8](https://v8.dev/))
- [x] DOM APIs
- [x] Ajax
- [x] XHR API
- [x] Fetch API
- [x] DOM dump
- [x] CDP/websockets server
- [x] Click
- [x] Input form
- [x] Cookies
- [x] Custom HTTP headers
- [x] Proxy support
- [x] Network interception
NOTE: There are hundreds of Web APIs. Developing a browser (even just for headless mode) is a huge task. Coverage will increase over time.
You can also follow the progress of our Javascript support in our dedicated [zig-js-runtime](https://github.com/lightpanda-io/zig-js-runtime#development) project.
## Build from sources
### Prerequisites
Browsercore is written with [Zig](https://ziglang.org/) `0.11.0`. You have to
Lightpanda is written with [Zig](https://ziglang.org/) `0.15.2`. You have to
install it with the right version in order to build the project.
Browsercore also depends on
[js-runtimelib](https://github.com/francisbouvier/jsruntime-lib/) and
[lexbor](https://github.com/lexbor/lexbor) libs.
Lightpanda also depends on
[zig-js-runtime](https://github.com/lightpanda-io/zig-js-runtime/) (with v8),
[Libcurl](https://curl.se/libcurl/) and [html5ever](https://github.com/servo/html5ever).
To be able to build the v8 engine for js-runtimelib, you have to install some libs:
To be able to build the v8 engine for zig-js-runtime, you have to install some libs:
For **Debian/Ubuntu based Linux**:
For Debian/Ubuntu based Linux:
```
sudo apt install xz-utils \
python3 ca-certificates git \
pkg-config libglib2.0-dev \
gperf libexpat1-dev \
cmake clang
sudo apt install xz-utils ca-certificates \
clang make curl git
```
You also need to [install Rust](https://rust-lang.org/tools/install/).
For systems with [**Nix**](https://nixos.org/download/), you can use the devShell:
```
nix develop
```
For MacOS, you only need Python 3 and cmake.
For **MacOS**, you need cmake and [Rust](https://rust-lang.org/tools/install/).
```
brew install cmake
```
To be able to build lexbor, you need to install also `cmake`.
### Install Git submodules
### Install and build dependencies
The project uses git submodules for dependencies.
The project uses git submodule for dependencies.
The `make install-submodule` will init and update the submodules in the `vendor/`
directory.
To init or update the submodules in the `vendor/` directory:
```
make install-submodule
```
### Build netsurf
This is an alias for `git submodule init && git submodule update`.
The command `make install-netsurf` will build netsurf libs used by browsercore.
### Build and run
You an build the entire browser with `make build` or `make build-dev` for debug
env.
But you can directly use the zig command: `zig build run`.
#### Embed v8 snapshot
Lighpanda uses v8 snapshot. By default, it is created on startup but you can
embed it by using the following commands:
Generate the snapshot.
```
make install-netsurf
zig build snapshot_creator -- src/snapshot.bin
```
### Build lexbor
The command `make install-lexbor` will build lexbor lib used by browsercore.
Build using the snapshot binary.
```
make install-lexbor
zig build -Dsnapshot_path=../../snapshot.bin
```
### Build jsruntime-lib
The command `make install-jsruntime-dev` uses jsruntime-lib's `zig-v8` dependency to build v8 engine lib.
Be aware the build task is very long and cpu consuming.
Build v8 engine for debug/dev version, it creates
`vendor/jsruntime-lib/vendor/v8/$ARCH/debug/libc_v8.a` file.
```
make install-jsruntime-dev
```
You should also build a release vesion of v8 with:
```
make install-jsruntime
```
### All in one build
You can run `make intall` and `make install-dev` to install deps all in one.
See [#1279](https://github.com/lightpanda-io/browser/pull/1279) for more details.
## Test
### Unit Tests
You can test browsercore by running `make test`.
You can test Lightpanda by running `make test`.
### End to end tests
To run end to end tests, you need to clone the [demo
repository](https://github.com/lightpanda-io/demo) into `../demo` dir.
You have to install the [demo's node
requirements](https://github.com/lightpanda-io/demo?tab=readme-ov-file#dependencies-1)
You also need to install [Go](https://go.dev) > v1.24.
```
make end2end
```
### Web Platform Tests
Browsercore is tested against the standardized [Web Platform
Lightpanda is tested against the standardized [Web Platform
Tests](https://web-platform-tests.org/).
The relevant tests cases for Browsercore are commit with the project.
All the tests cases executed are located in `tests/wpt` dir and come from an
external repository: https://github.com/lightpanda-io/wpt
The relevant tests cases are committed in a [dedicated repository](https://github.com/lightpanda-io/wpt) which is fetched by the `make install-submodule` command.
All the tests cases executed are located in the `tests/wpt` sub-directory.
For reference, you can easily execute a WPT test case with your browser via
[wpt.live](https://wpt.live).
*Run WPT test suite*
#### Run WPT test suite
To run all the tests:
You can run all the test.
The runner execute all the tests ending with `.html`.
```
make wpt
```
Or one specific test by using a suffix.
Or one specific test:
```
make wpt Node-childNodes.html
```
*Add a new WPT test case*
#### Add a new WPT test case
We add new tests cases files with implemented changes in Browsercore.
We add new relevant tests cases files when we implemented changes in Lightpanda.
Copy the test case you want to add from the [WPT
repo](https://github.com/web-platform-tests/wpt) into `tests/wpt` dir, commit
the files in the https://github.com/lightpanda-io/wpt repository and update the
git submodule in browsercore.
To add a new test, copy the file you want from the [WPT
repo](https://github.com/web-platform-tests/wpt) into the `tests/wpt` directory.
:warning: Please keep the original directory tree structure into `tests/wpt`.
:warning: Please keep the original directory tree structure of `tests/wpt`.
## Contributing
Lightpanda accepts pull requests through GitHub.
You have to sign our [CLA](CLA.md) during the pull request process otherwise
we're not able to accept your contributions.
## Why?
### Javascript execution is mandatory for the modern web
In the good old days, scraping a webpage was as easy as making an HTTP request, cURL-like. Its not possible anymore, because Javascript is everywhere, like it or not:
- Ajax, Single Page App, infinite loading, “click to display”, instant search, etc.
- JS web frameworks: React, Vue, Angular & others
### Chrome is not the right tool
If we need Javascript, why not use a real web browser? Take a huge desktop application, hack it, and run it on the server. Hundreds or thousands of instances of Chrome if you use it at scale. Are you sure its such a good idea?
- Heavy on RAM and CPU, expensive to run
- Hard to package, deploy and maintain at scale
- Bloated, lots of features are not useful in headless usage
### Lightpanda is built for performance
If we want both Javascript and performance in a true headless browser, we need to start from scratch. Not another iteration of Chromium, really from a blank page. Crazy right? But thats what we did:
- Not based on Chromium, Blink or WebKit
- Low-level system programming language (Zig) with optimisations in mind
- Opinionated: without graphical rendering

893
build.zig
View File

@@ -1,178 +1,743 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const builtin = @import("builtin");
const jsruntime_path = "vendor/jsruntime-lib/";
const jsruntime = @import("vendor/jsruntime-lib/build.zig");
const jsruntime_pkgs = jsruntime.packages(jsruntime_path);
/// Do not rename this constant. It is scanned by some scripts to determine
/// which zig version to install.
const recommended_zig_version = jsruntime.recommended_zig_version;
pub fn build(b: *std.build.Builder) !void {
switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) {
.eq => {},
.lt => {
@compileError("The minimum version of Zig required to compile is '" ++ recommended_zig_version ++ "', found '" ++ builtin.zig_version_string ++ "'.");
},
.gt => {
std.debug.print(
"WARNING: Recommended Zig version '{s}', but found '{s}', build may fail...\n\n",
.{ recommended_zig_version, builtin.zig_version_string },
);
},
}
const Build = std.Build;
pub fn build(b: *Build) !void {
const target = b.standardTargetOptions(.{});
const mode = b.standardOptimizeOption(.{});
const optimize = b.standardOptimizeOption(.{});
const options = try jsruntime.buildOptions(b);
const manifest = Manifest.init(b);
// browser
// -------
const git_commit = b.option([]const u8, "git_commit", "Current git commit");
const prebuilt_v8_path = b.option([]const u8, "prebuilt_v8_path", "Path to prebuilt libc_v8.a");
const snapshot_path = b.option([]const u8, "snapshot_path", "Path to v8 snapshot");
// compile and install
const exe = b.addExecutable(.{
.name = "browsercore",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = mode,
});
try common(exe, options);
b.installArtifact(exe);
var opts = b.addOptions();
opts.addOption([]const u8, "version", manifest.version);
opts.addOption([]const u8, "git_commit", git_commit orelse "dev");
opts.addOption(?[]const u8, "snapshot_path", snapshot_path);
// run
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer");
const enable_csan = b.option(std.zig.SanitizeC, "csan", "Enable C Sanitizers");
// step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const lightpanda_module = blk: {
const mod = b.addModule("lightpanda", .{
.root_source_file = b.path("src/lightpanda.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.link_libcpp = true,
.sanitize_c = enable_csan,
.sanitize_thread = enable_tsan,
});
mod.addImport("lightpanda", mod); // allow circular "lightpanda" import
// shell
// -----
try addDependencies(b, mod, opts, prebuilt_v8_path);
// compile and install
const shell = b.addExecutable(.{
.name = "browsercore-shell",
.root_source_file = .{ .path = "src/main_shell.zig" },
.target = target,
.optimize = mode,
});
try common(shell, options);
try jsruntime_pkgs.add_shell(shell);
// do not install shell binary
b.installArtifact(shell);
// run
const shell_cmd = b.addRunArtifact(shell);
shell_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
shell_cmd.addArgs(args);
}
// step
const shell_step = b.step("shell", "Run JS shell");
shell_step.dependOn(&shell_cmd.step);
// test
// ----
// compile
const tests = b.addTest(.{ .root_source_file = .{ .path = "src/run_tests.zig" } });
try common(tests, options);
tests.single_threaded = true;
tests.test_runner = "src/test_runner.zig";
const run_tests = b.addRunArtifact(tests);
// step
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
// wpt
// -----
// compile and install
const wpt = b.addExecutable(.{
.name = "browsercore-wpt",
.root_source_file = .{ .path = "src/main_wpt.zig" },
.target = target,
.optimize = mode,
});
try common(wpt, options);
b.installArtifact(wpt);
// run
const wpt_cmd = b.addRunArtifact(wpt);
wpt_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
wpt_cmd.addArgs(args);
}
// step
const wpt_step = b.step("wpt", "WPT tests");
wpt_step.dependOn(&wpt_cmd.step);
// get
// -----
// compile and install
const get = b.addExecutable(.{
.name = "browsercore-get",
.root_source_file = .{ .path = "src/main_get.zig" },
.target = target,
.optimize = mode,
});
try common(get, options);
b.installArtifact(get);
// run
const get_cmd = b.addRunArtifact(get);
get_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
get_cmd.addArgs(args);
}
// step
const get_step = b.step("get", "request URL");
get_step.dependOn(&get_cmd.step);
}
fn common(
step: *std.Build.CompileStep,
options: jsruntime.Options,
) !void {
try jsruntime_pkgs.add(step, options);
linkLexbor(step);
linkNetSurf(step);
}
fn linkLexbor(step: *std.build.LibExeObjStep) void {
// cmake . -DLEXBOR_BUILD_SHARED=OFF
const lib_path = "vendor/lexbor/liblexbor_static.a";
step.addObjectFile(.{ .path = lib_path });
step.addIncludePath(.{ .path = "vendor/lexbor-src/source" });
}
fn linkNetSurf(step: *std.build.LibExeObjStep) void {
// iconv
step.addObjectFile(.{ .path = "vendor/libiconv/lib/libiconv.a" });
step.addIncludePath(.{ .path = "vendor/libiconv/include" });
// netsurf libs
const ns = "vendor/netsurf/";
const libs: [4][]const u8 = .{
"libdom",
"libhubbub",
"libparserutils",
"libwapcaplet",
break :blk mod;
};
inline for (libs) |lib| {
step.addObjectFile(.{ .path = ns ++ "/lib/" ++ lib ++ ".a" });
step.addIncludePath(.{ .path = ns ++ lib ++ "/src" });
{
// browser
const exe = b.addExecutable(.{
.name = "lightpanda",
.use_llvm = true,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.sanitize_c = enable_csan,
.sanitize_thread = enable_tsan,
.imports = &.{
.{ .name = "lightpanda", .module = lightpanda_module },
},
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
{
// snapshot creator
const exe = b.addExecutable(.{
.name = "lightpanda-snapshot-creator",
.use_llvm = true,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main_snapshot_creator.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "lightpanda", .module = lightpanda_module },
},
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("snapshot_creator", "Generate a v8 snapshot");
run_step.dependOn(&run_cmd.step);
}
{
// test
const tests = b.addTest(.{
.root_module = lightpanda_module,
.use_llvm = true,
.test_runner = .{ .path = b.path("src/test_runner.zig"), .mode = .simple },
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
}
{
// browser
const exe = b.addExecutable(.{
.name = "legacy_test",
.use_llvm = true,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main_legacy_test.zig"),
.target = target,
.optimize = optimize,
.sanitize_c = enable_csan,
.sanitize_thread = enable_tsan,
.imports = &.{
.{ .name = "lightpanda", .module = lightpanda_module },
},
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("legacy_test", "Run the app");
run_step.dependOn(&run_cmd.step);
}
{
// wpt
const exe = b.addExecutable(.{
.name = "lightpanda-wpt",
.use_llvm = true,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main_wpt.zig"),
.target = target,
.optimize = optimize,
.sanitize_c = enable_csan,
.sanitize_thread = enable_tsan,
.imports = &.{
.{ .name = "lightpanda", .module = lightpanda_module },
},
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("wpt", "Run WPT tests");
run_step.dependOn(&run_cmd.step);
}
step.addIncludePath(.{ .path = ns ++ "/include" });
}
fn addDependencies(b: *Build, mod: *Build.Module, opts: *Build.Step.Options, prebuilt_v8_path: ?[]const u8) !void {
mod.addImport("build_config", opts.createModule());
const target = mod.resolved_target.?;
const dep_opts = .{
.target = target,
.optimize = mod.optimize.?,
.prebuilt_v8_path = prebuilt_v8_path,
.cache_root = b.pathFromRoot(".lp-cache"),
};
mod.addIncludePath(b.path("vendor/lightpanda"));
{
// html5ever
// Build step to install html5ever dependency.
const html5ever_argv = blk: {
const argv: []const []const u8 = &.{
"cargo",
"build",
// Seems cargo can figure out required paths out of Cargo.toml.
"--manifest-path",
"src/html5ever/Cargo.toml",
// TODO: We can prefer `--artifact-dir` once it become stable.
"--target-dir",
b.getInstallPath(.prefix, "html5ever"),
// This must be the last argument.
"--release",
};
break :blk switch (mod.optimize.?) {
// Prefer dev build on debug option.
.Debug => argv[0 .. argv.len - 1],
else => argv,
};
};
const html5ever_exec_cargo = b.addSystemCommand(html5ever_argv);
const html5ever_step = b.step("html5ever", "Install html5ever dependency (requires cargo)");
html5ever_step.dependOn(&html5ever_exec_cargo.step);
opts.step.dependOn(html5ever_step);
const html5ever_obj = switch (mod.optimize.?) {
.Debug => b.getInstallPath(.prefix, "html5ever/debug/liblitefetch_html5ever.a"),
// Release builds.
else => b.getInstallPath(.prefix, "html5ever/release/liblitefetch_html5ever.a"),
};
mod.addObjectFile(.{ .cwd_relative = html5ever_obj });
}
{
// v8
const v8_opts = b.addOptions();
v8_opts.addOption(bool, "inspector_subtype", false);
const v8_mod = b.dependency("v8", dep_opts).module("v8");
v8_mod.addOptions("default_exports", v8_opts);
mod.addImport("v8", v8_mod);
}
{
//curl
{
const is_linux = target.result.os.tag == .linux;
if (is_linux) {
mod.addCMacro("HAVE_LINUX_TCP_H", "1");
mod.addCMacro("HAVE_MSG_NOSIGNAL", "1");
mod.addCMacro("HAVE_GETHOSTBYNAME_R", "1");
}
mod.addCMacro("_FILE_OFFSET_BITS", "64");
mod.addCMacro("BUILDING_LIBCURL", "1");
mod.addCMacro("CURL_DISABLE_AWS", "1");
mod.addCMacro("CURL_DISABLE_DICT", "1");
mod.addCMacro("CURL_DISABLE_DOH", "1");
mod.addCMacro("CURL_DISABLE_FILE", "1");
mod.addCMacro("CURL_DISABLE_FTP", "1");
mod.addCMacro("CURL_DISABLE_GOPHER", "1");
mod.addCMacro("CURL_DISABLE_KERBEROS", "1");
mod.addCMacro("CURL_DISABLE_IMAP", "1");
mod.addCMacro("CURL_DISABLE_IPFS", "1");
mod.addCMacro("CURL_DISABLE_LDAP", "1");
mod.addCMacro("CURL_DISABLE_LDAPS", "1");
mod.addCMacro("CURL_DISABLE_MQTT", "1");
mod.addCMacro("CURL_DISABLE_NTLM", "1");
mod.addCMacro("CURL_DISABLE_PROGRESS_METER", "1");
mod.addCMacro("CURL_DISABLE_POP3", "1");
mod.addCMacro("CURL_DISABLE_RTSP", "1");
mod.addCMacro("CURL_DISABLE_SMB", "1");
mod.addCMacro("CURL_DISABLE_SMTP", "1");
mod.addCMacro("CURL_DISABLE_TELNET", "1");
mod.addCMacro("CURL_DISABLE_TFTP", "1");
mod.addCMacro("CURL_EXTERN_SYMBOL", "__attribute__ ((__visibility__ (\"default\"))");
mod.addCMacro("CURL_OS", if (is_linux) "\"Linux\"" else "\"mac\"");
mod.addCMacro("CURL_STATICLIB", "1");
mod.addCMacro("ENABLE_IPV6", "1");
mod.addCMacro("HAVE_ALARM", "1");
mod.addCMacro("HAVE_ALLOCA_H", "1");
mod.addCMacro("HAVE_ARPA_INET_H", "1");
mod.addCMacro("HAVE_ARPA_TFTP_H", "1");
mod.addCMacro("HAVE_ASSERT_H", "1");
mod.addCMacro("HAVE_BASENAME", "1");
mod.addCMacro("HAVE_BOOL_T", "1");
mod.addCMacro("HAVE_BROTLI", "1");
mod.addCMacro("HAVE_BUILTIN_AVAILABLE", "1");
mod.addCMacro("HAVE_CLOCK_GETTIME_MONOTONIC", "1");
mod.addCMacro("HAVE_DLFCN_H", "1");
mod.addCMacro("HAVE_ERRNO_H", "1");
mod.addCMacro("HAVE_FCNTL", "1");
mod.addCMacro("HAVE_FCNTL_H", "1");
mod.addCMacro("HAVE_FCNTL_O_NONBLOCK", "1");
mod.addCMacro("HAVE_FREEADDRINFO", "1");
mod.addCMacro("HAVE_FSETXATTR", "1");
mod.addCMacro("HAVE_FSETXATTR_5", "1");
mod.addCMacro("HAVE_FTRUNCATE", "1");
mod.addCMacro("HAVE_GETADDRINFO", "1");
mod.addCMacro("HAVE_GETEUID", "1");
mod.addCMacro("HAVE_GETHOSTBYNAME", "1");
mod.addCMacro("HAVE_GETHOSTBYNAME_R_6", "1");
mod.addCMacro("HAVE_GETHOSTNAME", "1");
mod.addCMacro("HAVE_GETPEERNAME", "1");
mod.addCMacro("HAVE_GETPPID", "1");
mod.addCMacro("HAVE_GETPPID", "1");
mod.addCMacro("HAVE_GETPROTOBYNAME", "1");
mod.addCMacro("HAVE_GETPWUID", "1");
mod.addCMacro("HAVE_GETPWUID_R", "1");
mod.addCMacro("HAVE_GETRLIMIT", "1");
mod.addCMacro("HAVE_GETSOCKNAME", "1");
mod.addCMacro("HAVE_GETTIMEOFDAY", "1");
mod.addCMacro("HAVE_GMTIME_R", "1");
mod.addCMacro("HAVE_IDN2_H", "1");
mod.addCMacro("HAVE_IF_NAMETOINDEX", "1");
mod.addCMacro("HAVE_IFADDRS_H", "1");
mod.addCMacro("HAVE_INET_ADDR", "1");
mod.addCMacro("HAVE_INET_PTON", "1");
mod.addCMacro("HAVE_INTTYPES_H", "1");
mod.addCMacro("HAVE_IOCTL", "1");
mod.addCMacro("HAVE_IOCTL_FIONBIO", "1");
mod.addCMacro("HAVE_IOCTL_SIOCGIFADDR", "1");
mod.addCMacro("HAVE_LDAP_URL_PARSE", "1");
mod.addCMacro("HAVE_LIBGEN_H", "1");
mod.addCMacro("HAVE_LIBZ", "1");
mod.addCMacro("HAVE_LL", "1");
mod.addCMacro("HAVE_LOCALE_H", "1");
mod.addCMacro("HAVE_LOCALTIME_R", "1");
mod.addCMacro("HAVE_LONGLONG", "1");
mod.addCMacro("HAVE_MALLOC_H", "1");
mod.addCMacro("HAVE_MEMORY_H", "1");
mod.addCMacro("HAVE_NET_IF_H", "1");
mod.addCMacro("HAVE_NETDB_H", "1");
mod.addCMacro("HAVE_NETINET_IN_H", "1");
mod.addCMacro("HAVE_NETINET_TCP_H", "1");
mod.addCMacro("HAVE_PIPE", "1");
mod.addCMacro("HAVE_POLL", "1");
mod.addCMacro("HAVE_POLL_FINE", "1");
mod.addCMacro("HAVE_POLL_H", "1");
mod.addCMacro("HAVE_POSIX_STRERROR_R", "1");
mod.addCMacro("HAVE_PTHREAD_H", "1");
mod.addCMacro("HAVE_PWD_H", "1");
mod.addCMacro("HAVE_RECV", "1");
mod.addCMacro("HAVE_SA_FAMILY_T", "1");
mod.addCMacro("HAVE_SELECT", "1");
mod.addCMacro("HAVE_SEND", "1");
mod.addCMacro("HAVE_SETJMP_H", "1");
mod.addCMacro("HAVE_SETLOCALE", "1");
mod.addCMacro("HAVE_SETRLIMIT", "1");
mod.addCMacro("HAVE_SETSOCKOPT", "1");
mod.addCMacro("HAVE_SIGACTION", "1");
mod.addCMacro("HAVE_SIGINTERRUPT", "1");
mod.addCMacro("HAVE_SIGNAL", "1");
mod.addCMacro("HAVE_SIGNAL_H", "1");
mod.addCMacro("HAVE_SIGSETJMP", "1");
mod.addCMacro("HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID", "1");
mod.addCMacro("HAVE_SOCKET", "1");
mod.addCMacro("HAVE_STDBOOL_H", "1");
mod.addCMacro("HAVE_STDINT_H", "1");
mod.addCMacro("HAVE_STDIO_H", "1");
mod.addCMacro("HAVE_STDLIB_H", "1");
mod.addCMacro("HAVE_STRCASECMP", "1");
mod.addCMacro("HAVE_STRDUP", "1");
mod.addCMacro("HAVE_STRERROR_R", "1");
mod.addCMacro("HAVE_STRING_H", "1");
mod.addCMacro("HAVE_STRINGS_H", "1");
mod.addCMacro("HAVE_STRSTR", "1");
mod.addCMacro("HAVE_STRTOK_R", "1");
mod.addCMacro("HAVE_STRTOLL", "1");
mod.addCMacro("HAVE_STRUCT_SOCKADDR_STORAGE", "1");
mod.addCMacro("HAVE_STRUCT_TIMEVAL", "1");
mod.addCMacro("HAVE_SYS_IOCTL_H", "1");
mod.addCMacro("HAVE_SYS_PARAM_H", "1");
mod.addCMacro("HAVE_SYS_POLL_H", "1");
mod.addCMacro("HAVE_SYS_RESOURCE_H", "1");
mod.addCMacro("HAVE_SYS_SELECT_H", "1");
mod.addCMacro("HAVE_SYS_SOCKET_H", "1");
mod.addCMacro("HAVE_SYS_STAT_H", "1");
mod.addCMacro("HAVE_SYS_TIME_H", "1");
mod.addCMacro("HAVE_SYS_TYPES_H", "1");
mod.addCMacro("HAVE_SYS_UIO_H", "1");
mod.addCMacro("HAVE_SYS_UN_H", "1");
mod.addCMacro("HAVE_TERMIO_H", "1");
mod.addCMacro("HAVE_TERMIOS_H", "1");
mod.addCMacro("HAVE_TIME_H", "1");
mod.addCMacro("HAVE_UNAME", "1");
mod.addCMacro("HAVE_UNISTD_H", "1");
mod.addCMacro("HAVE_UTIME", "1");
mod.addCMacro("HAVE_UTIME_H", "1");
mod.addCMacro("HAVE_UTIMES", "1");
mod.addCMacro("HAVE_VARIADIC_MACROS_C99", "1");
mod.addCMacro("HAVE_VARIADIC_MACROS_GCC", "1");
mod.addCMacro("HAVE_ZLIB_H", "1");
mod.addCMacro("RANDOM_FILE", "\"/dev/urandom\"");
mod.addCMacro("RECV_TYPE_ARG1", "int");
mod.addCMacro("RECV_TYPE_ARG2", "void *");
mod.addCMacro("RECV_TYPE_ARG3", "size_t");
mod.addCMacro("RECV_TYPE_ARG4", "int");
mod.addCMacro("RECV_TYPE_RETV", "ssize_t");
mod.addCMacro("SEND_QUAL_ARG2", "const");
mod.addCMacro("SEND_TYPE_ARG1", "int");
mod.addCMacro("SEND_TYPE_ARG2", "void *");
mod.addCMacro("SEND_TYPE_ARG3", "size_t");
mod.addCMacro("SEND_TYPE_ARG4", "int");
mod.addCMacro("SEND_TYPE_RETV", "ssize_t");
mod.addCMacro("SIZEOF_CURL_OFF_T", "8");
mod.addCMacro("SIZEOF_INT", "4");
mod.addCMacro("SIZEOF_LONG", "8");
mod.addCMacro("SIZEOF_OFF_T", "8");
mod.addCMacro("SIZEOF_SHORT", "2");
mod.addCMacro("SIZEOF_SIZE_T", "8");
mod.addCMacro("SIZEOF_TIME_T", "8");
mod.addCMacro("STDC_HEADERS", "1");
mod.addCMacro("TIME_WITH_SYS_TIME", "1");
mod.addCMacro("USE_NGHTTP2", "1");
mod.addCMacro("USE_OPENSSL", "1");
mod.addCMacro("OPENSSL_IS_BORINGSSL", "1");
mod.addCMacro("USE_THREADS_POSIX", "1");
mod.addCMacro("USE_UNIX_SOCKETS", "1");
}
try buildZlib(b, mod);
try buildBrotli(b, mod);
const boringssl_dep = b.dependency("boringssl-zig", .{
.target = target,
.optimize = mod.optimize.?,
.force_pic = true,
});
const ssl = boringssl_dep.artifact("ssl");
ssl.bundle_ubsan_rt = false;
const crypto = boringssl_dep.artifact("crypto");
crypto.bundle_ubsan_rt = false;
mod.linkLibrary(ssl);
mod.linkLibrary(crypto);
try buildNghttp2(b, mod);
try buildCurl(b, mod);
switch (target.result.os.tag) {
.macos => {
// needed for proxying on mac
mod.addSystemFrameworkPath(.{ .cwd_relative = "/System/Library/Frameworks" });
mod.linkFramework("CoreFoundation", .{});
mod.linkFramework("SystemConfiguration", .{});
},
else => {},
}
}
}
fn buildZlib(b: *Build, m: *Build.Module) !void {
const zlib = b.addLibrary(.{
.name = "zlib",
.root_module = m,
});
const root = "vendor/zlib/";
zlib.installHeader(b.path(root ++ "zlib.h"), "zlib.h");
zlib.installHeader(b.path(root ++ "zconf.h"), "zconf.h");
zlib.addCSourceFiles(.{ .flags = &.{
"-DHAVE_SYS_TYPES_H",
"-DHAVE_STDINT_H",
"-DHAVE_STDDEF_H",
}, .files = &.{
root ++ "adler32.c",
root ++ "compress.c",
root ++ "crc32.c",
root ++ "deflate.c",
root ++ "gzclose.c",
root ++ "gzlib.c",
root ++ "gzread.c",
root ++ "gzwrite.c",
root ++ "inflate.c",
root ++ "infback.c",
root ++ "inftrees.c",
root ++ "inffast.c",
root ++ "trees.c",
root ++ "uncompr.c",
root ++ "zutil.c",
} });
}
fn buildBrotli(b: *Build, m: *Build.Module) !void {
const brotli = b.addLibrary(.{
.name = "brotli",
.root_module = m,
});
const root = "vendor/brotli/c/";
brotli.addIncludePath(b.path(root ++ "include"));
brotli.addCSourceFiles(.{ .flags = &.{}, .files = &.{
root ++ "common/constants.c",
root ++ "common/context.c",
root ++ "common/dictionary.c",
root ++ "common/platform.c",
root ++ "common/shared_dictionary.c",
root ++ "common/transform.c",
root ++ "dec/bit_reader.c",
root ++ "dec/decode.c",
root ++ "dec/huffman.c",
root ++ "dec/prefix.c",
root ++ "dec/state.c",
root ++ "dec/static_init.c",
} });
}
fn buildNghttp2(b: *Build, m: *Build.Module) !void {
const nghttp2 = b.addLibrary(.{
.name = "nghttp2",
.root_module = m,
});
const root = "vendor/nghttp2/";
nghttp2.addIncludePath(b.path(root ++ "lib"));
nghttp2.addIncludePath(b.path(root ++ "lib/includes"));
nghttp2.addCSourceFiles(.{ .flags = &.{
"-DNGHTTP2_STATICLIB",
"-DHAVE_NETINET_IN",
"-DHAVE_TIME_H",
}, .files = &.{
root ++ "lib/sfparse.c",
root ++ "lib/nghttp2_alpn.c",
root ++ "lib/nghttp2_buf.c",
root ++ "lib/nghttp2_callbacks.c",
root ++ "lib/nghttp2_debug.c",
root ++ "lib/nghttp2_extpri.c",
root ++ "lib/nghttp2_frame.c",
root ++ "lib/nghttp2_hd.c",
root ++ "lib/nghttp2_hd_huffman.c",
root ++ "lib/nghttp2_hd_huffman_data.c",
root ++ "lib/nghttp2_helper.c",
root ++ "lib/nghttp2_http.c",
root ++ "lib/nghttp2_map.c",
root ++ "lib/nghttp2_mem.c",
root ++ "lib/nghttp2_option.c",
root ++ "lib/nghttp2_outbound_item.c",
root ++ "lib/nghttp2_pq.c",
root ++ "lib/nghttp2_priority_spec.c",
root ++ "lib/nghttp2_queue.c",
root ++ "lib/nghttp2_rcbuf.c",
root ++ "lib/nghttp2_session.c",
root ++ "lib/nghttp2_stream.c",
root ++ "lib/nghttp2_submit.c",
root ++ "lib/nghttp2_version.c",
root ++ "lib/nghttp2_ratelim.c",
root ++ "lib/nghttp2_time.c",
} });
}
fn buildCurl(b: *Build, m: *Build.Module) !void {
const curl = b.addLibrary(.{
.name = "curl",
.root_module = m,
});
const root = "vendor/curl/";
curl.addIncludePath(b.path(root ++ "lib"));
curl.addIncludePath(b.path(root ++ "include"));
curl.addIncludePath(b.path("vendor/zlib"));
curl.addCSourceFiles(.{
.flags = &.{},
.files = &.{
root ++ "lib/altsvc.c",
root ++ "lib/amigaos.c",
root ++ "lib/asyn-ares.c",
root ++ "lib/asyn-base.c",
root ++ "lib/asyn-thrdd.c",
root ++ "lib/bufq.c",
root ++ "lib/bufref.c",
root ++ "lib/cf-h1-proxy.c",
root ++ "lib/cf-h2-proxy.c",
root ++ "lib/cf-haproxy.c",
root ++ "lib/cf-https-connect.c",
root ++ "lib/cf-socket.c",
root ++ "lib/cfilters.c",
root ++ "lib/conncache.c",
root ++ "lib/connect.c",
root ++ "lib/content_encoding.c",
root ++ "lib/cookie.c",
root ++ "lib/cshutdn.c",
root ++ "lib/curl_addrinfo.c",
root ++ "lib/curl_des.c",
root ++ "lib/curl_endian.c",
root ++ "lib/curl_fnmatch.c",
root ++ "lib/curl_get_line.c",
root ++ "lib/curl_gethostname.c",
root ++ "lib/curl_gssapi.c",
root ++ "lib/curl_memrchr.c",
root ++ "lib/curl_ntlm_core.c",
root ++ "lib/curl_range.c",
root ++ "lib/curl_rtmp.c",
root ++ "lib/curl_sasl.c",
root ++ "lib/curl_sha512_256.c",
root ++ "lib/curl_sspi.c",
root ++ "lib/curl_threads.c",
root ++ "lib/curl_trc.c",
root ++ "lib/cw-out.c",
root ++ "lib/cw-pause.c",
root ++ "lib/dict.c",
root ++ "lib/doh.c",
root ++ "lib/dynhds.c",
root ++ "lib/easy.c",
root ++ "lib/easygetopt.c",
root ++ "lib/easyoptions.c",
root ++ "lib/escape.c",
root ++ "lib/fake_addrinfo.c",
root ++ "lib/file.c",
root ++ "lib/fileinfo.c",
root ++ "lib/fopen.c",
root ++ "lib/formdata.c",
root ++ "lib/ftp.c",
root ++ "lib/ftplistparser.c",
root ++ "lib/getenv.c",
root ++ "lib/getinfo.c",
root ++ "lib/gopher.c",
root ++ "lib/hash.c",
root ++ "lib/headers.c",
root ++ "lib/hmac.c",
root ++ "lib/hostip.c",
root ++ "lib/hostip4.c",
root ++ "lib/hostip6.c",
root ++ "lib/hsts.c",
root ++ "lib/http.c",
root ++ "lib/http1.c",
root ++ "lib/http2.c",
root ++ "lib/http_aws_sigv4.c",
root ++ "lib/http_chunks.c",
root ++ "lib/http_digest.c",
root ++ "lib/http_negotiate.c",
root ++ "lib/http_ntlm.c",
root ++ "lib/http_proxy.c",
root ++ "lib/httpsrr.c",
root ++ "lib/idn.c",
root ++ "lib/if2ip.c",
root ++ "lib/imap.c",
root ++ "lib/krb5.c",
root ++ "lib/ldap.c",
root ++ "lib/llist.c",
root ++ "lib/macos.c",
root ++ "lib/md4.c",
root ++ "lib/md5.c",
root ++ "lib/memdebug.c",
root ++ "lib/mime.c",
root ++ "lib/mprintf.c",
root ++ "lib/mqtt.c",
root ++ "lib/multi.c",
root ++ "lib/multi_ev.c",
root ++ "lib/netrc.c",
root ++ "lib/noproxy.c",
root ++ "lib/openldap.c",
root ++ "lib/parsedate.c",
root ++ "lib/pingpong.c",
root ++ "lib/pop3.c",
root ++ "lib/progress.c",
root ++ "lib/psl.c",
root ++ "lib/rand.c",
root ++ "lib/rename.c",
root ++ "lib/request.c",
root ++ "lib/rtsp.c",
root ++ "lib/select.c",
root ++ "lib/sendf.c",
root ++ "lib/setopt.c",
root ++ "lib/sha256.c",
root ++ "lib/share.c",
root ++ "lib/slist.c",
root ++ "lib/smb.c",
root ++ "lib/smtp.c",
root ++ "lib/socketpair.c",
root ++ "lib/socks.c",
root ++ "lib/socks_gssapi.c",
root ++ "lib/socks_sspi.c",
root ++ "lib/speedcheck.c",
root ++ "lib/splay.c",
root ++ "lib/strcase.c",
root ++ "lib/strdup.c",
root ++ "lib/strequal.c",
root ++ "lib/strerror.c",
root ++ "lib/system_win32.c",
root ++ "lib/telnet.c",
root ++ "lib/tftp.c",
root ++ "lib/transfer.c",
root ++ "lib/uint-bset.c",
root ++ "lib/uint-hash.c",
root ++ "lib/uint-spbset.c",
root ++ "lib/uint-table.c",
root ++ "lib/url.c",
root ++ "lib/urlapi.c",
root ++ "lib/version.c",
root ++ "lib/ws.c",
root ++ "lib/curlx/base64.c",
root ++ "lib/curlx/dynbuf.c",
root ++ "lib/curlx/inet_ntop.c",
root ++ "lib/curlx/nonblock.c",
root ++ "lib/curlx/strparse.c",
root ++ "lib/curlx/timediff.c",
root ++ "lib/curlx/timeval.c",
root ++ "lib/curlx/wait.c",
root ++ "lib/curlx/warnless.c",
root ++ "lib/vquic/curl_ngtcp2.c",
root ++ "lib/vquic/curl_osslq.c",
root ++ "lib/vquic/curl_quiche.c",
root ++ "lib/vquic/vquic.c",
root ++ "lib/vquic/vquic-tls.c",
root ++ "lib/vauth/cleartext.c",
root ++ "lib/vauth/cram.c",
root ++ "lib/vauth/digest.c",
root ++ "lib/vauth/digest_sspi.c",
root ++ "lib/vauth/gsasl.c",
root ++ "lib/vauth/krb5_gssapi.c",
root ++ "lib/vauth/krb5_sspi.c",
root ++ "lib/vauth/ntlm.c",
root ++ "lib/vauth/ntlm_sspi.c",
root ++ "lib/vauth/oauth2.c",
root ++ "lib/vauth/spnego_gssapi.c",
root ++ "lib/vauth/spnego_sspi.c",
root ++ "lib/vauth/vauth.c",
root ++ "lib/vtls/cipher_suite.c",
root ++ "lib/vtls/openssl.c",
root ++ "lib/vtls/hostcheck.c",
root ++ "lib/vtls/keylog.c",
root ++ "lib/vtls/vtls.c",
root ++ "lib/vtls/vtls_scache.c",
root ++ "lib/vtls/x509asn1.c",
},
});
}
const Manifest = struct {
version: []const u8,
minimum_zig_version: []const u8,
fn init(b: *std.Build) Manifest {
const input = @embedFile("build.zig.zon");
var diagnostics: std.zon.parse.Diagnostics = .{};
defer diagnostics.deinit(b.allocator);
return std.zon.parse.fromSlice(Manifest, b.allocator, input, &diagnostics, .{
.free_on_error = true,
.ignore_unknown_fields = true,
}) catch |err| {
switch (err) {
error.OutOfMemory => @panic("OOM"),
error.ParseZon => {
std.debug.print("Parse diagnostics:\n{f}\n", .{diagnostics});
std.process.exit(1);
},
}
};
}
};

18
build.zig.zon Normal file
View File

@@ -0,0 +1,18 @@
.{
.name = .browser,
.paths = .{""},
.version = "0.0.0",
.fingerprint = 0xda130f3af836cea0, // Changing this has security and trust implications.
.minimum_zig_version = "0.15.2",
.dependencies = .{
.v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/v0.2.4.tar.gz",
.hash = "v8-0.0.0-xddH66YvBAD0YI9xr6F0Xgnw9wN30FdZ10FLyuoV3e66",
},
// .v8 = .{ .path = "../zig-v8-fork" },
.@"boringssl-zig" = .{
.url = "git+https://github.com/Syndica/boringssl-zig.git#c53df00d06b02b755ad88bbf4d1202ed9687b096",
.hash = "boringssl-0.1.0-VtJeWehMAAA4RNnwRnzEvKcS9rjsR1QVRw1uJrwXxmVK",
},
},
}

219
flake.lock generated Normal file
View File

@@ -0,0 +1,219 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1763016383,
"narHash": "sha256-eYmo7FNvm3q08iROzwIi8i9dWuUbJJl3uLR3OLnSmdI=",
"owner": "nix-community",
"repo": "fenix",
"rev": "0fad5c0e5c531358e7174cd666af4608f08bc3ba",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"zlsPkg",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1763043403,
"narHash": "sha256-DgCTbHdIpzbXSlQlOZEWj8oPt2lrRMlSk03oIstvkVQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "75e04ecd084f93d4105ce68c07dac7656291fe2e",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"zigPkgs": "zigPkgs",
"zlsPkg": "zlsPkg"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1762860488,
"narHash": "sha256-rMfWMCOo/pPefM2We0iMBLi2kLBAnYoB9thi4qS7uk4=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "2efc80078029894eec0699f62ec8d5c1a56af763",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"zigPkgs": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762907712,
"narHash": "sha256-VNW/+VYIg6N4b9Iq+F0YZmm22n74IdFS7hsPLblWuOY=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "d16453ee78765e49527c56d23386cead799b6b53",
"type": "github"
},
"original": {
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}
},
"zlsPkg": {
"inputs": {
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"zig-overlay": [
"zigPkgs"
]
},
"locked": {
"lastModified": 1756048867,
"narHash": "sha256-GFzSHUljcxy7sM1PaabbkQUdUnLwpherekPWJFxXtnk=",
"owner": "zigtools",
"repo": "zls",
"rev": "ce6c8f02c78e622421cfc2405c67c5222819ec03",
"type": "github"
},
"original": {
"owner": "zigtools",
"ref": "0.15.0",
"repo": "zls",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

85
flake.nix Normal file
View File

@@ -0,0 +1,85 @@
{
description = "headless browser designed for AI and automation";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/release-25.05";
zigPkgs.url = "github:mitchellh/zig-overlay";
zigPkgs.inputs.nixpkgs.follows = "nixpkgs";
zlsPkg.url = "github:zigtools/zls/0.15.0";
zlsPkg.inputs.zig-overlay.follows = "zigPkgs";
zlsPkg.inputs.nixpkgs.follows = "nixpkgs";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
nixpkgs,
zigPkgs,
zlsPkg,
fenix,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
overlays = [
(final: prev: {
zigpkgs = zigPkgs.packages.${prev.system};
zls = zlsPkg.packages.${prev.system}.default;
})
];
pkgs = import nixpkgs {
inherit system overlays;
};
rustToolchain = fenix.packages.${system}.stable.toolchain;
# We need crtbeginS.o for building.
crtFiles = pkgs.runCommand "crt-files" { } ''
mkdir -p $out/lib
cp -r ${pkgs.gcc.cc}/lib/gcc $out/lib/gcc
'';
# This build pipeline is very unhappy without an FHS-compliant env.
fhs = pkgs.buildFHSEnv {
name = "fhs-shell";
multiArch = true;
targetPkgs =
pkgs: with pkgs; [
# Build Tools
zigpkgs."0.15.2"
zls
rustToolchain
python3
pkg-config
cmake
gperf
# GCC
gcc
gcc.cc.lib
crtFiles
# Libaries
expat.dev
glib.dev
glibc.dev
zlib
];
};
in
{
devShells.default = fhs.env;
}
);
}

139
src/App.zig Normal file
View File

@@ -0,0 +1,139 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Allocator = std.mem.Allocator;
const log = @import("log.zig");
const Http = @import("http/Http.zig");
const Snapshot = @import("browser/js/Snapshot.zig");
const Platform = @import("browser/js/Platform.zig");
const Notification = @import("Notification.zig");
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
// Container for global state / objects that various parts of the system
// might need.
const App = @This();
http: Http,
config: Config,
platform: Platform,
snapshot: Snapshot,
telemetry: Telemetry,
allocator: Allocator,
app_dir_path: ?[]const u8,
notification: *Notification,
shutdown: bool = false,
pub const RunMode = enum {
help,
fetch,
serve,
version,
};
pub const Config = struct {
run_mode: RunMode,
tls_verify_host: bool = true,
http_proxy: ?[:0]const u8 = null,
proxy_bearer_token: ?[:0]const u8 = null,
http_timeout_ms: ?u31 = null,
http_connect_timeout_ms: ?u31 = null,
http_max_host_open: ?u8 = null,
http_max_concurrent: ?u8 = null,
user_agent: [:0]const u8,
};
pub fn init(allocator: Allocator, config: Config) !*App {
const app = try allocator.create(App);
errdefer allocator.destroy(app);
app.config = config;
app.allocator = allocator;
app.notification = try Notification.init(allocator, null);
errdefer app.notification.deinit();
app.http = try Http.init(allocator, .{
.max_host_open = config.http_max_host_open orelse 4,
.max_concurrent = config.http_max_concurrent orelse 10,
.timeout_ms = config.http_timeout_ms orelse 5000,
.connect_timeout_ms = config.http_connect_timeout_ms orelse 0,
.http_proxy = config.http_proxy,
.tls_verify_host = config.tls_verify_host,
.proxy_bearer_token = config.proxy_bearer_token,
.user_agent = config.user_agent,
});
errdefer app.http.deinit();
app.platform = try Platform.init();
errdefer app.platform.deinit();
app.snapshot = try Snapshot.load();
errdefer app.snapshot.deinit();
app.app_dir_path = getAndMakeAppDir(allocator);
app.telemetry = try Telemetry.init(app, config.run_mode);
errdefer app.telemetry.deinit();
try app.telemetry.register(app.notification);
return app;
}
pub fn deinit(self: *App) void {
if (@atomicRmw(bool, &self.shutdown, .Xchg, true, .monotonic)) {
return;
}
const allocator = self.allocator;
if (self.app_dir_path) |app_dir_path| {
allocator.free(app_dir_path);
self.app_dir_path = null;
}
self.telemetry.deinit();
self.notification.deinit();
self.http.deinit();
self.snapshot.deinit();
self.platform.deinit();
allocator.destroy(self);
}
fn getAndMakeAppDir(allocator: Allocator) ?[]const u8 {
if (@import("builtin").is_test) {
return allocator.dupe(u8, "/tmp") catch unreachable;
}
const app_dir_path = std.fs.getAppDataDir(allocator, "lightpanda") catch |err| {
log.warn(.app, "get data dir", .{ .err = err });
return null;
};
std.fs.cwd().makePath(app_dir_path) catch |err| switch (err) {
error.PathAlreadyExists => return app_dir_path,
else => {
allocator.free(app_dir_path);
log.warn(.app, "create data dir", .{ .err = err, .path = app_dir_path });
return null;
},
};
return app_dir_path;
}

413
src/Notification.zig Normal file
View File

@@ -0,0 +1,413 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const log = @import("log.zig");
const Page = @import("browser/Page.zig");
const Transfer = @import("http/Client.zig").Transfer;
const Allocator = std.mem.Allocator;
const List = std.DoublyLinkedList;
// Allows code to register for and emit events.
// Keeps two lists
// 1 - for a given event type, a linked list of all the listeners
// 2 - for a given listener, a list of all it's registration
// The 2nd one is so that a listener can unregister all of it's listeners
// (there's currently no need for a listener to unregister only 1 or more
// specific listener).
//
// Scoping is important. Imagine we created a global singleton registry, and our
// CDP code registers for the "network_bytes_sent" event, because it needs to
// send messages to the client when this happens. Our HTTP client could then
// emit a "network_bytes_sent" message. It would be easy, and it would work.
// That is, it would work until the Telemetry code makes an HTTP request, and
// because everything's just one big global, that gets picked up by the
// registered CDP listener, and the telemetry network activity gets sent to the
// CDP client.
//
// To avoid this, one way or another, we need scoping. We could still have
// a global registry but every "register" and every "emit" has some type of
// "scope". This would have a run-time cost and still require some coordination
// between components to share a common scope.
//
// Instead, the approach that we take is to have a notification instance per
// scope. This makes some things harder, but we only plan on having 2
// notification instances at a given time: one in a Browser and one in the App.
// What about something like Telemetry, which lives outside of a Browser but
// still cares about Browser-events (like .page_navigate)? When the Browser
// notification is created, a `notification_created` event is raised in the
// App's notification, which Telemetry is registered for. This allows Telemetry
// to register for events in the Browser notification. See the Telemetry's
// register function.
const Notification = @This();
// Every event type (which are hard-coded), has a list of Listeners.
// When the event happens, we dispatch to those listener.
event_listeners: EventListeners,
// list of listeners for a specified receiver
// @intFromPtr(receiver) -> [listener1, listener2, ...]
// Used when `unregisterAll` is called.
listeners: std.AutoHashMapUnmanaged(usize, std.ArrayListUnmanaged(*Listener)),
allocator: Allocator,
mem_pool: std.heap.MemoryPool(Listener),
const EventListeners = struct {
page_remove: List = .{},
page_created: List = .{},
page_navigate: List = .{},
page_navigated: List = .{},
page_network_idle: List = .{},
page_network_almost_idle: List = .{},
http_request_fail: List = .{},
http_request_start: List = .{},
http_request_intercept: List = .{},
http_request_done: List = .{},
http_request_auth_required: List = .{},
http_response_data: List = .{},
http_response_header_done: List = .{},
notification_created: List = .{},
};
const Events = union(enum) {
page_remove: PageRemove,
page_created: *Page,
page_navigate: *const PageNavigate,
page_navigated: *const PageNavigated,
page_network_idle: *const PageNetworkIdle,
page_network_almost_idle: *const PageNetworkAlmostIdle,
http_request_fail: *const RequestFail,
http_request_start: *const RequestStart,
http_request_intercept: *const RequestIntercept,
http_request_auth_required: *const RequestAuthRequired,
http_request_done: *const RequestDone,
http_response_data: *const ResponseData,
http_response_header_done: *const ResponseHeaderDone,
notification_created: *Notification,
};
const EventType = std.meta.FieldEnum(Events);
pub const PageRemove = struct {};
pub const PageNavigate = struct {
req_id: usize,
timestamp: u64,
url: [:0]const u8,
opts: Page.NavigateOpts,
};
pub const PageNavigated = struct {
req_id: usize,
timestamp: u64,
url: [:0]const u8,
opts: Page.NavigatedOpts,
};
pub const PageNetworkIdle = struct {
timestamp: u64,
};
pub const PageNetworkAlmostIdle = struct {
timestamp: u64,
};
pub const RequestStart = struct {
transfer: *Transfer,
};
pub const RequestIntercept = struct {
transfer: *Transfer,
wait_for_interception: *bool,
};
pub const RequestAuthRequired = struct {
transfer: *Transfer,
wait_for_interception: *bool,
};
pub const ResponseData = struct {
data: []const u8,
transfer: *Transfer,
};
pub const ResponseHeaderDone = struct {
transfer: *Transfer,
};
pub const RequestDone = struct {
transfer: *Transfer,
};
pub const RequestFail = struct {
transfer: *Transfer,
err: anyerror,
};
pub fn init(allocator: Allocator, parent: ?*Notification) !*Notification {
// This is put on the heap because we want to raise a .notification_created
// event, so that, something like Telemetry, can receive the
// .page_navigate event on all notification instances. That can only work
// if we dispatch .notification_created with a *Notification.
const notification = try allocator.create(Notification);
errdefer allocator.destroy(notification);
notification.* = .{
.listeners = .{},
.event_listeners = .{},
.allocator = allocator,
.mem_pool = std.heap.MemoryPool(Listener).init(allocator),
};
if (parent) |pn| {
pn.dispatch(.notification_created, notification);
}
return notification;
}
pub fn deinit(self: *Notification) void {
const allocator = self.allocator;
var it = self.listeners.valueIterator();
while (it.next()) |listener| {
listener.deinit(allocator);
}
self.listeners.deinit(allocator);
self.mem_pool.deinit();
allocator.destroy(self);
}
pub fn register(self: *Notification, comptime event: EventType, receiver: anytype, func: EventFunc(event)) !void {
var list = &@field(self.event_listeners, @tagName(event));
var listener = try self.mem_pool.create();
errdefer self.mem_pool.destroy(listener);
listener.* = .{
.node = .{},
.list = list,
.receiver = receiver,
.event = event,
.func = @ptrCast(func),
.struct_name = @typeName(@typeInfo(@TypeOf(receiver)).pointer.child),
};
const allocator = self.allocator;
const gop = try self.listeners.getOrPut(allocator, @intFromPtr(receiver));
if (gop.found_existing == false) {
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(allocator, listener);
// we don't add this until we've successfully added the entry to
// self.listeners
list.append(&listener.node);
}
pub fn unregister(self: *Notification, comptime event: EventType, receiver: anytype) void {
var listeners = self.listeners.getPtr(@intFromPtr(receiver)) orelse return;
var i: usize = 0;
while (i < listeners.items.len) {
const listener = listeners.items[i];
if (listener.event != event) {
i += 1;
continue;
}
listener.list.remove(&listener.node);
self.mem_pool.destroy(listener);
_ = listeners.swapRemove(i);
}
if (listeners.items.len == 0) {
listeners.deinit(self.allocator);
const removed = self.listeners.remove(@intFromPtr(receiver));
lp.assert(removed == true, "Notification.unregister", .{ .type = event });
}
}
pub fn unregisterAll(self: *Notification, receiver: *anyopaque) void {
var kv = self.listeners.fetchRemove(@intFromPtr(receiver)) orelse return;
for (kv.value.items) |listener| {
listener.list.remove(&listener.node);
self.mem_pool.destroy(listener);
}
kv.value.deinit(self.allocator);
}
pub fn dispatch(self: *Notification, comptime event: EventType, data: ArgType(event)) void {
const list = &@field(self.event_listeners, @tagName(event));
var node = list.first;
while (node) |n| {
const listener: *Listener = @fieldParentPtr("node", n);
const func: EventFunc(event) = @ptrCast(@alignCast(listener.func));
func(listener.receiver, data) catch |err| {
log.err(.app, "dispatch error", .{
.err = err,
.event = event,
.source = "notification",
.listener = listener.struct_name,
});
};
node = n.next;
}
}
// Given an event type enum, returns the type of arg the event emits
fn ArgType(comptime event: Notification.EventType) type {
inline for (std.meta.fields(Notification.Events)) |f| {
if (std.mem.eql(u8, f.name, @tagName(event))) {
return f.type;
}
}
unreachable;
}
// Given an event type enum, returns the listening function type
fn EventFunc(comptime event: Notification.EventType) type {
return *const fn (*anyopaque, ArgType(event)) anyerror!void;
}
// A listener. This is 1 receiver, with its function, and the linked list
// node that goes in the appropriate EventListeners list.
const Listener = struct {
// the receiver of the event, i.e. the self parameter to `func`
receiver: *anyopaque,
// the function to call
func: *const anyopaque,
// For logging slightly better error
struct_name: []const u8,
event: Notification.EventType,
// intrusive linked list node
node: List.Node,
// The event list this listener belongs to.
// We need this in order to be able to remove the node from the list
list: *List,
};
const testing = std.testing;
test "Notification" {
var notifier = try Notification.init(testing.allocator, null);
defer notifier.deinit();
// noop
notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 4,
.url = undefined,
.opts = .{},
});
var tc = TestClient{};
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 4,
.url = undefined,
.opts = .{},
});
try testing.expectEqual(4, tc.page_navigate);
notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 10,
.url = undefined,
.opts = .{},
});
try testing.expectEqual(4, tc.page_navigate);
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 10,
.url = undefined,
.opts = .{},
});
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 100,
.url = undefined,
.opts = .{},
});
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
{
// unregister
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(1006, tc.page_navigated);
notifier.unregister(.page_navigate, &tc);
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
// already unregistered, try anyways
notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
}
}
const TestClient = struct {
page_navigate: u64 = 0,
page_navigated: u64 = 0,
fn pageNavigate(ptr: *anyopaque, data: *const Notification.PageNavigate) !void {
const self: *TestClient = @ptrCast(@alignCast(ptr));
self.page_navigate += data.timestamp;
}
fn pageNavigated(ptr: *anyopaque, data: *const Notification.PageNavigated) !void {
const self: *TestClient = @ptrCast(@alignCast(ptr));
self.page_navigated += data.timestamp;
}
};

1470
src/Server.zig Normal file

File diff suppressed because it is too large Load Diff

107
src/Sighandler.zig Normal file
View File

@@ -0,0 +1,107 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! This structure processes operating system signals (SIGINT, SIGTERM)
//! and runs callbacks to clean up the system gracefully.
//!
//! The structure does not clear the memory allocated in the arena,
//! clear the entire arena when exiting the program.
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const lp = @import("lightpanda");
const log = lp.log;
const SigHandler = @This();
arena: Allocator,
sigset: std.posix.sigset_t = undefined,
handle_thread: ?std.Thread = null,
attempt: u32 = 0,
listeners: std.ArrayList(Listener) = .empty,
pub const Listener = struct {
args: []const u8,
start: *const fn (context: *const anyopaque) void,
};
pub fn install(self: *SigHandler) !void {
// Block SIGINT and SIGTERM for the current thread and all created from it
self.sigset = std.posix.sigemptyset();
std.posix.sigaddset(&self.sigset, std.posix.SIG.INT);
std.posix.sigaddset(&self.sigset, std.posix.SIG.TERM);
std.posix.sigaddset(&self.sigset, std.posix.SIG.QUIT);
std.posix.sigprocmask(std.posix.SIG.BLOCK, &self.sigset, null);
self.handle_thread = try std.Thread.spawn(.{ .allocator = self.arena }, SigHandler.sighandle, .{self});
self.handle_thread.?.detach();
}
pub fn on(self: *SigHandler, func: anytype, args: std.meta.ArgsTuple(@TypeOf(func))) !void {
assert(@typeInfo(@TypeOf(func)).@"fn".return_type.? == void);
const Args = @TypeOf(args);
const TypeErased = struct {
fn start(context: *const anyopaque) void {
const args_casted: *const Args = @ptrCast(@alignCast(context));
@call(.auto, func, args_casted.*);
}
};
const buffer = try self.arena.alignedAlloc(u8, .of(Args), @sizeOf(Args));
errdefer self.arena.free(buffer);
const bytes: []const u8 = @ptrCast((&args)[0..1]);
@memcpy(buffer, bytes);
try self.listeners.append(self.arena, .{
.args = buffer,
.start = TypeErased.start,
});
}
fn sighandle(self: *SigHandler) noreturn {
while (true) {
var sig: c_int = 0;
const rc = std.c.sigwait(&self.sigset, &sig);
if (rc != 0) {
log.err(.app, "Unable to process signal {}", .{rc});
std.process.exit(1);
}
switch (sig) {
std.posix.SIG.INT, std.posix.SIG.TERM => {
if (self.attempt > 1) {
std.process.exit(1);
}
self.attempt += 1;
log.info(.app, "Received termination signal...", .{});
for (self.listeners.items) |*item| {
item.start(item.args.ptr);
}
continue;
},
else => continue,
}
}
}

142
src/TestHTTPServer.zig Normal file
View File

@@ -0,0 +1,142 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const TestHTTPServer = @This();
shutdown: bool,
listener: ?std.net.Server,
handler: Handler,
const Handler = *const fn (req: *std.http.Server.Request) anyerror!void;
pub fn init(handler: Handler) TestHTTPServer {
return .{
.shutdown = true,
.listener = null,
.handler = handler,
};
}
pub fn deinit(self: *TestHTTPServer) void {
self.shutdown = true;
if (self.listener) |*listener| {
listener.deinit();
}
}
pub fn run(self: *TestHTTPServer, wg: *std.Thread.WaitGroup) !void {
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
self.listener = try address.listen(.{ .reuse_address = true });
var listener = &self.listener.?;
wg.finish();
while (true) {
const conn = listener.accept() catch |err| {
if (self.shutdown) {
return;
}
return err;
};
const thrd = try std.Thread.spawn(.{}, handleConnection, .{ self, conn });
thrd.detach();
}
}
fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !void {
defer conn.stream.close();
var req_buf: [2048]u8 = undefined;
var conn_reader = conn.stream.reader(&req_buf);
var conn_writer = conn.stream.writer(&req_buf);
var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);
while (true) {
var req = http_server.receiveHead() catch |err| switch (err) {
error.ReadFailed => continue,
error.HttpConnectionClosing => continue,
else => {
std.debug.print("Test HTTP Server error: {}\n", .{err});
return err;
},
};
self.handler(&req) catch |err| {
std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err });
try req.respond("server error", .{ .status = .internal_server_error });
return;
};
}
}
pub fn sendFile(req: *std.http.Server.Request, file_path: []const u8) !void {
var file = std.fs.cwd().openFile(file_path, .{}) catch |err| switch (err) {
error.FileNotFound => return req.respond("server error", .{ .status = .not_found }),
else => return err,
};
defer file.close();
const stat = try file.stat();
var send_buffer: [4096]u8 = undefined;
var res = try req.respondStreaming(&send_buffer, .{
.content_length = stat.size,
.respond_options = .{
.extra_headers = &.{
.{ .name = "content-type", .value = getContentType(file_path) },
},
},
});
var read_buffer: [4096]u8 = undefined;
var reader = file.reader(&read_buffer);
_ = try res.writer.sendFileAll(&reader, .unlimited);
try res.writer.flush();
try res.end();
}
fn getContentType(file_path: []const u8) []const u8 {
if (std.mem.endsWith(u8, file_path, ".js")) {
return "application/json";
}
if (std.mem.endsWith(u8, file_path, ".html")) {
return "text/html";
}
if (std.mem.endsWith(u8, file_path, ".htm")) {
return "text/html";
}
if (std.mem.endsWith(u8, file_path, ".xml")) {
// some wpt tests do this
return "text/xml";
}
if (std.mem.endsWith(u8, file_path, ".mjs")) {
// mjs are ECMAScript modules
return "application/json";
}
std.debug.print("TestHTTPServer asked to serve an unknown file type: {s}\n", .{file_path});
return "text/html";
}

View File

@@ -1,19 +0,0 @@
const generate = @import("generate.zig");
const Console = @import("jsruntime").Console;
const DOM = @import("dom/dom.zig");
const HTML = @import("html/html.zig");
const Events = @import("events/event.zig");
const XHR = @import("xhr/xhr.zig");
pub const HTMLDocument = @import("html/document.zig").HTMLDocument;
// Interfaces
pub const Interfaces = generate.Tuple(.{
Console,
DOM.Interfaces,
Events.Interfaces,
HTML.Interfaces,
XHR.Interfaces,
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,115 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const os = std.os;
const io = std.io;
const assert = std.debug.assert;
const tcp = @import("tcp.zig");
pub const Stream = struct {
alloc: std.mem.Allocator,
conn: *tcp.Conn,
handle: std.os.socket_t,
pub fn close(self: Stream) void {
os.closeSocket(self.handle);
self.alloc.destroy(self.conn);
}
pub const ReadError = os.ReadError;
pub const WriteError = os.WriteError;
pub const Reader = io.Reader(Stream, ReadError, read);
pub const Writer = io.Writer(Stream, WriteError, write);
pub fn reader(self: Stream) Reader {
return .{ .context = self };
}
pub fn writer(self: Stream) Writer {
return .{ .context = self };
}
pub fn read(self: Stream, buffer: []u8) ReadError!usize {
return self.conn.receive(self.handle, buffer) catch |err| switch (err) {
else => return error.Unexpected,
};
}
pub fn readv(s: Stream, iovecs: []const os.iovec) ReadError!usize {
return os.readv(s.handle, iovecs);
}
/// Returns the number of bytes read. If the number read is smaller than
/// `buffer.len`, it means the stream reached the end. Reaching the end of
/// a stream is not an error condition.
pub fn readAll(s: Stream, buffer: []u8) ReadError!usize {
return readAtLeast(s, buffer, buffer.len);
}
/// Returns the number of bytes read, calling the underlying read function
/// the minimal number of times until the buffer has at least `len` bytes
/// filled. If the number read is less than `len` it means the stream
/// reached the end. Reaching the end of the stream is not an error
/// condition.
pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
assert(len <= buffer.len);
var index: usize = 0;
while (index < len) {
const amt = try s.read(buffer[index..]);
if (amt == 0) break;
index += amt;
}
return index;
}
/// TODO in evented I/O mode, this implementation incorrectly uses the event loop's
/// file system thread instead of non-blocking. It needs to be reworked to properly
/// use non-blocking I/O.
pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
return self.conn.send(self.handle, buffer) catch |err| switch (err) {
error.AccessDenied => error.AccessDenied,
error.WouldBlock => error.WouldBlock,
error.ConnectionResetByPeer => error.ConnectionResetByPeer,
error.MessageTooBig => error.FileTooBig,
error.BrokenPipe => error.BrokenPipe,
else => return error.Unexpected,
};
}
pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
var index: usize = 0;
while (index < bytes.len) {
index += try self.write(bytes[index..]);
}
}
/// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.fs.File.writev`.
pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize {
if (iovecs.len == 0) return 0;
const first_buffer = iovecs[0].iov_base[0..iovecs[0].iov_len];
return try self.write(first_buffer);
}
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial writes from the underlying OS layer.
/// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.fs.File.writevAll`.
pub fn writevAll(self: Stream, iovecs: []os.iovec_const) WriteError!void {
if (iovecs.len == 0) return;
var i: usize = 0;
while (true) {
var amt = try self.writev(iovecs[i..]);
while (amt >= iovecs[i].iov_len) {
amt -= iovecs[i].iov_len;
i += 1;
if (i >= iovecs.len) return;
}
iovecs[i].iov_base += amt;
iovecs[i].iov_len -= amt;
}
}
};

View File

@@ -1,94 +0,0 @@
const std = @import("std");
const net = std.net;
const Stream = @import("stream.zig").Stream;
const Loop = @import("jsruntime").Loop;
const NetworkImpl = Loop.Network(Conn.Command);
// Conn is a TCP connection using jsruntime Loop async I/O.
// connect, send and receive are blocking, but use async I/O in the background.
// Client doesn't own the socket used for the connection, the caller is
// responsible for closing it.
pub const Conn = struct {
const Command = struct {
impl: NetworkImpl,
done: bool = false,
err: ?anyerror = null,
ln: usize = 0,
fn ok(self: *Command, err: ?anyerror, ln: usize) void {
self.err = err;
self.ln = ln;
self.done = true;
}
fn wait(self: *Command) !usize {
while (!self.done) try self.impl.tick();
if (self.err) |err| return err;
return self.ln;
}
pub fn onConnect(self: *Command, err: ?anyerror) void {
self.ok(err, 0);
}
pub fn onSend(self: *Command, ln: usize, err: ?anyerror) void {
self.ok(err, ln);
}
pub fn onReceive(self: *Command, ln: usize, err: ?anyerror) void {
self.ok(err, ln);
}
};
loop: *Loop,
pub fn connect(self: *Conn, socket: std.os.socket_t, address: std.net.Address) !void {
var cmd = Command{ .impl = NetworkImpl.init(self.loop) };
cmd.impl.connect(&cmd, socket, address);
_ = try cmd.wait();
}
pub fn send(self: *Conn, socket: std.os.socket_t, buffer: []const u8) !usize {
var cmd = Command{ .impl = NetworkImpl.init(self.loop) };
cmd.impl.send(&cmd, socket, buffer);
return try cmd.wait();
}
pub fn receive(self: *Conn, socket: std.os.socket_t, buffer: []u8) !usize {
var cmd = Command{ .impl = NetworkImpl.init(self.loop) };
cmd.impl.receive(&cmd, socket, buffer);
return try cmd.wait();
}
};
pub fn tcpConnectToHost(alloc: std.mem.Allocator, loop: *Loop, name: []const u8, port: u16) !Stream {
// TODO async resolve
const list = try net.getAddressList(alloc, name, port);
defer list.deinit();
if (list.addrs.len == 0) return error.UnknownHostName;
for (list.addrs) |addr| {
return tcpConnectToAddress(alloc, loop, addr) catch |err| switch (err) {
error.ConnectionRefused => {
continue;
},
else => return err,
};
}
return std.os.ConnectError.ConnectionRefused;
}
pub fn tcpConnectToAddress(alloc: std.mem.Allocator, loop: *Loop, addr: net.Address) !Stream {
const sockfd = try std.os.socket(addr.any.family, std.os.SOCK.STREAM, std.os.IPPROTO.TCP);
errdefer std.os.closeSocket(sockfd);
var conn = try alloc.create(Conn);
conn.* = Conn{ .loop = loop };
try conn.connect(sockfd, addr);
return Stream{
.alloc = alloc,
.conn = conn,
.handle = sockfd,
};
}

View File

@@ -1,172 +0,0 @@
const std = @import("std");
const http = std.http;
const Client = @import("Client.zig");
const Request = @import("Client.zig").Request;
pub const Loop = @import("jsruntime").Loop;
const url = "https://w3.org";
test "blocking mode fetch API" {
const alloc = std.testing.allocator;
var loop = try Loop.init(alloc);
defer loop.deinit();
var client: Client = .{
.allocator = alloc,
.loop = &loop,
};
defer client.deinit();
// force client's CA cert scan from system.
try client.ca_bundle.rescan(client.allocator);
var res = try client.fetch(alloc, .{
.location = .{ .uri = try std.Uri.parse(url) },
.payload = .none,
});
defer res.deinit();
try std.testing.expect(res.status == .ok);
}
test "blocking mode open/send/wait API" {
const alloc = std.testing.allocator;
var loop = try Loop.init(alloc);
defer loop.deinit();
var client: Client = .{
.allocator = alloc,
.loop = &loop,
};
defer client.deinit();
// force client's CA cert scan from system.
try client.ca_bundle.rescan(client.allocator);
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{});
defer headers.deinit();
var req = try client.open(.GET, try std.Uri.parse(url), headers, .{});
defer req.deinit();
try req.send(.{});
try req.finish();
try req.wait();
try std.testing.expect(req.response.status == .ok);
}
// Example how to write an async http client using the modified standard client.
const AsyncClient = struct {
cli: Client,
const YieldImpl = Loop.Yield(AsyncRequest);
const AsyncRequest = struct {
const State = enum { new, open, send, finish, wait, done };
cli: *Client,
uri: std.Uri,
headers: std.http.Headers,
req: ?Request = undefined,
state: State = .new,
impl: YieldImpl,
err: ?anyerror = null,
pub fn deinit(self: *AsyncRequest) void {
if (self.req) |*r| r.deinit();
self.headers.deinit();
}
pub fn fetch(self: *AsyncRequest) void {
self.state = .new;
return self.impl.yield(self);
}
fn onerr(self: *AsyncRequest, err: anyerror) void {
self.state = .done;
self.err = err;
}
pub fn onYield(self: *AsyncRequest, err: ?anyerror) void {
if (err) |e| return self.onerr(e);
switch (self.state) {
.new => {
self.state = .open;
self.req = self.cli.open(.GET, self.uri, self.headers, .{}) catch |e| return self.onerr(e);
},
.open => {
self.state = .send;
self.req.?.send(.{}) catch |e| return self.onerr(e);
},
.send => {
self.state = .finish;
self.req.?.finish() catch |e| return self.onerr(e);
},
.finish => {
self.state = .wait;
self.req.?.wait() catch |e| return self.onerr(e);
},
.wait => {
self.state = .done;
return;
},
.done => return,
}
return self.impl.yield(self);
}
pub fn wait(self: *AsyncRequest) !void {
while (self.state != .done) try self.impl.tick();
if (self.err) |err| return err;
}
};
pub fn init(alloc: std.mem.Allocator, loop: *Loop) AsyncClient {
return .{
.cli = .{
.allocator = alloc,
.loop = loop,
},
};
}
pub fn deinit(self: *AsyncClient) void {
self.cli.deinit();
}
pub fn createRequest(self: *AsyncClient, uri: std.Uri) !AsyncRequest {
return .{
.impl = YieldImpl.init(self.cli.loop),
.cli = &self.cli,
.uri = uri,
.headers = .{ .allocator = self.cli.allocator, .owned = false },
};
}
};
test "non blocking client" {
const alloc = std.testing.allocator;
var loop = try Loop.init(alloc);
defer loop.deinit();
var client = AsyncClient.init(alloc, &loop);
defer client.deinit();
var reqs: [3]AsyncClient.AsyncRequest = undefined;
for (0..reqs.len) |i| {
reqs[i] = try client.createRequest(try std.Uri.parse(url));
reqs[i].fetch();
}
for (0..reqs.len) |i| {
try reqs[i].wait();
reqs[i].deinit();
}
}

114
src/browser/Browser.zig Normal file
View File

@@ -0,0 +1,114 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const js = @import("js/js.zig");
const log = @import("../log.zig");
const App = @import("../App.zig");
const HttpClient = @import("../http/Client.zig");
const Notification = @import("../Notification.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
const Session = @import("Session.zig");
// Browser is an instance of the browser.
// You can create multiple browser instances.
// A browser contains only one session.
const Browser = @This();
env: js.Env,
app: *App,
session: ?Session,
allocator: Allocator,
http_client: *HttpClient,
call_arena: ArenaAllocator,
page_arena: ArenaAllocator,
session_arena: ArenaAllocator,
transfer_arena: ArenaAllocator,
notification: *Notification,
pub fn init(app: *App) !Browser {
const allocator = app.allocator;
var env = try js.Env.init(allocator, &app.platform, &app.snapshot);
errdefer env.deinit();
const notification = try Notification.init(allocator, app.notification);
app.http.client.notification = notification;
app.http.client.next_request_id = 0; // Should we track ids in CDP only?
errdefer notification.deinit();
return .{
.app = app,
.env = env,
.session = null,
.allocator = allocator,
.notification = notification,
.http_client = app.http.client,
.call_arena = ArenaAllocator.init(allocator),
.page_arena = ArenaAllocator.init(allocator),
.session_arena = ArenaAllocator.init(allocator),
.transfer_arena = ArenaAllocator.init(allocator),
};
}
pub fn deinit(self: *Browser) void {
self.closeSession();
self.env.deinit();
self.call_arena.deinit();
self.page_arena.deinit();
self.session_arena.deinit();
self.transfer_arena.deinit();
self.http_client.notification = null;
self.notification.deinit();
}
pub fn newSession(self: *Browser) !*Session {
self.closeSession();
self.session = @as(Session, undefined);
const session = &self.session.?;
try Session.init(session, self);
return session;
}
pub fn closeSession(self: *Browser) void {
if (self.session) |*session| {
session.deinit();
self.session = null;
_ = self.session_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
self.env.lowMemoryNotification();
}
}
pub fn runMicrotasks(self: *const Browser) void {
self.env.runMicrotasks();
}
pub fn runMessageLoop(self: *const Browser) void {
while (self.env.pumpMessageLoop()) {
if (comptime IS_DEBUG) {
log.debug(.browser, "pumpMessageLoop", .{});
}
}
self.env.runIdleTasks();
}

View File

@@ -0,0 +1,517 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const builtin = @import("builtin");
const log = @import("../log.zig");
const String = @import("../string.zig").String;
const js = @import("js/js.zig");
const Page = @import("Page.zig");
const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.zig");
const EventTarget = @import("webapi/EventTarget.zig");
const Allocator = std.mem.Allocator;
const IS_DEBUG = builtin.mode == .Debug;
pub const EventManager = @This();
page: *Page,
arena: Allocator,
listener_pool: std.heap.MemoryPool(Listener),
list_pool: std.heap.MemoryPool(std.DoublyLinkedList),
lookup: std.AutoHashMapUnmanaged(usize, *std.DoublyLinkedList),
dispatch_depth: usize,
deferred_removals: std.ArrayList(struct { list: *std.DoublyLinkedList, listener: *Listener }),
pub fn init(page: *Page) EventManager {
return .{
.page = page,
.lookup = .{},
.arena = page.arena,
.list_pool = std.heap.MemoryPool(std.DoublyLinkedList).init(page.arena),
.listener_pool = std.heap.MemoryPool(Listener).init(page.arena),
.dispatch_depth = 0,
.deferred_removals = .{},
};
}
pub const RegisterOptions = struct {
once: bool = false,
capture: bool = false,
passive: bool = false,
signal: ?*@import("webapi/AbortSignal.zig") = null,
};
pub const Callback = union(enum) {
function: js.Function,
object: js.Object,
};
pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, callback: Callback, opts: RegisterOptions) !void {
if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.register", .{ .type = typ, .capture = opts.capture, .once = opts.once, .target = target });
}
// If a signal is provided and already aborted, don't register the listener
if (opts.signal) |signal| {
if (signal.getAborted()) {
return;
}
}
const gop = try self.lookup.getOrPut(self.arena, @intFromPtr(target));
if (gop.found_existing) {
// check for duplicate callbacks already registered
var node = gop.value_ptr.*.first;
while (node) |n| {
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
if (listener.typ.eqlSlice(typ)) {
const is_duplicate = switch (callback) {
.object => |obj| listener.function.eqlObject(obj),
.function => |func| listener.function.eqlFunction(func),
};
if (is_duplicate and listener.capture == opts.capture) {
return;
}
}
node = n.next;
}
} else {
gop.value_ptr.* = try self.list_pool.create();
gop.value_ptr.*.* = .{};
}
const func = switch (callback) {
.function => |f| Function{ .value = try f.persist() },
.object => |o| Function{ .object = try o.persist() },
};
const listener = try self.listener_pool.create();
listener.* = .{
.node = .{},
.once = opts.once,
.capture = opts.capture,
.passive = opts.passive,
.function = func,
.signal = opts.signal,
.typ = try String.init(self.arena, typ, .{}),
};
// append the listener to the list of listeners for this target
gop.value_ptr.*.append(&listener.node);
}
pub fn remove(self: *EventManager, target: *EventTarget, typ: []const u8, callback: Callback, use_capture: bool) void {
const list = self.lookup.get(@intFromPtr(target)) orelse return;
if (findListener(list, typ, callback, use_capture)) |listener| {
self.removeListener(list, listener);
}
}
pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void {
if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
}
event._target = target;
event._dispatch_target = target; // Store original target for composedPath()
var was_handled = false;
defer if (was_handled) {
self.page.js.runMicrotasks();
};
switch (target._type) {
.node => |node| try self.dispatchNode(node, event, &was_handled),
.xhr,
.window,
.abort_signal,
.media_query_list,
.message_port,
.text_track_cue,
.navigation,
.screen,
.screen_orientation,
.generic,
=> {
const list = self.lookup.get(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event, &was_handled);
},
}
}
// There are a lot of events that can be attached via addEventListener or as
// a property, like the XHR events, or window.onload. You might think that the
// property is just a shortcut for calling addEventListener, but they are distinct.
// An event set via property cannot be removed by removeEventListener. If you
// set both the property and add a listener, they both execute.
const DispatchWithFunctionOptions = struct {
context: []const u8,
inject_target: bool = true,
};
pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *Event, function_: ?js.Function, comptime opts: DispatchWithFunctionOptions) !void {
if (comptime IS_DEBUG) {
log.debug(.event, "dispatchWithFunction", .{ .type = event._type_string.str(), .context = opts.context, .has_function = function_ != null });
}
if (comptime opts.inject_target) {
event._target = target;
event._dispatch_target = target; // Store original target for composedPath()
}
var was_dispatched = false;
defer if (was_dispatched) {
self.page.js.runMicrotasks();
};
if (function_) |func| {
event._current_target = target;
if (func.callWithThis(void, target, .{event})) {
was_dispatched = true;
} else |err| {
// a non-JS error
log.warn(.event, opts.context, .{ .err = err });
}
}
const list = self.lookup.get(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event, &was_dispatched);
}
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled: *bool) !void {
const ShadowRoot = @import("webapi/ShadowRoot.zig");
// Defer runs even on early return - ensures event phase is reset
// and default actions execute (unless prevented)
defer {
event._event_phase = .none;
// Execute default action if not prevented
if (event._prevent_default) {
// can't return in a defer (╯°□°)╯︵ ┻━┻
} else if (event._type_string.eqlSlice("click")) {
self.page.handleClick(target) catch |err| {
log.warn(.event, "page.click", .{ .err = err });
};
} else if (event._type_string.eqlSlice("keydown")) {
self.page.handleKeydown(target, event) catch |err| {
log.warn(.event, "page.keydown", .{ .err = err });
};
}
}
var path_len: usize = 0;
var path_buffer: [128]*EventTarget = undefined;
var node: ?*Node = target;
while (node) |n| {
if (path_len >= path_buffer.len) break;
path_buffer[path_len] = n.asEventTarget();
path_len += 1;
// Check if this node is a shadow root
if (n.is(ShadowRoot)) |shadow| {
event._needs_retargeting = true;
// If event is not composed, stop at shadow boundary
if (!event._composed) {
break;
}
// Otherwise, jump to the shadow host and continue
node = shadow._host.asNode();
continue;
}
node = n._parent;
}
// Even though the window isn't part of the DOM, events always propagate
// through it in the capture phase (unless we stopped at a shadow boundary)
if (path_len < path_buffer.len) {
path_buffer[path_len] = self.page.window.asEventTarget();
path_len += 1;
}
const path = path_buffer[0..path_len];
// Phase 1: Capturing phase (root → target, excluding target)
// This happens for all events, regardless of bubbling
event._event_phase = .capturing_phase;
var i: usize = path_len;
while (i > 1) {
i -= 1;
const current_target = path[i];
if (self.lookup.get(@intFromPtr(current_target))) |list| {
try self.dispatchPhase(list, current_target, event, was_handled, true);
if (event._stop_propagation) {
return;
}
}
}
// Phase 2: At target
event._event_phase = .at_target;
const target_et = target.asEventTarget();
if (self.lookup.get(@intFromPtr(target_et))) |list| {
try self.dispatchPhase(list, target_et, event, was_handled, null);
if (event._stop_propagation) {
return;
}
}
// Phase 3: Bubbling phase (target → root, excluding target)
// This only happens if the event bubbles
if (event._bubbles) {
event._event_phase = .bubbling_phase;
for (path[1..]) |current_target| {
if (self.lookup.get(@intFromPtr(current_target))) |list| {
try self.dispatchPhase(list, current_target, event, was_handled, false);
if (event._stop_propagation) {
break;
}
}
}
}
}
fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool, comptime capture_only: ?bool) !void {
const page = self.page;
const typ = event._type_string;
// Track dispatch depth for deferred removal
self.dispatch_depth += 1;
defer {
const dispatch_depth = self.dispatch_depth;
// Only destroy deferred listeners when we exit the outermost dispatch
if (dispatch_depth == 1) {
for (self.deferred_removals.items) |removal| {
removal.list.remove(&removal.listener.node);
self.listener_pool.destroy(removal.listener);
}
self.deferred_removals.clearRetainingCapacity();
} else {
self.dispatch_depth = dispatch_depth - 1;
}
}
// Use the last listener in the list as sentinel - listeners added during dispatch will be after it
const last_node = list.last orelse return;
const last_listener: *Listener = @alignCast(@fieldParentPtr("node", last_node));
// Iterate through the list, stopping after we've encountered the last_listener
var node = list.first;
var is_done = false;
while (node) |n| {
if (is_done) {
break;
}
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
is_done = (listener == last_listener);
node = n.next;
// Skip non-matching listeners
if (!listener.typ.eql(typ)) {
continue;
}
if (comptime capture_only) |capture| {
if (listener.capture != capture) {
continue;
}
}
// Skip removed listeners
if (listener.removed) {
continue;
}
// If the listener has an aborted signal, remove it and skip
if (listener.signal) |signal| {
if (signal.getAborted()) {
self.removeListener(list, listener);
continue;
}
}
// Remove "once" listeners BEFORE calling them so nested dispatches don't see them
if (listener.once) {
self.removeListener(list, listener);
}
was_handled.* = true;
event._current_target = current_target;
// Compute adjusted target for shadow DOM retargeting (only if needed)
const original_target = event._target;
if (event._needs_retargeting) {
event._target = getAdjustedTarget(original_target, current_target);
}
switch (listener.function) {
.value => |value| try value.local().callWithThis(void, current_target, .{event}),
.string => |string| {
const str = try page.call_arena.dupeZ(u8, string.str());
try self.page.js.eval(str, null);
},
.object => |*obj_global| {
const obj = obj_global.local();
if (try obj.getFunction("handleEvent")) |handleEvent| {
try handleEvent.callWithThis(void, obj, .{event});
}
},
}
// Restore original target (only if we changed it)
if (event._needs_retargeting) {
event._target = original_target;
}
if (event._stop_immediate_propagation) {
return;
}
}
}
// Non-Node dispatching (XHR, Window without propagation)
fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool) !void {
return self.dispatchPhase(list, current_target, event, was_handled, null);
}
fn removeListener(self: *EventManager, list: *std.DoublyLinkedList, listener: *Listener) void {
// If we're in a dispatch, defer removal to avoid invalidating iteration
if (self.dispatch_depth > 0) {
listener.removed = true;
self.deferred_removals.append(self.arena, .{ .list = list, .listener = listener }) catch unreachable;
} else {
// Outside dispatch, remove immediately
list.remove(&listener.node);
self.listener_pool.destroy(listener);
}
}
fn findListener(list: *const std.DoublyLinkedList, typ: []const u8, callback: Callback, capture: bool) ?*Listener {
var node = list.first;
while (node) |n| {
node = n.next;
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
const matches = switch (callback) {
.object => |obj| listener.function.eqlObject(obj),
.function => |func| listener.function.eqlFunction(func),
};
if (!matches) {
continue;
}
if (listener.capture != capture) {
continue;
}
if (!listener.typ.eqlSlice(typ)) {
continue;
}
return listener;
}
return null;
}
const Listener = struct {
typ: String,
once: bool,
capture: bool,
passive: bool,
function: Function,
signal: ?*@import("webapi/AbortSignal.zig") = null,
node: std.DoublyLinkedList.Node,
removed: bool = false,
};
const Function = union(enum) {
value: js.Function.Global,
string: String,
object: js.Object.Global,
fn eqlFunction(self: Function, func: js.Function) bool {
return switch (self) {
.value => |v| v.isEqual(func),
else => false,
};
}
fn eqlObject(self: Function, obj: js.Object) bool {
return switch (self) {
.object => |o| return o.isEqual(obj),
else => false,
};
}
};
// Computes the adjusted target for shadow DOM event retargeting
// Returns the lowest shadow-including ancestor of original_target that is
// also an ancestor-or-self of current_target
fn getAdjustedTarget(original_target: ?*EventTarget, current_target: *EventTarget) ?*EventTarget {
const ShadowRoot = @import("webapi/ShadowRoot.zig");
const orig_node = switch ((original_target orelse return null)._type) {
.node => |n| n,
else => return original_target,
};
const curr_node = switch (current_target._type) {
.node => |n| n,
else => return original_target,
};
// Walk up from original target, checking if we can reach current target
var node: ?*Node = orig_node;
while (node) |n| {
// Check if current_target is an ancestor of n (or n itself)
if (isAncestorOrSelf(curr_node, n)) {
return n.asEventTarget();
}
// Cross shadow boundary if needed
if (n.is(ShadowRoot)) |shadow| {
node = shadow._host.asNode();
continue;
}
node = n._parent;
}
return original_target;
}
// Check if ancestor is an ancestor of (or the same as) node
// WITHOUT crossing shadow boundaries (just regular DOM tree)
fn isAncestorOrSelf(ancestor: *Node, node: *Node) bool {
if (ancestor == node) {
return true;
}
var current: ?*Node = node._parent;
while (current) |n| {
if (n == ancestor) {
return true;
}
current = n._parent;
}
return false;
}

489
src/browser/Factory.zig Normal file
View File

@@ -0,0 +1,489 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const builtin = @import("builtin");
const reflect = @import("reflect.zig");
const log = @import("../log.zig");
const String = @import("../string.zig").String;
const SlabAllocator = @import("../slab.zig").SlabAllocator;
const Page = @import("Page.zig");
const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.zig");
const UIEvent = @import("webapi/event/UIEvent.zig");
const Element = @import("webapi/Element.zig");
const Document = @import("webapi/Document.zig");
const EventTarget = @import("webapi/EventTarget.zig");
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
const Blob = @import("webapi/Blob.zig");
const AbstractRange = @import("webapi/AbstractRange.zig");
const IS_DEBUG = builtin.mode == .Debug;
const assert = std.debug.assert;
const Factory = @This();
_page: *Page,
_slab: SlabAllocator,
fn PrototypeChain(comptime types: []const type) type {
return struct {
const Self = @This();
memory: []u8,
fn totalSize() usize {
var size: usize = 0;
for (types) |T| {
size = std.mem.alignForward(usize, size, @alignOf(T));
size += @sizeOf(T);
}
return size;
}
fn maxAlign() std.mem.Alignment {
var alignment: std.mem.Alignment = .@"1";
for (types) |T| {
alignment = std.mem.Alignment.max(alignment, std.mem.Alignment.of(T));
}
return alignment;
}
fn getType(comptime index: usize) type {
return types[index];
}
fn allocate(allocator: std.mem.Allocator) !Self {
const size = comptime Self.totalSize();
const alignment = comptime Self.maxAlign();
const memory = try allocator.alignedAlloc(u8, alignment, size);
return .{ .memory = memory };
}
fn get(self: *const Self, comptime index: usize) *getType(index) {
var offset: usize = 0;
inline for (types, 0..) |T, i| {
offset = std.mem.alignForward(usize, offset, @alignOf(T));
if (i == index) {
return @as(*T, @ptrCast(@alignCast(self.memory.ptr + offset)));
}
offset += @sizeOf(T);
}
unreachable;
}
fn set(self: *const Self, comptime index: usize, value: getType(index)) void {
const ptr = self.get(index);
ptr.* = value;
}
fn setRoot(self: *const Self, comptime T: type) void {
const ptr = self.get(0);
ptr.* = .{ ._type = unionInit(T, self.get(1)) };
}
fn setMiddle(self: *const Self, comptime index: usize, comptime T: type) void {
assert(index >= 1);
assert(index < types.len);
const ptr = self.get(index);
ptr.* = .{ ._proto = self.get(index - 1), ._type = unionInit(T, self.get(index + 1)) };
}
fn setMiddleWithValue(self: *const Self, comptime index: usize, comptime T: type, value: anytype) void {
assert(index >= 1);
const ptr = self.get(index);
ptr.* = .{ ._proto = self.get(index - 1), ._type = unionInit(T, value) };
}
fn setLeaf(self: *const Self, comptime index: usize, value: anytype) void {
assert(index >= 1);
const ptr = self.get(index);
ptr.* = value;
ptr._proto = self.get(index - 1);
}
};
}
fn AutoPrototypeChain(comptime types: []const type) type {
return struct {
fn create(allocator: std.mem.Allocator, leaf_value: anytype) !*@TypeOf(leaf_value) {
const chain = try PrototypeChain(types).allocate(allocator);
const RootType = types[0];
chain.setRoot(RootType.Type);
inline for (1..types.len - 1) |i| {
const MiddleType = types[i];
chain.setMiddle(i, MiddleType.Type);
}
chain.setLeaf(types.len - 1, leaf_value);
return chain.get(types.len - 1);
}
};
}
pub fn init(page: *Page) Factory {
return .{
._page = page,
._slab = SlabAllocator.init(page.arena, 128),
};
}
// this is a root object
pub fn eventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const chain = try PrototypeChain(
&.{ EventTarget, @TypeOf(child) },
).allocate(allocator);
const event_ptr = chain.get(0);
event_ptr.* = .{
._type = unionInit(EventTarget.Type, chain.get(1)),
};
chain.setLeaf(1, child);
return chain.get(1);
}
fn eventInit(typ: []const u8, value: anytype, page: *Page) !Event {
// Round to 2ms for privacy (browsers do this)
const raw_timestamp = @import("../datetime.zig").milliTimestamp(.monotonic);
const time_stamp = (raw_timestamp / 2) * 2;
return .{
._type = unionInit(Event.Type, value),
._type_string = try String.init(page.arena, typ, .{}),
._time_stamp = time_stamp,
};
}
// this is a root object
pub fn event(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const chain = try PrototypeChain(
&.{ Event, @TypeOf(child) },
).allocate(allocator);
// Special case: Event has a _type_string field, so we need manual setup
const event_ptr = chain.get(0);
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
chain.setLeaf(1, child);
return chain.get(1);
}
pub fn uiEvent(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const chain = try PrototypeChain(
&.{ Event, UIEvent, @TypeOf(child) },
).allocate(allocator);
// Special case: Event has a _type_string field, so we need manual setup
const event_ptr = chain.get(0);
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
chain.setMiddle(1, UIEvent.Type);
chain.setLeaf(2, child);
return chain.get(2);
}
pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
// Special case: Blob has slice and mime fields, so we need manual setup
const chain = try PrototypeChain(
&.{ Blob, @TypeOf(child) },
).allocate(allocator);
const blob_ptr = chain.get(0);
blob_ptr.* = .{
._type = unionInit(Blob.Type, chain.get(1)),
._slice = "",
._mime = "",
};
chain.setLeaf(1, child);
return chain.get(1);
}
pub fn abstractRange(self: *Factory, child: anytype, page: *Page) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const chain = try PrototypeChain(&.{ AbstractRange, @TypeOf(child) }).allocate(allocator);
const doc = page.document.asNode();
chain.set(0, AbstractRange{
._type = unionInit(AbstractRange.Type, chain.get(1)),
._end_offset = 0,
._start_offset = 0,
._end_container = doc,
._start_container = doc,
});
chain.setLeaf(1, child);
return chain.get(1);
}
pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, Node, @TypeOf(child) },
).create(allocator, child);
}
pub fn document(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, Node, Document, @TypeOf(child) },
).create(allocator, child);
}
pub fn documentFragment(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, Node, Node.DocumentFragment, @TypeOf(child) },
).create(allocator, child);
}
pub fn element(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, Node, Element, @TypeOf(child) },
).create(allocator, child);
}
pub fn htmlElement(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, Node, Element, Element.Html, @TypeOf(child) },
).create(allocator, child);
}
pub fn htmlMediaElement(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, Node, Element, Element.Html, Element.Html.Media, @TypeOf(child) },
).create(allocator, child);
}
pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const ChildT = @TypeOf(child);
if (ChildT == Element.Svg) {
return self.element(child);
}
const chain = try PrototypeChain(
&.{ EventTarget, Node, Element, Element.Svg, ChildT },
).allocate(allocator);
chain.setRoot(EventTarget.Type);
chain.setMiddle(1, Node.Type);
chain.setMiddle(2, Element.Type);
// will never allocate, can't fail
const tag_name_str = String.init(self._page.arena, tag_name, .{}) catch unreachable;
// Manually set Element.Svg with the tag_name
chain.set(3, .{
._proto = chain.get(2),
._tag_name = tag_name_str,
._type = unionInit(Element.Svg.Type, chain.get(4)),
});
chain.setLeaf(4, child);
return chain.get(4);
}
pub fn xhrEventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
&.{ EventTarget, XMLHttpRequestEventTarget, @TypeOf(child) },
).create(allocator, child);
}
pub fn textTrackCue(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const TextTrackCue = @import("webapi/media/TextTrackCue.zig");
return try AutoPrototypeChain(
&.{ EventTarget, TextTrackCue, @TypeOf(child) },
).create(allocator, child);
}
fn hasChainRoot(comptime T: type) bool {
// Check if this is a root
if (@hasDecl(T, "_prototype_root")) {
return true;
}
// If no _proto field, we're at the top but not a recognized root
if (!@hasField(T, "_proto")) return false;
// Get the _proto field's type and recurse
const fields = @typeInfo(T).@"struct".fields;
inline for (fields) |field| {
if (std.mem.eql(u8, field.name, "_proto")) {
const ProtoType = reflect.Struct(field.type);
return hasChainRoot(ProtoType);
}
}
return false;
}
fn isChainType(comptime T: type) bool {
if (@hasField(T, "_proto")) return false;
return comptime hasChainRoot(T);
}
pub fn destroy(self: *Factory, value: anytype) void {
const S = reflect.Struct(@TypeOf(value));
if (comptime IS_DEBUG) {
// We should always destroy from the leaf down.
if (@hasDecl(S, "_prototype_root")) {
// A Event{._type == .generic} (or any other similar types)
// _should_ be destoyed directly. The _type = .generic is a pseudo
// child
if (S != Event or value._type != .generic) {
log.fatal(.bug, "factory.destroy.event", .{ .type = @typeName(S) });
unreachable;
}
}
}
if (comptime isChainType(S)) {
self.destroyChain(value, true, 0, std.mem.Alignment.@"1");
} else {
self.destroyStandalone(value);
}
}
pub fn destroyStandalone(self: *Factory, value: anytype) void {
const S = reflect.Struct(@TypeOf(value));
assert(!@hasDecl(S, "_prototype_root"));
const allocator = self._slab.allocator();
if (@hasDecl(S, "deinit")) {
// And it has a deinit, we'll call it
switch (@typeInfo(@TypeOf(S.deinit)).@"fn".params.len) {
1 => value.deinit(),
2 => value.deinit(self._page),
else => @compileLog(@typeName(S) ++ " has an invalid deinit function"),
}
}
allocator.destroy(value);
}
fn destroyChain(
self: *Factory,
value: anytype,
comptime first: bool,
old_size: usize,
old_align: std.mem.Alignment,
) void {
const S = reflect.Struct(@TypeOf(value));
const allocator = self._slab.allocator();
// aligns the old size to the alignment of this element
const current_size = std.mem.alignForward(usize, old_size, @alignOf(S));
const alignment = std.mem.Alignment.fromByteUnits(@alignOf(S));
const new_align = std.mem.Alignment.max(old_align, alignment);
const new_size = current_size + @sizeOf(S);
// This is initially called from a deinit. We don't want to call that
// same deinit. So when this is the first time destroyChain is called
// we don't call deinit (because we're in that deinit)
if (!comptime first) {
// But if it isn't the first time
if (@hasDecl(S, "deinit")) {
// And it has a deinit, we'll call it
switch (@typeInfo(@TypeOf(S.deinit)).@"fn".params.len) {
1 => value.deinit(),
2 => value.deinit(self._page),
else => @compileLog(@typeName(S) ++ " has an invalid deinit function"),
}
}
}
if (@hasField(S, "_proto")) {
self.destroyChain(value._proto, false, new_size, new_align);
} else if (@hasDecl(S, "JsApi")) {
// Doesn't have a _proto, but has a JsApi.
if (self._page.js.removeTaggedMapping(@intFromPtr(value))) |tagged| {
allocator.destroy(tagged);
}
} else {
// no proto so this is the head of the chain.
// we use this as the ptr to the start of the chain.
// and we have summed up the length.
assert(@hasDecl(S, "_prototype_root"));
const memory_ptr: [*]const u8 = @ptrCast(value);
const len = std.mem.alignForward(usize, new_size, new_align.toByteUnits());
allocator.free(memory_ptr[0..len]);
}
}
pub fn createT(self: *Factory, comptime T: type) !*T {
const allocator = self._slab.allocator();
return try allocator.create(T);
}
pub fn create(self: *Factory, value: anytype) !*@TypeOf(value) {
const ptr = try self.createT(@TypeOf(value));
ptr.* = value;
return ptr;
}
fn unionInit(comptime T: type, value: anytype) T {
const V = @TypeOf(value);
const field_name = comptime unionFieldName(T, V);
return @unionInit(T, field_name, value);
}
// There can be friction between comptime and runtime. Comptime has to
// account for all possible types, even if some runtime flow makes certain
// cases impossible. At runtime, we always call `unionFieldName` with the
// correct struct or pointer type. But at comptime time, `unionFieldName`
// is called with both variants (S and *S). So we use reflect.Struct().
// This only works because we never have a union with a field S and another
// field *S.
fn unionFieldName(comptime T: type, comptime V: type) []const u8 {
inline for (@typeInfo(T).@"union".fields) |field| {
if (reflect.Struct(field.type) == reflect.Struct(V)) {
return field.name;
}
}
@compileError(@typeName(V) ++ " is not a valid type for " ++ @typeName(T) ++ ".type");
}

530
src/browser/Mime.zig Normal file
View File

@@ -0,0 +1,530 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Mime = @This();
content_type: ContentType,
params: []const u8 = "",
// IANA defines max. charset value length as 40.
// We keep 41 for null-termination since HTML parser expects in this format.
charset: [41]u8 = default_charset,
charset_len: usize = 5,
/// String "UTF-8" continued by null characters.
pub const default_charset = .{ 'U', 'T', 'F', '-', '8' } ++ .{0} ** 36;
/// Mime with unknown Content-Type, empty params and empty charset.
pub const unknown = Mime{ .content_type = .{ .unknown = {} } };
pub const ContentTypeEnum = enum {
text_xml,
text_html,
text_javascript,
text_plain,
text_css,
application_json,
unknown,
other,
};
pub const ContentType = union(ContentTypeEnum) {
text_xml: void,
text_html: void,
text_javascript: void,
text_plain: void,
text_css: void,
application_json: void,
unknown: void,
other: struct { type: []const u8, sub_type: []const u8 },
};
pub fn contentTypeString(mime: *const Mime) []const u8 {
return switch (mime.content_type) {
.text_xml => "text/xml",
.text_html => "text/html",
.text_javascript => "application/javascript",
.text_plain => "text/plain",
.text_css => "text/css",
.application_json => "application/json",
else => "",
};
}
/// Returns the null-terminated charset value.
pub fn charsetStringZ(mime: *const Mime) [:0]const u8 {
return mime.charset[0..mime.charset_len :0];
}
pub fn charsetString(mime: *const Mime) []const u8 {
return mime.charset[0..mime.charset_len];
}
/// Removes quotes of value if quotes are given.
///
/// Currently we don't validate the charset.
/// See section 2.3 Naming Requirements:
/// https://datatracker.ietf.org/doc/rfc2978/
fn parseCharset(value: []const u8) error{ CharsetTooBig, Invalid }![]const u8 {
// Cannot be larger than 40.
// https://datatracker.ietf.org/doc/rfc2978/
if (value.len > 40) return error.CharsetTooBig;
// If the first char is a quote, look for a pair.
if (value[0] == '"') {
if (value.len < 3 or value[value.len - 1] != '"') {
return error.Invalid;
}
return value[1 .. value.len - 1];
}
// No quotes.
return value;
}
pub fn parse(input: []u8) !Mime {
if (input.len > 255) {
return error.TooBig;
}
// Zig's trim API is broken. The return type is always `[]const u8`,
// even if the input type is `[]u8`. @constCast is safe here.
var normalized = @constCast(std.mem.trim(u8, input, &std.ascii.whitespace));
_ = std.ascii.lowerString(normalized, normalized);
const content_type, const type_len = try parseContentType(normalized);
if (type_len >= normalized.len) {
return .{ .content_type = content_type };
}
const params = trimLeft(normalized[type_len..]);
var charset: [41]u8 = undefined;
var charset_len: usize = undefined;
var it = std.mem.splitScalar(u8, params, ';');
while (it.next()) |attr| {
const i = std.mem.indexOfScalarPos(u8, attr, 0, '=') orelse return error.Invalid;
const name = trimLeft(attr[0..i]);
const value = trimRight(attr[i + 1 ..]);
if (value.len == 0) {
return error.Invalid;
}
const attribute_name = std.meta.stringToEnum(enum {
charset,
}, name) orelse continue;
switch (attribute_name) {
.charset => {
if (value.len == 0) {
break;
}
const attribute_value = try parseCharset(value);
@memcpy(charset[0..attribute_value.len], attribute_value);
// Null-terminate right after attribute value.
charset[attribute_value.len] = 0;
charset_len = attribute_value.len;
},
}
}
return .{
.params = params,
.charset = charset,
.charset_len = charset_len,
.content_type = content_type,
};
}
pub fn sniff(body: []const u8) ?Mime {
// 0x0C is form feed
const content = std.mem.trimLeft(u8, body, &.{ ' ', '\t', '\n', '\r', 0x0C });
if (content.len == 0) {
return null;
}
if (content[0] != '<') {
if (std.mem.startsWith(u8, content, &.{ 0xEF, 0xBB, 0xBF })) {
// UTF-8 BOM
return .{ .content_type = .{ .text_plain = {} } };
}
if (std.mem.startsWith(u8, content, &.{ 0xFE, 0xFF })) {
// UTF-16 big-endian BOM
return .{ .content_type = .{ .text_plain = {} } };
}
if (std.mem.startsWith(u8, content, &.{ 0xFF, 0xFE })) {
// UTF-16 little-endian BOM
return .{ .content_type = .{ .text_plain = {} } };
}
return null;
}
// The longest prefix we have is "<!DOCTYPE HTML ", 15 bytes. If we're
// here, we already know content[0] == '<', so we can skip that. So 14
// bytes.
// +1 because we don't need the leading '<'
var buf: [14]u8 = undefined;
const stripped = content[1..];
const prefix_len = @min(stripped.len, buf.len);
const prefix = std.ascii.lowerString(&buf, stripped[0..prefix_len]);
// we already know it starts with a <
const known_prefixes = [_]struct { []const u8, ContentType }{
.{ "!doctype html", .{ .text_html = {} } },
.{ "html", .{ .text_html = {} } },
.{ "script", .{ .text_html = {} } },
.{ "iframe", .{ .text_html = {} } },
.{ "h1", .{ .text_html = {} } },
.{ "div", .{ .text_html = {} } },
.{ "font", .{ .text_html = {} } },
.{ "table", .{ .text_html = {} } },
.{ "a", .{ .text_html = {} } },
.{ "style", .{ .text_html = {} } },
.{ "title", .{ .text_html = {} } },
.{ "b", .{ .text_html = {} } },
.{ "body", .{ .text_html = {} } },
.{ "br", .{ .text_html = {} } },
.{ "p", .{ .text_html = {} } },
.{ "!--", .{ .text_html = {} } },
.{ "xml", .{ .text_xml = {} } },
};
inline for (known_prefixes) |kp| {
const known_prefix = kp.@"0";
if (std.mem.startsWith(u8, prefix, known_prefix) and prefix.len > known_prefix.len) {
const next = prefix[known_prefix.len];
// a "tag-terminating-byte"
if (next == ' ' or next == '>') {
return .{ .content_type = kp.@"1" };
}
}
}
return null;
}
pub fn isHTML(self: *const Mime) bool {
return self.content_type == .text_html;
}
// we expect value to be lowercase
fn parseContentType(value: []const u8) !struct { ContentType, usize } {
const end = std.mem.indexOfScalarPos(u8, value, 0, ';') orelse value.len;
const type_name = trimRight(value[0..end]);
const attribute_start = end + 1;
if (std.meta.stringToEnum(enum {
@"text/xml",
@"text/html",
@"text/css",
@"text/plain",
@"text/javascript",
@"application/javascript",
@"application/x-javascript",
@"application/json",
}, type_name)) |known_type| {
const ct: ContentType = switch (known_type) {
.@"text/xml" => .{ .text_xml = {} },
.@"text/html" => .{ .text_html = {} },
.@"text/javascript", .@"application/javascript", .@"application/x-javascript" => .{ .text_javascript = {} },
.@"text/plain" => .{ .text_plain = {} },
.@"text/css" => .{ .text_css = {} },
.@"application/json" => .{ .application_json = {} },
};
return .{ ct, attribute_start };
}
const separator = std.mem.indexOfScalarPos(u8, type_name, 0, '/') orelse return error.Invalid;
const main_type = value[0..separator];
const sub_type = trimRight(value[separator + 1 .. end]);
if (main_type.len == 0 or validType(main_type) == false) {
return error.Invalid;
}
if (sub_type.len == 0 or validType(sub_type) == false) {
return error.Invalid;
}
return .{ .{ .other = .{
.type = main_type,
.sub_type = sub_type,
} }, attribute_start };
}
const VALID_CODEPOINTS = blk: {
var v: [256]bool = undefined;
for (0..256) |i| {
v[i] = std.ascii.isAlphanumeric(i);
}
for ("!#$%&\\*+-.^'_`|~") |b| {
v[b] = true;
}
break :blk v;
};
fn validType(value: []const u8) bool {
for (value) |b| {
if (VALID_CODEPOINTS[b] == false) {
return false;
}
}
return true;
}
fn trimLeft(s: []const u8) []const u8 {
return std.mem.trimLeft(u8, s, &std.ascii.whitespace);
}
fn trimRight(s: []const u8) []const u8 {
return std.mem.trimRight(u8, s, &std.ascii.whitespace);
}
const testing = @import("../testing.zig");
test "Mime: invalid" {
defer testing.reset();
const invalids = [_][]const u8{
"",
"text",
"text /html",
"text/ html",
"text / html",
"text/html other",
"text/html; x",
"text/html; x=",
"text/html; x= ",
"text/html; = ",
"text/html;=",
"text/html; charset=\"\"",
"text/html; charset=\"",
"text/html; charset=\"\\",
};
for (invalids) |invalid| {
const mutable_input = try testing.arena_allocator.dupe(u8, invalid);
try testing.expectError(error.Invalid, Mime.parse(mutable_input));
}
}
test "Mime: parse common" {
defer testing.reset();
try expect(.{ .content_type = .{ .text_xml = {} } }, "text/xml");
try expect(.{ .content_type = .{ .text_html = {} } }, "text/html");
try expect(.{ .content_type = .{ .text_plain = {} } }, "text/plain");
try expect(.{ .content_type = .{ .text_xml = {} } }, "text/xml;");
try expect(.{ .content_type = .{ .text_html = {} } }, "text/html;");
try expect(.{ .content_type = .{ .text_plain = {} } }, "text/plain;");
try expect(.{ .content_type = .{ .text_xml = {} } }, " \ttext/xml");
try expect(.{ .content_type = .{ .text_html = {} } }, "text/html ");
try expect(.{ .content_type = .{ .text_plain = {} } }, "text/plain \t\t");
try expect(.{ .content_type = .{ .text_xml = {} } }, "TEXT/xml");
try expect(.{ .content_type = .{ .text_html = {} } }, "text/Html");
try expect(.{ .content_type = .{ .text_plain = {} } }, "TEXT/PLAIN");
try expect(.{ .content_type = .{ .text_xml = {} } }, " TeXT/xml");
try expect(.{ .content_type = .{ .text_html = {} } }, "teXt/HtML ;");
try expect(.{ .content_type = .{ .text_plain = {} } }, "tExT/PlAiN;");
try expect(.{ .content_type = .{ .text_javascript = {} } }, "text/javascript");
try expect(.{ .content_type = .{ .text_javascript = {} } }, "Application/JavaScript");
try expect(.{ .content_type = .{ .text_javascript = {} } }, "application/x-javascript");
try expect(.{ .content_type = .{ .application_json = {} } }, "application/json");
try expect(.{ .content_type = .{ .text_css = {} } }, "text/css");
}
test "Mime: parse uncommon" {
defer testing.reset();
const text_csv = Expectation{
.content_type = .{ .other = .{ .type = "text", .sub_type = "csv" } },
};
try expect(text_csv, "text/csv");
try expect(text_csv, "text/csv;");
try expect(text_csv, " text/csv\t ");
try expect(text_csv, " text/csv\t ;");
try expect(
.{ .content_type = .{ .other = .{ .type = "text", .sub_type = "csv" } } },
"Text/CSV",
);
}
test "Mime: parse charset" {
defer testing.reset();
try expect(.{
.content_type = .{ .text_xml = {} },
.charset = "utf-8",
.params = "charset=utf-8",
}, "text/xml; charset=utf-8");
try expect(.{
.content_type = .{ .text_xml = {} },
.charset = "utf-8",
.params = "charset=\"utf-8\"",
}, "text/xml;charset=\"UTF-8\"");
try expect(.{
.content_type = .{ .text_html = {} },
.charset = "iso-8859-1",
.params = "charset=\"iso-8859-1\"",
}, "text/html; charset=\"iso-8859-1\"");
try expect(.{
.content_type = .{ .text_html = {} },
.charset = "iso-8859-1",
.params = "charset=\"iso-8859-1\"",
}, "text/html; charset=\"ISO-8859-1\"");
try expect(.{
.content_type = .{ .text_xml = {} },
.charset = "custom-non-standard-charset-value",
.params = "charset=\"custom-non-standard-charset-value\"",
}, "text/xml;charset=\"custom-non-standard-charset-value\"");
}
test "Mime: isHTML" {
defer testing.reset();
const assert = struct {
fn assert(expected: bool, input: []const u8) !void {
const mutable_input = try testing.arena_allocator.dupe(u8, input);
var mime = try Mime.parse(mutable_input);
try testing.expectEqual(expected, mime.isHTML());
}
}.assert;
try assert(true, "text/html");
try assert(true, "text/html;");
try assert(true, "text/html; charset=utf-8");
try assert(false, "text/htm"); // htm not html
try assert(false, "text/plain");
try assert(false, "over/9000");
}
test "Mime: sniff" {
try testing.expectEqual(null, Mime.sniff(""));
try testing.expectEqual(null, Mime.sniff("<htm"));
try testing.expectEqual(null, Mime.sniff("<html!"));
try testing.expectEqual(null, Mime.sniff("<a_"));
try testing.expectEqual(null, Mime.sniff("<!doctype html"));
try testing.expectEqual(null, Mime.sniff("<!doctype html>"));
try testing.expectEqual(null, Mime.sniff("\n <!doctype html>"));
try testing.expectEqual(null, Mime.sniff("\n \t <font/>"));
const expectHTML = struct {
fn expect(input: []const u8) !void {
try testing.expectEqual(.text_html, std.meta.activeTag(Mime.sniff(input).?.content_type));
}
}.expect;
try expectHTML("<!doctype html ");
try expectHTML("\n \t <!DOCTYPE HTML ");
try expectHTML("<html ");
try expectHTML("\n \t <HtmL> even more stufff");
try expectHTML("<script>");
try expectHTML("\n \t <SCRIpt >alert(document.cookies)</script>");
try expectHTML("<iframe>");
try expectHTML(" \t <ifRAME >");
try expectHTML("<h1>");
try expectHTML(" <H1>");
try expectHTML("<div>");
try expectHTML("\n\r\r <DiV>");
try expectHTML("<font>");
try expectHTML(" <fonT>");
try expectHTML("<table>");
try expectHTML("\t\t<TAblE>");
try expectHTML("<a>");
try expectHTML("\n\n<A>");
try expectHTML("<style>");
try expectHTML(" \n\t <STyLE>");
try expectHTML("<title>");
try expectHTML(" \n\t <TITLE>");
try expectHTML("<b>");
try expectHTML(" \n\t <B>");
try expectHTML("<body>");
try expectHTML(" \n\t <BODY>");
try expectHTML("<br>");
try expectHTML(" \n\t <BR>");
try expectHTML("<p>");
try expectHTML(" \n\t <P>");
try expectHTML("<!-->");
try expectHTML(" \n\t <!-->");
}
const Expectation = struct {
content_type: Mime.ContentType,
params: []const u8 = "",
charset: ?[]const u8 = null,
};
fn expect(expected: Expectation, input: []const u8) !void {
const mutable_input = try testing.arena_allocator.dupe(u8, input);
const actual = try Mime.parse(mutable_input);
try testing.expectEqual(
std.meta.activeTag(expected.content_type),
std.meta.activeTag(actual.content_type),
);
switch (expected.content_type) {
.other => |e| {
const a = actual.content_type.other;
try testing.expectEqual(e.type, a.type);
try testing.expectEqual(e.sub_type, a.sub_type);
},
else => {}, // already asserted above
}
try testing.expectEqual(expected.params, actual.params);
if (expected.charset) |ec| {
// We remove the null characters for testing purposes here.
try testing.expectEqual(ec, actual.charsetString());
} else {
const m: Mime = .unknown;
try testing.expectEqual(m.charsetStringZ(), actual.charsetStringZ());
}
}

3068
src/browser/Page.zig Normal file

File diff suppressed because it is too large Load Diff

116
src/browser/Scheduler.zig Normal file
View File

@@ -0,0 +1,116 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const builtin = @import("builtin");
const log = @import("../log.zig");
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
const IS_DEBUG = builtin.mode == .Debug;
const Queue = std.PriorityQueue(Task, void, struct {
fn compare(_: void, a: Task, b: Task) std.math.Order {
const time_order = std.math.order(a.run_at, b.run_at);
if (time_order != .eq) return time_order;
// Break ties with sequence number to maintain FIFO order
return std.math.order(a.sequence, b.sequence);
}
}.compare);
const Scheduler = @This();
_sequence: u64,
low_priority: Queue,
high_priority: Queue,
pub fn init(allocator: std.mem.Allocator) Scheduler {
return .{
._sequence = 0,
.low_priority = Queue.init(allocator, {}),
.high_priority = Queue.init(allocator, {}),
};
}
const AddOpts = struct {
name: []const u8 = "",
low_priority: bool = false,
};
pub fn add(self: *Scheduler, ctx: *anyopaque, cb: Callback, run_in_ms: u32, opts: AddOpts) !void {
if (comptime IS_DEBUG) {
log.debug(.scheduler, "scheduler.add", .{ .name = opts.name, .run_in_ms = run_in_ms, .low_priority = opts.low_priority });
}
var queue = if (opts.low_priority) &self.low_priority else &self.high_priority;
const seq = self._sequence + 1;
self._sequence = seq;
return queue.add(.{
.ctx = ctx,
.callback = cb,
.sequence = seq,
.name = opts.name,
.run_at = milliTimestamp(.monotonic) + run_in_ms,
});
}
pub fn run(self: *Scheduler) !?u64 {
_ = try self.runQueue(&self.low_priority);
return self.runQueue(&self.high_priority);
}
fn runQueue(self: *Scheduler, queue: *Queue) !?u64 {
if (queue.count() == 0) {
return null;
}
const now = milliTimestamp(.monotonic);
while (queue.peek()) |*task_| {
if (task_.run_at > now) {
return @intCast(task_.run_at - now);
}
var task = queue.remove();
if (comptime IS_DEBUG) {
log.debug(.scheduler, "scheduler.runTask", .{ .name = task.name });
}
const repeat_in_ms = task.callback(task.ctx) catch |err| {
log.warn(.scheduler, "task.callback", .{ .name = task.name, .err = err });
continue;
};
if (repeat_in_ms) |ms| {
// Task cannot be repeated immediately, and they should know that
if (comptime IS_DEBUG) {
std.debug.assert(ms != 0);
}
task.run_at = now + ms;
try self.low_priority.add(task);
}
}
return null;
}
const Task = struct {
run_at: u64,
sequence: u64,
ctx: *anyopaque,
name: []const u8,
callback: Callback,
};
const Callback = *const fn (ctx: *anyopaque) anyerror!?u32;

File diff suppressed because it is too large Load Diff

176
src/browser/Session.zig Normal file
View File

@@ -0,0 +1,176 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const log = @import("../log.zig");
const js = @import("js/js.zig");
const storage = @import("webapi/storage/storage.zig");
const Navigation = @import("webapi/navigation/Navigation.zig");
const History = @import("webapi/History.zig");
const Page = @import("Page.zig");
const Browser = @import("Browser.zig");
const Allocator = std.mem.Allocator;
const IS_DEBUG = @import("builtin").mode == .Debug;
// Session is like a browser's tab.
// It owns the js env and the loader for all the pages of the session.
// You can create successively multiple pages for a session, but you must
// deinit a page before running another one.
const Session = @This();
browser: *Browser,
// Used to create our Inspector and in the BrowserContext.
arena: Allocator,
// The page's arena is unsuitable for data that has to existing while
// navigating from one page to another. For example, if we're clicking
// on an HREF, the URL exists in the original page (where the click
// originated) but also has to exist in the new page.
// While we could use the Session's arena, this could accumulate a lot of
// memory if we do many navigation events. The `transfer_arena` is meant to
// bridge the gap: existing long enough to store any data needed to end one
// page and start another.
transfer_arena: Allocator,
executor: js.ExecutionWorld,
cookie_jar: storage.Cookie.Jar,
storage_shed: storage.Shed,
history: History,
navigation: Navigation,
page: ?*Page = null,
pub fn init(self: *Session, browser: *Browser) !void {
var executor = try browser.env.newExecutionWorld();
errdefer executor.deinit();
const allocator = browser.app.allocator;
const session_allocator = browser.session_arena.allocator();
self.* = .{
.browser = browser,
.executor = executor,
.storage_shed = .{},
.arena = session_allocator,
.cookie_jar = storage.Cookie.Jar.init(allocator),
.navigation = .{},
.history = .{},
.transfer_arena = browser.transfer_arena.allocator(),
};
}
pub fn deinit(self: *Session) void {
if (self.page != null) {
self.removePage();
}
self.cookie_jar.deinit();
self.storage_shed.deinit(self.browser.app.allocator);
self.executor.deinit();
}
// NOTE: the caller is not the owner of the returned value,
// the pointer on Page is just returned as a convenience
pub fn createPage(self: *Session) !*Page {
lp.assert(self.page == null, "Session.createPage - page not null", .{});
const page_arena = &self.browser.page_arena;
_ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
self.page = try Page.init(page_arena.allocator(), self.browser.call_arena.allocator(), self);
const page = self.page.?;
// Creates a new NavigationEventTarget for this page.
try self.navigation.onNewPage(page);
if (comptime IS_DEBUG) {
log.debug(.browser, "create page", .{});
}
// start JS env
// Inform CDP the main page has been created such that additional context for other Worlds can be created as well
self.browser.notification.dispatch(.page_created, page);
return page;
}
pub fn removePage(self: *Session) void {
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
self.browser.notification.dispatch(.page_remove, .{});
lp.assert(self.page != null, "Session.removePage - page is null", .{});
self.page.?.deinit();
self.page = null;
self.navigation.onRemovePage();
if (comptime IS_DEBUG) {
log.debug(.browser, "remove page", .{});
}
}
pub fn currentPage(self: *Session) ?*Page {
return self.page orelse return null;
}
pub const WaitResult = enum {
done,
no_page,
cdp_socket,
navigate,
};
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
while (true) {
const page = self.page orelse return .no_page;
switch (page.wait(wait_ms)) {
.navigate => self.processScheduledNavigation() catch return .done,
else => |result| return result,
}
// if we've successfull navigated, we'll give the new page another
// page.wait(wait_ms)
}
}
fn processScheduledNavigation(self: *Session) !void {
const qn = self.page.?._queued_navigation.?;
defer _ = self.browser.transfer_arena.reset(.{ .retain_with_limit = 8 * 1024 });
// This was already aborted on the page, but it would be pretty
// bad if old requests went to the new page, so let's make double sure
self.browser.http_client.abort();
self.removePage();
const page = self.createPage() catch |err| {
log.err(.browser, "queued navigation page error", .{
.err = err,
.url = qn.url,
});
return err;
};
page.navigate(qn.url, qn.opts) catch |err| {
log.err(.browser, "queued navigation error", .{ .err = err, .url = qn.url });
return err;
};
}

759
src/browser/URL.zig Normal file
View File

@@ -0,0 +1,759 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const Allocator = std.mem.Allocator;
const ResolveOpts = struct {
always_dupe: bool = false,
};
// path is anytype, so that it can be used with both []const u8 and [:0]const u8
pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime opts: ResolveOpts) ![:0]const u8 {
const PT = @TypeOf(path);
if (base.len == 0 or isCompleteHTTPUrl(path)) {
if (comptime opts.always_dupe or !isNullTerminated(PT)) {
return allocator.dupeZ(u8, path);
}
return path;
}
if (path.len == 0) {
if (comptime opts.always_dupe) {
return allocator.dupeZ(u8, base);
}
return base;
}
if (path[0] == '?') {
const base_path_end = std.mem.indexOfAny(u8, base, "?#") orelse base.len;
return std.mem.joinZ(allocator, "", &.{ base[0..base_path_end], path });
}
if (path[0] == '#') {
const base_fragment_start = std.mem.indexOfScalar(u8, base, '#') orelse base.len;
return std.mem.joinZ(allocator, "", &.{ base[0..base_fragment_start], path });
}
if (std.mem.startsWith(u8, path, "//")) {
// network-path reference
const index = std.mem.indexOfScalar(u8, base, ':') orelse {
if (comptime isNullTerminated(PT)) {
return path;
}
return allocator.dupeZ(u8, path);
};
const protocol = base[0 .. index + 1];
return std.mem.joinZ(allocator, "", &.{ protocol, path });
}
const scheme_end = std.mem.indexOf(u8, base, "://");
const authority_start = if (scheme_end) |end| end + 3 else 0;
const path_start = std.mem.indexOfScalarPos(u8, base, authority_start, '/') orelse base.len;
if (path[0] == '/') {
return std.mem.joinZ(allocator, "", &.{ base[0..path_start], path });
}
var normalized_base: []const u8 = base[0..path_start];
if (path_start < base.len) {
if (std.mem.lastIndexOfScalar(u8, base[path_start + 1 ..], '/')) |pos| {
normalized_base = base[0 .. path_start + 1 + pos];
}
}
// trailing space so that we always have space to append the null terminator
var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " });
const end = out.len - 1;
const path_marker = path_start + 1;
// Strip out ./ and ../. This is done in-place, because doing so can
// only ever make `out` smaller. After this, `out` cannot be freed by
// an allocator, which is ok, because we expect allocator to be an arena.
var in_i: usize = 0;
var out_i: usize = 0;
while (in_i < end) {
if (std.mem.startsWith(u8, out[in_i..], "./")) {
in_i += 2;
continue;
}
if (std.mem.startsWith(u8, out[in_i..], "../")) {
lp.assert(out[out_i - 1] == '/', "URL.resolve", .{ .out = out });
if (out_i > path_marker) {
// go back before the /
out_i -= 2;
while (out_i > 1 and out[out_i - 1] != '/') {
out_i -= 1;
}
} else {
// if out_i == path_marker, than we've reached the start of
// the path. We can't ../ any more. E.g.:
// http://www.example.com/../hello.
// You might think that's an error, but, at least with
// new URL('../hello', 'http://www.example.com/')
// it just ignores the extra ../
}
in_i += 3;
continue;
}
out[out_i] = out[in_i];
in_i += 1;
out_i += 1;
}
// we always have an extra space
out[out_i] = 0;
return out[0..out_i :0];
}
fn isNullTerminated(comptime value: type) bool {
return @typeInfo(value).pointer.sentinel_ptr != null;
}
pub fn isCompleteHTTPUrl(url: []const u8) bool {
if (url.len < 3) { // Minimum is "x://"
return false;
}
// very common case
if (url[0] == '/') {
return false;
}
// Check if there's a scheme (protocol) ending with ://
const colon_pos = std.mem.indexOfScalar(u8, url, ':') orelse return false;
// Check if it's followed by //
if (colon_pos + 2 >= url.len or url[colon_pos + 1] != '/' or url[colon_pos + 2] != '/') {
return false;
}
// Validate that everything before the colon is a valid scheme
// A scheme must start with a letter and contain only letters, digits, +, -, .
if (colon_pos == 0) {
return false;
}
const scheme = url[0..colon_pos];
if (!std.ascii.isAlphabetic(scheme[0])) {
return false;
}
for (scheme[1..]) |c| {
if (!std.ascii.isAlphanumeric(c) and c != '+' and c != '-' and c != '.') {
return false;
}
}
return true;
}
pub fn getUsername(raw: [:0]const u8) []const u8 {
const user_info = getUserInfo(raw) orelse return "";
const pos = std.mem.indexOfScalarPos(u8, user_info, 0, ':') orelse return user_info;
return user_info[0..pos];
}
pub fn getPassword(raw: [:0]const u8) []const u8 {
const user_info = getUserInfo(raw) orelse return "";
const pos = std.mem.indexOfScalarPos(u8, user_info, 0, ':') orelse return "";
return user_info[pos + 1 ..];
}
pub fn getPathname(raw: [:0]const u8) []const u8 {
const protocol_end = std.mem.indexOf(u8, raw, "://") orelse 0;
const path_start = std.mem.indexOfScalarPos(u8, raw, if (protocol_end > 0) protocol_end + 3 else 0, '/') orelse raw.len;
const query_or_hash_start = std.mem.indexOfAnyPos(u8, raw, path_start, "?#") orelse raw.len;
if (path_start >= query_or_hash_start) {
if (std.mem.indexOf(u8, raw, "://") != null) return "/";
return "";
}
return raw[path_start..query_or_hash_start];
}
pub fn getProtocol(raw: [:0]const u8) []const u8 {
const pos = std.mem.indexOfScalarPos(u8, raw, 0, ':') orelse return "";
return raw[0 .. pos + 1];
}
pub fn isHTTPS(raw: [:0]const u8) bool {
return std.mem.startsWith(u8, raw, "https:");
}
pub fn getHostname(raw: [:0]const u8) []const u8 {
const host = getHost(raw);
const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return host;
return host[0..pos];
}
pub fn getPort(raw: [:0]const u8) []const u8 {
const host = getHost(raw);
const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return "";
if (pos + 1 >= host.len) {
return "";
}
for (host[pos + 1 ..]) |c| {
if (c < '0' or c > '9') {
return "";
}
}
return host[pos + 1 ..];
}
pub fn getSearch(raw: [:0]const u8) []const u8 {
const pos = std.mem.indexOfScalarPos(u8, raw, 0, '?') orelse return "";
const query_part = raw[pos..];
if (std.mem.indexOfScalarPos(u8, query_part, 0, '#')) |fragment_start| {
return query_part[0..fragment_start];
}
return query_part;
}
pub fn getHash(raw: [:0]const u8) []const u8 {
const start = std.mem.indexOfScalarPos(u8, raw, 0, '#') orelse return "";
return raw[start..];
}
pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 {
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null;
// Only HTTP and HTTPS schemes have origins
const protocol = raw[0 .. scheme_end + 1];
if (!std.mem.eql(u8, protocol, "http:") and !std.mem.eql(u8, protocol, "https:")) {
return null;
}
var authority_start = scheme_end + 3;
const has_user_info = if (std.mem.indexOf(u8, raw[authority_start..], "@")) |pos| blk: {
authority_start += pos + 1;
break :blk true;
} else false;
// Find end of authority (start of path/query/fragment or end of string)
const authority_end_relative = std.mem.indexOfAny(u8, raw[authority_start..], "/?#");
const authority_end = if (authority_end_relative) |end|
authority_start + end
else
raw.len;
// Check for port in the host:port section
const host_part = raw[authority_start..authority_end];
if (std.mem.lastIndexOfScalar(u8, host_part, ':')) |colon_pos_in_host| {
const port = host_part[colon_pos_in_host + 1 ..];
// Validate it's actually a port (all digits)
for (port) |c| {
if (c < '0' or c > '9') {
// Not a port (probably IPv6)
if (has_user_info) {
// Need to allocate to exclude user info
return try std.fmt.allocPrint(allocator, "{s}//{s}", .{ raw[0 .. scheme_end + 1], host_part });
}
// Can return a slice
return raw[0..authority_end];
}
}
// Check if it's a default port that should be excluded from origin
const is_default =
(std.mem.eql(u8, protocol, "http:") and std.mem.eql(u8, port, "80")) or
(std.mem.eql(u8, protocol, "https:") and std.mem.eql(u8, port, "443"));
if (is_default or has_user_info) {
// Need to allocate to build origin without default port and/or user info
const hostname = host_part[0..colon_pos_in_host];
if (is_default) {
return try std.fmt.allocPrint(allocator, "{s}//{s}", .{ protocol, hostname });
} else {
return try std.fmt.allocPrint(allocator, "{s}//{s}", .{ protocol, host_part });
}
}
} else if (has_user_info) {
// No port, but has user info - need to allocate
return try std.fmt.allocPrint(allocator, "{s}//{s}", .{ raw[0 .. scheme_end + 1], host_part });
}
// Common case: no user info, no default port - return slice (zero allocation!)
return raw[0..authority_end];
}
fn getUserInfo(raw: [:0]const u8) ?[]const u8 {
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null;
const authority_start = scheme_end + 3;
const pos = std.mem.indexOfScalar(u8, raw[authority_start..], '@') orelse return null;
const path_start = std.mem.indexOfScalarPos(u8, raw, authority_start, '/') orelse raw.len;
const full_pos = authority_start + pos;
if (full_pos < path_start) {
return raw[authority_start..full_pos];
}
return null;
}
pub fn getHost(raw: [:0]const u8) []const u8 {
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return "";
var authority_start = scheme_end + 3;
if (std.mem.indexOf(u8, raw[authority_start..], "@")) |pos| {
authority_start += pos + 1;
}
const authority = raw[authority_start..];
const path_start = std.mem.indexOfAny(u8, authority, "/?#") orelse return authority;
return authority[0..path_start];
}
// Returns true if these two URLs point to the same document.
pub fn eqlDocument(first: [:0]const u8, second: [:0]const u8) bool {
// First '#' signifies the start of the fragment.
const first_hash_index = std.mem.indexOfScalar(u8, first, '#') orelse first.len;
const second_hash_index = std.mem.indexOfScalar(u8, second, '#') orelse second.len;
return std.mem.eql(u8, first[0..first_hash_index], second[0..second_hash_index]);
}
// Helper function to build a URL from components
pub fn buildUrl(
allocator: Allocator,
protocol: []const u8,
host: []const u8,
pathname: []const u8,
search: []const u8,
hash: []const u8,
) ![:0]const u8 {
return std.fmt.allocPrintSentinel(allocator, "{s}//{s}{s}{s}{s}", .{
protocol,
host,
pathname,
search,
hash,
}, 0);
}
pub fn setProtocol(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
const host = getHost(current);
const pathname = getPathname(current);
const search = getSearch(current);
const hash = getHash(current);
// Add : suffix if not present
const protocol = if (value.len > 0 and value[value.len - 1] != ':')
try std.fmt.allocPrint(allocator, "{s}:", .{value})
else
value;
return buildUrl(allocator, protocol, host, pathname, search, hash);
}
pub fn setHost(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
const protocol = getProtocol(current);
const pathname = getPathname(current);
const search = getSearch(current);
const hash = getHash(current);
// Check if the host includes a port
const colon_pos = std.mem.lastIndexOfScalar(u8, value, ':');
const clean_host = if (colon_pos) |pos| blk: {
const port_str = value[pos + 1 ..];
// Remove default ports
if (std.mem.eql(u8, protocol, "https:") and std.mem.eql(u8, port_str, "443")) {
break :blk value[0..pos];
}
if (std.mem.eql(u8, protocol, "http:") and std.mem.eql(u8, port_str, "80")) {
break :blk value[0..pos];
}
break :blk value;
} else value;
return buildUrl(allocator, protocol, clean_host, pathname, search, hash);
}
pub fn setHostname(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
const current_port = getPort(current);
const new_host = if (current_port.len > 0)
try std.fmt.allocPrint(allocator, "{s}:{s}", .{ value, current_port })
else
value;
return setHost(current, new_host, allocator);
}
pub fn setPort(current: [:0]const u8, value: ?[]const u8, allocator: Allocator) ![:0]const u8 {
const hostname = getHostname(current);
const protocol = getProtocol(current);
// Handle null or default ports
const new_host = if (value) |port_str| blk: {
if (port_str.len == 0) {
break :blk hostname;
}
// Check if this is a default port for the protocol
if (std.mem.eql(u8, protocol, "https:") and std.mem.eql(u8, port_str, "443")) {
break :blk hostname;
}
if (std.mem.eql(u8, protocol, "http:") and std.mem.eql(u8, port_str, "80")) {
break :blk hostname;
}
break :blk try std.fmt.allocPrint(allocator, "{s}:{s}", .{ hostname, port_str });
} else hostname;
return setHost(current, new_host, allocator);
}
pub fn setPathname(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
const protocol = getProtocol(current);
const host = getHost(current);
const search = getSearch(current);
const hash = getHash(current);
// Add / prefix if not present and value is not empty
const pathname = if (value.len > 0 and value[0] != '/')
try std.fmt.allocPrint(allocator, "/{s}", .{value})
else
value;
return buildUrl(allocator, protocol, host, pathname, search, hash);
}
pub fn setSearch(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
const protocol = getProtocol(current);
const host = getHost(current);
const pathname = getPathname(current);
const hash = getHash(current);
// Add ? prefix if not present and value is not empty
const search = if (value.len > 0 and value[0] != '?')
try std.fmt.allocPrint(allocator, "?{s}", .{value})
else
value;
return buildUrl(allocator, protocol, host, pathname, search, hash);
}
pub fn setHash(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
const protocol = getProtocol(current);
const host = getHost(current);
const pathname = getPathname(current);
const search = getSearch(current);
// Add # prefix if not present and value is not empty
const hash = if (value.len > 0 and value[0] != '#')
try std.fmt.allocPrint(allocator, "#{s}", .{value})
else
value;
return buildUrl(allocator, protocol, host, pathname, search, hash);
}
pub fn concatQueryString(arena: Allocator, url: []const u8, query_string: []const u8) ![:0]const u8 {
if (query_string.len == 0) {
return arena.dupeZ(u8, url);
}
var buf: std.ArrayList(u8) = .empty;
// the most space well need is the url + ('?' or '&') + the query_string + null terminator
try buf.ensureTotalCapacity(arena, url.len + 2 + query_string.len);
buf.appendSliceAssumeCapacity(url);
if (std.mem.indexOfScalar(u8, url, '?')) |index| {
const last_index = url.len - 1;
if (index != last_index and url[last_index] != '&') {
buf.appendAssumeCapacity('&');
}
} else {
buf.appendAssumeCapacity('?');
}
buf.appendSliceAssumeCapacity(query_string);
buf.appendAssumeCapacity(0);
return buf.items[0 .. buf.items.len - 1 :0];
}
const testing = @import("../testing.zig");
test "URL: isCompleteHTTPUrl" {
try testing.expectEqual(true, isCompleteHTTPUrl("http://example.com/about"));
try testing.expectEqual(true, isCompleteHTTPUrl("HttP://example.com/about"));
try testing.expectEqual(true, isCompleteHTTPUrl("httpS://example.com/about"));
try testing.expectEqual(true, isCompleteHTTPUrl("HTTPs://example.com/about"));
try testing.expectEqual(true, isCompleteHTTPUrl("ftp://example.com/about"));
try testing.expectEqual(false, isCompleteHTTPUrl("/example.com"));
try testing.expectEqual(false, isCompleteHTTPUrl("../../about"));
try testing.expectEqual(false, isCompleteHTTPUrl("about"));
}
test "URL: resolve regression (#1093)" {
defer testing.reset();
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
expected: [:0]const u8,
};
const cases = [_]Case{
.{
.base = "https://alas.aws.amazon.com/alas2.html",
.path = "../static/bootstrap.min.css",
.expected = "https://alas.aws.amazon.com/static/bootstrap.min.css",
},
};
for (cases) |case| {
const result = try resolve(testing.arena_allocator, case.base, case.path, .{});
try testing.expectString(case.expected, result);
}
}
test "URL: resolve" {
defer testing.reset();
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
expected: [:0]const u8,
};
const cases = [_]Case{
.{
.base = "https://example/xyz/abc/123",
.path = "something.js",
.expected = "https://example/xyz/abc/something.js",
},
.{
.base = "https://example/xyz/abc/123",
.path = "/something.js",
.expected = "https://example/something.js",
},
.{
.base = "https://example/",
.path = "something.js",
.expected = "https://example/something.js",
},
.{
.base = "https://example/",
.path = "/something.js",
.expected = "https://example/something.js",
},
.{
.base = "https://example",
.path = "something.js",
.expected = "https://example/something.js",
},
.{
.base = "https://example",
.path = "abc/something.js",
.expected = "https://example/abc/something.js",
},
.{
.base = "https://example/nested",
.path = "abc/something.js",
.expected = "https://example/abc/something.js",
},
.{
.base = "https://example/nested/",
.path = "abc/something.js",
.expected = "https://example/nested/abc/something.js",
},
.{
.base = "https://example/nested/",
.path = "/abc/something.js",
.expected = "https://example/abc/something.js",
},
.{
.base = "https://example/nested/",
.path = "http://www.github.com/example/",
.expected = "http://www.github.com/example/",
},
.{
.base = "https://example/nested/",
.path = "",
.expected = "https://example/nested/",
},
.{
.base = "https://example/abc/aaa",
.path = "./hello/./world",
.expected = "https://example/abc/hello/world",
},
.{
.base = "https://example/abc/aaa/",
.path = "../hello",
.expected = "https://example/abc/hello",
},
.{
.base = "https://example/abc/aaa",
.path = "../hello",
.expected = "https://example/hello",
},
.{
.base = "https://example/abc/aaa/",
.path = "./.././.././hello",
.expected = "https://example/hello",
},
.{
.base = "some/page",
.path = "hello",
.expected = "some/hello",
},
.{
.base = "some/page/",
.path = "hello",
.expected = "some/page/hello",
},
.{
.base = "some/page/other",
.path = ".././hello",
.expected = "some/hello",
},
.{
.base = "https://www.example.com/hello/world",
.path = "//example/about",
.expected = "https://example/about",
},
.{
.base = "http:",
.path = "//example.com/over/9000",
.expected = "http://example.com/over/9000",
},
.{
.base = "https://example.com/",
.path = "../hello",
.expected = "https://example.com/hello",
},
.{
.base = "https://www.example.com/hello/world/",
.path = "../../../../example/about",
.expected = "https://www.example.com/example/about",
},
};
for (cases) |case| {
const result = try resolve(testing.arena_allocator, case.base, case.path, .{});
try testing.expectString(case.expected, result);
}
}
test "URL: eqlDocument" {
defer testing.reset();
{
const url = "https://lightpanda.io/about";
try testing.expectEqual(true, eqlDocument(url, url));
}
{
const url1 = "https://lightpanda.io/about";
const url2 = "http://lightpanda.io/about";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about";
const url2 = "https://example.com/about";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io:8080/about";
const url2 = "https://lightpanda.io:9090/about";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about";
const url2 = "https://lightpanda.io/contact";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about?foo=bar";
const url2 = "https://lightpanda.io/about?baz=qux";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about#section1";
const url2 = "https://lightpanda.io/about#section2";
try testing.expectEqual(true, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about";
const url2 = "https://lightpanda.io/about/";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about?foo=bar";
const url2 = "https://lightpanda.io/about";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about";
const url2 = "https://lightpanda.io/about?foo=bar";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about?foo=bar";
const url2 = "https://lightpanda.io/about?foo=bar";
try testing.expectEqual(true, eqlDocument(url1, url2));
}
{
const url1 = "https://lightpanda.io/about?";
const url2 = "https://lightpanda.io/about";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
{
const url1 = "https://duckduckgo.com/";
const url2 = "https://duckduckgo.com/?q=lightpanda";
try testing.expectEqual(false, eqlDocument(url1, url2));
}
}
test "URL: concatQueryString" {
defer testing.reset();
const arena = testing.arena_allocator;
{
const url = try concatQueryString(arena, "https://www.lightpanda.io/", "");
try testing.expectEqual("https://www.lightpanda.io/", url);
}
{
const url = try concatQueryString(arena, "https://www.lightpanda.io/index?", "");
try testing.expectEqual("https://www.lightpanda.io/index?", url);
}
{
const url = try concatQueryString(arena, "https://www.lightpanda.io/index?", "a=b");
try testing.expectEqual("https://www.lightpanda.io/index?a=b", url);
}
{
const url = try concatQueryString(arena, "https://www.lightpanda.io/index?1=2", "a=b");
try testing.expectEqual("https://www.lightpanda.io/index?1=2&a=b", url);
}
{
const url = try concatQueryString(arena, "https://www.lightpanda.io/index?1=2&", "a=b");
try testing.expectEqual("https://www.lightpanda.io/index?1=2&a=b", url);
}
}

View File

@@ -1,450 +0,0 @@
const std = @import("std");
const Types = @import("root").Types;
const parser = @import("../netsurf.zig");
const Loader = @import("loader.zig").Loader;
const Dump = @import("dump.zig");
const Mime = @import("mime.zig");
const jsruntime = @import("jsruntime");
const Loop = jsruntime.Loop;
const Env = jsruntime.Env;
const apiweb = @import("../apiweb.zig");
const Window = @import("../html/window.zig").Window;
const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
const FetchResult = std.http.Client.FetchResult;
const log = std.log.scoped(.browser);
// Browser is an instance of the browser.
// You can create multiple browser instances.
// A browser contains only one session.
// TODO allow multiple sessions per browser.
pub const Browser = struct {
session: *Session,
pub fn init(alloc: std.mem.Allocator, vm: jsruntime.VM) !Browser {
// We want to ensure the caller initialised a VM, but the browser
// doesn't use it directly...
_ = vm;
return Browser{
.session = try Session.init(alloc, "about:blank"),
};
}
pub fn deinit(self: *Browser) void {
self.session.deinit();
}
pub fn currentSession(self: *Browser) *Session {
return self.session;
}
};
// Session is like a browser's tab.
// It owns the js env and the loader for all the pages of the session.
// You can create successively multiple pages for a session, but you must
// deinit a page before running another one.
pub const Session = struct {
// allocator used to init the arena.
alloc: std.mem.Allocator,
// The arena is used only to bound the js env init b/c it leaks memory.
// see https://github.com/lightpanda-io/jsruntime-lib/issues/181
//
// The arena is initialised with self.alloc allocator.
// all others Session deps use directly self.alloc and not the arena.
arena: std.heap.ArenaAllocator,
uri: []const u8,
// TODO handle proxy
loader: Loader,
env: Env = undefined,
loop: Loop,
window: Window,
jstypes: [Types.len]usize = undefined,
fn init(alloc: std.mem.Allocator, uri: []const u8) !*Session {
var self = try alloc.create(Session);
self.* = Session{
.uri = uri,
.alloc = alloc,
.arena = std.heap.ArenaAllocator.init(alloc),
.window = Window.create(null),
.loader = Loader.init(alloc),
.loop = try Loop.init(alloc),
};
self.env = try Env.init(self.arena.allocator(), &self.loop);
try self.env.load(&self.jstypes);
return self;
}
fn deinit(self: *Session) void {
self.env.deinit();
self.arena.deinit();
self.loader.deinit();
self.loop.deinit();
self.alloc.destroy(self);
}
pub fn createPage(self: *Session) !Page {
return Page.init(self.alloc, self);
}
};
// Page navigates to an url.
// You can navigates multiple urls with the same page, but you have to call
// end() to stop the previous navigation before starting a new one.
// The page handle all its memory in an arena allocator. The arena is reseted
// when end() is called.
pub const Page = struct {
arena: std.heap.ArenaAllocator,
session: *Session,
doc: ?*parser.Document = null,
// handle url
rawuri: ?[]const u8 = null,
uri: std.Uri = undefined,
raw_data: ?[]const u8 = null,
fn init(
alloc: std.mem.Allocator,
session: *Session,
) Page {
return Page{
.arena = std.heap.ArenaAllocator.init(alloc),
.session = session,
};
}
// reset js env and mem arena.
pub fn end(self: *Page) void {
self.session.env.stop();
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
_ = self.arena.reset(.free_all);
}
pub fn deinit(self: *Page) void {
self.arena.deinit();
}
// dump writes the page content into the given file.
pub fn dump(self: *Page, out: std.fs.File) !void {
// if no HTML document pointer available, dump the data content only.
if (self.doc == null) {
// no data loaded, nothing to do.
if (self.raw_data == null) return;
return try out.writeAll(self.raw_data.?);
}
// if the page has a pointer to a document, dumps the HTML.
try Dump.htmlFile(self.doc.?, out);
}
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
pub fn navigate(self: *Page, uri: []const u8) !void {
const alloc = self.arena.allocator();
log.debug("starting GET {s}", .{uri});
// own the url
if (self.rawuri) |prev| alloc.free(prev);
self.rawuri = try alloc.dupe(u8, uri);
self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseWithoutScheme(self.rawuri.?);
// TODO handle fragment in url.
// load the data
var resp = try self.session.loader.get(alloc, self.uri);
defer resp.deinit();
const req = resp.req;
log.info("GET {any} {d}", .{ self.uri, req.response.status });
// TODO handle redirection
if (req.response.status != .ok) {
log.debug("{?} {d} {s}\n{any}", .{
req.response.version,
req.response.status,
req.response.reason,
req.response.headers,
});
return error.BadStatusCode;
}
// TODO handle charset
// https://html.spec.whatwg.org/#content-type
const ct = req.response.headers.getFirstValue("Content-Type") orelse {
// no content type in HTTP headers.
// TODO try to sniff mime type from the body.
log.info("no content-type HTTP header", .{});
return;
};
log.debug("header content-type: {s}", .{ct});
const mime = try Mime.parse(ct);
if (mime.eql(Mime.HTML)) {
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8");
} else {
log.info("non-HTML document: {s}", .{ct});
// save the body into the page.
self.raw_data = try req.reader().readAllAlloc(alloc, 16 * 1024 * 1024);
}
}
// https://html.spec.whatwg.org/#read-html
fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void {
const alloc = self.arena.allocator();
log.debug("parse html with charset {s}", .{charset});
const ccharset = try alloc.dupeZ(u8, charset);
defer alloc.free(ccharset);
const html_doc = try parser.documentHTMLParse(reader, ccharset);
const doc = parser.documentHTMLToDocument(html_doc);
// save a document's pointer in the page.
self.doc = doc;
// TODO set document.readyState to interactive
// https://html.spec.whatwg.org/#reporting-document-loading-status
// TODO inject the URL to the document including the fragment.
// TODO set the referrer to the document.
self.session.window.replaceDocument(doc);
// https://html.spec.whatwg.org/#read-html
// start JS env
// TODO load the js env concurrently with the HTML parsing.
log.debug("start js env", .{});
try self.session.env.start(alloc);
// add global objects
log.debug("setup global env", .{});
try self.session.env.addObject(self.session.window, "window");
try self.session.env.addObject(self.session.window, "self");
try self.session.env.addObject(html_doc, "document");
// browse the DOM tree to retrieve scripts
// TODO execute the synchronous scripts during the HTL parsing.
// TODO fetch the script resources concurrently but execute them in the
// declaration order for synchronous ones.
// sasync stores scripts which can be run asynchronously.
// for now they are just run after the non-async one in order to
// dispatch DOMContentLoaded the sooner as possible.
var sasync = std.ArrayList(*parser.Element).init(alloc);
defer sasync.deinit();
const root = parser.documentToNode(doc);
const walker = Walker{};
var next: ?*parser.Node = null;
while (true) {
next = try walker.get_next(root, next) orelse break;
// ignore non-elements nodes.
if (try parser.nodeType(next.?) != .element) {
continue;
}
const e = parser.nodeToElement(next.?);
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e)));
// ignore non-script tags
if (tag != .script) continue;
// ignore non-js script.
// > type
// > Attribute is not set (default), an empty string, or a JavaScript MIME
// > type indicates that the script is a "classic script", containing
// > JavaScript code.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
const stype = try parser.elementGetAttribute(e, "type");
if (!isJS(stype)) {
continue;
}
// Ignore the defer attribute b/c we analyze all script
// after the document has been parsed.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
// TODO use fetchpriority
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#fetchpriority
// > async
// > For classic scripts, if the async attribute is present,
// > then the classic script will be fetched in parallel to
// > parsing and evaluated as soon as it is available.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async
if (try parser.elementGetAttribute(e, "async") != null) {
try sasync.append(e);
continue;
}
// TODO handle for attribute
// TODO handle event attribute
// TODO defer
// > This Boolean attribute is set to indicate to a browser
// > that the script is meant to be executed after the
// > document has been parsed, but before firing
// > DOMContentLoaded.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
// defer allow us to load a script w/o blocking the rest of
// evaluations.
// > Scripts without async, defer or type="module"
// > attributes, as well as inline scripts without the
// > type="module" attribute, are fetched and executed
// > immediately before the browser continues to parse the
// > page.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#notes
self.evalScript(e) catch |err| log.warn("evaljs: {any}", .{err});
}
// TODO wait for deferred scripts
// dispatch DOMContentLoaded before the transition to "complete",
// at the point where all subresources apart from async script elements
// have loaded.
// https://html.spec.whatwg.org/#reporting-document-loading-status
const evt = try parser.eventCreate();
try parser.eventInit(evt, "DOMContentLoaded", .{ .bubbles = true, .cancelable = true });
_ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, html_doc), evt);
// eval async scripts.
for (sasync.items) |e| {
self.evalScript(e) catch |err| log.warn("evaljs: {any}", .{err});
}
// TODO wait for async scripts
// TODO set document.readyState to complete
// dispatch window.load event
const loadevt = try parser.eventCreate();
try parser.eventInit(loadevt, "load", .{});
_ = try parser.eventTargetDispatchEvent(parser.toEventTarget(Window, &self.session.window), loadevt);
}
// evalScript evaluates the src in priority.
// if no src is present, we evaluate the text source.
// https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
fn evalScript(self: *Page, e: *parser.Element) !void {
const alloc = self.arena.allocator();
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
const opt_src = try parser.elementGetAttribute(e, "src");
if (opt_src) |src| {
log.debug("starting GET {s}", .{src});
self.fetchScript(src) catch |err| {
switch (err) {
FetchError.BadStatusCode => return err,
// TODO If el's result is null, then fire an event named error at
// el, and return.
FetchError.NoBody => return,
FetchError.JsErr => {}, // nothing to do here.
else => return err,
}
};
// TODO If el's from an external file is true, then fire an event
// named load at el.
return;
}
const opt_text = try parser.nodeTextContent(parser.elementToNode(e));
if (opt_text) |text| {
// TODO handle charset attribute
var res = jsruntime.JSResult{};
try self.session.env.run(alloc, text, "", &res, null);
defer res.deinit(alloc);
if (res.success) {
log.debug("eval inline: {s}", .{res.result});
} else {
log.info("eval inline: {s}", .{res.result});
}
return;
}
// nothing has been loaded.
// TODO If el's result is null, then fire an event named error at
// el, and return.
}
const FetchError = error{
BadStatusCode,
NoBody,
JsErr,
};
// fetchScript senf a GET request to the src and execute the script
// received.
fn fetchScript(self: *Page, src: []const u8) !void {
const alloc = self.arena.allocator();
log.debug("starting fetch script {s}", .{src});
const u = std.Uri.parse(src) catch try std.Uri.parseWithoutScheme(src);
const ru = try std.Uri.resolve(self.uri, u, false, alloc);
var fetchres = try self.session.loader.fetch(alloc, ru);
defer fetchres.deinit();
log.info("fech script {any}: {d}", .{ ru, fetchres.status });
if (fetchres.status != .ok) return FetchError.BadStatusCode;
// TODO check content-type
// check no body
if (fetchres.body == null) return FetchError.NoBody;
var res = jsruntime.JSResult{};
try self.session.env.run(alloc, fetchres.body.?, src, &res, null);
defer res.deinit(alloc);
if (res.success) {
log.debug("eval remote {s}: {s}", .{ src, res.result });
} else {
log.info("eval remote {s}: {s}", .{ src, res.result });
return FetchError.JsErr;
}
}
// > type
// > Attribute is not set (default), an empty string, or a JavaScript MIME
// > type indicates that the script is a "classic script", containing
// > JavaScript code.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
fn isJS(stype: ?[]const u8) bool {
if (stype == null or stype.?.len == 0) return true;
if (std.mem.eql(u8, stype.?, "application/javascript")) return true;
if (!std.mem.eql(u8, stype.?, "module")) return true;
return false;
}
};

298
src/browser/color.zig Normal file
View File

@@ -0,0 +1,298 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Io = std.Io;
pub fn isHexColor(value: []const u8) bool {
if (value.len == 0) {
return false;
}
if (value[0] != '#') {
return false;
}
const hex_part = value[1..];
switch (hex_part.len) {
3, 4, 6, 8 => for (hex_part) |c| if (!std.ascii.isHex(c)) return false,
else => return false,
}
return true;
}
pub const RGBA = packed struct(u32) {
r: u8,
g: u8,
b: u8,
/// Opaque by default.
a: u8 = std.math.maxInt(u8),
pub const Named = struct {
// Basic colors (CSS Level 1)
pub const black: RGBA = .init(0, 0, 0, 1);
pub const silver: RGBA = .init(192, 192, 192, 1);
pub const gray: RGBA = .init(128, 128, 128, 1);
pub const white: RGBA = .init(255, 255, 255, 1);
pub const maroon: RGBA = .init(128, 0, 0, 1);
pub const red: RGBA = .init(255, 0, 0, 1);
pub const purple: RGBA = .init(128, 0, 128, 1);
pub const fuchsia: RGBA = .init(255, 0, 255, 1);
pub const green: RGBA = .init(0, 128, 0, 1);
pub const lime: RGBA = .init(0, 255, 0, 1);
pub const olive: RGBA = .init(128, 128, 0, 1);
pub const yellow: RGBA = .init(255, 255, 0, 1);
pub const navy: RGBA = .init(0, 0, 128, 1);
pub const blue: RGBA = .init(0, 0, 255, 1);
pub const teal: RGBA = .init(0, 128, 128, 1);
pub const aqua: RGBA = .init(0, 255, 255, 1);
// Extended colors (CSS Level 2+)
pub const aliceblue: RGBA = .init(240, 248, 255, 1);
pub const antiquewhite: RGBA = .init(250, 235, 215, 1);
pub const aquamarine: RGBA = .init(127, 255, 212, 1);
pub const azure: RGBA = .init(240, 255, 255, 1);
pub const beige: RGBA = .init(245, 245, 220, 1);
pub const bisque: RGBA = .init(255, 228, 196, 1);
pub const blanchedalmond: RGBA = .init(255, 235, 205, 1);
pub const blueviolet: RGBA = .init(138, 43, 226, 1);
pub const brown: RGBA = .init(165, 42, 42, 1);
pub const burlywood: RGBA = .init(222, 184, 135, 1);
pub const cadetblue: RGBA = .init(95, 158, 160, 1);
pub const chartreuse: RGBA = .init(127, 255, 0, 1);
pub const chocolate: RGBA = .init(210, 105, 30, 1);
pub const coral: RGBA = .init(255, 127, 80, 1);
pub const cornflowerblue: RGBA = .init(100, 149, 237, 1);
pub const cornsilk: RGBA = .init(255, 248, 220, 1);
pub const crimson: RGBA = .init(220, 20, 60, 1);
pub const cyan: RGBA = .init(0, 255, 255, 1); // Synonym of aqua
pub const darkblue: RGBA = .init(0, 0, 139, 1);
pub const darkcyan: RGBA = .init(0, 139, 139, 1);
pub const darkgoldenrod: RGBA = .init(184, 134, 11, 1);
pub const darkgray: RGBA = .init(169, 169, 169, 1);
pub const darkgreen: RGBA = .init(0, 100, 0, 1);
pub const darkgrey: RGBA = .init(169, 169, 169, 1); // Synonym of darkgray
pub const darkkhaki: RGBA = .init(189, 183, 107, 1);
pub const darkmagenta: RGBA = .init(139, 0, 139, 1);
pub const darkolivegreen: RGBA = .init(85, 107, 47, 1);
pub const darkorange: RGBA = .init(255, 140, 0, 1);
pub const darkorchid: RGBA = .init(153, 50, 204, 1);
pub const darkred: RGBA = .init(139, 0, 0, 1);
pub const darksalmon: RGBA = .init(233, 150, 122, 1);
pub const darkseagreen: RGBA = .init(143, 188, 143, 1);
pub const darkslateblue: RGBA = .init(72, 61, 139, 1);
pub const darkslategray: RGBA = .init(47, 79, 79, 1);
pub const darkslategrey: RGBA = .init(47, 79, 79, 1); // Synonym of darkslategray
pub const darkturquoise: RGBA = .init(0, 206, 209, 1);
pub const darkviolet: RGBA = .init(148, 0, 211, 1);
pub const deeppink: RGBA = .init(255, 20, 147, 1);
pub const deepskyblue: RGBA = .init(0, 191, 255, 1);
pub const dimgray: RGBA = .init(105, 105, 105, 1);
pub const dimgrey: RGBA = .init(105, 105, 105, 1); // Synonym of dimgray
pub const dodgerblue: RGBA = .init(30, 144, 255, 1);
pub const firebrick: RGBA = .init(178, 34, 34, 1);
pub const floralwhite: RGBA = .init(255, 250, 240, 1);
pub const forestgreen: RGBA = .init(34, 139, 34, 1);
pub const gainsboro: RGBA = .init(220, 220, 220, 1);
pub const ghostwhite: RGBA = .init(248, 248, 255, 1);
pub const gold: RGBA = .init(255, 215, 0, 1);
pub const goldenrod: RGBA = .init(218, 165, 32, 1);
pub const greenyellow: RGBA = .init(173, 255, 47, 1);
pub const grey: RGBA = .init(128, 128, 128, 1); // Synonym of gray
pub const honeydew: RGBA = .init(240, 255, 240, 1);
pub const hotpink: RGBA = .init(255, 105, 180, 1);
pub const indianred: RGBA = .init(205, 92, 92, 1);
pub const indigo: RGBA = .init(75, 0, 130, 1);
pub const ivory: RGBA = .init(255, 255, 240, 1);
pub const khaki: RGBA = .init(240, 230, 140, 1);
pub const lavender: RGBA = .init(230, 230, 250, 1);
pub const lavenderblush: RGBA = .init(255, 240, 245, 1);
pub const lawngreen: RGBA = .init(124, 252, 0, 1);
pub const lemonchiffon: RGBA = .init(255, 250, 205, 1);
pub const lightblue: RGBA = .init(173, 216, 230, 1);
pub const lightcoral: RGBA = .init(240, 128, 128, 1);
pub const lightcyan: RGBA = .init(224, 255, 255, 1);
pub const lightgoldenrodyellow: RGBA = .init(250, 250, 210, 1);
pub const lightgray: RGBA = .init(211, 211, 211, 1);
pub const lightgreen: RGBA = .init(144, 238, 144, 1);
pub const lightgrey: RGBA = .init(211, 211, 211, 1); // Synonym of lightgray
pub const lightpink: RGBA = .init(255, 182, 193, 1);
pub const lightsalmon: RGBA = .init(255, 160, 122, 1);
pub const lightseagreen: RGBA = .init(32, 178, 170, 1);
pub const lightskyblue: RGBA = .init(135, 206, 250, 1);
pub const lightslategray: RGBA = .init(119, 136, 153, 1);
pub const lightslategrey: RGBA = .init(119, 136, 153, 1); // Synonym of lightslategray
pub const lightsteelblue: RGBA = .init(176, 196, 222, 1);
pub const lightyellow: RGBA = .init(255, 255, 224, 1);
pub const limegreen: RGBA = .init(50, 205, 50, 1);
pub const linen: RGBA = .init(250, 240, 230, 1);
pub const magenta: RGBA = .init(255, 0, 255, 1); // Synonym of fuchsia
pub const mediumaquamarine: RGBA = .init(102, 205, 170, 1);
pub const mediumblue: RGBA = .init(0, 0, 205, 1);
pub const mediumorchid: RGBA = .init(186, 85, 211, 1);
pub const mediumpurple: RGBA = .init(147, 112, 219, 1);
pub const mediumseagreen: RGBA = .init(60, 179, 113, 1);
pub const mediumslateblue: RGBA = .init(123, 104, 238, 1);
pub const mediumspringgreen: RGBA = .init(0, 250, 154, 1);
pub const mediumturquoise: RGBA = .init(72, 209, 204, 1);
pub const mediumvioletred: RGBA = .init(199, 21, 133, 1);
pub const midnightblue: RGBA = .init(25, 25, 112, 1);
pub const mintcream: RGBA = .init(245, 255, 250, 1);
pub const mistyrose: RGBA = .init(255, 228, 225, 1);
pub const moccasin: RGBA = .init(255, 228, 181, 1);
pub const navajowhite: RGBA = .init(255, 222, 173, 1);
pub const oldlace: RGBA = .init(253, 245, 230, 1);
pub const olivedrab: RGBA = .init(107, 142, 35, 1);
pub const orange: RGBA = .init(255, 165, 0, 1);
pub const orangered: RGBA = .init(255, 69, 0, 1);
pub const orchid: RGBA = .init(218, 112, 214, 1);
pub const palegoldenrod: RGBA = .init(238, 232, 170, 1);
pub const palegreen: RGBA = .init(152, 251, 152, 1);
pub const paleturquoise: RGBA = .init(175, 238, 238, 1);
pub const palevioletred: RGBA = .init(219, 112, 147, 1);
pub const papayawhip: RGBA = .init(255, 239, 213, 1);
pub const peachpuff: RGBA = .init(255, 218, 185, 1);
pub const peru: RGBA = .init(205, 133, 63, 1);
pub const pink: RGBA = .init(255, 192, 203, 1);
pub const plum: RGBA = .init(221, 160, 221, 1);
pub const powderblue: RGBA = .init(176, 224, 230, 1);
pub const rebeccapurple: RGBA = .init(102, 51, 153, 1);
pub const rosybrown: RGBA = .init(188, 143, 143, 1);
pub const royalblue: RGBA = .init(65, 105, 225, 1);
pub const saddlebrown: RGBA = .init(139, 69, 19, 1);
pub const salmon: RGBA = .init(250, 128, 114, 1);
pub const sandybrown: RGBA = .init(244, 164, 96, 1);
pub const seagreen: RGBA = .init(46, 139, 87, 1);
pub const seashell: RGBA = .init(255, 245, 238, 1);
pub const sienna: RGBA = .init(160, 82, 45, 1);
pub const skyblue: RGBA = .init(135, 206, 235, 1);
pub const slateblue: RGBA = .init(106, 90, 205, 1);
pub const slategray: RGBA = .init(112, 128, 144, 1);
pub const slategrey: RGBA = .init(112, 128, 144, 1); // Synonym of slategray
pub const snow: RGBA = .init(255, 250, 250, 1);
pub const springgreen: RGBA = .init(0, 255, 127, 1);
pub const steelblue: RGBA = .init(70, 130, 180, 1);
pub const tan: RGBA = .init(210, 180, 140, 1);
pub const thistle: RGBA = .init(216, 191, 216, 1);
pub const tomato: RGBA = .init(255, 99, 71, 1);
pub const transparent: RGBA = .init(0, 0, 0, 0);
pub const turquoise: RGBA = .init(64, 224, 208, 1);
pub const violet: RGBA = .init(238, 130, 238, 1);
pub const wheat: RGBA = .init(245, 222, 179, 1);
pub const whitesmoke: RGBA = .init(245, 245, 245, 1);
pub const yellowgreen: RGBA = .init(154, 205, 50, 1);
};
pub fn init(r: u8, g: u8, b: u8, a: f32) RGBA {
const clamped = std.math.clamp(a, 0, 1);
return .{ .r = r, .g = g, .b = b, .a = @intFromFloat(clamped * 255) };
}
/// Finds a color by its name.
pub fn find(name: []const u8) ?RGBA {
const match = std.meta.stringToEnum(std.meta.DeclEnum(Named), name) orelse return null;
return switch (match) {
inline else => |comptime_enum| @field(Named, @tagName(comptime_enum)),
};
}
/// Parses the given color.
/// Currently we only parse hex colors and named colors; other variants
/// require CSS evaluation.
pub fn parse(input: []const u8) !RGBA {
if (!isHexColor(input)) {
// Try named colors.
return find(input) orelse return error.Invalid;
}
const slice = input[1..];
switch (slice.len) {
// This means the digit for a color is repeated.
// Given HEX is #f0c, its interpreted the same as #FF00CC.
3 => {
const r = try std.fmt.parseInt(u8, &.{ slice[0], slice[0] }, 16);
const g = try std.fmt.parseInt(u8, &.{ slice[1], slice[1] }, 16);
const b = try std.fmt.parseInt(u8, &.{ slice[2], slice[2] }, 16);
return .{ .r = r, .g = g, .b = b, .a = 255 };
},
4 => {
const r = try std.fmt.parseInt(u8, &.{ slice[0], slice[0] }, 16);
const g = try std.fmt.parseInt(u8, &.{ slice[1], slice[1] }, 16);
const b = try std.fmt.parseInt(u8, &.{ slice[2], slice[2] }, 16);
const a = try std.fmt.parseInt(u8, &.{ slice[3], slice[3] }, 16);
return .{ .r = r, .g = g, .b = b, .a = a };
},
// Regular HEX format.
6 => {
const r = try std.fmt.parseInt(u8, slice[0..2], 16);
const g = try std.fmt.parseInt(u8, slice[2..4], 16);
const b = try std.fmt.parseInt(u8, slice[4..6], 16);
return .{ .r = r, .g = g, .b = b, .a = 255 };
},
8 => {
const r = try std.fmt.parseInt(u8, slice[0..2], 16);
const g = try std.fmt.parseInt(u8, slice[2..4], 16);
const b = try std.fmt.parseInt(u8, slice[4..6], 16);
const a = try std.fmt.parseInt(u8, slice[6..8], 16);
return .{ .r = r, .g = g, .b = b, .a = a };
},
else => return error.Invalid,
}
}
/// By default, browsers prefer lowercase formatting.
const format_upper = false;
/// Formats the `Color` according to web expectations.
/// If color is opaque, HEX is preferred; RGBA otherwise.
pub fn format(self: *const RGBA, writer: *Io.Writer) Io.Writer.Error!void {
if (self.isOpaque()) {
// Convert RGB to HEX.
// https://gristle.tripod.com/hexconv.html
// Hexadecimal characters up to 15.
const char: []const u8 = "0123456789" ++ if (format_upper) "ABCDEF" else "abcdef";
// This variant always prefers 6 digit format, +1 is for hash char.
const buffer = [7]u8{
'#',
char[self.r >> 4],
char[self.r & 15],
char[self.g >> 4],
char[self.g & 15],
char[self.b >> 4],
char[self.b & 15],
};
return writer.writeAll(&buffer);
}
// Prefer RGBA format for everything else.
return writer.print("rgba({d}, {d}, {d}, {d:.2})", .{ self.r, self.g, self.b, self.normalizedAlpha() });
}
/// Returns true if `Color` is opaque.
pub inline fn isOpaque(self: *const RGBA) bool {
return self.a == std.math.maxInt(u8);
}
/// Returns the normalized alpha value.
pub inline fn normalizedAlpha(self: *const RGBA) f32 {
return @as(f32, @floatFromInt(self.a)) / 255;
}
};

295
src/browser/css/Parser.zig Normal file
View File

@@ -0,0 +1,295 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Tokenizer = @import("Tokenizer.zig");
pub const Declaration = struct {
name: []const u8,
value: []const u8,
important: bool,
};
const TokenSpan = struct {
token: Tokenizer.Token,
start: usize,
end: usize,
};
const TokenStream = struct {
tokenizer: Tokenizer,
peeked: ?TokenSpan = null,
fn init(input: []const u8) TokenStream {
return .{ .tokenizer = .{ .input = input } };
}
fn nextRaw(self: *TokenStream) ?TokenSpan {
const start = self.tokenizer.position;
const token = self.tokenizer.next() orelse return null;
const end = self.tokenizer.position;
return .{ .token = token, .start = start, .end = end };
}
fn next(self: *TokenStream) ?TokenSpan {
if (self.peeked) |token| {
self.peeked = null;
return token;
}
return self.nextRaw();
}
fn peek(self: *TokenStream) ?TokenSpan {
if (self.peeked == null) {
self.peeked = self.nextRaw();
}
return self.peeked;
}
};
pub fn parseDeclarationsList(input: []const u8) DeclarationsIterator {
return DeclarationsIterator.init(input);
}
pub const DeclarationsIterator = struct {
input: []const u8,
stream: TokenStream,
pub fn init(input: []const u8) DeclarationsIterator {
return .{
.input = input,
.stream = TokenStream.init(input),
};
}
pub fn next(self: *DeclarationsIterator) ?Declaration {
while (true) {
self.skipTriviaAndSemicolons();
const peeked = self.stream.peek() orelse return null;
switch (peeked.token) {
.at_keyword => {
_ = self.stream.next();
self.skipAtRule();
},
.ident => |name| {
_ = self.stream.next();
if (self.consumeDeclaration(name)) |declaration| {
return declaration;
}
},
else => {
_ = self.stream.next();
self.skipInvalidDeclaration();
},
}
}
return null;
}
fn consumeDeclaration(self: *DeclarationsIterator, name: []const u8) ?Declaration {
self.skipTrivia();
const colon = self.stream.next() orelse return null;
if (!isColon(colon.token)) {
self.skipInvalidDeclaration();
return null;
}
const value = self.consumeValue() orelse return null;
return .{
.name = name,
.value = value.value,
.important = value.important,
};
}
const ValueResult = struct {
value: []const u8,
important: bool,
};
fn consumeValue(self: *DeclarationsIterator) ?ValueResult {
self.skipTrivia();
var depth: usize = 0;
var start: ?usize = null;
var last_sig: ?TokenSpan = null;
var prev_sig: ?TokenSpan = null;
while (true) {
const peeked = self.stream.peek() orelse break;
if (isSemicolon(peeked.token) and depth == 0) {
_ = self.stream.next();
break;
}
const span = self.stream.next() orelse break;
if (isWhitespaceOrComment(span.token)) {
continue;
}
if (start == null) start = span.start;
prev_sig = last_sig;
last_sig = span;
updateDepth(span.token, &depth);
}
const value_start = start orelse return null;
const last = last_sig orelse return null;
var important = false;
var end_pos = last.end;
if (isImportantPair(prev_sig, last)) {
important = true;
const bang = prev_sig orelse return null;
if (value_start >= bang.start) return null;
end_pos = bang.start;
}
var value_slice = self.input[value_start..end_pos];
value_slice = std.mem.trim(u8, value_slice, &std.ascii.whitespace);
if (value_slice.len == 0) return null;
return .{ .value = value_slice, .important = important };
}
fn skipTrivia(self: *DeclarationsIterator) void {
while (self.stream.peek()) |peeked| {
if (!isWhitespaceOrComment(peeked.token)) break;
_ = self.stream.next();
}
}
fn skipTriviaAndSemicolons(self: *DeclarationsIterator) void {
while (self.stream.peek()) |peeked| {
if (isWhitespaceOrComment(peeked.token) or isSemicolon(peeked.token)) {
_ = self.stream.next();
} else {
break;
}
}
}
fn skipAtRule(self: *DeclarationsIterator) void {
var depth: usize = 0;
var saw_block = false;
while (true) {
const peeked = self.stream.peek() orelse return;
if (!saw_block and isSemicolon(peeked.token) and depth == 0) {
_ = self.stream.next();
return;
}
const span = self.stream.next() orelse return;
if (isWhitespaceOrComment(span.token)) continue;
if (isBlockStart(span.token)) {
depth += 1;
saw_block = true;
} else if (isBlockEnd(span.token)) {
if (depth > 0) depth -= 1;
if (saw_block and depth == 0) return;
}
}
}
fn skipInvalidDeclaration(self: *DeclarationsIterator) void {
var depth: usize = 0;
while (self.stream.peek()) |peeked| {
if (isSemicolon(peeked.token) and depth == 0) {
_ = self.stream.next();
return;
}
const span = self.stream.next() orelse return;
if (isWhitespaceOrComment(span.token)) continue;
updateDepth(span.token, &depth);
}
}
};
fn isWhitespaceOrComment(token: Tokenizer.Token) bool {
return switch (token) {
.white_space, .comment => true,
else => false,
};
}
fn isSemicolon(token: Tokenizer.Token) bool {
return switch (token) {
.semicolon => true,
else => false,
};
}
fn isColon(token: Tokenizer.Token) bool {
return switch (token) {
.colon => true,
else => false,
};
}
fn isBlockStart(token: Tokenizer.Token) bool {
return switch (token) {
.curly_bracket_block, .square_bracket_block, .parenthesis_block, .function => true,
else => false,
};
}
fn isBlockEnd(token: Tokenizer.Token) bool {
return switch (token) {
.close_curly_bracket, .close_parenthesis, .close_square_bracket => true,
else => false,
};
}
fn updateDepth(token: Tokenizer.Token, depth: *usize) void {
if (isBlockStart(token)) {
depth.* += 1;
return;
}
if (isBlockEnd(token)) {
if (depth.* > 0) depth.* -= 1;
}
}
fn isImportantPair(prev_sig: ?TokenSpan, last_sig: TokenSpan) bool {
if (!isIdentImportant(last_sig.token)) return false;
const prev = prev_sig orelse return false;
return isBang(prev.token);
}
fn isIdentImportant(token: Tokenizer.Token) bool {
return switch (token) {
.ident => |name| std.ascii.eqlIgnoreCase(name, "important"),
else => false,
};
}
fn isBang(token: Tokenizer.Token) bool {
return switch (token) {
.delim => |c| c == '!',
else => false,
};
}

View File

@@ -0,0 +1,824 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! This file implements the tokenization step defined in the CSS Syntax Module Level 3 specification.
//!
//! The algorithm accepts a valid UTF-8 string and returns a stream of tokens.
//! The tokenization step never fails, even for complete gibberish.
//! Validity must then be checked by the parser.
//!
//! NOTE: The tokenizer is not thread-safe and does not own any memory, and does not check the validity of utf8.
//!
//! See spec for more info: https://drafts.csswg.org/css-syntax/#tokenization
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Tokenizer = @This();
pub const Token = union(enum) {
/// A `<ident-token>`
ident: []const u8,
/// A `<function-token>`
///
/// The value (name) does not include the `(` marker.
function: []const u8,
/// A `<at-keyword-token>`
///
/// The value does not include the `@` marker.
at_keyword: []const u8,
/// A `<hash-token>` with the type flag set to "id"
///
/// The value does not include the `#` marker.
id_hash: []const u8, // Hash that is a valid ID selector.
/// A `<hash-token>` with the type flag set to "unrestricted"
///
/// The value does not include the `#` marker.
unrestricted_hash: []const u8,
/// A `<string-token>`
///
/// The value does not include the quotes.
string: []const u8,
/// A `<bad-string-token>`
///
/// This token always indicates a parse error.
bad_string: []const u8,
/// A `<url-token>`
///
/// The value does not include the `url(` `)` markers. Note that `url( <string-token> )` is represented by a
/// `Function` token.
url: []const u8,
/// A `<bad-url-token>`
///
/// This token always indicates a parse error.
bad_url: []const u8,
/// A `<delim-token>`
delim: u8,
/// A `<number-token>`
number: struct {
/// Whether the number had a `+` or `-` sign.
///
/// This is used is some cases like the <An+B> micro syntax. (See the `parse_nth` function.)
has_sign: bool,
/// If the origin source did not include a fractional part, the value as an integer.
int_value: ?i32,
/// The value as a float
value: f32,
},
/// A `<percentage-token>`
percentage: struct {
/// Whether the number had a `+` or `-` sign.
has_sign: bool,
/// If the origin source did not include a fractional part, the value as an integer.
/// It is **not** divided by 100.
int_value: ?i32,
/// The value as a float, divided by 100 so that the nominal range is 0.0 to 1.0.
unit_value: f32,
},
/// A `<dimension-token>`
dimension: struct {
/// Whether the number had a `+` or `-` sign.
///
/// This is used is some cases like the <An+B> micro syntax. (See the `parse_nth` function.)
has_sign: bool,
/// If the origin source did not include a fractional part, the value as an integer.
int_value: ?i32,
/// The value as a float
value: f32,
/// The unit, e.g. "px" in `12px`
unit: []const u8,
},
/// A `<unicode-range-token>`
unicode_range: struct { bgn: u32, end: i32 },
/// A `<whitespace-token>`
white_space: []const u8,
/// A `<!--` `<CDO-token>`
cdo,
/// A `-->` `<CDC-token>`
cdc,
/// A `:` `<colon-token>`
colon, // :
/// A `;` `<semicolon-token>`
semicolon, // ;
/// A `,` `<comma-token>`
comma, // ,
/// A `<[-token>`
square_bracket_block,
/// A `<]-token>`
///
/// When obtained from one of the `Parser::next*` methods,
/// this token is always unmatched and indicates a parse error.
close_square_bracket,
/// A `<(-token>`
parenthesis_block,
/// A `<)-token>`
///
/// When obtained from one of the `Parser::next*` methods,
/// this token is always unmatched and indicates a parse error.
close_parenthesis,
/// A `<{-token>`
curly_bracket_block,
/// A `<}-token>`
///
/// When obtained from one of the `Parser::next*` methods,
/// this token is always unmatched and indicates a parse error.
close_curly_bracket,
/// A comment.
///
/// The CSS Syntax spec does not generate tokens for comments,
/// But we do for simplicity of the interface.
///
/// The value does not include the `/*` `*/` markers.
comment: []const u8,
};
input: []const u8,
/// Counted in bytes, not code points. From 0.
position: usize = 0,
// If true, the input has at least `n` bytes left *after* the current one.
// That is, `Lexer.byteAt(n)` will not panic.
fn hasAtLeast(self: *const Tokenizer, n: usize) bool {
return self.position + n < self.input.len;
}
fn isEof(self: *const Tokenizer) bool {
return !self.hasAtLeast(0);
}
fn byteAt(self: *const Tokenizer, offset: usize) u8 {
return self.input[self.position + offset];
}
// Assumes non-EOF
fn nextByteUnchecked(self: *const Tokenizer) u8 {
return self.byteAt(0);
}
fn nextByte(self: *const Tokenizer) ?u8 {
return if (self.isEof())
null
else
self.input[self.position];
}
fn startsWith(self: *const Tokenizer, needle: []const u8) bool {
return std.mem.startsWith(u8, self.input[self.position..], needle);
}
fn slice(self: *const Tokenizer, start: usize, end: usize) []const u8 {
return self.input[start..end];
}
fn sliceFrom(self: *const Tokenizer, start_pos: usize) []const u8 {
return self.slice(start_pos, self.position);
}
// Advance over N bytes in the input. This function can advance
// over ASCII bytes (excluding newlines), or UTF-8 sequence
// leaders (excluding leaders for 4-byte sequences).
fn advance(self: *Tokenizer, n: usize) void {
if (builtin.mode == .Debug) {
// Each byte must either be an ASCII byte or a sequence leader,
// but not a 4-byte leader; also newlines are rejected.
for (0..n) |i| {
const b = self.byteAt(i);
assert(b != '\r' and b != '\n' and b != '\x0C');
assert(b <= 0x7F or (b & 0xF0 != 0xF0 and b & 0xC0 != 0x80));
}
}
self.position += n;
}
fn hasNewlineAt(self: *const Tokenizer, offset: usize) bool {
if (!self.hasAtLeast(offset)) return false;
return switch (self.byteAt(offset)) {
'\n', '\r', '\x0C' => true,
else => false,
};
}
fn hasNonAsciiAt(self: *const Tokenizer, offset: usize) bool {
if (!self.hasAtLeast(offset)) return false;
const byte = self.byteAt(offset);
const len_utf8 = std.unicode.utf8ByteSequenceLength(byte) catch return false;
if (!self.hasAtLeast(offset + len_utf8 - 1)) return false;
const start = self.position + offset;
const bytes = self.slice(start, start + len_utf8);
const codepoint = std.unicode.utf8Decode(bytes) catch return false;
// https://drafts.csswg.org/css-syntax/#non-ascii-ident-code-point
return switch (codepoint) {
'\u{00B7}', '\u{200C}', '\u{200D}', '\u{203F}', '\u{2040}' => true,
'\u{00C0}'...'\u{00D6}' => true,
'\u{00D8}'...'\u{00F6}' => true,
'\u{00F8}'...'\u{037D}' => true,
'\u{037F}'...'\u{1FFF}' => true,
'\u{2070}'...'\u{218F}' => true,
'\u{2C00}'...'\u{2FEF}' => true,
'\u{3001}'...'\u{D7FF}' => true,
'\u{F900}'...'\u{FDCF}' => true,
'\u{FDF0}'...'\u{FFFD}' => true,
else => codepoint >= '\u{10000}',
};
}
fn isIdentStart(self: *Tokenizer) bool {
if (self.isEof()) return false;
var b = self.nextByteUnchecked();
if (b == '-') {
b = if (self.hasAtLeast(1)) self.byteAt(1) else return false;
}
return switch (b) {
'a'...'z', 'A'...'Z', '_', 0x0 => true,
'\\' => !self.hasNewlineAt(1),
else => b > 0x7F, // not is ascii
};
}
fn consumeChar(self: *Tokenizer) void {
const byte = self.nextByteUnchecked();
const len_utf8 = std.unicode.utf8ByteSequenceLength(byte) catch 1;
self.position += len_utf8;
}
// Given that a newline has been seen, advance over the newline
// and update the state.
fn consumeNewline(self: *Tokenizer) void {
const byte = self.nextByteUnchecked();
assert(byte == '\r' or byte == '\n' or byte == '\x0C');
self.position += 1;
if (byte == '\r' and self.nextByte() == '\n') {
self.position += 1;
}
}
fn consumeWhiteSpace(self: *Tokenizer, newline: bool) Token {
const start_position = self.position;
if (newline) {
self.consumeNewline();
} else {
self.advance(1);
}
while (!self.isEof()) {
const b = self.nextByteUnchecked();
switch (b) {
' ', '\t' => {
self.advance(1);
},
'\n', '\x0C', '\r' => {
self.consumeNewline();
},
else => break,
}
}
return .{ .white_space = self.sliceFrom(start_position) };
}
fn consumeComment(self: *Tokenizer) []const u8 {
self.advance(2); // consume "/*"
const start_position = self.position;
while (!self.isEof()) {
switch (self.nextByteUnchecked()) {
'*' => {
const end_position = self.position;
self.advance(1);
if (self.nextByte() == '/') {
self.advance(1);
return self.slice(start_position, end_position);
}
},
'\n', '\x0C', '\r' => {
self.consumeNewline();
},
0x0 => self.advance(1),
else => self.consumeChar(),
}
}
return self.sliceFrom(start_position);
}
fn byteToHexDigit(b: u8) ?u32 {
return switch (b) {
'0'...'9' => b - '0',
'a'...'f' => b - 'a' + 10,
'A'...'F' => b - 'A' + 10,
else => null,
};
}
fn byteToDecimalDigit(b: u8) ?u32 {
return if (std.ascii.isDigit(b)) b - '0' else null;
}
// (value, number of digits up to 6)
fn consumeHexDigits(self: *Tokenizer) void {
var value: u32 = 0;
var digits: u32 = 0;
while (digits < 6 and !self.isEof()) {
if (byteToHexDigit(self.nextByteUnchecked())) |digit| {
value = value * 16 + digit;
digits += 1;
self.advance(1);
} else {
break;
}
}
_ = &value;
}
// Assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed
// and that the next input character has already been verified
// to not be a newline.
fn consumeEscape(self: *Tokenizer) void {
if (self.isEof())
return; // Escaped EOF
switch (self.nextByteUnchecked()) {
'0'...'9', 'A'...'F', 'a'...'f' => {
consumeHexDigits(self);
if (!self.isEof()) {
switch (self.nextByteUnchecked()) {
' ', '\t' => {
self.advance(1);
},
'\n', '\x0C', '\r' => {
self.consumeNewline();
},
else => {},
}
}
},
else => self.consumeChar(),
}
}
/// https://drafts.csswg.org/css-syntax/#consume-string-token
fn consumeString(self: *Tokenizer, single_quote: bool) Token {
self.advance(1); // Skip the initial quote
// start_pos is at code point boundary, after " or '
const start_pos = self.position;
while (!self.isEof()) {
switch (self.nextByteUnchecked()) {
'"' => {
if (!single_quote) {
const value = self.sliceFrom(start_pos);
self.advance(1);
return .{ .string = value };
}
self.advance(1);
},
'\'' => {
if (single_quote) {
const value = self.sliceFrom(start_pos);
self.advance(1);
return .{ .string = value };
}
self.advance(1);
},
'\n', '\r', '\x0C' => {
return .{ .bad_string = self.sliceFrom(start_pos) };
},
'\\' => {
self.advance(1);
if (self.isEof())
continue; // escaped EOF, do nothing.
switch (self.nextByteUnchecked()) {
// Escaped newline
'\n', '\x0C', '\r' => self.consumeNewline(),
// Spec calls for replacing escape sequences with characters,
// but this would require allocating a new string.
// Therefore, we leave it as is and let the parser handle the escaping.
else => self.consumeEscape(),
}
},
else => self.consumeChar(),
}
}
return .{ .string = self.sliceFrom(start_pos) };
}
fn consumeName(self: *Tokenizer) []const u8 {
// start_pos is the end of the previous token, therefore at a code point boundary
const start_pos = self.position;
while (!self.isEof()) {
switch (self.nextByteUnchecked()) {
'a'...'z', 'A'...'Z', '0'...'9', '_', '-' => self.advance(1),
'\\' => {
if (self.hasNewlineAt(1)) {
break;
}
self.advance(1);
self.consumeEscape();
},
0x0 => self.advance(1),
'\x80'...'\xBF', '\xC0'...'\xEF', '\xF0'...'\xFF' => {
// This byte *is* part of a multi-byte code point,
// well end up copying the whole code point before this loop does something else.
self.advance(1);
},
else => {
if (self.hasNonAsciiAt(0)) {
self.consumeChar();
} else {
break; // ASCII
}
},
}
}
return self.sliceFrom(start_pos);
}
fn consumeMark(self: *Tokenizer) Token {
const byte = self.nextByteUnchecked();
self.advance(1);
return switch (byte) {
',' => .comma,
':' => .colon,
';' => .semicolon,
'(' => .parenthesis_block,
')' => .close_parenthesis,
'{' => .curly_bracket_block,
'}' => .close_curly_bracket,
'[' => .square_bracket_block,
']' => .close_square_bracket,
else => unreachable,
};
}
fn consumeNumeric(self: *Tokenizer) Token {
// Parse [+-]?\d*(\.\d+)?([eE][+-]?\d+)?
// But this is always called so that there is at least one digit in \d*(\.\d+)?
// Do all the math in f64 so that large numbers overflow to +/-inf
// and i32::{MIN, MAX} are within range.
var sign: f64 = 1.0;
var has_sign = false;
switch (self.nextByteUnchecked()) {
'+' => {
has_sign = true;
},
'-' => {
has_sign = true;
sign = -1.0;
},
else => {},
}
if (has_sign) {
self.advance(1);
}
var is_integer = true;
var integral_part: f64 = 0.0;
var fractional_part: f64 = 0.0;
while (!self.isEof()) {
if (byteToDecimalDigit(self.nextByteUnchecked())) |digit| {
integral_part = integral_part * 10.0 + @as(f64, @floatFromInt(digit));
self.advance(1);
} else {
break;
}
}
if (self.hasAtLeast(1) and self.nextByteUnchecked() == '.' and std.ascii.isDigit(self.byteAt(1))) {
is_integer = false;
self.advance(1); // Consume '.'
var factor: f64 = 0.1;
while (!self.isEof()) {
if (byteToDecimalDigit(self.nextByteUnchecked())) |digit| {
fractional_part += @as(f64, @floatFromInt(digit)) * factor;
factor *= 0.1;
self.advance(1);
} else {
break;
}
}
}
var value = sign * (integral_part + fractional_part);
blk: {
const e = self.nextByte() orelse break :blk;
if (e != 'e' and e != 'E') break :blk;
var mul: f64 = 1.0;
if (self.hasAtLeast(2) and (self.byteAt(1) == '+' or self.byteAt(1) == '-') and std.ascii.isDigit(self.byteAt(2))) {
mul = switch (self.byteAt(1)) {
'-' => -1.0,
'+' => 1.0,
else => unreachable,
};
self.advance(2);
} else if (self.hasAtLeast(1) and std.ascii.isDigit(self.byteAt(2))) {
self.advance(1);
} else {
break :blk;
}
is_integer = false;
var exponent: f64 = 0.0;
while (!self.isEof()) {
if (byteToDecimalDigit(self.nextByteUnchecked())) |digit| {
exponent = exponent * 10.0 + @as(f64, @floatFromInt(digit));
self.advance(1);
} else {
break;
}
}
value *= std.math.pow(f64, 10.0, mul * exponent);
}
const int_value: ?i32 = if (is_integer) blk: {
if (value >= std.math.maxInt(i32)) {
break :blk std.math.maxInt(i32);
}
if (value <= std.math.minInt(i32)) {
break :blk std.math.minInt(i32);
}
break :blk @as(i32, @intFromFloat(value));
} else null;
if (!self.isEof() and self.nextByteUnchecked() == '%') {
self.advance(1);
return .{ .percentage = .{
.has_sign = has_sign,
.int_value = int_value,
.unit_value = @as(f32, @floatCast(value / 100.0)),
} };
}
if (isIdentStart(self)) {
return .{ .dimension = .{
.has_sign = has_sign,
.int_value = int_value,
.value = @as(f32, @floatCast(value)),
.unit = consumeName(self),
} };
}
return .{ .number = .{
.has_sign = has_sign,
.int_value = int_value,
.value = @as(f32, @floatCast(value)),
} };
}
fn consumeUnquotedUrl(self: *Tokenizer) ?Token {
// TODO: true url parser
if (self.nextByte()) |it| {
return self.consumeString(it == '\'');
}
return null;
}
fn consumeIdentLike(self: *Tokenizer) Token {
const value = self.consumeName();
if (!self.isEof() and self.nextByteUnchecked() == '(') {
self.advance(1);
if (std.ascii.eqlIgnoreCase(value, "url")) {
if (self.consumeUnquotedUrl()) |result| {
return result;
}
}
return .{ .function = value };
}
return .{ .ident = value };
}
pub fn next(self: *Tokenizer) ?Token {
if (self.isEof()) {
return null;
}
const b = self.nextByteUnchecked();
return switch (b) {
// Consume comments
'/' => {
if (self.startsWith("/*")) {
return .{ .comment = self.consumeComment() };
} else {
self.advance(1);
return .{ .delim = '/' };
}
},
// Consume marks
'(', ')', '{', '}', '[', ']', ',', ':', ';' => {
return self.consumeMark();
},
// Consume as much whitespace as possible. Return a <whitespace-token>.
' ', '\t' => self.consumeWhiteSpace(false),
'\n', '\x0C', '\r' => self.consumeWhiteSpace(true),
// Consume a string token and return it.
'"' => self.consumeString(false),
'\'' => self.consumeString(true),
'0'...'9' => self.consumeNumeric(),
'a'...'z', 'A'...'Z', '_', 0x0 => self.consumeIdentLike(),
'+' => {
if ((self.hasAtLeast(1) and std.ascii.isDigit(self.byteAt(1))) or
(self.hasAtLeast(2) and self.byteAt(1) == '.' and std.ascii.isDigit(self.byteAt(2))))
{
return self.consumeNumeric();
}
self.advance(1);
return .{ .delim = '+' };
},
'-' => {
if ((self.hasAtLeast(1) and std.ascii.isDigit(self.byteAt(1))) or
(self.hasAtLeast(2) and self.byteAt(1) == '.' and std.ascii.isDigit(self.byteAt(2))))
{
return self.consumeNumeric();
}
if (self.startsWith("-->")) {
self.advance(3);
return .cdc;
}
if (isIdentStart(self)) {
return self.consumeIdentLike();
}
self.advance(1);
return .{ .delim = '-' };
},
'.' => {
if (self.hasAtLeast(1) and std.ascii.isDigit(self.byteAt(1))) {
return self.consumeNumeric();
}
self.advance(1);
return .{ .delim = '.' };
},
// Consume hash token
'#' => {
self.advance(1);
if (self.isIdentStart()) {
return .{ .id_hash = self.consumeName() };
}
if (self.nextByte()) |it| {
switch (it) {
// Any other valid case here already resulted in IDHash.
'0'...'9', '-' => return .{ .unrestricted_hash = self.consumeName() },
else => {},
}
}
return .{ .delim = '#' };
},
// Consume at-rules
'@' => {
self.advance(1);
return if (isIdentStart(self))
.{ .at_keyword = consumeName(self) }
else
.{ .delim = '@' };
},
'<' => {
if (self.startsWith("<!--")) {
self.advance(4);
return .cdo;
} else {
self.advance(1);
return .{ .delim = '<' };
}
},
'\\' => {
if (!self.hasNewlineAt(1)) {
return self.consumeIdentLike();
}
self.advance(1);
return .{ .delim = '\\' };
},
else => {
if (b > 0x7F) { // not is ascii
return self.consumeIdentLike();
}
self.advance(1);
return .{ .delim = b };
},
};
}
const testing = std.testing;
fn expectTokensEqual(input: []const u8, tokens: []const Token) !void {
var lexer = Tokenizer{ .input = input };
var i: usize = 0;
while (lexer.next()) |token| : (i += 1) {
assert(i < tokens.len);
try testing.expectEqualDeep(tokens[i], token);
}
try testing.expectEqual(i, tokens.len);
try testing.expectEqualDeep(null, lexer.next());
}
test "smoke" {
try expectTokensEqual(
\\.lightpanda {color:red;}
, &.{
.{ .delim = '.' },
.{ .ident = "lightpanda" },
.{ .white_space = " " },
.curly_bracket_block,
.{ .ident = "color" },
.colon,
.{ .ident = "red" },
.semicolon,
.close_curly_bracket,
});
}

View File

@@ -1,96 +1,323 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const File = std.fs.File;
const Page = @import("Page.zig");
const Node = @import("webapi/Node.zig");
const Slot = @import("webapi/element/html/Slot.zig");
const parser = @import("../netsurf.zig");
const Walker = @import("../dom/walker.zig").WalkerChildren;
pub const RootOpts = struct {
with_base: bool = false,
strip: Opts.Strip = .{},
shadow: Opts.Shadow = .rendered,
};
pub fn htmlFile(doc: *parser.Document, out: File) !void {
try out.writeAll("<!DOCTYPE html>\n");
try nodeFile(parser.documentToNode(doc), out);
try out.writeAll("\n");
pub const Opts = struct {
strip: Strip = .{},
shadow: Shadow = .rendered,
pub const Strip = struct {
js: bool = false,
ui: bool = false,
css: bool = false,
};
pub const Shadow = enum {
// Skip shadow DOM entirely (innerHTML/outerHTML)
skip,
// Dump everyhting (like "view source")
complete,
// Resolve slot elements (like what actually gets rendered)
rendered,
};
};
pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: *Page) !void {
if (doc.is(Node.Document.HTMLDocument)) |html_doc| {
try writer.writeAll("<!DOCTYPE html>");
if (opts.with_base) {
const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode();
const base = try doc.createElement("base", null, page);
try base.setAttributeSafe("base", page.base(), page);
_ = try parent.insertBefore(base.asNode(), parent.firstChild(), page);
}
}
return deep(doc.asNode(), .{ .strip = opts.strip, .shadow = opts.shadow }, writer, page);
}
fn nodeFile(root: *parser.Node, out: File) !void {
const walker = Walker{};
var next: ?*parser.Node = null;
while (true) {
next = try walker.get_next(root, next) orelse break;
switch (try parser.nodeType(next.?)) {
.element => {
// open the tag
const tag = try parser.nodeLocalName(next.?);
try out.writeAll("<");
try out.writeAll(tag);
pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) error{WriteFailed}!void {
return _deep(node, opts, false, writer, page);
}
// write the attributes
const map = try parser.nodeGetAttributes(next.?);
const ln = try parser.namedNodeMapGetLength(map);
var i: u32 = 0;
while (i < ln) {
const attr = try parser.namedNodeMapItem(map, i) orelse break;
try out.writeAll(" ");
try out.writeAll(try parser.attributeGetName(attr));
try out.writeAll("=\"");
try out.writeAll(try parser.attributeGetValue(attr) orelse "");
try out.writeAll("\"");
i += 1;
fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Writer, page: *Page) error{WriteFailed}!void {
switch (node._type) {
.cdata => |cd| {
if (node.is(Node.CData.Comment)) |_| {
try writer.writeAll("<!--");
try writer.writeAll(cd.getData());
try writer.writeAll("-->");
} else if (node.is(Node.CData.ProcessingInstruction)) |pi| {
try writer.writeAll("<?");
try writer.writeAll(pi._target);
try writer.writeAll(" ");
try writer.writeAll(cd.getData());
try writer.writeAll("?>");
} else {
if (shouldEscapeText(node._parent)) {
try writeEscapedText(cd.getData(), writer);
} else {
try writer.writeAll(cd.getData());
}
}
},
.element => |el| {
if (shouldStripElement(el, opts)) {
return;
}
try out.writeAll(">");
// When opts.shadow == .rendered, we normally skip any element with
// a slot attribute. Only the "active" element will get rendered into
// the <slot name="X">. However, the `deep` function is itself used
// to render that "active" content, so when we're trying to render
// it, we don't want to skip it.
if ((comptime force_slot == false) and opts.shadow == .rendered) {
if (el.getAttributeSafe("slot")) |_| {
// Skip - will be rendered by the Slot if it's the active container
return;
}
}
// write the children
// TODO avoid recursion
try nodeFile(next.?, out);
try el.format(writer);
// close the tag
try out.writeAll("</");
try out.writeAll(tag);
try out.writeAll(">");
},
.text => {
const v = try parser.nodeValue(next.?) orelse continue;
try out.writeAll(v);
},
.cdata_section => {
const v = try parser.nodeValue(next.?) orelse continue;
try out.writeAll("<![CDATA[");
try out.writeAll(v);
try out.writeAll("]]>");
},
.comment => {
const v = try parser.nodeValue(next.?) orelse continue;
try out.writeAll("<!--");
try out.writeAll(v);
try out.writeAll("-->");
},
// TODO handle processing instruction dump
.processing_instruction => continue,
// document fragment is outside of the main document DOM, so we
// don't output it.
.document_fragment => continue,
// document will never be called, but required for completeness.
.document => continue,
// done globally instead, but required for completeness.
.document_type => continue,
// deprecated
.attribute => continue,
.entity_reference => continue,
.entity => continue,
.notation => continue,
}
if (opts.shadow == .rendered) {
if (el.is(Slot)) |slot| {
try dumpSlotContent(slot, opts, writer, page);
return writer.writeAll("</slot>");
}
}
if (opts.shadow != .skip) {
if (page._element_shadow_roots.get(el)) |shadow| {
try children(shadow.asNode(), opts, writer, page);
// In rendered mode, light DOM is only shown through slots, not directly
if (opts.shadow == .rendered) {
// Skip rendering light DOM children
if (!isVoidElement(el)) {
try writer.writeAll("</");
try writer.writeAll(el.getTagNameDump());
try writer.writeByte('>');
}
return;
}
}
}
try children(node, opts, writer, page);
if (!isVoidElement(el)) {
try writer.writeAll("</");
try writer.writeAll(el.getTagNameDump());
try writer.writeByte('>');
}
},
.document => try children(node, opts, writer, page),
.document_type => |dt| {
try writer.writeAll("<!DOCTYPE ");
try writer.writeAll(dt.getName());
const public_id = dt.getPublicId();
const system_id = dt.getSystemId();
if (public_id.len != 0 and system_id.len != 0) {
try writer.writeAll(" PUBLIC \"");
try writeEscapedText(public_id, writer);
try writer.writeAll("\" \"");
try writeEscapedText(system_id, writer);
try writer.writeByte('"');
} else if (public_id.len != 0) {
try writer.writeAll(" PUBLIC \"");
try writeEscapedText(public_id, writer);
try writer.writeByte('"');
} else if (system_id.len != 0) {
try writer.writeAll(" SYSTEM \"");
try writeEscapedText(system_id, writer);
try writer.writeByte('"');
}
try writer.writeAll(">\n");
},
.document_fragment => try children(node, opts, writer, page),
.attribute => unreachable,
}
}
// HTMLFileTestFn is run by run_tests.zig
pub fn HTMLFileTestFn(out: File) !void {
const file = try std.fs.cwd().openFile("test.html", .{});
defer file.close();
const doc_html = try parser.documentHTMLParse(file.reader(), "UTF-8");
// ignore close error
defer parser.documentHTMLClose(doc_html) catch {};
const doc = parser.documentHTMLToDocument(doc_html);
try htmlFile(doc, out);
pub fn children(parent: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
var it = parent.childrenIterator();
while (it.next()) |child| {
try deep(child, opts, writer, page);
}
}
pub fn toJSON(node: *Node, writer: *std.json.Stringify) !void {
try writer.beginObject();
try writer.objectField("type");
switch (node.type) {
.cdata => {
try writer.write("cdata");
},
.document => {
try writer.write("document");
},
.document_type => {
try writer.write("document_type");
},
.element => |*el| {
try writer.write("element");
try writer.objectField("tag");
try writer.write(el.tagName());
try writer.objectField("attributes");
try writer.beginObject();
var it = el.attributeIterator();
while (it.next()) |attr| {
try writer.objectField(attr.name);
try writer.write(attr.value);
}
try writer.endObject();
},
}
try writer.objectField("children");
try writer.beginArray();
var it = node.childrenIterator();
while (it.next()) |child| {
try toJSON(child, writer);
}
try writer.endArray();
try writer.endObject();
}
fn dumpSlotContent(slot: *Slot, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
const assigned = slot.assignedNodes(null, page) catch return;
if (assigned.len > 0) {
for (assigned) |assigned_node| {
try _deep(assigned_node, opts, true, writer, page);
}
} else {
try children(slot.asNode(), opts, writer, page);
}
}
fn isVoidElement(el: *const Node.Element) bool {
return switch (el._type) {
.html => |html| switch (html._type) {
.br, .hr, .img, .input, .link, .meta => true,
else => false,
},
.svg => false,
};
}
fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
const tag_name = el.getTagNameDump();
if (opts.strip.js) {
if (std.mem.eql(u8, tag_name, "script")) return true;
if (std.mem.eql(u8, tag_name, "noscript")) return true;
if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe("as")) |as| {
if (std.mem.eql(u8, as, "script")) return true;
}
if (el.getAttributeSafe("rel")) |rel| {
if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) {
if (el.getAttributeSafe("as")) |as| {
if (std.mem.eql(u8, as, "script")) return true;
}
}
}
}
}
if (opts.strip.css or opts.strip.ui) {
if (std.mem.eql(u8, tag_name, "style")) return true;
if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe("rel")) |rel| {
if (std.mem.eql(u8, rel, "stylesheet")) return true;
}
}
}
if (opts.strip.ui) {
if (std.mem.eql(u8, tag_name, "img")) return true;
if (std.mem.eql(u8, tag_name, "picture")) return true;
if (std.mem.eql(u8, tag_name, "video")) return true;
if (std.mem.eql(u8, tag_name, "audio")) return true;
if (std.mem.eql(u8, tag_name, "svg")) return true;
if (std.mem.eql(u8, tag_name, "canvas")) return true;
if (std.mem.eql(u8, tag_name, "iframe")) return true;
}
return false;
}
fn shouldEscapeText(node_: ?*Node) bool {
const node = node_ orelse return true;
if (node.is(Node.Element.Html.Script) != null) {
return false;
}
return true;
}
fn writeEscapedText(text: []const u8, writer: *std.Io.Writer) !void {
// Fast path: if no special characters, write directly
const first_special = std.mem.indexOfAnyPos(u8, text, 0, &.{ '&', '<', '>', 194 }) orelse {
return writer.writeAll(text);
};
try writer.writeAll(text[0..first_special]);
var remaining = try writeEscapedByte(text, first_special, writer);
while (std.mem.indexOfAnyPos(u8, remaining, 0, &.{ '&', '<', '>', 194 })) |offset| {
try writer.writeAll(remaining[0..offset]);
remaining = try writeEscapedByte(remaining, offset, writer);
}
if (remaining.len > 0) {
try writer.writeAll(remaining);
}
}
fn writeEscapedByte(input: []const u8, index: usize, writer: *std.Io.Writer) ![]const u8 {
switch (input[index]) {
'&' => try writer.writeAll("&amp;"),
'<' => try writer.writeAll("&lt;"),
'>' => try writer.writeAll("&gt;"),
194 => {
// non breaking space
if (input.len > index + 1 and input[index + 1] == 160) {
try writer.writeAll("&nbsp;");
return input[index + 2 ..];
}
try writer.writeByte(194);
},
else => unreachable,
}
return input[index + 1 ..];
}

68
src/browser/js/Array.zig Normal file
View File

@@ -0,0 +1,68 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const Array = @This();
ctx: *js.Context,
handle: *const v8.Array,
pub fn len(self: Array) usize {
return v8.v8__Array__Length(self.handle);
}
pub fn get(self: Array, index: u32) !js.Value {
const ctx = self.ctx;
const idx = js.Integer.init(ctx.isolate.handle, index);
const handle = v8.v8__Object__Get(@ptrCast(self.handle), ctx.handle, idx.handle) orelse {
return error.JsException;
};
return .{
.ctx = self.ctx,
.handle = handle,
};
}
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
const ctx = self.ctx;
const js_value = try ctx.zigValueToJs(value, opts);
var out: v8.MaybeBool = undefined;
v8.v8__Object__SetAtIndex(@ptrCast(self.handle), ctx.handle, index, js_value.handle, &out);
return out.has_value;
}
pub fn toObject(self: Array) js.Object {
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn toValue(self: Array) js.Value {
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}

41
src/browser/js/BigInt.zig Normal file
View File

@@ -0,0 +1,41 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const BigInt = @This();
handle: *const v8.Integer,
pub fn init(isolate: *v8.Isolate, val: anytype) BigInt {
const handle = switch (@TypeOf(val)) {
i8, i16, i32, i64, isize => v8.v8__BigInt__New(isolate, val).?,
u8, u16, u32, u64, usize => v8.v8__BigInt__NewFromUnsigned(isolate, val).?,
else => |T| @compileError("cannot create v8::BigInt from: " ++ @typeName(T)),
};
return .{ .handle = handle };
}
pub fn getInt64(self: BigInt) i64 {
return v8.v8__BigInt__Int64Value(self.handle, null);
}
pub fn getUint64(self: BigInt) u64 {
return v8.v8__BigInt__Uint64Value(self.handle, null);
}

2083
src/browser/js/Context.zig Normal file

File diff suppressed because it is too large Load Diff

221
src/browser/js/Env.zig Normal file
View File

@@ -0,0 +1,221 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const log = @import("../../log.zig");
const bridge = @import("bridge.zig");
const Context = @import("Context.zig");
const Platform = @import("Platform.zig");
const Snapshot = @import("Snapshot.zig");
const Inspector = @import("Inspector.zig");
const ExecutionWorld = @import("ExecutionWorld.zig");
const JsApis = bridge.JsApis;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
// The Env maps to a V8 isolate, which represents a isolated sandbox for
// executing JavaScript. The Env is where we'll define our V8 <-> Zig bindings,
// and it's where we'll start ExecutionWorlds, which actually execute JavaScript.
// The `S` parameter is arbitrary state. When we start an ExecutionWorld, an instance
// of S must be given. This instance is available to any Zig binding.
// The `types` parameter is a tuple of Zig structures we want to bind to V8.
const Env = @This();
allocator: Allocator,
platform: *const Platform,
// the global isolate
isolate: js.Isolate,
// just kept around because we need to free it on deinit
isolate_params: *v8.CreateParams,
context_id: usize,
// Global handles that need to be freed on deinit
eternal_function_templates: []v8.Eternal,
// Dynamic slice to avoid circular dependency on JsApis.len at comptime
templates: []*const v8.FunctionTemplate,
pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot) !Env {
var params = try allocator.create(v8.CreateParams);
errdefer allocator.destroy(params);
v8.v8__Isolate__CreateParams__CONSTRUCT(params);
params.snapshot_blob = @ptrCast(&snapshot.startup_data);
params.array_buffer_allocator = v8.v8__ArrayBuffer__Allocator__NewDefaultAllocator().?;
errdefer v8.v8__ArrayBuffer__Allocator__DELETE(params.array_buffer_allocator.?);
params.external_references = &snapshot.external_references;
var isolate = js.Isolate.init(params);
errdefer isolate.deinit();
v8.v8__Isolate__SetHostImportModuleDynamicallyCallback(isolate.handle, Context.dynamicModuleCallback);
v8.v8__Isolate__SetPromiseRejectCallback(isolate.handle, promiseRejectCallback);
v8.v8__Isolate__SetMicrotasksPolicy(isolate.handle, v8.kExplicit);
v8.v8__Isolate__SetFatalErrorHandler(isolate.handle, fatalCallback);
v8.v8__Isolate__SetOOMErrorHandler(isolate.handle, oomCallback);
isolate.enter();
errdefer isolate.exit();
v8.v8__Isolate__SetHostInitializeImportMetaObjectCallback(isolate.handle, Context.metaObjectCallback);
// Allocate arrays dynamically to avoid comptime dependency on JsApis.len
const eternal_function_templates = try allocator.alloc(v8.Eternal, JsApis.len);
errdefer allocator.free(eternal_function_templates);
const templates = try allocator.alloc(*const v8.FunctionTemplate, JsApis.len);
errdefer allocator.free(templates);
{
var temp_scope: js.HandleScope = undefined;
temp_scope.init(isolate);
defer temp_scope.deinit();
inline for (JsApis, 0..) |JsApi, i| {
JsApi.Meta.class_id = i;
const data = v8.v8__Isolate__GetDataFromSnapshotOnce(isolate.handle, snapshot.data_start + i);
const function_handle: *const v8.FunctionTemplate = @ptrCast(data);
// Make function template eternal
v8.v8__Eternal__New(isolate.handle, @ptrCast(function_handle), &eternal_function_templates[i]);
// Extract the local handle from the global for easy access
const eternal_ptr = v8.v8__Eternal__Get(&eternal_function_templates[i], isolate.handle);
templates[i] = @ptrCast(@alignCast(eternal_ptr.?));
}
}
return .{
.context_id = 0,
.isolate = isolate,
.platform = platform,
.allocator = allocator,
.templates = templates,
.isolate_params = params,
.eternal_function_templates = eternal_function_templates,
};
}
pub fn deinit(self: *Env) void {
self.allocator.free(self.templates);
self.allocator.free(self.eternal_function_templates);
self.isolate.exit();
self.isolate.deinit();
v8.v8__ArrayBuffer__Allocator__DELETE(self.isolate_params.array_buffer_allocator.?);
self.allocator.destroy(self.isolate_params);
}
pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !*Inspector {
const inspector = try arena.create(Inspector);
try Inspector.init(inspector, self.isolate.handle, ctx);
return inspector;
}
pub fn runMicrotasks(self: *const Env) void {
self.isolate.performMicrotasksCheckpoint();
}
pub fn pumpMessageLoop(self: *const Env) bool {
return v8.v8__Platform__PumpMessageLoop(self.platform.handle, self.isolate.handle, false);
}
pub fn runIdleTasks(self: *const Env) void {
v8.v8__Platform__RunIdleTasks(self.platform.handle, self.isolate.handle, 1);
}
pub fn newExecutionWorld(self: *Env) !ExecutionWorld {
return .{
.env = self,
.context = null,
.context_arena = ArenaAllocator.init(self.allocator),
};
}
// V8 doesn't immediately free memory associated with
// a Context, it's managed by the garbage collector. We use the
// `lowMemoryNotification` call on the isolate to encourage v8 to free
// any contexts which have been freed.
pub fn lowMemoryNotification(self: *Env) void {
var handle_scope: js.HandleScope = undefined;
handle_scope.init(self.isolate);
defer handle_scope.deinit();
self.isolate.lowMemoryNotification();
}
pub fn dumpMemoryStats(self: *Env) void {
const stats = self.isolate.getHeapStatistics();
std.debug.print(
\\ Total Heap Size: {d}
\\ Total Heap Size Executable: {d}
\\ Total Physical Size: {d}
\\ Total Available Size: {d}
\\ Used Heap Size: {d}
\\ Heap Size Limit: {d}
\\ Malloced Memory: {d}
\\ External Memory: {d}
\\ Peak Malloced Memory: {d}
\\ Number Of Native Contexts: {d}
\\ Number Of Detached Contexts: {d}
\\ Total Global Handles Size: {d}
\\ Used Global Handles Size: {d}
\\ Zap Garbage: {any}
\\
, .{ stats.total_heap_size, stats.total_heap_size_executable, stats.total_physical_size, stats.total_available_size, stats.used_heap_size, stats.heap_size_limit, stats.malloced_memory, stats.external_memory, stats.peak_malloced_memory, stats.number_of_native_contexts, stats.number_of_detached_contexts, stats.total_global_handles_size, stats.used_global_handles_size, stats.does_zap_garbage });
}
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
const isolate_handle = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
const js_isolate = js.Isolate{ .handle = isolate_handle };
const context = Context.fromIsolate(js_isolate);
const value =
if (v8.v8__PromiseRejectMessage__GetValue(&message_handle)) |v8_value|
context.valueToString(.{ .ctx = context, .handle = v8_value }, .{}) catch |err| @errorName(err)
else
"no value";
log.debug(.js, "unhandled rejection", .{
.value = value,
.stack = context.stackTrace() catch |err| @errorName(err) orelse "???",
.note = "This should be updated to call window.unhandledrejection",
});
}
fn fatalCallback(c_location: [*c]const u8, c_message: [*c]const u8) callconv(.c) void {
const location = std.mem.span(c_location);
const message = std.mem.span(c_message);
log.fatal(.app, "V8 fatal callback", .{ .location = location, .message = message });
@import("../../crash_handler.zig").crash("Fatal V8 Error", .{ .location = location, .message = message }, @returnAddress());
}
fn oomCallback(c_location: [*c]const u8, details: ?*const v8.OOMDetails) callconv(.c) void {
const location = std.mem.span(c_location);
const detail = if (details) |d| std.mem.span(d.detail) else "";
log.fatal(.app, "V8 OOM", .{ .location = location, .detail = detail });
@import("../../crash_handler.zig").crash("V8 OOM", .{ .location = location, .detail = detail }, @returnAddress());
}

View File

@@ -0,0 +1,163 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const log = @import("../../log.zig");
const Page = @import("../Page.zig");
const js = @import("js.zig");
const v8 = js.v8;
const Env = @import("Env.zig");
const bridge = @import("bridge.zig");
const Context = @import("Context.zig");
const ArenaAllocator = std.heap.ArenaAllocator;
const IS_DEBUG = @import("builtin").mode == .Debug;
const CONTEXT_ARENA_RETAIN = 1024 * 64;
// ExecutionWorld closely models a JS World.
// https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#World
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld
const ExecutionWorld = @This();
env: *Env,
// Arena whose lifetime is for a single page load. Where
// the call_arena lives for a single function call, the context_arena
// lives for the lifetime of the entire page. The allocator will be
// owned by the Context, but the arena itself is owned by the ExecutionWorld
// so that we can re-use it from context to context.
context_arena: ArenaAllocator,
// Currently a context maps to a Browser's Page. Here though, it's only a
// mechanism to organization page-specific memory. The ExecutionWorld
// does all the work, but having all page-specific data structures
// grouped together helps keep things clean.
context: ?Context = null,
persisted_context: ?js.Global(Context) = null,
// no init, must be initialized via env.newExecutionWorld()
pub fn deinit(self: *ExecutionWorld) void {
if (self.context != null) {
self.removeContext();
}
self.context_arena.deinit();
}
// Only the top Context in the Main ExecutionWorld should hold a handle_scope.
// A js.HandleScope is like an arena. Once created, any "Local" that
// v8 creates will be released (or at least, releasable by the v8 GC)
// when the handle_scope is freed.
// We also maintain our own "context_arena" which allows us to have
// all page related memory easily managed.
pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context {
lp.assert(self.context == null, "ExecptionWorld.createContext has context", .{});
const env = self.env;
const isolate = env.isolate;
const arena = self.context_arena.allocator();
const persisted_context: js.Global(Context) = blk: {
var temp_scope: js.HandleScope = undefined;
temp_scope.init(isolate);
defer temp_scope.deinit();
// Getting this into the snapshot is tricky (anything involving the
// global is tricky). Easier to do here
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate.handle, env.templates);
v8.v8__ObjectTemplate__SetNamedHandler(global_template, &.{
.getter = bridge.unknownPropertyCallback,
.setter = null,
.query = null,
.deleter = null,
.enumerator = null,
.definer = null,
.descriptor = null,
.data = null,
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
});
const context_handle = v8.v8__Context__New(isolate.handle, global_template, null).?;
break :blk js.Global(Context).init(isolate.handle, context_handle);
};
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
const v8_context = persisted_context.local();
var handle_scope: ?js.HandleScope = null;
if (enter) {
handle_scope = @as(js.HandleScope, undefined);
handle_scope.?.init(isolate);
v8.v8__Context__Enter(v8_context);
}
errdefer if (enter) {
v8.v8__Context__Exit(v8_context);
handle_scope.?.deinit();
};
const context_id = env.context_id;
env.context_id = context_id + 1;
self.context = Context{
.page = page,
.id = context_id,
.isolate = isolate,
.handle = v8_context,
.templates = env.templates,
.handle_scope = handle_scope,
.script_manager = &page._script_manager,
.call_arena = page.call_arena,
.arena = arena,
};
self.persisted_context = persisted_context;
var context = &self.context.?;
// Store a pointer to our context inside the v8 context so that, given
// a v8 context, we can get our context out
const data = isolate.initBigInt(@intFromPtr(context));
v8.v8__Context__SetEmbedderData(context.handle, 1, @ptrCast(data.handle));
try context.setupGlobal();
return context;
}
pub fn removeContext(self: *ExecutionWorld) void {
var context = &(self.context orelse return);
context.deinit();
self.context = null;
self.persisted_context.?.deinit();
self.persisted_context = null;
self.env.isolate.notifyContextDisposed();
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
}
pub fn terminateExecution(self: *const ExecutionWorld) void {
self.env.isolate.terminateExecution();
}
pub fn resumeExecution(self: *const ExecutionWorld) void {
self.env.isolate.cancelTerminateExecution();
}

208
src/browser/js/Function.zig Normal file
View File

@@ -0,0 +1,208 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const Allocator = std.mem.Allocator;
const Function = @This();
ctx: *js.Context,
this: ?*const v8.Object = null,
handle: *const v8.Function,
pub const Result = struct {
stack: ?[]const u8,
exception: []const u8,
};
pub fn withThis(self: *const Function, value: anytype) !Function {
const this_obj = if (@TypeOf(value) == js.Object)
value.handle
else
(try self.ctx.zigValueToJs(value, .{})).handle;
return .{
.ctx = self.ctx,
.this = this_obj,
.handle = self.handle,
};
}
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
const ctx = self.ctx;
var try_catch: js.TryCatch = undefined;
try_catch.init(ctx);
defer try_catch.deinit();
// This creates a new instance using this Function as a constructor.
// const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{}));
const handle = v8.v8__Function__NewInstance(self.handle, ctx.handle, 0, null) orelse {
caught.* = try_catch.caughtOrError(ctx.call_arena, error.Unknown);
return error.JsConstructorFailed;
};
return .{
.ctx = ctx,
.handle = handle,
};
}
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
return self.callWithThis(T, self.getThis(), args);
}
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T {
return self.tryCallWithThis(T, self.getThis(), args, caught);
}
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
var try_catch: js.TryCatch = undefined;
try_catch.init(self.ctx);
defer try_catch.deinit();
return self.callWithThis(T, this, args) catch |err| {
caught.* = try_catch.caughtOrError(self.ctx.call_arena, err);
return err;
};
}
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
const ctx = self.ctx;
// When we're calling a function from within JavaScript itself, this isn't
// necessary. We're within a Caller instantiation, which will already have
// incremented the call_depth and it won't decrement it until the Caller is
// done.
// But some JS functions are initiated from Zig code, and not v8. For
// example, Observers, some event and window callbacks. In those cases, we
// need to increase the call_depth so that the call_arena remains valid for
// the duration of the function call. If we don't do this, the call_arena
// will be reset after each statement of the function which executes Zig code.
const call_depth = ctx.call_depth;
ctx.call_depth = call_depth + 1;
defer ctx.call_depth = call_depth;
const js_this = blk: {
if (@TypeOf(this) == js.Object) {
break :blk this;
}
break :blk try ctx.zigValueToJs(this, .{});
};
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
const js_args: []const *const v8.Value = switch (@typeInfo(@TypeOf(aargs))) {
.@"struct" => |s| blk: {
const fields = s.fields;
var js_args: [fields.len]*const v8.Value = undefined;
inline for (fields, 0..) |f, i| {
js_args[i] = (try ctx.zigValueToJs(@field(aargs, f.name), .{})).handle;
}
const cargs: [fields.len]*const v8.Value = js_args;
break :blk &cargs;
},
.pointer => blk: {
var values = try ctx.call_arena.alloc(*const v8.Value, args.len);
for (args, 0..) |a, i| {
values[i] = (try ctx.zigValueToJs(a, .{})).handle;
}
break :blk values;
},
else => @compileError("JS Function called with invalid paremter type"),
};
const c_args = @as(?[*]const ?*v8.Value, @ptrCast(js_args.ptr));
const handle = v8.v8__Function__Call(self.handle, ctx.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
// std.debug.print("CB ERR: {s}\n", .{self.src() catch "???"});
return error.JSExecCallback;
};
if (@typeInfo(T) == .void) {
return {};
}
return ctx.jsValueToZig(T, .{ .ctx = ctx, .handle = handle });
}
fn getThis(self: *const Function) js.Object {
const handle = if (self.this) |t| t else v8.v8__Context__Global(self.ctx.handle).?;
return .{
.ctx = self.ctx,
.handle = handle,
};
}
pub fn src(self: *const Function) ![]const u8 {
return self.context.valueToString(.{ .handle = @ptrCast(self.handle) }, .{});
}
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
const ctx = self.ctx;
const key = ctx.isolate.initStringHandle(name);
const handle = v8.v8__Object__Get(self.handle, ctx.handle, key) orelse {
return error.JsException;
};
return .{
.ctx = ctx,
.handle = handle,
};
}
pub fn persist(self: *const Function) !Global {
var ctx = self.ctx;
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_functions.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
};
}
pub fn persistWithThis(self: *const Function, value: anytype) !Global {
const with_this = try self.withThis(value);
return with_this.persist();
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) Function {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: Function) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
};

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const HandleScope = @This();
handle: v8.HandleScope,
// V8 takes an address of the value that's passed in, so it needs to be stable.
// We can't create the v8.HandleScope here, pass it to v8 and then return the
// value, as v8 will then have taken the address of the function-scopped (and no
// longer valid) local.
pub fn init(self: *HandleScope, isolate: js.Isolate) void {
v8.v8__HandleScope__CONSTRUCT(&self.handle, isolate.handle);
}
pub fn deinit(self: *HandleScope) void {
v8.v8__HandleScope__DESTRUCT(&self.handle);
}

View File

@@ -0,0 +1,501 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const Context = @import("Context.zig");
const Allocator = std.mem.Allocator;
const RndGen = std.Random.DefaultPrng;
const CONTEXT_GROUP_ID = 1;
const CLIENT_TRUST_LEVEL = 1;
const Inspector = @This();
handle: *v8.Inspector,
isolate: *v8.Isolate,
client: Client,
channel: Channel,
session: Session,
rnd: RndGen = RndGen.init(0),
default_context: ?*const v8.Context = null,
// We expect allocator to be an arena
// Note: This initializes the pre-allocated inspector in-place
pub fn init(self: *Inspector, isolate: *v8.Isolate, ctx: anytype) !void {
const ContextT = @TypeOf(ctx);
const Container = switch (@typeInfo(ContextT)) {
.@"struct" => ContextT,
.pointer => |ptr| ptr.child,
.void => NoopInspector,
else => @compileError("invalid context type"),
};
// If necessary, turn a void context into something we can safely ptrCast
const safe_context: *anyopaque = if (ContextT == void) @ptrCast(@constCast(&{})) else ctx;
// Initialize the fields that callbacks need first
self.* = .{
.handle = undefined,
.isolate = isolate,
.client = undefined,
.channel = undefined,
.rnd = RndGen.init(0),
.default_context = null,
.session = undefined,
};
// Create client and set inspector data BEFORE creating the inspector
// because V8 will call generateUniqueId during inspector creation
const client = Client.init();
self.client = client;
client.setInspector(self);
// Now create the inspector - generateUniqueId will work because data is set
const handle = v8.v8_inspector__Inspector__Create(isolate, client.handle).?;
self.handle = handle;
// Create the channel
const channel = Channel.init(
safe_context,
Container.onInspectorResponse,
Container.onInspectorEvent,
Container.onRunMessageLoopOnPause,
Container.onQuitMessageLoopOnPause,
isolate,
);
self.channel = channel;
channel.setInspector(self);
// Create the session
const session_handle = v8.v8_inspector__Inspector__Connect(
handle,
CONTEXT_GROUP_ID,
channel.handle,
CLIENT_TRUST_LEVEL,
).?;
self.session = .{ .handle = session_handle };
}
pub fn deinit(self: *const Inspector) void {
var temp_scope: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&temp_scope, self.isolate);
defer v8.v8__HandleScope__DESTRUCT(&temp_scope);
self.session.deinit();
self.client.deinit();
self.channel.deinit();
v8.v8_inspector__Inspector__DELETE(self.handle);
}
pub fn send(self: *const Inspector, msg: []const u8) void {
// Can't assume the main Context exists (with its HandleScope)
// available when doing this. Pages (and thus the HandleScope)
// comes and goes, but CDP can keep sending messages.
const isolate = self.isolate;
var temp_scope: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&temp_scope, isolate);
defer v8.v8__HandleScope__DESTRUCT(&temp_scope);
self.session.dispatchProtocolMessage(isolate, msg);
}
// From CDP docs
// https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ExecutionContextDescription
// ----
// - name: Human readable name describing given context.
// - origin: Execution context origin (ie. URL who initialised the request)
// - auxData: Embedder-specific auxiliary data likely matching
// {isDefault: boolean, type: 'default'|'isolated'|'worker', frameId: string}
// - is_default_context: Whether the execution context is default, should match the auxData
pub fn contextCreated(
self: *Inspector,
context: *const Context,
name: []const u8,
origin: []const u8,
aux_data: []const u8,
is_default_context: bool,
) void {
v8.v8_inspector__Inspector__ContextCreated(
self.handle,
name.ptr,
name.len,
origin.ptr,
origin.len,
aux_data.ptr,
aux_data.len,
CONTEXT_GROUP_ID,
context.handle,
);
if (is_default_context) {
self.default_context = context.handle;
}
}
// Retrieves the RemoteObject for a given value.
// The value is loaded through the ExecutionWorld's mapZigInstanceToJs function,
// just like a method return value. Therefore, if we've mapped this
// value before, we'll get the existing js.Global(js.Object) and if not
// we'll create it and track it for cleanup when the context ends.
pub fn getRemoteObject(
self: *const Inspector,
context: *Context,
group: []const u8,
value: anytype,
) !RemoteObject {
const js_value = try context.zigValueToJs(value, .{});
// We do not want to expose this as a parameter for now
const generate_preview = false;
return self.session.wrapObject(
context.isolate.handle,
context.handle,
js_value.handle,
group,
generate_preview,
);
}
// Gets a value by object ID regardless of which context it is in.
// Our TaggedAnyOpaque stores the "resolved" ptr value (the most specific _type,
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
// the pointer to the Node, so we need to use the same resolution mechanism which
// is used when we're calling a function to turn the Div into a Node, which is
// what Context.typeTaggedAnyOpaque does.
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8) !*anyopaque {
const unwrapped = try self.session.unwrapObject(allocator, object_id);
// The values context and groupId are not used here
const js_val = unwrapped.value;
if (!v8.v8__Value__IsObject(js_val)) {
return error.ObjectIdIsNotANode;
}
const Node = @import("../webapi/Node.zig");
// Cast to *const v8.Object for typeTaggedAnyOpaque
return Context.typeTaggedAnyOpaque(*Node, @ptrCast(js_val)) catch {
return error.ObjectIdIsNotANode;
};
}
pub const RemoteObject = struct {
handle: *v8.RemoteObject,
pub fn deinit(self: RemoteObject) void {
v8.v8_inspector__RemoteObject__DELETE(self.handle);
}
pub fn getType(self: RemoteObject, allocator: Allocator) ![]const u8 {
var ctype_: v8.CZigString = .{ .ptr = null, .len = 0 };
if (!v8.v8_inspector__RemoteObject__getType(self.handle, &allocator, &ctype_)) return error.V8AllocFailed;
return cZigStringToString(ctype_) orelse return error.InvalidType;
}
pub fn getSubtype(self: RemoteObject, allocator: Allocator) !?[]const u8 {
if (!v8.v8_inspector__RemoteObject__hasSubtype(self.handle)) return null;
var csubtype: v8.CZigString = .{ .ptr = null, .len = 0 };
if (!v8.v8_inspector__RemoteObject__getSubtype(self.handle, &allocator, &csubtype)) return error.V8AllocFailed;
return cZigStringToString(csubtype);
}
pub fn getClassName(self: RemoteObject, allocator: Allocator) !?[]const u8 {
if (!v8.v8_inspector__RemoteObject__hasClassName(self.handle)) return null;
var cclass_name: v8.CZigString = .{ .ptr = null, .len = 0 };
if (!v8.v8_inspector__RemoteObject__getClassName(self.handle, &allocator, &cclass_name)) return error.V8AllocFailed;
return cZigStringToString(cclass_name);
}
pub fn getDescription(self: RemoteObject, allocator: Allocator) !?[]const u8 {
if (!v8.v8_inspector__RemoteObject__hasDescription(self.handle)) return null;
var description: v8.CZigString = .{ .ptr = null, .len = 0 };
if (!v8.v8_inspector__RemoteObject__getDescription(self.handle, &allocator, &description)) return error.V8AllocFailed;
return cZigStringToString(description);
}
pub fn getObjectId(self: RemoteObject, allocator: Allocator) !?[]const u8 {
if (!v8.v8_inspector__RemoteObject__hasObjectId(self.handle)) return null;
var cobject_id: v8.CZigString = .{ .ptr = null, .len = 0 };
if (!v8.v8_inspector__RemoteObject__getObjectId(self.handle, &allocator, &cobject_id)) return error.V8AllocFailed;
return cZigStringToString(cobject_id);
}
};
const Session = struct {
handle: *v8.InspectorSession,
fn deinit(self: Session) void {
v8.v8_inspector__Session__DELETE(self.handle);
}
fn dispatchProtocolMessage(self: Session, isolate: *v8.Isolate, msg: []const u8) void {
v8.v8_inspector__Session__dispatchProtocolMessage(
self.handle,
isolate,
msg.ptr,
msg.len,
);
}
fn wrapObject(
self: Session,
isolate: *v8.Isolate,
ctx: *const v8.Context,
val: *const v8.Value,
grpname: []const u8,
generatepreview: bool,
) !RemoteObject {
const remote_object = v8.v8_inspector__Session__wrapObject(
self.handle,
isolate,
ctx,
val,
grpname.ptr,
grpname.len,
generatepreview,
).?;
return .{ .handle = remote_object };
}
fn unwrapObject(
self: Session,
allocator: Allocator,
object_id: []const u8,
) !UnwrappedObject {
const in_object_id = v8.CZigString{
.ptr = object_id.ptr,
.len = object_id.len,
};
var out_error: v8.CZigString = .{ .ptr = null, .len = 0 };
var out_value_handle: ?*v8.Value = null;
var out_context_handle: ?*v8.Context = null;
var out_object_group: v8.CZigString = .{ .ptr = null, .len = 0 };
const result = v8.v8_inspector__Session__unwrapObject(
self.handle,
&allocator,
&out_error,
in_object_id,
&out_value_handle,
&out_context_handle,
&out_object_group,
);
if (!result) {
const error_str = cZigStringToString(out_error) orelse return error.UnwrapFailed;
std.log.err("unwrapObject failed: {s}", .{error_str});
return error.UnwrapFailed;
}
return .{
.value = out_value_handle.?,
.context = out_context_handle.?,
.object_group = cZigStringToString(out_object_group),
};
}
};
const UnwrappedObject = struct {
value: *const v8.Value,
context: *const v8.Context,
object_group: ?[]const u8,
};
const Channel = struct {
handle: *v8.InspectorChannelImpl,
// callbacks
ctx: *anyopaque,
onNotif: onNotifFn = undefined,
onResp: onRespFn = undefined,
onRunMessageLoopOnPause: onRunMessageLoopOnPauseFn = undefined,
onQuitMessageLoopOnPause: onQuitMessageLoopOnPauseFn = undefined,
pub const onNotifFn = *const fn (ctx: *anyopaque, msg: []const u8) void;
pub const onRespFn = *const fn (ctx: *anyopaque, call_id: u32, msg: []const u8) void;
pub const onRunMessageLoopOnPauseFn = *const fn (ctx: *anyopaque, context_group_id: u32) void;
pub const onQuitMessageLoopOnPauseFn = *const fn (ctx: *anyopaque) void;
fn init(
ctx: *anyopaque,
onResp: onRespFn,
onNotif: onNotifFn,
onRunMessageLoopOnPause: onRunMessageLoopOnPauseFn,
onQuitMessageLoopOnPause: onQuitMessageLoopOnPauseFn,
isolate: *v8.Isolate,
) Channel {
const handle = v8.v8_inspector__Channel__IMPL__CREATE(isolate);
return .{
.handle = handle,
.ctx = ctx,
.onResp = onResp,
.onNotif = onNotif,
.onRunMessageLoopOnPause = onRunMessageLoopOnPause,
.onQuitMessageLoopOnPause = onQuitMessageLoopOnPause,
};
}
fn deinit(self: Channel) void {
v8.v8_inspector__Channel__IMPL__DELETE(self.handle);
}
fn setInspector(self: Channel, inspector: *anyopaque) void {
v8.v8_inspector__Channel__IMPL__SET_DATA(self.handle, inspector);
}
fn resp(self: Channel, call_id: u32, msg: []const u8) void {
self.onResp(self.ctx, call_id, msg);
}
fn notif(self: Channel, msg: []const u8) void {
self.onNotif(self.ctx, msg);
}
};
const Client = struct {
handle: *v8.InspectorClientImpl,
fn init() Client {
return .{ .handle = v8.v8_inspector__Client__IMPL__CREATE() };
}
fn deinit(self: Client) void {
v8.v8_inspector__Client__IMPL__DELETE(self.handle);
}
fn setInspector(self: Client, inspector: *anyopaque) void {
v8.v8_inspector__Client__IMPL__SET_DATA(self.handle, inspector);
}
};
const NoopInspector = struct {
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
pub fn onRunMessageLoopOnPause(_: *anyopaque, _: u32) void {}
pub fn onQuitMessageLoopOnPause(_: *anyopaque) void {}
};
fn fromData(data: *anyopaque) *Inspector {
return @ptrCast(@alignCast(data));
}
pub fn getTaggedAnyOpaque(value: *const v8.Value) ?*js.TaggedAnyOpaque {
if (!v8.v8__Value__IsObject(value)) {
return null;
}
const internal_field_count = v8.v8__Object__InternalFieldCount(value);
if (internal_field_count == 0) {
return null;
}
const external_value = v8.v8__Object__GetInternalField(value, 0).?;
const external_data = v8.v8__External__Value(external_value).?;
return @ptrCast(@alignCast(external_data));
}
fn cZigStringToString(s: v8.CZigString) ?[]const u8 {
if (s.ptr == null) return null;
return s.ptr[0..s.len];
}
// C export functions for Inspector callbacks
pub export fn v8_inspector__Client__IMPL__generateUniqueId(
_: *v8.InspectorClientImpl,
data: *anyopaque,
) callconv(.c) i64 {
const inspector: *Inspector = @ptrCast(@alignCast(data));
return inspector.rnd.random().int(i64);
}
pub export fn v8_inspector__Client__IMPL__runMessageLoopOnPause(
_: *v8.InspectorClientImpl,
data: *anyopaque,
ctx_group_id: c_int,
) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data));
inspector.channel.onRunMessageLoopOnPause(inspector.channel.ctx, @intCast(ctx_group_id));
}
pub export fn v8_inspector__Client__IMPL__quitMessageLoopOnPause(
_: *v8.InspectorClientImpl,
data: *anyopaque,
) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data));
inspector.channel.onQuitMessageLoopOnPause(inspector.channel.ctx);
}
pub export fn v8_inspector__Client__IMPL__runIfWaitingForDebugger(
_: *v8.InspectorClientImpl,
_: *anyopaque,
_: c_int,
) callconv(.c) void {
// TODO
}
pub export fn v8_inspector__Client__IMPL__consoleAPIMessage(
_: *v8.InspectorClientImpl,
_: *anyopaque,
_: c_int,
_: v8.MessageErrorLevel,
_: *v8.StringView,
_: *v8.StringView,
_: c_uint,
_: c_uint,
_: *v8.StackTrace,
) callconv(.c) void {}
pub export fn v8_inspector__Client__IMPL__ensureDefaultContextInGroup(
_: *v8.InspectorClientImpl,
data: *anyopaque,
) callconv(.c) ?*const v8.Context {
const inspector: *Inspector = @ptrCast(@alignCast(data));
return inspector.default_context;
}
pub export fn v8_inspector__Channel__IMPL__sendResponse(
_: *v8.InspectorChannelImpl,
data: *anyopaque,
call_id: c_int,
msg: [*c]u8,
length: usize,
) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data));
inspector.channel.resp(@as(u32, @intCast(call_id)), msg[0..length]);
}
pub export fn v8_inspector__Channel__IMPL__sendNotification(
_: *v8.InspectorChannelImpl,
data: *anyopaque,
msg: [*c]u8,
length: usize,
) callconv(.c) void {
const inspector: *Inspector = @ptrCast(@alignCast(data));
inspector.channel.notif(msg[0..length]);
}
pub export fn v8_inspector__Channel__IMPL__flushProtocolNotifications(
_: *v8.InspectorChannelImpl,
_: *anyopaque,
) callconv(.c) void {
// TODO
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const Integer = @This();
handle: *const v8.Integer,
pub fn init(isolate: *v8.Isolate, value: anytype) Integer {
const handle = switch (@TypeOf(value)) {
i8, i16, i32 => v8.v8__Integer__New(isolate, value).?,
u8, u16, u32 => v8.v8__Integer__NewFromUnsigned(isolate, value).?,
else => |T| @compileError("cannot create v8::Integer from: " ++ @typeName(T)),
};
return .{ .handle = handle };
}

118
src/browser/js/Isolate.zig Normal file
View File

@@ -0,0 +1,118 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const Isolate = @This();
handle: *v8.Isolate,
pub fn init(params: *v8.CreateParams) Isolate {
return .{
.handle = v8.v8__Isolate__New(params).?,
};
}
pub fn deinit(self: Isolate) void {
v8.v8__Isolate__Dispose(self.handle);
}
pub fn enter(self: Isolate) void {
v8.v8__Isolate__Enter(self.handle);
}
pub fn exit(self: Isolate) void {
v8.v8__Isolate__Exit(self.handle);
}
pub fn performMicrotasksCheckpoint(self: Isolate) void {
v8.v8__Isolate__PerformMicrotaskCheckpoint(self.handle);
}
pub fn enqueueMicrotask(self: Isolate, callback: anytype, data: anytype) void {
v8.v8__Isolate__EnqueueMicrotask(self.handle, callback, data);
}
pub fn enqueueMicrotaskFunc(self: Isolate, function: js.Function) void {
v8.v8__Isolate__EnqueueMicrotaskFunc(self.handle, function.handle);
}
pub fn lowMemoryNotification(self: Isolate) void {
v8.v8__Isolate__LowMemoryNotification(self.handle);
}
pub fn notifyContextDisposed(self: Isolate) void {
_ = v8.v8__Isolate__ContextDisposedNotification(self.handle);
}
pub fn getHeapStatistics(self: Isolate) v8.HeapStatistics {
var res: v8.HeapStatistics = undefined;
v8.v8__Isolate__GetHeapStatistics(self.handle, &res);
return res;
}
pub fn throwException(self: Isolate, value: *const v8.Value) *const v8.Value {
return v8.v8__Isolate__ThrowException(self.handle, value).?;
}
pub fn initStringHandle(self: Isolate, str: []const u8) *const v8.String {
return v8.v8__String__NewFromUtf8(self.handle, str.ptr, v8.kNormal, @as(c_int, @intCast(str.len))).?;
}
pub fn createError(self: Isolate, msg: []const u8) *const v8.Value {
const message = self.initStringHandle(msg);
return v8.v8__Exception__Error(message).?;
}
pub fn createTypeError(self: Isolate, msg: []const u8) *const v8.Value {
const message = self.initStringHandle(msg);
return v8.v8__Exception__TypeError(message).?;
}
pub fn initNull(self: Isolate) *const v8.Value {
return v8.v8__Null(self.handle).?;
}
pub fn initUndefined(self: Isolate) *const v8.Value {
return v8.v8__Undefined(self.handle).?;
}
pub fn initFalse(self: Isolate) *const v8.Value {
return v8.v8__False(self.handle).?;
}
pub fn initTrue(self: Isolate) *const v8.Value {
return v8.v8__True(self.handle).?;
}
pub fn initInteger(self: Isolate, val: anytype) js.Integer {
return js.Integer.init(self.handle, val);
}
pub fn initBigInt(self: Isolate, val: anytype) js.BigInt {
return js.BigInt.init(self.handle, val);
}
pub fn initNumber(self: Isolate, val: anytype) js.Number {
return js.Number.init(self.handle, val);
}
pub fn createExternal(self: Isolate, val: *anyopaque) *const v8.External {
return v8.v8__External__New(self.handle, val).?;
}

142
src/browser/js/Module.zig Normal file
View File

@@ -0,0 +1,142 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const Module = @This();
ctx: *js.Context,
handle: *const v8.Module,
pub const Status = enum(u32) {
kUninstantiated = v8.kUninstantiated,
kInstantiating = v8.kInstantiating,
kInstantiated = v8.kInstantiated,
kEvaluating = v8.kEvaluating,
kEvaluated = v8.kEvaluated,
kErrored = v8.kErrored,
};
pub fn getStatus(self: Module) Status {
return @enumFromInt(v8.v8__Module__GetStatus(self.handle));
}
pub fn getException(self: Module) js.Value {
return .{
.ctx = self.ctx,
.handle = v8.v8__Module__GetException(self.handle).?,
};
}
pub fn getModuleRequests(self: Module) Requests {
return .{
.ctx = self.ctx.handle,
.handle = v8.v8__Module__GetModuleRequests(self.handle).?,
};
}
pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
var out: v8.MaybeBool = undefined;
v8.v8__Module__InstantiateModule(self.handle, self.ctx.handle, cb, &out);
if (out.has_value) {
return out.value;
}
return error.JsException;
}
pub fn evaluate(self: Module) !js.Value {
const ctx = self.ctx;
const res = v8.v8__Module__Evaluate(self.handle, ctx.handle) orelse return error.JsException;
if (self.getStatus() == .kErrored) {
return error.JsException;
}
return .{
.ctx = ctx,
.handle = res,
};
}
pub fn getIdentityHash(self: Module) u32 {
return @bitCast(v8.v8__Module__GetIdentityHash(self.handle));
}
pub fn getModuleNamespace(self: Module) js.Value {
return .{
.ctx = self.ctx,
.handle = v8.v8__Module__GetModuleNamespace(self.handle).?,
};
}
pub fn getScriptId(self: Module) u32 {
return @intCast(v8.v8__Module__ScriptId(self.handle));
}
pub fn persist(self: Module) !Global {
var ctx = self.ctx;
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_modules.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
};
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) Module {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: Module) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
};
const Requests = struct {
ctx: *const v8.Context,
handle: *const v8.FixedArray,
pub fn len(self: Requests) usize {
return @intCast(v8.v8__FixedArray__Length(self.handle));
}
pub fn get(self: Requests, idx: usize) Request {
return .{ .handle = v8.v8__FixedArray__Get(self.handle, self.ctx, @intCast(idx)).? };
}
};
const Request = struct {
handle: *const v8.ModuleRequest,
pub fn specifier(self: Request) *const v8.String {
return v8.v8__ModuleRequest__GetSpecifier(self.handle).?;
}
};

24
src/browser/js/Name.zig Normal file
View File

@@ -0,0 +1,24 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const Name = @This();
handle: *const v8.Name,

31
src/browser/js/Number.zig Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const Number = @This();
handle: *const v8.Number,
pub fn init(isolate: *v8.Isolate, value: anytype) Number {
const handle = v8.v8__Number__New(isolate, value).?;
return .{ .handle = handle };
}

218
src/browser/js/Object.zig Normal file
View File

@@ -0,0 +1,218 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Context = @import("Context.zig");
const Allocator = std.mem.Allocator;
const Object = @This();
ctx: *js.Context,
handle: *const v8.Object,
pub fn getId(self: Object) u32 {
return @bitCast(v8.v8__Object__GetIdentityHash(self.handle));
}
pub fn has(self: Object, key: anytype) bool {
const ctx = self.ctx;
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
var out: v8.MaybeBool = undefined;
v8.v8__Object__Has(self.handle, self.ctx.handle, key_handle, &out);
if (out.has_value) {
return out.value;
}
return false;
}
pub fn get(self: Object, key: anytype) !js.Value {
const ctx = self.ctx;
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, key_handle) orelse return error.JsException;
return .{
.ctx = ctx,
.handle = js_val_handle,
};
}
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
const ctx = self.ctx;
const js_value = try ctx.zigValueToJs(value, opts);
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
var out: v8.MaybeBool = undefined;
v8.v8__Object__Set(self.handle, ctx.handle, key_handle, js_value.handle, &out);
return out.has_value;
}
pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr: v8.PropertyAttribute) ?bool {
const ctx = self.ctx;
const name_handle = ctx.isolate.initStringHandle(name);
var out: v8.MaybeBool = undefined;
v8.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(name_handle), value.handle, attr, &out);
if (out.has_value) {
return out.value;
} else {
return null;
}
}
pub fn toString(self: Object) ![]const u8 {
return self.ctx.valueToString(self.toValue(), .{});
}
pub fn toValue(self: Object) js.Value {
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn format(self: Object, writer: *std.Io.Writer) !void {
if (comptime IS_DEBUG) {
return self.ctx.debugValue(self.toValue(), writer);
}
const str = self.toString() catch return error.WriteFailed;
return writer.writeAll(str);
}
pub fn persist(self: Object) !Global {
var ctx = self.ctx;
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_objects.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
};
}
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
if (self.isNullOrUndefined()) {
return null;
}
const ctx = self.ctx;
const js_name = ctx.isolate.initStringHandle(name);
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, js_name) orelse return error.JsException;
if (v8.v8__Value__IsFunction(js_val_handle) == false) {
return null;
}
return .{
.ctx = ctx,
.handle = @ptrCast(js_val_handle),
};
}
pub fn callMethod(self: Object, comptime T: type, method_name: []const u8, args: anytype) !T {
const func = try self.getFunction(method_name) orelse return error.MethodNotFound;
return func.callWithThis(T, self, args);
}
pub fn isNullOrUndefined(self: Object) bool {
return v8.v8__Value__IsNullOrUndefined(@ptrCast(self.handle));
}
pub fn getOwnPropertyNames(self: Object) js.Array {
const handle = v8.v8__Object__GetOwnPropertyNames(self.handle, self.ctx.handle).?;
return .{
.ctx = self.ctx,
.handle = handle,
};
}
pub fn getPropertyNames(self: Object) js.Array {
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.ctx.handle).?;
return .{
.ctx = self.ctx,
.handle = handle,
};
}
pub fn nameIterator(self: Object) NameIterator {
const ctx = self.ctx;
const handle = v8.v8__Object__GetPropertyNames(self.handle, ctx.handle).?;
const count = v8.v8__Array__Length(handle);
return .{
.ctx = ctx,
.handle = handle,
.count = count,
};
}
pub fn toZig(self: Object, comptime T: type) !T {
const js_value = js.Value{ .ctx = self.ctx, .handle = @ptrCast(self.handle) };
return self.ctx.jsValueToZig(T, js_value);
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) Object {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: Object) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
};
pub const NameIterator = struct {
count: u32,
idx: u32 = 0,
ctx: *Context,
handle: *const v8.Array,
pub fn next(self: *NameIterator) !?[]const u8 {
const idx = self.idx;
if (idx == self.count) {
return null;
}
self.idx += 1;
const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.ctx.handle, idx) orelse return error.JsException;
const js_val = js.Value{ .ctx = self.ctx, .handle = js_val_handle };
return try self.ctx.valueToString(js_val, .{});
}
};

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const Platform = @This();
handle: *v8.Platform,
pub fn init() !Platform {
if (v8.v8__V8__InitializeICU() == false) {
return error.FailedToInitializeICU;
}
// 0 - threadpool size, 0 == let v8 decide
// 1 - idle_task_support, 1 == enabled
const handle = v8.v8__Platform__NewDefaultPlatform(0, 1).?;
v8.v8__V8__InitializePlatform(handle);
v8.v8__V8__Initialize();
return .{ .handle = handle };
}
pub fn deinit(self: Platform) void {
_ = v8.v8__V8__Dispose();
v8.v8__V8__DisposePlatform();
v8.v8__Platform__DELETE(self.handle);
}

View File

@@ -0,0 +1,83 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const Promise = @This();
ctx: *js.Context,
handle: *const v8.Promise,
pub fn toObject(self: Promise) js.Object {
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn toValue(self: Promise) js.Value {
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn thenAndCatch(self: Promise, on_fulfilled: js.Function, on_rejected: js.Function) !Promise {
if (v8.v8__Promise__Then2(self.handle, self.ctx.handle, on_fulfilled.handle, on_rejected.handle)) |handle| {
return .{
.ctx = self.ctx,
.handle = handle,
};
}
return error.PromiseChainFailed;
}
pub fn persist(self: Promise) !Global {
var ctx = self.ctx;
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_promises.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
};
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) Promise {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: Promise) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
pub fn promise(self: *const Global) Promise {
return self.local();
}
};

View File

@@ -0,0 +1,107 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const log = @import("../../log.zig");
const PromiseResolver = @This();
ctx: *js.Context,
handle: *const v8.PromiseResolver,
pub fn init(ctx: *js.Context) PromiseResolver {
return .{
.ctx = ctx,
.handle = v8.v8__Promise__Resolver__New(ctx.handle).?,
};
}
pub fn promise(self: PromiseResolver) js.Promise {
return .{
.ctx = self.ctx,
.handle = v8.v8__Promise__Resolver__GetPromise(self.handle).?,
};
}
pub fn resolve(self: PromiseResolver, comptime source: []const u8, value: anytype) void {
self._resolve(value) catch |err| {
log.err(.bug, "resolve", .{ .source = source, .err = err, .persistent = false });
};
}
fn _resolve(self: PromiseResolver, value: anytype) !void {
const ctx: *js.Context = @constCast(self.ctx);
const js_value = try ctx.zigValueToJs(value, .{});
var out: v8.MaybeBool = undefined;
v8.v8__Promise__Resolver__Resolve(self.handle, self.ctx.handle, js_value.handle, &out);
if (!out.has_value or !out.value) {
return error.FailedToResolvePromise;
}
ctx.runMicrotasks();
}
pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void {
self._reject(value) catch |err| {
log.err(.bug, "reject", .{ .source = source, .err = err, .persistent = false });
};
}
fn _reject(self: PromiseResolver, value: anytype) !void {
const ctx = self.ctx;
const js_value = try ctx.zigValueToJs(value, .{});
var out: v8.MaybeBool = undefined;
v8.v8__Promise__Resolver__Reject(self.handle, ctx.handle, js_value.handle, &out);
if (!out.has_value or !out.value) {
return error.FailedToRejectPromise;
}
ctx.runMicrotasks();
}
pub fn persist(self: PromiseResolver) !Global {
var ctx = self.ctx;
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_promise_resolvers.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
};
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) PromiseResolver {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: PromiseResolver) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
};

536
src/browser/js/Snapshot.zig Normal file
View File

@@ -0,0 +1,536 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const bridge = @import("bridge.zig");
const log = @import("../../log.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
const Window = @import("../webapi/Window.zig");
const v8 = js.v8;
const JsApis = bridge.JsApis;
const Allocator = std.mem.Allocator;
const Snapshot = @This();
const embedded_snapshot_blob = if (@import("build_config").snapshot_path) |path| @embedFile(path) else "";
// When creating our Snapshot, we use local function templates for every Zig type.
// You cannot, from what I can tell, create persisted FunctionTemplates at
// snapshot creation time. But you can embedd those templates (or any other v8
// Data) so that it's available to contexts created from the snapshot. This is
// the starting index of those function templates, which we can extract. At
// creation time, in debug, we assert that this is actually a consecutive integer
// sequence
data_start: usize,
// The snapshot data (v8.StartupData is a ptr to the data and len).
startup_data: v8.StartupData,
// V8 doesn't know how to serialize external references, and pretty much any hook
// into Zig is an external reference (e.g. every accessor and function callback).
// When we create the snapshot, we give it an array with the address of every
// external reference. When we load the snapshot, we need to give it the same
// array with the exact same number of entries in the same order (but, of course
// cross-process, the value (address) might be different).
external_references: [countExternalReferences()]isize,
// Track whether this snapshot owns its data (was created in-process)
// If false, the data points into embedded_snapshot_blob and will not be freed
owns_data: bool = false,
pub fn load() !Snapshot {
if (loadEmbedded()) |snapshot| {
return snapshot;
}
return create();
}
fn loadEmbedded() ?Snapshot {
// Binary format: [data_start: usize][blob data]
const min_size = @sizeOf(usize) + 1000;
if (embedded_snapshot_blob.len < min_size) {
// our blob should be in the MB, this is just a quick sanity check
return null;
}
const data_start = std.mem.readInt(usize, embedded_snapshot_blob[0..@sizeOf(usize)], .little);
const blob = embedded_snapshot_blob[@sizeOf(usize)..];
const startup_data = v8.StartupData{ .data = blob.ptr, .raw_size = @intCast(blob.len) };
if (!v8.v8__StartupData__IsValid(startup_data)) {
return null;
}
return .{
.owns_data = false,
.data_start = data_start,
.startup_data = startup_data,
.external_references = collectExternalReferences(),
};
}
pub fn deinit(self: Snapshot) void {
// Only free if we own the data (was created in-process)
if (self.owns_data) {
// V8 allocated this with `new char[]`, so we need to use the C++ delete[] operator
v8.v8__StartupData__DELETE(self.startup_data.data);
}
}
pub fn write(self: Snapshot, writer: *std.Io.Writer) !void {
if (!self.isValid()) {
return error.InvalidSnapshot;
}
try writer.writeInt(usize, self.data_start, .little);
try writer.writeAll(self.startup_data.data[0..@intCast(self.startup_data.raw_size)]);
}
pub fn fromEmbedded(self: Snapshot) bool {
// if the snapshot comes from the embedFile, then it'll be flagged as not
// owning (aka, not needing to free) the data.
return self.owns_data == false;
}
fn isValid(self: Snapshot) bool {
return v8.v8__StartupData__IsValid(self.startup_data);
}
pub fn createGlobalTemplate(isolate: *v8.Isolate, templates: anytype) *const v8.ObjectTemplate {
// Set up the global template to inherit from Window's template
// This way the global object gets all Window properties through inheritance
const js_global = v8.v8__FunctionTemplate__New__DEFAULT(isolate);
const window_name = v8.v8__String__NewFromUtf8(isolate, "Window", v8.kNormal, 6);
v8.v8__FunctionTemplate__SetClassName(js_global, window_name);
// Find Window in JsApis by name (avoids circular import)
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
v8.v8__FunctionTemplate__Inherit(js_global, templates[window_index]);
return v8.v8__FunctionTemplate__InstanceTemplate(js_global).?;
}
pub fn create() !Snapshot {
var external_references = collectExternalReferences();
var params: v8.CreateParams = undefined;
v8.v8__Isolate__CreateParams__CONSTRUCT(&params);
params.array_buffer_allocator = v8.v8__ArrayBuffer__Allocator__NewDefaultAllocator();
defer v8.v8__ArrayBuffer__Allocator__DELETE(params.array_buffer_allocator.?);
params.external_references = @ptrCast(&external_references);
const snapshot_creator = v8.v8__SnapshotCreator__CREATE(&params);
defer v8.v8__SnapshotCreator__DESTRUCT(snapshot_creator);
var data_start: usize = 0;
const isolate = v8.v8__SnapshotCreator__getIsolate(snapshot_creator).?;
{
// CreateBlob, which we'll call once everything is setup, MUST NOT
// be called from an active HandleScope. Hence we have this scope to
// clean it up before we call CreateBlob
var handle_scope: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&handle_scope, isolate);
defer v8.v8__HandleScope__DESTRUCT(&handle_scope);
// Create templates (constructors only) FIRST
var templates: [JsApis.len]*v8.FunctionTemplate = undefined;
inline for (JsApis, 0..) |JsApi, i| {
@setEvalBranchQuota(10_000);
templates[i] = generateConstructor(JsApi, isolate);
attachClass(JsApi, isolate, templates[i]);
}
// Set up prototype chains BEFORE attaching properties
// This must come before attachClass so inheritance is set up first
inline for (JsApis, 0..) |JsApi, i| {
if (comptime protoIndexLookup(JsApi)) |proto_index| {
v8.v8__FunctionTemplate__Inherit(templates[i], templates[proto_index]);
}
}
// Set up the global template to inherit from Window's template
// This way the global object gets all Window properties through inheritance
const global_template = createGlobalTemplate(isolate, templates[0..]);
const context = v8.v8__Context__New(isolate, global_template, null);
v8.v8__Context__Enter(context);
defer v8.v8__Context__Exit(context);
// Add templates to context snapshot
var last_data_index: usize = 0;
inline for (JsApis, 0..) |_, i| {
@setEvalBranchQuota(10_000);
const data_index = v8.v8__SnapshotCreator__AddData(snapshot_creator, @ptrCast(templates[i]));
if (i == 0) {
data_start = data_index;
last_data_index = data_index;
} else {
// This isn't strictly required, but it means we only need to keep
// the first data_index. This is based on the assumption that
// addDataWithContext always increases by 1. If we ever hit this
// error, then that assumption is wrong and we should capture
// all the indexes explicitly in an array.
if (data_index != last_data_index + 1) {
return error.InvalidDataIndex;
}
last_data_index = data_index;
}
}
// Realize all templates by getting their functions and attaching to global
const global_obj = v8.v8__Context__Global(context);
inline for (JsApis, 0..) |JsApi, i| {
const func = v8.v8__FunctionTemplate__GetFunction(templates[i], context);
// Attach to global if it has a name
if (@hasDecl(JsApi.Meta, "name")) {
if (@hasDecl(JsApi.Meta, "constructor_alias")) {
const alias = JsApi.Meta.constructor_alias;
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, alias.ptr, v8.kNormal, @intCast(alias.len));
var maybe_result: v8.MaybeBool = undefined;
v8.v8__Object__Set(global_obj, context, v8_class_name, func, &maybe_result);
// @TODO: This is wrong. This name should be registered with the
// illegalConstructorCallback. I.e. new Image() is OK, but
// new HTMLImageElement() isn't.
// But we _have_ to register the name, i.e. HTMLImageElement
// has to be registered so, for now, instead of creating another
// template, we just hook it into the constructor.
const name = JsApi.Meta.name;
const illegal_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
var maybe_result2: v8.MaybeBool = undefined;
v8.v8__Object__Set(global_obj, context, illegal_class_name, func, &maybe_result2);
} else {
const name = JsApi.Meta.name;
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
var maybe_result: v8.MaybeBool = undefined;
v8.v8__Object__Set(global_obj, context, v8_class_name, func, &maybe_result);
}
}
}
{
// If we want to overwrite the built-in console, we have to
// delete the built-in one.
const console_key = v8.v8__String__NewFromUtf8(isolate, "console", v8.kNormal, 7);
var maybe_deleted: v8.MaybeBool = undefined;
v8.v8__Object__Delete(global_obj, context, console_key, &maybe_deleted);
if (maybe_deleted.value == false) {
return error.ConsoleDeleteError;
}
}
// This shouldn't be necessary, but it is:
// https://groups.google.com/g/v8-users/c/qAQQBmbi--8
// TODO: see if newer V8 engines have a way around this.
inline for (JsApis, 0..) |JsApi, i| {
if (comptime protoIndexLookup(JsApi)) |proto_index| {
const proto_func = v8.v8__FunctionTemplate__GetFunction(templates[proto_index], context);
const proto_obj: *const v8.Object = @ptrCast(proto_func);
const self_func = v8.v8__FunctionTemplate__GetFunction(templates[i], context);
const self_obj: *const v8.Object = @ptrCast(self_func);
var maybe_result: v8.MaybeBool = undefined;
v8.v8__Object__SetPrototype(self_obj, context, proto_obj, &maybe_result);
}
}
{
// Custom exception
// TODO: this is an horrible hack, I can't figure out how to do this cleanly.
const code_str = "DOMException.prototype.__proto__ = Error.prototype";
const code = v8.v8__String__NewFromUtf8(isolate, code_str.ptr, v8.kNormal, @intCast(code_str.len));
const script = v8.v8__Script__Compile(context, code, null) orelse return error.ScriptCompileFailed;
_ = v8.v8__Script__Run(script, context) orelse return error.ScriptRunFailed;
}
v8.v8__SnapshotCreator__setDefaultContext(snapshot_creator, context);
}
const blob = v8.v8__SnapshotCreator__createBlob(snapshot_creator, v8.kKeep);
return .{
.owns_data = true,
.data_start = data_start,
.external_references = external_references,
.startup_data = blob,
};
}
// Count total callbacks needed for external_references array
fn countExternalReferences() comptime_int {
@setEvalBranchQuota(100_000);
// +1 for the illegal constructor callback
var count: comptime_int = 1;
inline for (JsApis) |JsApi| {
// Constructor (only if explicit)
if (@hasDecl(JsApi, "constructor")) {
count += 1;
}
// Callable (htmldda)
if (@hasDecl(JsApi, "callable")) {
count += 1;
}
// All other callbacks
const declarations = @typeInfo(JsApi).@"struct".decls;
inline for (declarations) |d| {
const value = @field(JsApi, d.name);
const T = @TypeOf(value);
if (T == bridge.Accessor) {
count += 1; // getter
if (value.setter != null) count += 1; // setter
} else if (T == bridge.Function) {
count += 1;
} else if (T == bridge.Iterator) {
count += 1;
} else if (T == bridge.Indexed) {
count += 1;
} else if (T == bridge.NamedIndexed) {
count += 1; // getter
if (value.setter != null) count += 1;
if (value.deleter != null) count += 1;
}
}
}
return count + 1; // +1 for null terminator
}
fn collectExternalReferences() [countExternalReferences()]isize {
var idx: usize = 0;
var references = std.mem.zeroes([countExternalReferences()]isize);
references[idx] = @bitCast(@intFromPtr(&illegalConstructorCallback));
idx += 1;
inline for (JsApis) |JsApi| {
if (@hasDecl(JsApi, "constructor")) {
references[idx] = @bitCast(@intFromPtr(JsApi.constructor.func));
idx += 1;
}
if (@hasDecl(JsApi, "callable")) {
references[idx] = @bitCast(@intFromPtr(JsApi.callable.func));
idx += 1;
}
const declarations = @typeInfo(JsApi).@"struct".decls;
inline for (declarations) |d| {
const value = @field(JsApi, d.name);
const T = @TypeOf(value);
if (T == bridge.Accessor) {
references[idx] = @bitCast(@intFromPtr(value.getter));
idx += 1;
if (value.setter) |setter| {
references[idx] = @bitCast(@intFromPtr(setter));
idx += 1;
}
} else if (T == bridge.Function) {
references[idx] = @bitCast(@intFromPtr(value.func));
idx += 1;
} else if (T == bridge.Iterator) {
references[idx] = @bitCast(@intFromPtr(value.func));
idx += 1;
} else if (T == bridge.Indexed) {
references[idx] = @bitCast(@intFromPtr(value.getter));
idx += 1;
} else if (T == bridge.NamedIndexed) {
references[idx] = @bitCast(@intFromPtr(value.getter));
idx += 1;
if (value.setter) |setter| {
references[idx] = @bitCast(@intFromPtr(setter));
idx += 1;
}
if (value.deleter) |deleter| {
references[idx] = @bitCast(@intFromPtr(deleter));
idx += 1;
}
}
}
}
return references;
}
// Even if a struct doesn't have a `constructor` function, we still
// `generateConstructor`, because this is how we create our
// FunctionTemplate. Such classes exist, but they can't be instantiated
// via `new ClassName()` - but they could, for example, be created in
// Zig and returned from a function call, which is why we need the
// FunctionTemplate.
fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionTemplate {
const callback = blk: {
if (@hasDecl(JsApi, "constructor")) {
break :blk JsApi.constructor.func;
}
// Use shared illegal constructor callback
break :blk illegalConstructorCallback;
};
const template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?);
if (!@hasDecl(JsApi.Meta, "empty_with_no_proto")) {
const instance_template = v8.v8__FunctionTemplate__InstanceTemplate(template);
v8.v8__ObjectTemplate__SetInternalFieldCount(instance_template, 1);
}
const name_str = if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi);
const class_name = v8.v8__String__NewFromUtf8(isolate, name_str.ptr, v8.kNormal, @intCast(name_str.len));
v8.v8__FunctionTemplate__SetClassName(template, class_name);
return template;
}
// Attaches JsApi members to the prototype template (normal case)
fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.FunctionTemplate) void {
const target = v8.v8__FunctionTemplate__PrototypeTemplate(template);
const instance = v8.v8__FunctionTemplate__InstanceTemplate(template);
const declarations = @typeInfo(JsApi).@"struct".decls;
inline for (declarations) |d| {
const name: [:0]const u8 = d.name;
const value = @field(JsApi, name);
const definition = @TypeOf(value);
switch (definition) {
bridge.Accessor => {
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
const getter_callback = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, value.getter).?);
if (value.setter == null) {
if (value.static) {
v8.v8__Template__SetAccessorProperty__DEFAULT(@ptrCast(template), js_name, getter_callback);
} else {
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(target, js_name, getter_callback);
}
} else {
if (comptime IS_DEBUG) {
std.debug.assert(value.static == false);
}
const setter_callback = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, value.setter.?).?);
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT2(target, js_name, getter_callback, setter_callback);
}
},
bridge.Function => {
const function_template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, value.func).?);
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
if (value.static) {
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
} else {
v8.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.None);
}
},
bridge.Indexed => {
var configuration: v8.IndexedPropertyHandlerConfiguration = .{
.getter = value.getter,
.setter = null,
.query = null,
.deleter = null,
.enumerator = null,
.definer = null,
.descriptor = null,
.data = null,
.flags = 0,
};
v8.v8__ObjectTemplate__SetIndexedHandler(instance, &configuration);
},
bridge.NamedIndexed => {
var configuration: v8.NamedPropertyHandlerConfiguration = .{
.getter = value.getter,
.setter = value.setter,
.query = null,
.deleter = value.deleter,
.enumerator = null,
.definer = null,
.descriptor = null,
.data = null,
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
};
v8.v8__ObjectTemplate__SetNamedHandler(instance, &configuration);
},
bridge.Iterator => {
const function_template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, value.func).?);
const js_name = if (value.async)
v8.v8__Symbol__GetAsyncIterator(isolate)
else
v8.v8__Symbol__GetIterator(isolate);
v8.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.None);
},
bridge.Property => {
// simpleZigValueToJs now returns raw handle directly
const js_value = switch (value) {
.int => |v| js.simpleZigValueToJs(.{ .handle = isolate }, v, true, false),
};
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
// apply it both to the type itself
v8.v8__Template__Set(@ptrCast(template), js_name, js_value, v8.ReadOnly + v8.DontDelete);
// and to instances of the type
v8.v8__Template__Set(@ptrCast(target), js_name, js_value, v8.ReadOnly + v8.DontDelete);
},
bridge.Constructor => {}, // already handled in generateConstructor
else => {},
}
}
if (@hasDecl(JsApi.Meta, "htmldda")) {
v8.v8__ObjectTemplate__MarkAsUndetectable(instance);
v8.v8__ObjectTemplate__SetCallAsFunctionHandler(instance, JsApi.Meta.callable.func);
}
if (@hasDecl(JsApi.Meta, "name")) {
const js_name = v8.v8__Symbol__GetToStringTag(isolate);
const js_value = v8.v8__String__NewFromUtf8(isolate, JsApi.Meta.name.ptr, v8.kNormal, @intCast(JsApi.Meta.name.len));
v8.v8__Template__Set(@ptrCast(instance), js_name, js_value, v8.ReadOnly + v8.DontDelete);
}
}
fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt {
@setEvalBranchQuota(2000);
comptime {
const T = JsApi.bridge.type;
if (!@hasField(T, "_proto")) {
return null;
}
const Ptr = std.meta.fieldInfo(T, ._proto).type;
const F = @typeInfo(Ptr).pointer.child;
return bridge.JsApiLookup.getId(F.JsApi);
}
}
// Shared illegal constructor callback for types without explicit constructors
fn illegalConstructorCallback(raw_info: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(raw_info);
log.warn(.js, "Illegal constructor call", .{});
const message = v8.v8__String__NewFromUtf8(isolate, "Illegal Constructor", v8.kNormal, 19);
const js_exception = v8.v8__Exception__TypeError(message);
_ = v8.v8__Isolate__ThrowException(isolate, js_exception);
var return_value: v8.ReturnValue = undefined;
v8.v8__FunctionCallbackInfo__GetReturnValue(raw_info, &return_value);
v8.v8__ReturnValue__Set(return_value, js_exception);
}

53
src/browser/js/String.zig Normal file
View File

@@ -0,0 +1,53 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const Allocator = std.mem.Allocator;
const v8 = js.v8;
const String = @This();
ctx: *js.Context,
handle: *const v8.String,
pub const ToZigOpts = struct {
allocator: ?Allocator = null,
};
pub fn toZig(self: String, opts: ToZigOpts) ![]u8 {
return self._toZig(false, opts);
}
pub fn toZigZ(self: String, opts: ToZigOpts) ![:0]u8 {
return self._toZig(true, opts);
}
fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
const isolate = self.ctx.isolate.handle;
const allocator = opts.allocator orelse self.ctx.call_arena;
const len: u32 = @intCast(v8.v8__String__Utf8Length(self.handle, isolate));
const buf = if (null_terminate) try allocator.allocSentinel(u8, len, 0) else try allocator.alloc(u8, len);
const options = v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8;
const n = v8.v8__String__WriteUtf8(self.handle, isolate, buf.ptr, buf.len, options);
std.debug.assert(n == len);
return buf;
}

107
src/browser/js/TryCatch.zig Normal file
View File

@@ -0,0 +1,107 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const Allocator = std.mem.Allocator;
const TryCatch = @This();
ctx: *js.Context,
handle: v8.TryCatch,
pub fn init(self: *TryCatch, ctx: *js.Context) void {
self.ctx = ctx;
v8.v8__TryCatch__CONSTRUCT(&self.handle, ctx.isolate.handle);
}
pub fn hasCaught(self: TryCatch) bool {
return v8.v8__TryCatch__HasCaught(&self.handle);
}
pub fn caught(self: TryCatch, allocator: Allocator) ?Caught {
if (!self.hasCaught()) {
return null;
}
const ctx = self.ctx;
var hs: js.HandleScope = undefined;
hs.init(ctx.isolate);
defer hs.deinit();
const line: ?u32 = blk: {
const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null;
const l = v8.v8__Message__GetLineNumber(handle, ctx.handle);
break :blk if (l < 0) null else @intCast(l);
};
const exception: ?[]const u8 = blk: {
const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null;
break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err);
};
const stack: ?[]const u8 = blk: {
const handle = v8.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse break :blk null;
break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err);
};
return .{
.line = line,
.stack = stack,
.caught = true,
.exception = exception,
};
}
pub fn caughtOrError(self: TryCatch, allocator: Allocator, err: anyerror) Caught {
return self.caught(allocator) orelse .{
.caught = false,
.line = null,
.stack = null,
.exception = @errorName(err),
};
}
pub fn deinit(self: *TryCatch) void {
v8.v8__TryCatch__DESTRUCT(&self.handle);
}
pub const Caught = struct {
line: ?u32,
caught: bool,
stack: ?[]const u8,
exception: ?[]const u8,
pub fn format(self: Caught, writer: *std.Io.Writer) !void {
const separator = @import("../../log.zig").separator();
try writer.print("{s}exception: {?s}", .{ separator, self.exception });
try writer.print("{s}stack: {?s}", .{ separator, self.stack });
try writer.print("{s}line: {?d}", .{ separator, self.line });
try writer.print("{s}caught: {any}", .{ separator, self.caught });
}
pub fn logFmt(self: Caught, comptime prefix: []const u8, writer: anytype) !void {
try writer.write(prefix ++ ".exception", self.exception orelse "???");
try writer.write(prefix ++ ".stack", self.stack orelse "na");
try writer.write(prefix ++ ".line", self.line);
try writer.write(prefix ++ ".caught", self.caught);
}
};

322
src/browser/js/Value.zig Normal file
View File

@@ -0,0 +1,322 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Allocator = std.mem.Allocator;
const Value = @This();
ctx: *js.Context,
handle: *const v8.Value,
pub fn isObject(self: Value) bool {
return v8.v8__Value__IsObject(self.handle);
}
pub fn isString(self: Value) bool {
return v8.v8__Value__IsString(self.handle);
}
pub fn isArray(self: Value) bool {
return v8.v8__Value__IsArray(self.handle);
}
pub fn isSymbol(self: Value) bool {
return v8.v8__Value__IsSymbol(self.handle);
}
pub fn isFunction(self: Value) bool {
return v8.v8__Value__IsFunction(self.handle);
}
pub fn isNull(self: Value) bool {
return v8.v8__Value__IsNull(self.handle);
}
pub fn isUndefined(self: Value) bool {
return v8.v8__Value__IsUndefined(self.handle);
}
pub fn isNullOrUndefined(self: Value) bool {
return v8.v8__Value__IsNullOrUndefined(self.handle);
}
pub fn isNumber(self: Value) bool {
return v8.v8__Value__IsNumber(self.handle);
}
pub fn isNumberObject(self: Value) bool {
return v8.v8__Value__IsNumberObject(self.handle);
}
pub fn isInt32(self: Value) bool {
return v8.v8__Value__IsInt32(self.handle);
}
pub fn isUint32(self: Value) bool {
return v8.v8__Value__IsUint32(self.handle);
}
pub fn isBigInt(self: Value) bool {
return v8.v8__Value__IsBigInt(self.handle);
}
pub fn isBigIntObject(self: Value) bool {
return v8.v8__Value__IsBigIntObject(self.handle);
}
pub fn isBoolean(self: Value) bool {
return v8.v8__Value__IsBoolean(self.handle);
}
pub fn isBooleanObject(self: Value) bool {
return v8.v8__Value__IsBooleanObject(self.handle);
}
pub fn isTrue(self: Value) bool {
return v8.v8__Value__IsTrue(self.handle);
}
pub fn isFalse(self: Value) bool {
return v8.v8__Value__IsFalse(self.handle);
}
pub fn isTypedArray(self: Value) bool {
return v8.v8__Value__IsTypedArray(self.handle);
}
pub fn isArrayBufferView(self: Value) bool {
return v8.v8__Value__IsArrayBufferView(self.handle);
}
pub fn isArrayBuffer(self: Value) bool {
return v8.v8__Value__IsArrayBuffer(self.handle);
}
pub fn isUint8Array(self: Value) bool {
return v8.v8__Value__IsUint8Array(self.handle);
}
pub fn isUint8ClampedArray(self: Value) bool {
return v8.v8__Value__IsUint8ClampedArray(self.handle);
}
pub fn isInt8Array(self: Value) bool {
return v8.v8__Value__IsInt8Array(self.handle);
}
pub fn isUint16Array(self: Value) bool {
return v8.v8__Value__IsUint16Array(self.handle);
}
pub fn isInt16Array(self: Value) bool {
return v8.v8__Value__IsInt16Array(self.handle);
}
pub fn isUint32Array(self: Value) bool {
return v8.v8__Value__IsUint32Array(self.handle);
}
pub fn isInt32Array(self: Value) bool {
return v8.v8__Value__IsInt32Array(self.handle);
}
pub fn isBigUint64Array(self: Value) bool {
return v8.v8__Value__IsBigUint64Array(self.handle);
}
pub fn isBigInt64Array(self: Value) bool {
return v8.v8__Value__IsBigInt64Array(self.handle);
}
pub fn isPromise(self: Value) bool {
return v8.v8__Value__IsPromise(self.handle);
}
pub fn toBool(self: Value) bool {
return v8.v8__Value__BooleanValue(self.handle, self.ctx.isolate.handle);
}
pub fn typeOf(self: Value) js.String {
const str_handle = v8.v8__Value__TypeOf(self.handle, self.ctx.isolate.handle).?;
return js.String{ .ctx = self.ctx, .handle = str_handle };
}
pub fn toF32(self: Value) !f32 {
return @floatCast(try self.toF64());
}
pub fn toF64(self: Value) !f64 {
var maybe: v8.MaybeF64 = undefined;
v8.v8__Value__NumberValue(self.handle, self.ctx.handle, &maybe);
if (!maybe.has_value) {
return error.JsException;
}
return maybe.value;
}
pub fn toI32(self: Value) !i32 {
var maybe: v8.MaybeI32 = undefined;
v8.v8__Value__Int32Value(self.handle, self.ctx.handle, &maybe);
if (!maybe.has_value) {
return error.JsException;
}
return maybe.value;
}
pub fn toU32(self: Value) !u32 {
var maybe: v8.MaybeU32 = undefined;
v8.v8__Value__Uint32Value(self.handle, self.ctx.handle, &maybe);
if (!maybe.has_value) {
return error.JsException;
}
return maybe.value;
}
pub fn toPromise(self: Value) js.Promise {
if (comptime IS_DEBUG) {
std.debug.assert(self.isPromise());
}
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn toString(self: Value, opts: js.String.ToZigOpts) ![]u8 {
return self._toString(false, opts);
}
pub fn toStringZ(self: Value, opts: js.String.ToZigOpts) ![:0]u8 {
return self._toString(true, opts);
}
pub fn toJson(self: Value, allocator: Allocator) ![]u8 {
const json_str_handle = v8.v8__JSON__Stringify(self.ctx.handle, self.handle, null) orelse return error.JsException;
return self.ctx.jsStringToZig(json_str_handle, .{ .allocator = allocator });
}
fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
const ctx = self.ctx;
if (self.isSymbol()) {
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?;
return _toString(.{ .handle = @ptrCast(sym_handle), .ctx = ctx }, null_terminate, opts);
}
const str_handle = v8.v8__Value__ToString(self.handle, ctx.handle) orelse {
return error.JsException;
};
const str = js.String{ .ctx = ctx, .handle = str_handle };
if (comptime null_terminate) {
return js.String.toZigZ(str, opts);
}
return js.String.toZig(str, opts);
}
pub fn fromJson(ctx: *js.Context, json: []const u8) !Value {
const v8_isolate = v8.Isolate{ .handle = ctx.isolate.handle };
const json_string = v8.String.initUtf8(v8_isolate, json);
const v8_context = v8.Context{ .handle = ctx.handle };
const value = try v8.Json.parse(v8_context, json_string);
return .{ .ctx = ctx, .handle = value.handle };
}
pub fn persist(self: Value) !Global {
var ctx = self.ctx;
var global: v8.Global = undefined;
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
try ctx.global_values.append(ctx.arena, global);
return .{
.handle = global,
.ctx = ctx,
};
}
pub fn toZig(self: Value, comptime T: type) !T {
return self.ctx.jsValueToZig(T, self);
}
pub fn toObject(self: Value) js.Object {
if (comptime IS_DEBUG) {
std.debug.assert(self.isObject());
}
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn toArray(self: Value) js.Array {
if (comptime IS_DEBUG) {
std.debug.assert(self.isArray());
}
return .{
.ctx = self.ctx,
.handle = @ptrCast(self.handle),
};
}
pub fn toBigInt(self: Value) js.BigInt {
if (comptime IS_DEBUG) {
std.debug.assert(self.isBigInt());
}
return .{
.handle = @ptrCast(self.handle),
};
}
pub fn format(self: Value, writer: *std.Io.Writer) !void {
if (comptime IS_DEBUG) {
return self.ctx.debugValue(self, writer);
}
const str = self.toString(.{}) catch return error.WriteFailed;
return writer.writeAll(str);
}
pub const Global = struct {
handle: v8.Global,
ctx: *js.Context,
pub fn deinit(self: *Global) void {
v8.v8__Global__Reset(&self.handle);
}
pub fn local(self: *const Global) Value {
return .{
.ctx = self.ctx,
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
};
}
pub fn isEqual(self: *const Global, other: Value) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
};

1275
src/browser/js/bridge.zig Normal file

File diff suppressed because it is too large Load Diff

48
src/browser/js/global.zig Normal file
View File

@@ -0,0 +1,48 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const v8 = js.v8;
pub fn Global(comptime T: type) type {
const H = @FieldType(T, "handle");
return struct {
global: v8.Global,
const Self = @This();
pub fn init(isolate: *v8.Isolate, handle: H) Self {
var global: v8.Global = undefined;
v8.v8__Global__New(isolate, handle, &global);
return .{
.global = global,
};
}
pub fn deinit(self: *Self) void {
v8.v8__Global__Reset(&self.global);
}
pub fn local(self: *const Self) H {
return @ptrCast(@alignCast(@as(*const anyopaque, @ptrFromInt(self.global.data_ptr))));
}
};
}

308
src/browser/js/js.zig Normal file
View File

@@ -0,0 +1,308 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
pub const v8 = @import("v8").c;
const log = @import("../../log.zig");
pub const Env = @import("Env.zig");
pub const bridge = @import("bridge.zig");
pub const ExecutionWorld = @import("ExecutionWorld.zig");
pub const Context = @import("Context.zig");
pub const Inspector = @import("Inspector.zig");
pub const Snapshot = @import("Snapshot.zig");
pub const Platform = @import("Platform.zig");
pub const Isolate = @import("Isolate.zig");
pub const HandleScope = @import("HandleScope.zig");
pub const Name = @import("Name.zig");
pub const Value = @import("Value.zig");
pub const Array = @import("Array.zig");
pub const String = @import("String.zig");
pub const Object = @import("Object.zig");
pub const TryCatch = @import("TryCatch.zig");
pub const Function = @import("Function.zig");
pub const Promise = @import("Promise.zig");
pub const Module = @import("Module.zig");
pub const BigInt = @import("BigInt.zig");
pub const Number = @import("Number.zig");
pub const Integer = @import("Integer.zig");
pub const Global = @import("global.zig").Global;
pub const PromiseResolver = @import("PromiseResolver.zig");
const Allocator = std.mem.Allocator;
pub fn Bridge(comptime T: type) type {
return bridge.Builder(T);
}
// If a function returns a []i32, should that map to a plain-old
// JavaScript array, or a Int32Array? It's ambiguous. By default, we'll
// map arrays/slices to the JavaScript arrays. If you want a TypedArray
// wrap it in this.
// Also, this type has nothing to do with the Env. But we place it here
// for consistency. Want a callback? Env.Callback. Want a JsObject?
// Env.JsObject. Want a TypedArray? Env.TypedArray.
pub fn TypedArray(comptime T: type) type {
return struct {
values: []const T,
pub fn dupe(self: TypedArray(T), allocator: Allocator) !TypedArray(T) {
return .{ .values = try allocator.dupe(T, self.values) };
}
};
}
pub const ArrayBuffer = struct {
values: []const u8,
pub fn dupe(self: ArrayBuffer, allocator: Allocator) !ArrayBuffer {
return .{ .values = try allocator.dupe(u8, self.values) };
}
};
pub const Exception = struct {
ctx: *const Context,
handle: *const v8.Value,
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
return self.context.valueToString(self.inner, .{ .allocator = allocator });
}
};
// These are simple types that we can convert to JS with only an isolate. This
// is separated from the Caller's zigValueToJs to make it available when we
// don't have a caller (i.e., when setting static attributes on types)
pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool, comptime null_as_undefined: bool) if (fail) *const v8.Value else ?*const v8.Value {
switch (@typeInfo(@TypeOf(value))) {
.void => return isolate.initUndefined(),
.null => if (comptime null_as_undefined) return isolate.initUndefined() else return isolate.initNull(),
.bool => return if (value) isolate.initTrue() else isolate.initFalse(),
.int => |n| {
if (comptime n.bits <= 32) {
return @ptrCast(isolate.initInteger(value).handle);
}
if (value >= 0 and value <= 4_294_967_295) {
return @ptrCast(isolate.initInteger(@as(u32, @intCast(value))).handle);
}
return @ptrCast(isolate.initBigInt(value).handle);
},
.comptime_int => {
if (value > -2_147_483_648 and value <= 4_294_967_295) {
return @ptrCast(isolate.initInteger(value).handle);
}
return @ptrCast(isolate.initBigInt(value).handle);
},
.float, .comptime_float => return @ptrCast(isolate.initNumber(value).handle),
.pointer => |ptr| {
if (ptr.size == .slice and ptr.child == u8) {
return @ptrCast(isolate.initStringHandle(value));
}
if (ptr.size == .one) {
const one_info = @typeInfo(ptr.child);
if (one_info == .array and one_info.array.child == u8) {
return @ptrCast(isolate.initStringHandle(value));
}
}
},
.array => return simpleZigValueToJs(isolate, &value, fail, null_as_undefined),
.optional => {
if (value) |v| {
return simpleZigValueToJs(isolate, v, fail, null_as_undefined);
}
if (comptime null_as_undefined) {
return isolate.initUndefined();
}
return isolate.initNull();
},
.@"struct" => {
switch (@TypeOf(value)) {
ArrayBuffer => {
const values = value.values;
const len = values.len;
const backing_store = v8.v8__ArrayBuffer__NewBackingStore(isolate.handle, len);
const data: [*]u8 = @ptrCast(@alignCast(v8.v8__BackingStore__Data(backing_store)));
@memcpy(data[0..len], @as([]const u8, @ptrCast(values))[0..len]);
const backing_store_ptr = v8.v8__BackingStore__TO_SHARED_PTR(backing_store);
return @ptrCast(v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?);
},
// zig fmt: off
TypedArray(u8), TypedArray(u16), TypedArray(u32), TypedArray(u64),
TypedArray(i8), TypedArray(i16), TypedArray(i32), TypedArray(i64),
TypedArray(f32), TypedArray(f64),
// zig fmt: on
=> {
const values = value.values;
const value_type = @typeInfo(@TypeOf(values)).pointer.child;
const len = values.len;
const bits = switch (@typeInfo(value_type)) {
.int => |n| n.bits,
.float => |f| f.bits,
else => @compileError("Invalid TypeArray type: " ++ @typeName(value_type)),
};
var array_buffer: *const v8.ArrayBuffer = undefined;
if (len == 0) {
array_buffer = v8.v8__ArrayBuffer__New(isolate.handle, 0).?;
} else {
const buffer_len = len * bits / 8;
const backing_store = v8.v8__ArrayBuffer__NewBackingStore(isolate.handle, buffer_len).?;
const data: [*]u8 = @ptrCast(@alignCast(v8.v8__BackingStore__Data(backing_store)));
@memcpy(data[0..buffer_len], @as([]const u8, @ptrCast(values))[0..buffer_len]);
const backing_store_ptr = v8.v8__BackingStore__TO_SHARED_PTR(backing_store);
array_buffer = v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?;
}
switch (@typeInfo(value_type)) {
.int => |n| switch (n.signedness) {
.unsigned => switch (n.bits) {
8 => return @ptrCast(v8.v8__Uint8Array__New(array_buffer, 0, len).?),
16 => return @ptrCast(v8.v8__Uint16Array__New(array_buffer, 0, len).?),
32 => return @ptrCast(v8.v8__Uint32Array__New(array_buffer, 0, len).?),
64 => return @ptrCast(v8.v8__BigUint64Array__New(array_buffer, 0, len).?),
else => {},
},
.signed => switch (n.bits) {
8 => return @ptrCast(v8.v8__Int8Array__New(array_buffer, 0, len).?),
16 => return @ptrCast(v8.v8__Int16Array__New(array_buffer, 0, len).?),
32 => return @ptrCast(v8.v8__Int32Array__New(array_buffer, 0, len).?),
64 => return @ptrCast(v8.v8__BigInt64Array__New(array_buffer, 0, len).?),
else => {},
},
},
.float => |f| switch (f.bits) {
32 => return @ptrCast(v8.v8__Float32Array__New(array_buffer, 0, len).?),
64 => return @ptrCast(v8.v8__Float64Array__New(array_buffer, 0, len).?),
else => {},
},
else => {},
}
// We normally don't fail in this function unless fail == true
// but this can never be valid.
@compileError("Invalid TypeArray type: " ++ @typeName(value_type));
},
inline String, BigInt, Integer, Number, Value, Object => return value.handle,
else => {},
}
},
.@"union" => return simpleZigValueToJs(isolate, std.meta.activeTag(value), fail, null_as_undefined),
.@"enum" => {
const T = @TypeOf(value);
if (@hasDecl(T, "toString")) {
return simpleZigValueToJs(isolate, value.toString(), fail, null_as_undefined);
}
},
else => {},
}
if (fail) {
@compileError("Unsupported Zig type " ++ @typeName(@TypeOf(value)));
}
return null;
}
// When we return a Zig object to V8, we put it on the heap and pass it into
// v8 as an *anyopaque (i.e. void *). When V8 gives us back the value, say, as a
// function parameter, we know what type it _should_ be.
//
// In a simple/perfect world, we could use this knowledge to cast the *anyopaque
// to the parameter type:
// const arg: @typeInfo(@TypeOf(function)).@"fn".params[0] = @ptrCast(v8_data);
//
// But there are 2 reasons we can't do that.
//
// == Reason 1 ==
// The JS code might pass the wrong type:
//
// var cat = new Cat();
// cat.setOwner(new Cat());
//
// The zig_setOwner method expects the 2nd parameter to be an *Owner, but
// the JS code passed a *Cat.
//
// To solve this issue, we tag every returned value so that we can check what
// type it is. In the above case, we'd expect an *Owner, but the tag would tell
// us that we got a *Cat. We use the type index in our Types lookup as the tag.
//
// == Reason 2 ==
// Because of prototype inheritance, even "correct" code can be a challenge. For
// example, say the above JavaScript is fixed:
//
// var cat = new Cat();
// cat.setOwner(new Owner("Leto"));
//
// The issue is that setOwner might not expect an *Owner, but rather a
// *Person, which is the prototype for Owner. Now our Zig code is expecting
// a *Person, but it was (correctly) given an *Owner.
// For this reason, we also store the prototype chain.
pub const TaggedAnyOpaque = struct {
prototype_len: u16,
prototype_chain: [*]const PrototypeChainEntry,
// Ptr to the Zig instance. Between the context where it's called (i.e.
// we have the comptime parameter info for all functions), and the index field
// we can figure out what type this is.
value: *anyopaque,
// When we're asked to describe an object via the Inspector, we _must_ include
// the proper subtype (and description) fields in the returned JSON.
// V8 will give us a Value and ask us for the subtype. From the js.Value we
// can get a js.Object, and from the js.Object, we can get out TaggedAnyOpaque
// which is where we store the subtype.
subtype: ?bridge.SubType,
};
pub const PrototypeChainEntry = struct {
index: bridge.JsApiLookup.BackingInt,
offset: u16, // offset to the _proto field
};
// These are here, and not in Inspector.zig, because Inspector.zig isn't always
// included (e.g. in the wpt build).
// This is called from V8. Whenever the v8 inspector has to describe a value
// it'll call this function to gets its [optional] subtype - which, from V8's
// point of view, is an arbitrary string.
pub export fn v8_inspector__Client__IMPL__valueSubtype(
_: *v8.InspectorClientImpl,
c_value: *const v8.Value,
) callconv(.c) [*c]const u8 {
const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null;
return if (external_entry.subtype) |st| @tagName(st) else null;
}
// Same as valueSubType above, but for the optional description field.
// From what I can tell, some drivers _need_ the description field to be
// present, even if it's empty. So if we have a subType for the value, we'll
// put an empty description.
pub export fn v8_inspector__Client__IMPL__descriptionForValueSubtype(
_: *v8.InspectorClientImpl,
v8_context: *const v8.Context,
c_value: *const v8.Value,
) callconv(.c) [*c]const u8 {
_ = v8_context;
// We _must_ include a non-null description in order for the subtype value
// to be included. Besides that, I don't know if the value has any meaning
const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null;
return if (external_entry.subtype == null) null else "";
}
test "TaggedAnyOpaque" {
// If we grow this, fine, but it should be a conscious decision
try std.testing.expectEqual(24, @sizeOf(TaggedAnyOpaque));
}

View File

@@ -1,86 +0,0 @@
const std = @import("std");
const user_agent = "Lightpanda.io/1.0";
pub const Loader = struct {
client: std.http.Client,
pub const Response = struct {
alloc: std.mem.Allocator,
req: *std.http.Client.Request,
pub fn deinit(self: *Response) void {
self.req.deinit();
self.alloc.destroy(self.req);
}
};
pub fn init(alloc: std.mem.Allocator) Loader {
return Loader{
.client = std.http.Client{
.allocator = alloc,
},
};
}
pub fn deinit(self: *Loader) void {
self.client.deinit();
}
// the caller must deinit the FetchResult.
pub fn fetch(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !std.http.Client.FetchResult {
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{
.{ .name = "User-Agent", .value = user_agent },
.{ .name = "Accept", .value = "*/*" },
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
});
defer headers.deinit();
return try self.client.fetch(alloc, .{
.location = .{ .uri = uri },
.headers = headers,
.payload = .none,
});
}
// see
// https://ziglang.org/documentation/master/std/#A;std:http.Client.fetch
// for reference.
// The caller is responsible for calling `deinit()` on the `Response`.
pub fn get(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !Response {
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{
.{ .name = "User-Agent", .value = user_agent },
.{ .name = "Accept", .value = "*/*" },
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
});
defer headers.deinit();
var resp = Response{
.alloc = alloc,
.req = try alloc.create(std.http.Client.Request),
};
errdefer alloc.destroy(resp.req);
resp.req.* = try self.client.open(.GET, uri, headers, .{
.handle_redirects = true, // TODO handle redirects manually
});
errdefer resp.req.deinit();
try resp.req.send(.{});
try resp.req.finish();
try resp.req.wait();
return resp;
}
};
test "basic url fetch" {
const alloc = std.testing.allocator;
var loader = Loader.init(alloc);
defer loader.deinit();
var result = try loader.fetch(alloc, "https://en.wikipedia.org/wiki/Main_Page");
defer result.deinit();
try std.testing.expect(result.status == std.http.Status.ok);
}

View File

@@ -1,220 +0,0 @@
const std = @import("std");
const testing = std.testing;
const Self = @This();
const MimeError = error{
Empty,
TooBig,
Invalid,
InvalidChar,
};
mtype: []const u8,
msubtype: []const u8,
params: []const u8 = "",
charset: ?[]const u8 = null,
boundary: ?[]const u8 = null,
pub const Empty = Self{ .mtype = "", .msubtype = "" };
pub const HTML = Self{ .mtype = "text", .msubtype = "html" };
pub const Javascript = Self{ .mtype = "application", .msubtype = "javascript" };
const reader = struct {
s: []const u8,
i: usize = 0,
fn until(self: *reader, c: u8) []const u8 {
const ln = self.s.len;
const start = self.i;
while (self.i < ln) {
if (c == self.s[self.i]) return self.s[start..self.i];
self.i += 1;
}
return self.s[start..self.i];
}
fn tail(self: *reader) []const u8 {
if (self.i > self.s.len) return "";
defer self.i = self.s.len;
return self.s[self.i..];
}
fn skip(self: *reader) bool {
if (self.i >= self.s.len) return false;
self.i += 1;
return true;
}
};
test "reader.skip" {
var r = reader{ .s = "foo" };
try testing.expect(r.skip());
try testing.expect(r.skip());
try testing.expect(r.skip());
try testing.expect(!r.skip());
try testing.expect(!r.skip());
}
test "reader.tail" {
var r = reader{ .s = "foo" };
try testing.expectEqualStrings("foo", r.tail());
try testing.expectEqualStrings("", r.tail());
}
test "reader.until" {
var r = reader{ .s = "foo.bar.baz" };
try testing.expectEqualStrings("foo", r.until('.'));
_ = r.skip();
try testing.expectEqualStrings("bar", r.until('.'));
_ = r.skip();
try testing.expectEqualStrings("baz", r.until('.'));
r = reader{ .s = "foo" };
try testing.expectEqualStrings("foo", r.until('.'));
r = reader{ .s = "" };
try testing.expectEqualStrings("", r.until('.'));
}
fn trim(s: []const u8) []const u8 {
const ln = s.len;
if (ln == 0) {
return "";
}
var start: usize = 0;
while (start < ln) {
if (!std.ascii.isWhitespace(s[start])) break;
start += 1;
}
var end: usize = ln;
while (end > 0) {
if (!std.ascii.isWhitespace(s[end - 1])) break;
end -= 1;
}
return s[start..end];
}
test "trim" {
try testing.expectEqualStrings("", trim(""));
try testing.expectEqualStrings("foo", trim("foo"));
try testing.expectEqualStrings("foo", trim(" \n\tfoo"));
try testing.expectEqualStrings("foo", trim("foo \n\t"));
}
// https://mimesniff.spec.whatwg.org/#http-token-code-point
fn isHTTPCodePoint(c: u8) bool {
return switch (c) {
'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^' => return true,
'_', '`', '|', '~' => return true,
else => std.ascii.isAlphanumeric(c),
};
}
fn valid(s: []const u8) bool {
const ln = s.len;
var i: usize = 0;
while (i < ln) {
if (!isHTTPCodePoint(s[i])) return false;
i += 1;
}
return true;
}
// https://mimesniff.spec.whatwg.org/#parsing-a-mime-type
pub fn parse(s: []const u8) Self.MimeError!Self {
const ln = s.len;
if (ln == 0) return MimeError.Empty;
// limit input size
if (ln > 255) return MimeError.TooBig;
var res = Self{ .mtype = "", .msubtype = "" };
var r = reader{ .s = s };
res.mtype = trim(r.until('/'));
if (res.mtype.len == 0) return MimeError.Invalid;
if (!valid(res.mtype)) return MimeError.InvalidChar;
if (!r.skip()) return MimeError.Invalid;
res.msubtype = trim(r.until(';'));
if (res.msubtype.len == 0) return MimeError.Invalid;
if (!valid(res.msubtype)) return MimeError.InvalidChar;
if (!r.skip()) return res;
res.params = trim(r.tail());
if (res.params.len == 0) return MimeError.Invalid;
// parse well known parameters.
// don't check invalid parameter format.
var rp = reader{ .s = res.params };
while (true) {
const name = trim(rp.until('='));
if (!rp.skip()) return res;
const value = trim(rp.until(';'));
if (std.ascii.eqlIgnoreCase(name, "charset")) {
res.charset = value;
}
if (std.ascii.eqlIgnoreCase(name, "boundary")) {
res.boundary = value;
}
if (!rp.skip()) return res;
}
return res;
}
test "parse valid" {
for ([_][]const u8{
"text/html",
" \ttext/html",
"text \t/html",
"text/ \thtml",
"text/html \t",
}) |tc| {
const m = try Self.parse(tc);
try testing.expectEqualStrings("text", m.mtype);
try testing.expectEqualStrings("html", m.msubtype);
}
const m2 = try Self.parse("text/javascript1.5");
try testing.expectEqualStrings("text", m2.mtype);
try testing.expectEqualStrings("javascript1.5", m2.msubtype);
const m3 = try Self.parse("text/html; charset=utf-8");
try testing.expectEqualStrings("text", m3.mtype);
try testing.expectEqualStrings("html", m3.msubtype);
try testing.expectEqualStrings("charset=utf-8", m3.params);
try testing.expectEqualStrings("utf-8", m3.charset.?);
const m4 = try Self.parse("text/html; boundary=----");
try testing.expectEqualStrings("text", m4.mtype);
try testing.expectEqualStrings("html", m4.msubtype);
try testing.expectEqualStrings("boundary=----", m4.params);
try testing.expectEqualStrings("----", m4.boundary.?);
}
test "parse invalid" {
for ([_][]const u8{
"",
"te xt/html;",
"te@xt/html;",
"text/ht@ml;",
"text/html;",
"/text/html",
"/html",
}) |tc| {
_ = Self.parse(tc) catch continue;
try testing.expect(false);
}
}
// Compare type and subtype.
pub fn eql(self: Self, b: Self) bool {
if (!std.mem.eql(u8, self.mtype, b.mtype)) return false;
return std.mem.eql(u8, self.msubtype, b.msubtype);
}

View File

@@ -0,0 +1,448 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const h5e = @import("html5ever.zig");
const Page = @import("../Page.zig");
const Node = @import("../webapi/Node.zig");
const Element = @import("../webapi/Element.zig");
const Allocator = std.mem.Allocator;
pub const ParsedNode = struct {
node: *Node,
// Data associated with this element to be passed back to html5ever as needed
// We only have this for Elements. For other types, like comments, it's null.
// html5ever should never ask us for this data on a non-element, and we'll
// assert that, with this opitonal, to make sure our assumption is correct.
data: ?*anyopaque,
};
const Parser = @This();
page: *Page,
err: ?Error,
container: ParsedNode,
arena: Allocator,
strings: std.StringHashMapUnmanaged(void),
pub fn init(arena: Allocator, node: *Node, page: *Page) Parser {
return .{
.err = null,
.page = page,
.strings = .empty,
.arena = arena,
.container = ParsedNode{
.data = null,
.node = node,
},
};
}
const Error = struct {
err: anyerror,
source: Source,
const Source = enum {
pop,
append,
create_element,
create_comment,
create_processing_instruction,
append_doctype_to_document,
add_attrs_if_missing,
get_template_content,
remove_from_parent,
reparent_children,
append_before_sibling,
append_based_on_parent_node,
};
};
pub fn parse(self: *Parser, html: []const u8) void {
h5e.html5ever_parse_document(
html.ptr,
html.len,
&self.container,
self,
createElementCallback,
getDataCallback,
appendCallback,
parseErrorCallback,
popCallback,
createCommentCallback,
createProcessingInstruction,
appendDoctypeToDocument,
addAttrsIfMissingCallback,
getTemplateContentsCallback,
removeFromParentCallback,
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
);
}
pub fn parseXML(self: *Parser, xml: []const u8) void {
h5e.xml5ever_parse_document(
xml.ptr,
xml.len,
&self.container,
self,
createXMLElementCallback,
getDataCallback,
appendCallback,
parseErrorCallback,
popCallback,
createCommentCallback,
createProcessingInstruction,
appendDoctypeToDocument,
addAttrsIfMissingCallback,
getTemplateContentsCallback,
removeFromParentCallback,
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
);
}
pub fn parseFragment(self: *Parser, html: []const u8) void {
h5e.html5ever_parse_fragment(
html.ptr,
html.len,
&self.container,
self,
createElementCallback,
getDataCallback,
appendCallback,
parseErrorCallback,
popCallback,
createCommentCallback,
createProcessingInstruction,
appendDoctypeToDocument,
addAttrsIfMissingCallback,
getTemplateContentsCallback,
removeFromParentCallback,
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
);
}
pub const Streaming = struct {
parser: Parser,
handle: ?*anyopaque,
pub fn init(arena: Allocator, node: *Node, page: *Page) Streaming {
return .{
.handle = null,
.parser = Parser.init(arena, node, page),
};
}
pub fn deinit(self: *Streaming) void {
if (self.handle) |handle| {
h5e.html5ever_streaming_parser_destroy(handle);
}
}
pub fn start(self: *Streaming) !void {
lp.assert(self.handle == null, "Parser.start non-null handle", .{});
self.handle = h5e.html5ever_streaming_parser_create(
&self.parser.container,
&self.parser,
createElementCallback,
getDataCallback,
appendCallback,
parseErrorCallback,
popCallback,
createCommentCallback,
createProcessingInstruction,
appendDoctypeToDocument,
addAttrsIfMissingCallback,
getTemplateContentsCallback,
removeFromParentCallback,
reparentChildrenCallback,
appendBeforeSiblingCallback,
appendBasedOnParentNodeCallback,
) orelse return error.ParserCreationFailed;
}
pub fn read(self: *Streaming, data: []const u8) !void {
const result = h5e.html5ever_streaming_parser_feed(
self.handle.?,
data.ptr,
data.len,
);
if (result != 0) {
// Parser panicked - clean up and return error
// Note: deinit will destroy the handle if it exists
if (self.handle) |handle| {
h5e.html5ever_streaming_parser_destroy(handle);
self.handle = null;
}
return error.ParserPanic;
}
}
pub fn done(self: *Streaming) void {
h5e.html5ever_streaming_parser_finish(self.handle.?);
}
};
fn parseErrorCallback(ctx: *anyopaque, err: h5e.StringSlice) callconv(.c) void {
_ = ctx;
_ = err;
// std.debug.print("PEC: {s}\n", .{err.slice()});
}
fn popCallback(ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._popCallback(getNode(node_ref)) catch |err| {
self.err = .{ .err = err, .source = .pop };
};
}
fn _popCallback(self: *Parser, node: *Node) !void {
try self.page.nodeComplete(node);
}
fn createElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque {
return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .unknown);
}
fn createXMLElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque {
return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .xml);
}
fn _createElementCallbackWithDefaultnamespace(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) ?*anyopaque {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._createElementCallback(data, qname, attributes, default_namespace) catch |err| {
self.err = .{ .err = err, .source = .create_element };
return null;
};
}
fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) !*anyopaque {
const page = self.page;
const name = qname.local.slice();
const namespace_string = qname.ns.slice();
const namespace = if (namespace_string.len == 0) default_namespace else Element.Namespace.parse(namespace_string);
const node = try page.createElementNS(namespace, name, attributes);
const pn = try self.arena.create(ParsedNode);
pn.* = .{
.data = data,
.node = node,
};
return pn;
}
fn createCommentCallback(ctx: *anyopaque, str: h5e.StringSlice) callconv(.c) ?*anyopaque {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._createCommentCallback(str.slice()) catch |err| {
self.err = .{ .err = err, .source = .create_comment };
return null;
};
}
fn _createCommentCallback(self: *Parser, str: []const u8) !*anyopaque {
const page = self.page;
const node = try page.createComment(str);
const pn = try self.arena.create(ParsedNode);
pn.* = .{
.data = null,
.node = node,
};
return pn;
}
fn createProcessingInstruction(ctx: *anyopaque, target: h5e.StringSlice, data: h5e.StringSlice) callconv(.c) ?*anyopaque {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._createProcessingInstruction(target.slice(), data.slice()) catch |err| {
self.err = .{ .err = err, .source = .create_processing_instruction };
return null;
};
}
fn _createProcessingInstruction(self: *Parser, target: []const u8, data: []const u8) !*anyopaque {
const page = self.page;
const node = try page.createProcessingInstruction(target, data);
const pn = try self.arena.create(ParsedNode);
pn.* = .{
.data = null,
.node = node,
};
return pn;
}
fn appendDoctypeToDocument(ctx: *anyopaque, name: h5e.StringSlice, public_id: h5e.StringSlice, system_id: h5e.StringSlice) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._appendDoctypeToDocument(name.slice(), public_id.slice(), system_id.slice()) catch |err| {
self.err = .{ .err = err, .source = .append_doctype_to_document };
};
}
fn _appendDoctypeToDocument(self: *Parser, name: []const u8, public_id: []const u8, system_id: []const u8) !void {
const page = self.page;
// Create the DocumentType node
const DocumentType = @import("../webapi/DocumentType.zig");
const doctype = try page._factory.node(DocumentType{
._proto = undefined,
._name = try page.dupeString(name),
._public_id = try page.dupeString(public_id),
._system_id = try page.dupeString(system_id),
});
// Append it to the document
try page.appendNew(self.container.node, .{ .node = doctype.asNode() });
}
fn addAttrsIfMissingCallback(ctx: *anyopaque, target_ref: *anyopaque, attributes: h5e.AttributeIterator) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._addAttrsIfMissingCallback(getNode(target_ref), attributes) catch |err| {
self.err = .{ .err = err, .source = .add_attrs_if_missing };
};
}
fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.AttributeIterator) !void {
const element = node.as(Element);
const page = self.page;
const attr_list = try element.getOrCreateAttributeList(page);
while (attributes.next()) |attr| {
const name = attr.name.local.slice();
const value = attr.value.slice();
// putNew only adds if the attribute doesn't already exist
try attr_list.putNew(name, value, page);
}
}
fn getTemplateContentsCallback(ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._getTemplateContentsCallback(getNode(target_ref)) catch |err| {
self.err = .{ .err = err, .source = .get_template_content };
return null;
};
}
fn _getTemplateContentsCallback(self: *Parser, node: *Node) !*anyopaque {
const element = node.as(Element);
const template = element._type.html.is(Element.Html.Template) orelse unreachable;
const content_node = template.getContent().asNode();
// Create a ParsedNode wrapper for the content DocumentFragment
const pn = try self.arena.create(ParsedNode);
pn.* = .{
.data = null,
.node = content_node,
};
return pn;
}
fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque {
const pn: *ParsedNode = @ptrCast(@alignCast(ctx));
// For non-elements, data is null. But, we expect this to only ever
// be called for elements.
lp.assert(pn.data != null, "Parser.getDataCallback null data", .{});
return pn.data.?;
}
fn appendCallback(ctx: *anyopaque, parent_ref: *anyopaque, node_or_text: h5e.NodeOrText) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._appendCallback(getNode(parent_ref), node_or_text) catch |err| {
self.err = .{ .err = err, .source = .append };
};
}
fn _appendCallback(self: *Parser, parent: *Node, node_or_text: h5e.NodeOrText) !void {
// child node is guaranteed not to belong to another parent
switch (node_or_text.toUnion()) {
.node => |cpn| {
const child = getNode(cpn);
try self.page.appendNew(parent, .{ .node = child });
},
.text => |txt| try self.page.appendNew(parent, .{ .text = txt }),
}
}
fn removeFromParentCallback(ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._removeFromParentCallback(getNode(target_ref)) catch |err| {
self.err = .{ .err = err, .source = .remove_from_parent };
};
}
fn _removeFromParentCallback(self: *Parser, node: *Node) !void {
const parent = node.parentNode() orelse return;
_ = try parent.removeChild(node, self.page);
}
fn reparentChildrenCallback(ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._reparentChildrenCallback(getNode(node_ref), getNode(new_parent_ref)) catch |err| {
self.err = .{ .err = err, .source = .reparent_children };
};
}
fn _reparentChildrenCallback(self: *Parser, node: *Node, new_parent: *Node) !void {
try self.page.appendAllChildren(node, new_parent);
}
fn appendBeforeSiblingCallback(ctx: *anyopaque, sibling_ref: *anyopaque, node_or_text: h5e.NodeOrText) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._appendBeforeSiblingCallback(getNode(sibling_ref), node_or_text) catch |err| {
self.err = .{ .err = err, .source = .append_before_sibling };
};
}
fn _appendBeforeSiblingCallback(self: *Parser, sibling: *Node, node_or_text: h5e.NodeOrText) !void {
const parent = sibling.parentNode() orelse return error.NoParent;
const node: *Node = switch (node_or_text.toUnion()) {
.node => |cpn| getNode(cpn),
.text => |txt| try self.page.createTextNode(txt),
};
try self.page.insertNodeRelative(parent, node, .{ .before = sibling }, .{});
}
fn appendBasedOnParentNodeCallback(ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, node_or_text: h5e.NodeOrText) callconv(.c) void {
const self: *Parser = @ptrCast(@alignCast(ctx));
self._appendBasedOnParentNodeCallback(getNode(element_ref), getNode(prev_element_ref), node_or_text) catch |err| {
self.err = .{ .err = err, .source = .append_based_on_parent_node };
};
}
fn _appendBasedOnParentNodeCallback(self: *Parser, element: *Node, prev_element: *Node, node_or_text: h5e.NodeOrText) !void {
if (element.parentNode()) |_| {
try self._appendBeforeSiblingCallback(element, node_or_text);
} else {
try self._appendCallback(prev_element, node_or_text);
}
}
fn getNode(ref: *anyopaque) *Node {
const pn: *ParsedNode = @ptrCast(@alignCast(ref));
return pn.node;
}
fn asUint(comptime string: anytype) std.meta.Int(
.unsigned,
@bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0
) {
const byteLength = @sizeOf(@TypeOf(string.*)) - 1;
const expectedType = *const [byteLength:0]u8;
if (@TypeOf(string) != expectedType) {
@compileError("expected : " ++ @typeName(expectedType) ++ ", got: " ++ @typeName(@TypeOf(string)));
}
return @bitCast(@as(*const [byteLength]u8, string).*);
}

View File

@@ -0,0 +1,194 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const ParsedNode = @import("Parser.zig").ParsedNode;
pub extern "c" fn html5ever_parse_document(
html: [*c]const u8,
len: usize,
doc: *anyopaque,
ctx: *anyopaque,
createElementCallback: *const fn (ctx: *anyopaque, data: *anyopaque, QualName, AttributeIterator) callconv(.c) ?*anyopaque,
elemNameCallback: *const fn (node_ref: *anyopaque) callconv(.c) *anyopaque,
appendCallback: *const fn (ctx: *anyopaque, parent_ref: *anyopaque, NodeOrText) callconv(.c) void,
parseErrorCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) void,
popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void,
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
createProcessingInstruction: *const fn (ctx: *anyopaque, StringSlice, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
removeFromParentCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) void,
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
) void;
pub extern "c" fn html5ever_parse_fragment(
html: [*c]const u8,
len: usize,
doc: *anyopaque,
ctx: *anyopaque,
createElementCallback: *const fn (ctx: *anyopaque, data: *anyopaque, QualName, AttributeIterator) callconv(.c) ?*anyopaque,
elemNameCallback: *const fn (node_ref: *anyopaque) callconv(.c) *anyopaque,
appendCallback: *const fn (ctx: *anyopaque, parent_ref: *anyopaque, NodeOrText) callconv(.c) void,
parseErrorCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) void,
popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void,
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
createProcessingInstruction: *const fn (ctx: *anyopaque, StringSlice, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
removeFromParentCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) void,
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
) void;
pub extern "c" fn html5ever_attribute_iterator_next(ctx: *anyopaque) Nullable(Attribute);
pub extern "c" fn html5ever_attribute_iterator_count(ctx: *anyopaque) usize;
pub extern "c" fn html5ever_get_memory_usage() MemoryUsage;
pub const MemoryUsage = extern struct {
resident: usize,
allocated: usize,
};
// Streaming parser API
pub extern "c" fn html5ever_streaming_parser_create(
doc: *anyopaque,
ctx: *anyopaque,
createElementCallback: *const fn (ctx: *anyopaque, data: *anyopaque, QualName, AttributeIterator) callconv(.c) ?*anyopaque,
elemNameCallback: *const fn (node_ref: *anyopaque) callconv(.c) *anyopaque,
appendCallback: *const fn (ctx: *anyopaque, parent_ref: *anyopaque, NodeOrText) callconv(.c) void,
parseErrorCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) void,
popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void,
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
createProcessingInstruction: *const fn (ctx: *anyopaque, StringSlice, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
removeFromParentCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) void,
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
) ?*anyopaque;
pub extern "c" fn html5ever_streaming_parser_feed(
parser: *anyopaque,
html: [*c]const u8,
len: usize,
) c_int;
pub extern "c" fn html5ever_streaming_parser_finish(
parser: *anyopaque,
) void;
pub extern "c" fn html5ever_streaming_parser_destroy(
parser: *anyopaque,
) void;
pub fn Nullable(comptime T: type) type {
return extern struct {
tag: u8,
value: T,
pub fn unwrap(self: @This()) ?T {
return if (self.tag == 0) null else self.value;
}
pub fn none() @This() {
return .{ .tag = 0, .value = undefined };
}
};
}
pub const StringSlice = Slice(u8);
pub fn Slice(comptime T: type) type {
return extern struct {
ptr: [*]const T,
len: usize,
pub fn slice(self: @This()) []const T {
return self.ptr[0..self.len];
}
};
}
pub const QualName = extern struct {
prefix: Nullable(StringSlice),
ns: StringSlice,
local: StringSlice,
};
pub const Attribute = extern struct {
name: QualName,
value: StringSlice,
};
pub const AttributeIterator = extern struct {
iter: *anyopaque,
pub fn next(self: AttributeIterator) ?Attribute {
return html5ever_attribute_iterator_next(self.iter).unwrap();
}
pub fn count(self: AttributeIterator) usize {
return html5ever_attribute_iterator_count(self.iter);
}
};
pub const NodeOrText = extern struct {
tag: u8,
node: *anyopaque,
text: StringSlice,
pub fn toUnion(self: NodeOrText) Union {
if (self.tag == 0) {
return .{ .node = @ptrCast(@alignCast(self.node)) };
}
return .{ .text = self.text.slice() };
}
const Union = union(enum) {
node: *ParsedNode,
text: []const u8,
};
};
pub extern "c" fn xml5ever_parse_document(
html: [*c]const u8,
len: usize,
doc: *anyopaque,
ctx: *anyopaque,
createElementCallback: *const fn (ctx: *anyopaque, data: *anyopaque, QualName, AttributeIterator) callconv(.c) ?*anyopaque,
elemNameCallback: *const fn (node_ref: *anyopaque) callconv(.c) *anyopaque,
appendCallback: *const fn (ctx: *anyopaque, parent_ref: *anyopaque, NodeOrText) callconv(.c) void,
parseErrorCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) void,
popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void,
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
createProcessingInstruction: *const fn (ctx: *anyopaque, StringSlice, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
removeFromParentCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) void,
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
) void;

64
src/browser/reflect.zig Normal file
View File

@@ -0,0 +1,64 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
// Gets the Parent of child.
// HtmlElement.of(script) -> *HTMLElement
pub fn Struct(comptime T: type) type {
return switch (@typeInfo(T)) {
.pointer => |ptr| ptr.child,
.@"struct" => T,
.void => T,
else => unreachable,
};
}
// Creates an enum of N enums. Doesn't perserve their underlying integer
pub fn mergeEnums(comptime enums: []const type) type {
const field_count = blk: {
var count: usize = 0;
inline for (enums) |e| {
count += @typeInfo(e).@"enum".fields.len;
}
break :blk count;
};
var i: usize = 0;
var fields: [field_count]std.builtin.Type.EnumField = undefined;
for (enums) |e| {
for (@typeInfo(e).@"enum".fields) |f| {
fields[i] = .{
.name = f.name,
.value = i,
};
i += 1;
}
}
return @Type(.{ .@"enum" = .{
.decls = &.{},
.tag_type = blk: {
if (field_count <= std.math.maxInt(u8)) break :blk u8;
if (field_count <= std.math.maxInt(u16)) break :blk u16;
unreachable;
},
.fields = &fields,
.is_exhaustive = true,
} });
}

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=animation>
let a1 = document.createElement('div').animate(null, null);
testing.expectEqual('finished', a1.playState);
let cb = [];
a1.ready.then(() => { cb.push('ready') });
a1.finished.then((x) => {
cb.push('finished');
cb.push(x == a1);
});
testing.eventually(() => testing.expectEqual(['finished', true], cb));
</script>

136
src/browser/tests/blob.html Normal file
View File

@@ -0,0 +1,136 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<script src="./testing.js"></script>
<script id=basic>
{
const parts = ["\r\nthe quick brown\rfo\rx\r", "\njumps over\r\nthe\nlazy\r", "\ndog"];
// "transparent" ending should not modify the final buffer.
const blob = new Blob(parts, { type: "text/html" });
const expected = parts.join("");
testing.expectEqual(expected.length, blob.size);
testing.expectEqual("text/html", blob.type);
testing.async(async () => { testing.expectEqual(expected, await blob.text()) });
}
{
const parts = ["\rhello\r", "\nwor\r\nld"];
// "native" ending should modify the final buffer.
const blob = new Blob(parts, { endings: "native" });
const expected = "\nhello\n\nwor\nld";
testing.expectEqual(expected.length, blob.size);
testing.async(async () => { testing.expectEqual(expected, await blob.text()) });
}
</script>
<!-- Firefox and Safari only -->
<script id=bytes>
{
const parts = ["light ", "panda ", "rocks ", "!"];
const blob = new Blob(parts);
testing.async(async() => {
const expected = new Uint8Array([108, 105, 103, 104, 116, 32, 112, 97,
110, 100, 97, 32, 114, 111, 99, 107, 115,
32, 33]);
const result = await blob.bytes();
testing.expectEqual(true, result instanceof Uint8Array);
testing.expectEqual(expected, result);
});
}
// Test for SIMD.
{
const parts = [
"\rThe opened package\r\nof potato\nchi\rps",
"held the\r\nanswer to the\r mystery. Both det\rectives looke\r\rd\r",
"\rat it but failed to realize\nit was\r\nthe\rkey\r\n",
"\r\nto solve the \rcrime.\r"
];
const blob = new Blob(parts, { type: "text/html", endings: "native" });
testing.expectEqual(161, blob.size);
testing.expectEqual("text/html", blob.type);
testing.async(async() => {
const expected = new Uint8Array([10, 84, 104, 101, 32, 111, 112, 101, 110,
101, 100, 32, 112, 97, 99, 107, 97, 103,
101, 10, 111, 102, 32, 112, 111, 116, 97,
116, 111, 10, 99, 104, 105, 10, 112, 115,
104, 101, 108, 100, 32, 116, 104, 101, 10,
97, 110, 115, 119, 101, 114, 32, 116, 111,
32, 116, 104, 101, 10, 32, 109, 121, 115,
116, 101, 114, 121, 46, 32, 66, 111, 116,
104, 32, 100, 101, 116, 10, 101, 99, 116,
105, 118, 101, 115, 32, 108, 111, 111, 107,
101, 10, 10, 100, 10, 10, 97, 116, 32, 105,
116, 32, 98, 117, 116, 32, 102, 97, 105, 108,
101, 100, 32, 116, 111, 32, 114, 101, 97,
108, 105, 122, 101, 10, 105, 116, 32, 119, 97,
115, 10, 116, 104, 101, 10, 107, 101, 121,
10, 10, 116, 111, 32, 115, 111, 108, 118, 101,
32, 116, 104, 101, 32, 10, 99, 114, 105, 109,
101, 46, 10]);
const result = await blob.bytes();
testing.expectEqual(true, result instanceof Uint8Array);
testing.expectEqual(expected, result);
});
}
</script>
<script id=stream>
{
const parts = ["may", "thy", "knife", "chip", "and", "shatter"];
const blob = new Blob(parts);
const reader = blob.stream().getReader();
testing.async(async () => {
const {done: done, value: value} = await reader.read()
const expected = new Uint8Array([109, 97, 121, 116, 104, 121, 107, 110,
105, 102, 101, 99, 104, 105, 112, 97,
110, 100, 115, 104, 97, 116, 116, 101,
114]);
testing.expectEqual(false, done);
testing.expectEqual(true, value instanceof Uint8Array);
testing.expectEqual(expected, value);
});
}
</script>
<script id=slice>
{
const parts = ["la", "symphonie", "des", "éclairs"];
const blob = new Blob(parts);
testing.async(async () => {
const result = await blob.arrayBuffer();
testing.expectEqual(true, result instanceof ArrayBuffer)
});
let temp = blob.slice(0);
testing.expectEqual(blob.size, temp.size);
testing.async(async () => {
testing.expectEqual("lasymphoniedeséclairs", await temp.text());
});
temp = blob.slice(-4, -2, "custom");
testing.expectEqual(2, temp.size);
testing.expectEqual("custom", temp.type);
testing.async(async () => {
testing.expectEqual("ai", await temp.text());
});
temp = blob.slice(14);
testing.expectEqual(8, temp.size);
testing.async(async () => {
testing.expectEqual("éclairs", await temp.text());
});
temp = blob.slice(6, -10, "text/eclair");
testing.expectEqual(6, temp.size);
testing.expectEqual("text/eclair", temp.type);
testing.async(async () => {
testing.expectEqual("honied", await temp.text());
});
}
</script>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=CanvasRenderingContext2D>
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
testing.expectEqual(true, ctx instanceof CanvasRenderingContext2D);
// We can't really test this but let's try to call it at least.
ctx.fillRect(0, 0, 0, 0);
}
</script>
<script id=CanvasRenderingContext2D#fillStyle>
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
// Black by default.
testing.expectEqual(ctx.fillStyle, "#000000");
ctx.fillStyle = "red";
testing.expectEqual(ctx.fillStyle, "#ff0000");
ctx.fillStyle = "rebeccapurple";
testing.expectEqual(ctx.fillStyle, "#663399");
// No changes made if color is invalid.
ctx.fillStyle = "invalid-color";
testing.expectEqual(ctx.fillStyle, "#663399");
ctx.fillStyle = "#fc0";
testing.expectEqual(ctx.fillStyle, "#ffcc00");
ctx.fillStyle = "#ff0000";
testing.expectEqual(ctx.fillStyle, "#ff0000");
ctx.fillStyle = "#fF00000F";
testing.expectEqual(ctx.fillStyle, "rgba(255, 0, 0, 0.06)");
}
</script>

View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=WebGLRenderingContext#getSupportedExtensions>
{
const element = document.createElement("canvas");
const ctx = element.getContext("webgl");
testing.expectEqual(true, ctx instanceof WebGLRenderingContext);
const supportedExtensions = ctx.getSupportedExtensions();
// The order Chrome prefer.
const expectedExtensions = [
"ANGLE_instanced_arrays",
"EXT_blend_minmax",
"EXT_clip_control",
"EXT_color_buffer_half_float",
"EXT_depth_clamp",
"EXT_disjoint_timer_query",
"EXT_float_blend",
"EXT_frag_depth",
"EXT_polygon_offset_clamp",
"EXT_shader_texture_lod",
"EXT_texture_compression_bptc",
"EXT_texture_compression_rgtc",
"EXT_texture_filter_anisotropic",
"EXT_texture_mirror_clamp_to_edge",
"EXT_sRGB",
"KHR_parallel_shader_compile",
"OES_element_index_uint",
"OES_fbo_render_mipmap",
"OES_standard_derivatives",
"OES_texture_float",
"OES_texture_float_linear",
"OES_texture_half_float",
"OES_texture_half_float_linear",
"OES_vertex_array_object",
"WEBGL_blend_func_extended",
"WEBGL_color_buffer_float",
"WEBGL_compressed_texture_astc",
"WEBGL_compressed_texture_etc",
"WEBGL_compressed_texture_etc1",
"WEBGL_compressed_texture_pvrtc",
"WEBGL_compressed_texture_s3tc",
"WEBGL_compressed_texture_s3tc_srgb",
"WEBGL_debug_renderer_info",
"WEBGL_debug_shaders",
"WEBGL_depth_texture",
"WEBGL_draw_buffers",
"WEBGL_lose_context",
"WEBGL_multi_draw",
"WEBGL_polygon_mode"
];
testing.expectEqual(expectedExtensions.length, supportedExtensions.length);
for (let i = 0; i < expectedExtensions.length; i++) {
testing.expectEqual(expectedExtensions[i], supportedExtensions[i]);
}
}
</script>
<script id=WebGLRenderingCanvas#getExtension>
// WEBGL_debug_renderer_info
{
const element = document.createElement("canvas");
const ctx = element.getContext("webgl");
const rendererInfo = ctx.getExtension("WEBGL_debug_renderer_info");
testing.expectEqual(true, rendererInfo instanceof WEBGL_debug_renderer_info);
const { UNMASKED_VENDOR_WEBGL, UNMASKED_RENDERER_WEBGL } = rendererInfo;
testing.expectEqual(UNMASKED_VENDOR_WEBGL, 0x9245);
testing.expectEqual(UNMASKED_RENDERER_WEBGL, 0x9246);
testing.expectEqual("", ctx.getParameter(UNMASKED_VENDOR_WEBGL));
testing.expectEqual("", ctx.getParameter(UNMASKED_RENDERER_WEBGL));
}
// WEBGL_lose_context
{
const element = document.createElement("canvas");
const ctx = element.getContext("webgl");
const loseContext = ctx.getExtension("WEBGL_lose_context");
testing.expectEqual(true, loseContext instanceof WEBGL_lose_context);
loseContext.loseContext();
loseContext.restoreContext();
}
</script>

View File

@@ -0,0 +1,217 @@
cdataClassName<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="container"></div>
<script id="createInHTMLDocument">
{
try {
document.createCDATASection('test');
testing.fail('Should have thrown NotSupportedError');
} catch (err) {
testing.expectEqual('NotSupportedError', err.name);
}
}
</script>
<script id="createInXMLDocument">
{
const doc = new Document();
const cdata = doc.createCDATASection('Hello World');
testing.expectEqual(4, cdata.nodeType);
testing.expectEqual('#cdata-section', cdata.nodeName);
testing.expectEqual('Hello World', cdata.data);
testing.expectEqual(11, cdata.length);
}
</script>
<script id="cdataWithSpecialChars">
{
const doc = new Document();
const cdata = doc.createCDATASection('<tag>&amp;"quotes"</tag>');
testing.expectEqual('<tag>&amp;"quotes"</tag>', cdata.data);
}
</script>
<script id="cdataRejectsEndMarker">
{
const doc = new Document();
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => doc.createCDATASection('foo ]]> bar'));
}
</script>
<script id="cdataRejectsEndMarkerEdgeCase">
{
const doc = new Document();
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => doc.createCDATASection(']]>'));
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => doc.createCDATASection('start]]>end'));
}
</script>
<script id="cdataAllowsSimilarPatterns">
{
const doc = new Document();
const cdata1 = doc.createCDATASection(']>');
testing.expectEqual(']>', cdata1.data);
const cdata2 = doc.createCDATASection(']]');
testing.expectEqual(']]', cdata2.data);
const cdata3 = doc.createCDATASection('] ]>');
testing.expectEqual('] ]>', cdata3.data);
}
</script>
<script id="cdataCharacterDataMethods">
{
const doc = new Document();
const cdata = doc.createCDATASection('Hello');
cdata.appendData(' World');
testing.expectEqual('Hello World', cdata.data);
testing.expectEqual(11, cdata.length);
cdata.deleteData(5, 6);
testing.expectEqual('Hello', cdata.data);
cdata.insertData(0, 'Hi ');
testing.expectEqual('Hi Hello', cdata.data);
cdata.replaceData(0, 3, 'Bye');
testing.expectEqual('ByeHello', cdata.data);
const sub = cdata.substringData(0, 3);
testing.expectEqual('Bye', sub);
}
</script>
<script id="cdataInheritance">
{
const doc = new Document();
const cdata = doc.createCDATASection('test');
testing.expectEqual(true, cdata instanceof CDATASection);
testing.expectEqual(true, cdata instanceof Text);
testing.expectEqual(true, cdata instanceof CharacterData);
testing.expectEqual(true, cdata instanceof Node);
}
</script>
<script id="cdataWholeText">
{
const doc = new Document();
const cdata = doc.createCDATASection('test data');
testing.expectEqual('test data', cdata.wholeText);
}
</script>
<script id="cdataClone">
{
const doc = new Document();
const cdata = doc.createCDATASection('original data');
const clone = cdata.cloneNode(false);
testing.expectEqual(4, clone.nodeType);
testing.expectEqual('#cdata-section', clone.nodeName);
testing.expectEqual('original data', clone.data);
testing.expectEqual(true, clone !== cdata);
}
</script>
<script id="cdataRemove">
{
const doc = new Document();
const cdata = doc.createCDATASection('test');
const root = doc.createElement('root');
doc.appendChild(root);
root.appendChild(cdata);
testing.expectEqual(1, root.childNodes.length);
testing.expectEqual(root, cdata.parentNode);
cdata.remove();
testing.expectEqual(0, root.childNodes.length);
testing.expectEqual(null, cdata.parentNode);
}
</script>
<script id="cdataBeforeAfter">
{
const doc = new Document();
const root = doc.createElement('root');
doc.appendChild(root);
const cdata = doc.createCDATASection('middle');
root.appendChild(cdata);
const text1 = doc.createTextNode('before');
const text2 = doc.createTextNode('after');
cdata.before(text1);
cdata.after(text2);
testing.expectEqual(3, root.childNodes.length);
}
</script>
<script id="cdataReplaceWith">
{
const doc = new Document();
const root = doc.createElement('root');
doc.appendChild(root);
const cdata = doc.createCDATASection('old');
root.appendChild(cdata);
const replacement = doc.createTextNode('new');
cdata.replaceWith(replacement);
testing.expectEqual(1, root.childNodes.length);
testing.expectEqual('new', root.childNodes[0].data);
testing.expectEqual(null, cdata.parentNode);
}
</script>
<script id="cdataSiblingNavigation">
{
const doc = new Document();
const root = doc.createElement('root');
doc.appendChild(root);
const elem1 = doc.createElement('first');
const cdata = doc.createCDATASection('middle');
const elem2 = doc.createElement('last');
root.appendChild(elem1);
root.appendChild(cdata);
root.appendChild(elem2);
testing.expectEqual('last', cdata.nextElementSibling.tagName);
testing.expectEqual('first', cdata.previousElementSibling.tagName);
}
</script>
<script id="cdataEmptyString">
{
const doc = new Document();
const cdata = doc.createCDATASection('');
testing.expectEqual('', cdata.data);
testing.expectEqual(0, cdata.length);
}
</script>

View File

@@ -0,0 +1,730 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="container"></div>
<script id="lengthProperty">
{
// length property
const text = document.createTextNode('Hello');
testing.expectEqual(5, text.length);
testing.expectEqual(5, text.data.length);
const empty = document.createTextNode('');
testing.expectEqual(0, empty.length);
const comment = document.createComment('test comment');
testing.expectEqual(12, comment.length);
}
</script>
<script id="appendDataBasic">
{
// appendData basic
const text = document.createTextNode('Hello');
text.appendData(' World');
testing.expectEqual('Hello World', text.data);
testing.expectEqual(11, text.length);
}
</script>
<script id="appendDataEmpty">
{
// appendData to empty
const text = document.createTextNode('');
text.appendData('First');
testing.expectEqual('First', text.data);
// appendData empty string
text.appendData('');
testing.expectEqual('First', text.data);
}
</script>
<script id="deleteDataBasic">
{
// deleteData from middle
const text = document.createTextNode('Hello World');
text.deleteData(5, 6); // Remove ' World'
testing.expectEqual('Hello', text.data);
testing.expectEqual(5, text.length);
}
</script>
<script id="deleteDataStart">
{
// deleteData from start
const text = document.createTextNode('Hello World');
text.deleteData(0, 6); // Remove 'Hello '
testing.expectEqual('World', text.data);
}
</script>
<script id="deleteDataEnd">
{
// deleteData from end
const text = document.createTextNode('Hello World');
text.deleteData(5, 100); // Remove ' World' (count exceeds length)
testing.expectEqual('Hello', text.data);
}
</script>
<script id="deleteDataAll">
{
// deleteData everything
const text = document.createTextNode('Hello');
text.deleteData(0, 5);
testing.expectEqual('', text.data);
testing.expectEqual(0, text.length);
}
</script>
<script id="deleteDataZeroCount">
{
// deleteData with count=0
const text = document.createTextNode('Hello');
text.deleteData(2, 0);
testing.expectEqual('Hello', text.data);
}
</script>
<script id="deleteDataInvalidOffset">
{
// deleteData with invalid offset
const text = document.createTextNode('Hello');
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => text.deleteData(10, 5));
testing.expectEqual('Hello', text.data); // unchanged
}
</script>
<script id="insertDataMiddle">
{
// insertData in middle
const text = document.createTextNode('Hello');
text.insertData(5, ' World');
testing.expectEqual('Hello World', text.data);
}
</script>
<script id="insertDataStart">
{
// insertData at start
const text = document.createTextNode('World');
text.insertData(0, 'Hello ');
testing.expectEqual('Hello World', text.data);
}
</script>
<script id="insertDataEnd">
{
// insertData at end
const text = document.createTextNode('Hello');
text.insertData(5, ' World');
testing.expectEqual('Hello World', text.data);
}
</script>
<script id="insertDataEmpty">
{
// insertData into empty
const text = document.createTextNode('');
text.insertData(0, 'Hello');
testing.expectEqual('Hello', text.data);
}
</script>
<script id="insertDataInvalidOffset">
{
// insertData with invalid offset
const text = document.createTextNode('Hello');
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => text.insertData(10, 'X'));
testing.expectEqual('Hello', text.data);
}
</script>
<script id="replaceDataBasic">
{
// replaceData basic
const text = document.createTextNode('Hello World');
text.replaceData(6, 5, 'Universe');
testing.expectEqual('Hello Universe', text.data);
}
</script>
<script id="replaceDataShorter">
{
// replaceData with shorter string
const text = document.createTextNode('Hello World');
text.replaceData(6, 5, 'Hi');
testing.expectEqual('Hello Hi', text.data);
}
</script>
<script id="replaceDataLonger">
{
// replaceData with longer string
const text = document.createTextNode('Hello Hi');
text.replaceData(6, 2, 'World');
testing.expectEqual('Hello World', text.data);
}
</script>
<script id="replaceDataExceedingCount">
{
// replaceData with count exceeding length
const text = document.createTextNode('Hello World');
text.replaceData(6, 100, 'Everyone');
testing.expectEqual('Hello Everyone', text.data);
}
</script>
<script id="replaceDataZeroCount">
{
// replaceData with count=0 (acts like insert)
const text = document.createTextNode('Hello World');
text.replaceData(5, 0, '!!!');
testing.expectEqual('Hello!!! World', text.data);
}
</script>
<script id="substringDataBasic">
{
// substringData basic
const text = document.createTextNode('Hello World');
const sub = text.substringData(0, 5);
testing.expectEqual('Hello', sub);
testing.expectEqual('Hello World', text.data); // original unchanged
}
</script>
<script id="substringDataMiddle">
{
// substringData from middle
const text = document.createTextNode('Hello World');
const sub = text.substringData(6, 5);
testing.expectEqual('World', sub);
}
</script>
<script id="substringDataExceedingCount">
{
// substringData with count exceeding length
const text = document.createTextNode('Hello World');
const sub = text.substringData(6, 100);
testing.expectEqual('World', sub);
}
</script>
<script id="substringDataZeroCount">
{
// substringData with count=0
const text = document.createTextNode('Hello');
const sub = text.substringData(0, 0);
testing.expectEqual('', sub);
}
</script>
<script id="substringDataInvalidOffset">
{
// substringData with invalid offset
const text = document.createTextNode('Hello');
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => text.substringData(10, 5));
}
</script>
<script id="commentCharacterData">
{
// CharacterData methods work on comments too
const comment = document.createComment('Hello');
comment.appendData(' World');
testing.expectEqual('Hello World', comment.data);
comment.deleteData(5, 6);
testing.expectEqual('Hello', comment.data);
comment.insertData(0, 'Start: ');
testing.expectEqual('Start: Hello', comment.data);
comment.replaceData(0, 7, 'End: ');
testing.expectEqual('End: Hello', comment.data);
const sub = comment.substringData(5, 5);
testing.expectEqual('Hello', sub);
}
</script>
<script id="dataChangeNotifications">
{
// Verify data changes are reflected in DOM
const container = $('#container');
container.innerHTML = '';
const text = document.createTextNode('Original');
container.appendChild(text);
text.appendData(' Text');
testing.expectEqual('Original Text', container.textContent);
text.deleteData(0, 9);
testing.expectEqual('Text', container.textContent);
text.data = 'Changed';
testing.expectEqual('Changed', container.textContent);
}
</script>
<script id="removeWithSiblings">
{
// remove() when node has siblings
const container = $('#container');
container.innerHTML = '';
const text1 = document.createTextNode('A');
const text2 = document.createTextNode('B');
const text3 = document.createTextNode('C');
container.appendChild(text1);
container.appendChild(text2);
container.appendChild(text3);
testing.expectEqual(3, container.childNodes.length);
testing.expectEqual('ABC', container.textContent);
text2.remove();
testing.expectEqual(2, container.childNodes.length);
testing.expectEqual('AC', container.textContent);
testing.expectEqual(null, text2.parentNode);
testing.expectEqual(text3, text1.nextSibling);
}
</script>
<script id="removeOnlyChild">
{
// remove() when node is only child
const container = $('#container');
container.innerHTML = '';
const text = document.createTextNode('Only');
container.appendChild(text);
testing.expectEqual(1, container.childNodes.length);
text.remove();
testing.expectEqual(0, container.childNodes.length);
testing.expectEqual(null, text.parentNode);
}
</script>
<script id="removeNoParent">
{
// remove() when node has no parent (should do nothing)
const text = document.createTextNode('Orphan');
text.remove(); // Should not throw
testing.expectEqual(null, text.parentNode);
}
</script>
<script id="removeCommentWithElementSiblings">
{
// remove() comment with element siblings
const container = $('#container');
container.innerHTML = '';
const div1 = document.createElement('div');
div1.textContent = 'First';
const comment = document.createComment('middle');
const div2 = document.createElement('div');
div2.textContent = 'Last';
container.appendChild(div1);
container.appendChild(comment);
container.appendChild(div2);
testing.expectEqual(3, container.childNodes.length);
comment.remove();
testing.expectEqual(2, container.childNodes.length);
testing.expectEqual('DIV', container.childNodes[0].tagName);
testing.expectEqual('DIV', container.childNodes[1].tagName);
testing.expectEqual(div2, div1.nextSibling);
}
</script>
<script id="beforeWithSiblings">
{
// before() when node has siblings
const container = $('#container');
container.innerHTML = '';
const text1 = document.createTextNode('A');
const text2 = document.createTextNode('C');
container.appendChild(text1);
container.appendChild(text2);
const textB = document.createTextNode('B');
text2.before(textB);
testing.expectEqual(3, container.childNodes.length);
testing.expectEqual('ABC', container.textContent);
testing.expectEqual(textB, text1.nextSibling);
testing.expectEqual(text2, textB.nextSibling);
}
</script>
<script id="beforeMultipleNodes">
{
// before() with multiple nodes
const container = $('#container');
container.innerHTML = '';
const text = document.createTextNode('Z');
container.appendChild(text);
const text1 = document.createTextNode('A');
const text2 = document.createTextNode('B');
const text3 = document.createTextNode('C');
text.before(text1, text2, text3);
testing.expectEqual(4, container.childNodes.length);
testing.expectEqual('ABCZ', container.textContent);
}
</script>
<script id="beforeMixedTypes">
{
// before() with mixed node types
const container = $('#container');
container.innerHTML = '';
const target = document.createTextNode('Target');
container.appendChild(target);
const elem = document.createElement('span');
elem.textContent = 'E';
const text = document.createTextNode('T');
const comment = document.createComment('C');
target.before(elem, text, comment);
testing.expectEqual(4, container.childNodes.length);
testing.expectEqual(1, container.childNodes[0].nodeType); // ELEMENT_NODE
testing.expectEqual(3, container.childNodes[1].nodeType); // TEXT_NODE
testing.expectEqual(8, container.childNodes[2].nodeType); // COMMENT_NODE
testing.expectEqual(3, container.childNodes[3].nodeType); // TEXT_NODE (target)
}
</script>
<script id="beforeNoParent">
{
// before() when node has no parent (should do nothing)
const orphan = document.createTextNode('Orphan');
const text = document.createTextNode('Test');
orphan.before(text);
testing.expectEqual(null, orphan.parentNode);
testing.expectEqual(null, text.parentNode);
}
</script>
<script id="beforeOnlyChild">
{
// before() when target is only child
const container = $('#container');
container.innerHTML = '';
const target = document.createTextNode('B');
container.appendChild(target);
const textA = document.createTextNode('A');
target.before(textA);
testing.expectEqual(2, container.childNodes.length);
testing.expectEqual('AB', container.textContent);
testing.expectEqual(textA, container.firstChild);
testing.expectEqual(target, textA.nextSibling);
}
</script>
<script id="afterWithSiblings">
{
// after() when node has siblings
const container = $('#container');
container.innerHTML = '';
const text1 = document.createTextNode('A');
const text2 = document.createTextNode('C');
container.appendChild(text1);
container.appendChild(text2);
const textB = document.createTextNode('B');
text1.after(textB);
testing.expectEqual(3, container.childNodes.length);
testing.expectEqual('ABC', container.textContent);
testing.expectEqual(textB, text1.nextSibling);
testing.expectEqual(text2, textB.nextSibling);
}
</script>
<script id="afterMultipleNodes">
{
// after() with multiple nodes
const container = $('#container');
container.innerHTML = '';
const text = document.createTextNode('A');
container.appendChild(text);
const text1 = document.createTextNode('B');
const text2 = document.createTextNode('C');
const text3 = document.createTextNode('D');
text.after(text1, text2, text3);
testing.expectEqual(4, container.childNodes.length);
testing.expectEqual('ABCD', container.textContent);
}
</script>
<script id="afterMixedTypes">
{
// after() with mixed node types
const container = $('#container');
container.innerHTML = '';
const target = document.createTextNode('Start');
container.appendChild(target);
const elem = document.createElement('div');
elem.textContent = 'E';
const comment = document.createComment('comment');
const text = document.createTextNode('T');
target.after(elem, comment, text);
testing.expectEqual(4, container.childNodes.length);
testing.expectEqual(3, container.childNodes[0].nodeType); // TEXT_NODE (target)
testing.expectEqual(1, container.childNodes[1].nodeType); // ELEMENT_NODE
testing.expectEqual(8, container.childNodes[2].nodeType); // COMMENT_NODE
testing.expectEqual(3, container.childNodes[3].nodeType); // TEXT_NODE
}
</script>
<script id="afterNoParent">
{
// after() when node has no parent (should do nothing)
const orphan = document.createTextNode('Orphan');
const text = document.createTextNode('Test');
orphan.after(text);
testing.expectEqual(null, orphan.parentNode);
testing.expectEqual(null, text.parentNode);
}
</script>
<script id="afterAsLastChild">
{
// after() when target is last child
const container = $('#container');
container.innerHTML = '';
const target = document.createTextNode('A');
container.appendChild(target);
const textB = document.createTextNode('B');
target.after(textB);
testing.expectEqual(2, container.childNodes.length);
testing.expectEqual('AB', container.textContent);
testing.expectEqual(target, container.firstChild);
testing.expectEqual(textB, container.lastChild);
testing.expectEqual(null, textB.nextSibling);
}
</script>
<script id="replaceWithSingleNode">
{
// replaceWith() with single node
const container = $('#container');
container.innerHTML = '';
const old = document.createTextNode('Old');
container.appendChild(old);
const replacement = document.createTextNode('New');
old.replaceWith(replacement);
testing.expectEqual(1, container.childNodes.length);
testing.expectEqual('New', container.textContent);
testing.expectEqual(null, old.parentNode);
testing.expectEqual(container, replacement.parentNode);
}
</script>
<script id="replaceWithMultipleNodes">
{
// replaceWith() with multiple nodes
const container = $('#container');
container.innerHTML = '';
const old = document.createTextNode('X');
container.appendChild(old);
const text1 = document.createTextNode('A');
const text2 = document.createTextNode('B');
const text3 = document.createTextNode('C');
old.replaceWith(text1, text2, text3);
testing.expectEqual(3, container.childNodes.length);
testing.expectEqual('ABC', container.textContent);
testing.expectEqual(null, old.parentNode);
}
</script>
<script id="replaceWithOnlyChild">
{
// replaceWith() when target is only child
const container = $('#container');
container.innerHTML = '';
const old = document.createTextNode('Only');
container.appendChild(old);
testing.expectEqual(1, container.childNodes.length);
const replacement = document.createTextNode('Replaced');
old.replaceWith(replacement);
testing.expectEqual(1, container.childNodes.length);
testing.expectEqual('Replaced', container.textContent);
testing.expectEqual(replacement, container.firstChild);
}
</script>
<script id="replaceWithBetweenSiblings">
{
// replaceWith() when node has siblings on both sides
const container = $('#container');
container.innerHTML = '';
const text1 = document.createTextNode('A');
const text2 = document.createTextNode('X');
const text3 = document.createTextNode('C');
container.appendChild(text1);
container.appendChild(text2);
container.appendChild(text3);
const replacement = document.createTextNode('B');
text2.replaceWith(replacement);
testing.expectEqual(3, container.childNodes.length);
testing.expectEqual('ABC', container.textContent);
testing.expectEqual(replacement, text1.nextSibling);
testing.expectEqual(text3, replacement.nextSibling);
}
</script>
<script id="replaceWithMixedTypes">
{
// replaceWith() with mixed node types
const container = $('#container');
container.innerHTML = '';
const old = document.createComment('old');
container.appendChild(old);
const elem = document.createElement('span');
elem.textContent = 'E';
const text = document.createTextNode('T');
const comment = document.createComment('C');
old.replaceWith(elem, text, comment);
testing.expectEqual(3, container.childNodes.length);
testing.expectEqual(1, container.childNodes[0].nodeType); // ELEMENT_NODE
testing.expectEqual(3, container.childNodes[1].nodeType); // TEXT_NODE
testing.expectEqual(8, container.childNodes[2].nodeType); // COMMENT_NODE
testing.expectEqual(null, old.parentNode);
}
</script>
<script id="nextElementSiblingText">
{
// nextElementSibling on text node with element siblings
const container = $('#container');
container.innerHTML = '';
const text1 = document.createTextNode('A');
const comment = document.createComment('comment');
const div = document.createElement('div');
div.id = 'found';
const text2 = document.createTextNode('B');
container.appendChild(text1);
container.appendChild(comment);
container.appendChild(div);
container.appendChild(text2);
testing.expectEqual('found', text1.nextElementSibling.id);
testing.expectEqual('found', comment.nextElementSibling.id);
testing.expectEqual(null, text2.nextElementSibling);
}
</script>
<script id="nextElementSiblingNoElement">
{
// nextElementSibling when there's no element sibling
const container = $('#container');
container.innerHTML = '';
const text = document.createTextNode('A');
const comment = document.createComment('B');
container.appendChild(text);
container.appendChild(comment);
testing.expectEqual(null, text.nextElementSibling);
testing.expectEqual(null, comment.nextElementSibling);
}
</script>
<script id="previousElementSiblingComment">
{
// previousElementSibling on comment with element siblings
const container = $('#container');
container.innerHTML = '';
const div = document.createElement('div');
div.id = 'found';
const text = document.createTextNode('text');
const comment = document.createComment('comment');
container.appendChild(div);
container.appendChild(text);
container.appendChild(comment);
testing.expectEqual('found', text.previousElementSibling.id);
testing.expectEqual('found', comment.previousElementSibling.id);
testing.expectEqual(null, div.previousElementSibling);
}
</script>
<script id="previousElementSiblingNoElement">
{
// previousElementSibling when there's no element sibling
const container = $('#container');
container.innerHTML = '';
const text = document.createTextNode('A');
const comment = document.createComment('B');
container.appendChild(text);
container.appendChild(comment);
testing.expectEqual(null, text.previousElementSibling);
testing.expectEqual(null, comment.previousElementSibling);
}
</script>

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=comment>
testing.expectEqual('', new Comment().data);
testing.expectEqual('over 9000! ', new Comment('over 9000! ').data);
</script>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=a><!-- spice --></div>
<div id=b>flow</div>
<script id=data>
testing.expectEqual(' spice ', $('#a').firstChild.data);
testing.expectEqual('flow', $('#b').firstChild.data);
</script>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<a id="link" href="foo" class="ok">OK</a>
<script src="../../testing.js"></script>
<script id=text>
let t = new Text('foo');
testing.expectEqual('foo', t.data);
let emptyt = new Text();
testing.expectEqual('', emptyt.data);
let text = $('#link').firstChild;
testing.expectEqual('OK', text.wholeText);
text.data = 'OK modified';
let split = text.splitText('OK'.length);
testing.expectEqual(' modified', split.data);
testing.expectEqual('OK', text.data);
</script>

View File

@@ -0,0 +1 @@
<p>1</p> <p>2</p>

View File

@@ -0,0 +1 @@
<div><p>2</p></div>

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
</head>
<body>
<h1>Test Page</h1>
<nav>
<a href="/page1" id="link1">First Link</a>
<a href="/page2" id="link2">Second Link</a>
</nav>
<form id="testForm" action="/submit" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" placeholder="Enter username">
<label for="email">Email:</label>
<input type="email" id="email" name="email" placeholder="Enter email">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<button type="submit">Submit</button>
</form>
</body>
</html>

View File

@@ -0,0 +1 @@
<a id=a1>link1</a><div id=d2><p>other</p></div>

View File

@@ -0,0 +1 @@
<a id=a1></a><a id=a2></a>

View File

@@ -0,0 +1 @@
<a id=a1></a><div id=d2><a id=a2></a></div>

View File

@@ -0,0 +1,213 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<!-- Test fixtures for RadioNodeList -->
<form id="test_form">
<input type="radio" name="color" value="red">
<input type="radio" name="color" value="green">
<input type="radio" name="color" value="blue">
<input type="text" name="single_field" value="test">
</form>
<script id="multiple_returns_radio_node_list">
{
const form = $('#test_form');
const result = form.elements.namedItem('color');
testing.expectEqual('RadioNodeList', result.constructor.name);
}
</script>
<script id="single_returns_element">
{
const form = $('#test_form');
const result = form.elements.namedItem('single_field');
testing.expectEqual('HTMLInputElement', result.constructor.name);
testing.expectEqual('single_field', result.name);
}
</script>
<script id="none_returns_null">
{
const form = $('#test_form');
const result = form.elements.namedItem('nonexistent');
testing.expectEqual(null, result);
}
</script>
<script id="length">
{
const form = $('#test_form');
const radios = form.elements.namedItem('color');
testing.expectEqual(3, radios.length);
}
</script>
<script id="indexed_access">
{
const form = $('#test_form');
const radios = form.elements.namedItem('color');
testing.expectEqual('red', radios[0].value);
testing.expectEqual('green', radios[1].value);
testing.expectEqual('blue', radios[2].value);
}
</script>
<script id="value_getter_no_checked">
{
const form = $('#test_form');
const radios = form.elements.namedItem('color');
testing.expectEqual('', radios.value);
}
</script>
<script id="value_getter_with_checked">
{
const form = $('#test_form');
const inputs = form.querySelectorAll('input[name="color"]');
inputs[1].checked = true;
const radios = form.elements.namedItem('color');
testing.expectEqual('green', radios.value);
}
</script>
<script id="value_getter_on_default">
{
const form = document.createElement('form');
const r1 = document.createElement('input');
r1.type = 'radio';
r1.name = 'test';
r1.checked = true;
// no value attribute
const r2 = document.createElement('input');
r2.type = 'radio';
r2.name = 'test';
// no value attribute
form.appendChild(r1);
form.appendChild(r2);
document.body.appendChild(form);
// Multiple elements with same name returns RadioNodeList
const radios = form.elements.namedItem('test');
testing.expectEqual('RadioNodeList', radios.constructor.name);
testing.expectEqual('on', radios.value); // Checked radio with no value returns "on"
form.remove();
}
</script>
<script id="value_setter">
{
const form = $('#test_form');
const radios = form.elements.namedItem('color');
const inputs = form.querySelectorAll('input[name="color"]');
radios.value = 'blue';
testing.expectEqual(false, inputs[0].checked);
testing.expectEqual(false, inputs[1].checked);
testing.expectEqual(true, inputs[2].checked);
testing.expectEqual('blue', radios.value);
}
</script>
<script id="value_setter_on">
{
const form = document.createElement('form');
const r1 = document.createElement('input');
r1.type = 'radio';
r1.name = 'test';
// no value attribute
const r2 = document.createElement('input');
r2.type = 'radio';
r2.name = 'test';
r2.value = 'on';
const r3 = document.createElement('input');
r3.type = 'radio';
r3.name = 'test';
r3.value = 'other';
form.appendChild(r1);
form.appendChild(r2);
form.appendChild(r3);
document.body.appendChild(form);
const radios = form.elements.namedItem('test');
radios.value = 'on';
// Should check first match (r1 with no value attribute)
testing.expectEqual(true, r1.checked);
testing.expectEqual(false, r2.checked);
testing.expectEqual(false, r3.checked);
form.remove();
}
</script>
<script id="live_collection">
{
const form = document.createElement('form');
const r1 = document.createElement('input');
r1.type = 'radio';
r1.name = 'dynamic';
r1.value = 'a';
const r2 = document.createElement('input');
r2.type = 'radio';
r2.name = 'dynamic';
r2.value = 'b';
form.appendChild(r1);
form.appendChild(r2);
document.body.appendChild(form);
const radios = form.elements.namedItem('dynamic');
testing.expectEqual('RadioNodeList', radios.constructor.name);
testing.expectEqual(2, radios.length);
const r3 = document.createElement('input');
r3.type = 'radio';
r3.name = 'dynamic';
r3.value = 'c';
form.appendChild(r3);
testing.expectEqual(3, radios.length);
testing.expectEqual('c', radios[2].value);
r1.remove();
testing.expectEqual(2, radios.length);
testing.expectEqual('b', radios[0].value);
form.remove();
}
</script>
<!-- Test non-radio elements with same name -->
<form id="mixed_form">
<input type="text" name="mixed" value="text1">
<input type="text" name="mixed" value="text2">
</form>
<script id="non_radio_elements">
{
const form = $('#mixed_form');
const result = form.elements.namedItem('mixed');
// Should still return RadioNodeList even for non-radio elements
testing.expectEqual('RadioNodeList', result.constructor.name);
testing.expectEqual(2, result.length);
// getValue should return "" for non-radio elements
testing.expectEqual('', result.value);
}
</script>

View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<script src="testing.js"></script>
<script id=getRandomValues>
function isRandom(ta) {
let uniq = new Set(Array.from(ta));
testing.expectEqual(true, (uniq.size / ta.length) * 100 > 0.7)
}
{
let tu8a = new Uint8Array(100)
testing.expectEqual(tu8a, crypto.getRandomValues(tu8a))
isRandom(tu8a)
let ti8a = new Int8Array(100)
testing.expectEqual(ti8a, crypto.getRandomValues(ti8a))
isRandom(ti8a)
}
// {
// let tu16a = new Uint16Array(100)
// testing.expectEqual(tu16a, crypto.getRandomValues(tu16a))
// isRandom(tu16a)
// let ti16a = new Int16Array(100)
// testing.expectEqual(ti16a, crypto.getRandomValues(ti16a))
// isRandom(ti16a)
// }
// {
// let tu32a = new Uint32Array(100)
// testing.expectEqual(tu32a, crypto.getRandomValues(tu32a))
// isRandom(tu32a)
// let ti32a = new Int32Array(100)
// testing.expectEqual(ti32a, crypto.getRandomValues(ti32a))
// isRandom(ti32a)
// }
// {
// let tu64a = new BigUint64Array(100)
// testing.expectEqual(tu64a, crypto.getRandomValues(tu64a))
// isRandom(tu64a)
// let ti64a = new BigInt64Array(100)
// testing.expectEqual(ti64a, crypto.getRandomValues(ti64a))
// isRandom(ti64a)
// }
</script>
<!-- <script id="randomUUID">
const uuid = crypto.randomUUID();
testing.expectEqual('string', typeof uuid);
testing.expectEqual(36, uuid.length);
const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
testing.expectEqual(true, regex.test(uuid));
</script> -->
<script id=SubtleCrypto>
testing.expectEqual(true, crypto.subtle instanceof SubtleCrypto);
</script>
<script id=sign-and-verify-hmac>
testing.async(async () => {
let key = await crypto.subtle.generateKey(
{
name: "HMAC",
hash: { name: "SHA-512" },
},
true,
["sign", "verify"],
);
testing.expectEqual(true, key instanceof CryptoKey);
const raw = await crypto.subtle.exportKey("raw", key);
testing.expectEqual(128, raw.byteLength);
const encoder = new TextEncoder();
const signature = await crypto.subtle.sign(
"HMAC",
key,
encoder.encode("Hello, world!")
);
testing.expectEqual(true, signature instanceof ArrayBuffer);
const result = await window.crypto.subtle.verify(
{ name: "HMAC" },
key,
signature,
encoder.encode("Hello, world!")
);
testing.expectEqual(true, result);
});
</script>
<script id=derive-shared-key-x25519>
testing.async(async () => {
const { privateKey, publicKey } = await crypto.subtle.generateKey(
{ name: "X25519" },
true,
["deriveBits"],
);
testing.expectEqual(true, privateKey instanceof CryptoKey);
testing.expectEqual(true, publicKey instanceof CryptoKey);
const sharedKey = await crypto.subtle.deriveBits(
{
name: "X25519",
public: publicKey,
},
privateKey,
128,
);
testing.expectEqual(16, sharedKey.byteLength);
});
</script>

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<script src="testing.js"></script>
<script id="exists">
testing.expectEqual('object', typeof CSS);
testing.expectEqual('function', typeof CSS.escape);
testing.expectEqual('function', typeof CSS.supports);
</script>
<script id="escape_basic">
{
testing.expectEqual('hello', CSS.escape('hello'));
testing.expectEqual('world123', CSS.escape('world123'));
testing.expectEqual('foo-bar', CSS.escape('foo-bar'));
testing.expectEqual('_test', CSS.escape('_test'));
}
</script>
<script id="escape_first_character">
{
testing.expectEqual('\\30 abc', CSS.escape('0abc'));
testing.expectEqual('\\31 23', CSS.escape('123'));
testing.expectEqual('\\-test', CSS.escape('-test'));
testing.expectEqual('\\--test', CSS.escape('--test'));
}
</script>
<script id="escape_special_characters">
{
testing.expectEqual('hello\\ world', CSS.escape('hello world'));
testing.expectEqual('test\\!', CSS.escape('test!'));
testing.expectEqual('foo\\#bar', CSS.escape('foo#bar'));
testing.expectEqual('a\\(b\\)', CSS.escape('a(b)'));
testing.expectEqual('test\\@example', CSS.escape('test@example'));
testing.expectEqual('a\\[b\\]', CSS.escape('a[b]'));
testing.expectEqual('a\\{b\\}', CSS.escape('a{b}'));
testing.expectEqual('test\\:value', CSS.escape('test:value'));
testing.expectEqual('a\\.b', CSS.escape('a.b'));
testing.expectEqual('a\\,b', CSS.escape('a,b'));
}
</script>
<script id="escape_quotes">
{
testing.expectEqual('test\\"value', CSS.escape('test"value'));
testing.expectEqual('test\\\'value', CSS.escape("test'value"));
}
</script>
<script id="supports_basic">
{
testing.expectEqual(true, CSS.supports('display', 'block'));
testing.expectEqual(true, CSS.supports('position', 'relative'));
testing.expectEqual(true, CSS.supports('width', '100px'));
testing.expectEqual(true, CSS.supports('color', 'red'));
}
</script>
<script id="supports_common_properties">
{
testing.expectEqual(true, CSS.supports('margin', '10px'));
testing.expectEqual(true, CSS.supports('padding', '5px'));
testing.expectEqual(true, CSS.supports('border', '1px solid black'));
testing.expectEqual(true, CSS.supports('background-color', 'blue'));
testing.expectEqual(true, CSS.supports('font-size', '16px'));
testing.expectEqual(true, CSS.supports('opacity', '0.5'));
testing.expectEqual(true, CSS.supports('z-index', '10'));
}
</script>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<head>
<script src="../testing.js"></script>
</head>
<body>
</body>
<script id=matchMedia_basic>
{
const mql = window.matchMedia('(min-width: 600px)');
testing.expectEqual('object', typeof mql);
testing.expectEqual('(min-width: 600px)', mql.media);
testing.expectEqual(false, mql.matches);
}
</script>
<script id=matchMedia_different_queries>
{
const mql1 = window.matchMedia('(max-width: 1024px)');
testing.expectEqual('(max-width: 1024px)', mql1.media);
testing.expectEqual(false, mql1.matches);
const mql2 = window.matchMedia('(prefers-color-scheme: dark)');
testing.expectEqual('(prefers-color-scheme: dark)', mql2.media);
testing.expectEqual(false, mql2.matches);
}
</script>
<script id=matchMedia_event_target>
{
const mql = window.matchMedia('(orientation: portrait)');
testing.expectEqual('function', typeof mql.addEventListener);
testing.expectEqual('function', typeof mql.removeEventListener);
testing.expectEqual('function', typeof mql.dispatchEvent);
}
</script>

View File

@@ -0,0 +1,207 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id="document_styleSheets">
{
const sheets = document.styleSheets;
testing.expectTrue(sheets !== null);
testing.expectEqual(0, sheets.length);
// Should return same instance
const sheets2 = document.styleSheets;
testing.expectTrue(sheets === sheets2);
}
</script>
<script id="CSSStyleSheet_basic">
{
const sheets = document.styleSheets;
testing.expectEqual('StyleSheetList', sheets.constructor.name);
// Test indexed access on empty list
testing.expectEqual(undefined, sheets[0]);
testing.expectEqual(undefined, sheets[99]);
}
</script>
<script id="CSSStyleDeclaration_setCssText_basic">
{
const div = document.createElement('div');
const style = div.style;
// Set single property
style.cssText = 'color: red';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual(1, style.length);
}
</script>
<script id="CSSStyleDeclaration_setCssText_multiple">
{
const div = document.createElement('div');
const style = div.style;
// Set multiple properties
style.cssText = 'color: red; background: blue; margin: 10px';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual('blue', style.getPropertyValue('background'));
testing.expectEqual('10px', style.getPropertyValue('margin'));
testing.expectEqual(3, style.length);
}
</script>
<script id="CSSStyleDeclaration_setCssText_important">
{
const div = document.createElement('div');
const style = div.style;
// Set property with !important
style.cssText = 'color: red !important';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual('important', style.getPropertyPriority('color'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_important_multiple">
{
const div = document.createElement('div');
const style = div.style;
// Mix of important and non-important
style.cssText = 'color: red !important; background: blue; margin: 10px !important';
testing.expectEqual('important', style.getPropertyPriority('color'));
testing.expectEqual('', style.getPropertyPriority('background'));
testing.expectEqual('important', style.getPropertyPriority('margin'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_whitespace">
{
const div = document.createElement('div');
const style = div.style;
// Test whitespace handling
style.cssText = ' color : red ; background : blue ';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual('blue', style.getPropertyValue('background'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_trailing_semicolon">
{
const div = document.createElement('div');
const style = div.style;
// Trailing semicolon should be handled
style.cssText = 'color: red;';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual(1, style.length);
}
</script>
<script id="CSSStyleDeclaration_setCssText_no_semicolon">
{
const div = document.createElement('div');
const style = div.style;
// Single property without semicolon
style.cssText = 'color: red';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual(1, style.length);
}
</script>
<script id="CSSStyleDeclaration_setCssText_empty_declarations">
{
const div = document.createElement('div');
const style = div.style;
// Multiple semicolons should be ignored
style.cssText = 'color: red;;; background: blue';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual('blue', style.getPropertyValue('background'));
testing.expectEqual(2, style.length);
}
</script>
<script id="CSSStyleDeclaration_setCssText_replace">
{
const div = document.createElement('div');
const style = div.style;
// Set initial properties
style.cssText = 'color: red; background: blue';
testing.expectEqual(2, style.length);
// Replace with new properties
style.cssText = 'margin: 10px';
testing.expectEqual('', style.getPropertyValue('color'));
testing.expectEqual('', style.getPropertyValue('background'));
testing.expectEqual('10px', style.getPropertyValue('margin'));
testing.expectEqual(1, style.length);
}
</script>
<script id="CSSStyleDeclaration_setCssText_clear">
{
const div = document.createElement('div');
const style = div.style;
style.cssText = 'color: red; background: blue';
testing.expectEqual(2, style.length);
// Clear all properties
style.cssText = '';
testing.expectEqual(0, style.length);
testing.expectEqual('', style.getPropertyValue('color'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_colon_in_value">
{
const div = document.createElement('div');
const style = div.style;
// URL value with colon
style.cssText = 'background: url(http://example.com/image.png)';
testing.expectEqual('url(http://example.com/image.png)', style.getPropertyValue('background'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_important_whitespace">
{
const div = document.createElement('div');
const style = div.style;
// Various whitespace around !important
style.cssText = 'color: red!important';
testing.expectEqual('important', style.getPropertyPriority('color'));
style.cssText = 'color: red ! important';
testing.expectEqual('important', style.getPropertyPriority('color'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_case_insensitive_property">
{
const div = document.createElement('div');
const style = div.style;
// Property names should be normalized to lowercase
style.cssText = 'COLOR: red; BACKGROUND: blue';
testing.expectEqual('red', style.getPropertyValue('color'));
testing.expectEqual('blue', style.getPropertyValue('background'));
}
</script>
<script id="CSSStyleDeclaration_setCssText_exclamation_not_important">
{
const div = document.createElement('div');
const style = div.style;
// Exclamation mark without "important" should be kept in value
style.cssText = 'content: "hello!"';
testing.expectEqual('"hello!"', style.getPropertyValue('content'));
testing.expectEqual('', style.getPropertyPriority('content'));
}
</script>

View File

@@ -0,0 +1,165 @@
<!DOCTYPE html>
<body>
<script src="../testing.js"></script>
<script id="attribute_changed">
{
let callbackCalls = [];
class MyElement extends HTMLElement {
static get observedAttributes() {
return ['data-foo', 'data-bar'];
}
attributeChangedCallback(name, oldValue, newValue) {
callbackCalls.push({ name, oldValue, newValue });
}
}
customElements.define('my-element', MyElement);
const el = document.createElement('my-element');
testing.expectEqual(0, callbackCalls.length);
el.setAttribute('data-foo', 'value1');
testing.expectEqual(1, callbackCalls.length);
testing.expectEqual('data-foo', callbackCalls[0].name);
testing.expectEqual(null, callbackCalls[0].oldValue);
testing.expectEqual('value1', callbackCalls[0].newValue);
el.setAttribute('data-foo', 'value2');
testing.expectEqual(2, callbackCalls.length);
testing.expectEqual('data-foo', callbackCalls[1].name);
testing.expectEqual('value1', callbackCalls[1].oldValue);
testing.expectEqual('value2', callbackCalls[1].newValue);
el.setAttribute('data-bar', 'bar-value');
testing.expectEqual(3, callbackCalls.length);
testing.expectEqual('data-bar', callbackCalls[2].name);
testing.expectEqual(null, callbackCalls[2].oldValue);
testing.expectEqual('bar-value', callbackCalls[2].newValue);
}
{
let callbackCalls = [];
class ObservedElement extends HTMLElement {
static get observedAttributes() {
return ['watched'];
}
attributeChangedCallback(name, oldValue, newValue) {
callbackCalls.push({ name, oldValue, newValue });
}
}
customElements.define('observed-element', ObservedElement);
const el = document.createElement('observed-element');
el.setAttribute('unwatched', 'value');
testing.expectEqual(0, callbackCalls.length);
el.setAttribute('watched', 'value');
testing.expectEqual(1, callbackCalls.length);
}
{
let callbackCalls = [];
class RemoveAttrElement extends HTMLElement {
static get observedAttributes() {
return ['test'];
}
attributeChangedCallback(name, oldValue, newValue) {
callbackCalls.push({ name, oldValue, newValue });
}
}
customElements.define('remove-attr-element', RemoveAttrElement);
const el = document.createElement('remove-attr-element');
el.setAttribute('test', 'value');
testing.expectEqual(1, callbackCalls.length);
el.removeAttribute('test');
testing.expectEqual(2, callbackCalls.length);
testing.expectEqual('test', callbackCalls[1].name);
testing.expectEqual('value', callbackCalls[1].oldValue);
testing.expectEqual(null, callbackCalls[1].newValue);
}
{
let callbackCalls = [];
class NoObservedElement extends HTMLElement {
attributeChangedCallback(name, oldValue, newValue) {
callbackCalls.push({ name, oldValue, newValue });
}
}
customElements.define('no-observed-element', NoObservedElement);
const el = document.createElement('no-observed-element');
el.setAttribute('test', 'value');
testing.expectEqual(0, callbackCalls.length);
}
{
let callbackCalls = [];
class UpgradeAttrElement extends HTMLElement {
static get observedAttributes() {
return ['existing'];
}
attributeChangedCallback(name, oldValue, newValue) {
callbackCalls.push({ name, oldValue, newValue });
}
}
const el = document.createElement('upgrade-attr-element');
el.setAttribute('existing', 'before-upgrade');
testing.expectEqual(0, callbackCalls.length);
customElements.define('upgrade-attr-element', UpgradeAttrElement);
testing.expectEqual(0, callbackCalls.length);
document.body.appendChild(el);
testing.expectEqual(1, callbackCalls.length);
testing.expectEqual('existing', callbackCalls[0].name);
testing.expectEqual(null, callbackCalls[0].oldValue);
testing.expectEqual('before-upgrade', callbackCalls[0].newValue);
el.setAttribute('existing', 'after-upgrade');
testing.expectEqual(2, callbackCalls.length);
testing.expectEqual('existing', callbackCalls[1].name);
testing.expectEqual('before-upgrade', callbackCalls[1].oldValue);
testing.expectEqual('after-upgrade', callbackCalls[1].newValue);
}
{
let callbackCalls = [];
class SetAttrMethodElement extends HTMLElement {
static get observedAttributes() {
return ['test'];
}
attributeChangedCallback(name, oldValue, newValue) {
callbackCalls.push({ name, oldValue, newValue });
}
}
customElements.define('set-attr-method-element', SetAttrMethodElement);
const el = document.createElement('set-attr-method-element');
el.setAttribute('test', 'initial');
testing.expectEqual(1, callbackCalls.length);
el.setAttribute('test', 'initial');
testing.expectEqual(2, callbackCalls.length);
testing.expectEqual('initial', callbackCalls[1].oldValue);
testing.expectEqual('initial', callbackCalls[1].newValue);
}
</script>

View File

@@ -0,0 +1,137 @@
<!DOCTYPE html>
<body>
<script src="../testing.js"></script>
<script id="built_in">
{
class FancyButton extends HTMLElement {}
customElements.define('fancy-button', FancyButton, { extends: 'button' });
const definition = customElements.get('fancy-button');
testing.expectEqual(FancyButton, definition);
}
{
let threw = false;
try {
class BadExtends extends HTMLElement {}
customElements.define('bad-extends', BadExtends, { extends: 'not-a-real-element' });
} catch (e) {
threw = true;
}
testing.expectEqual(true, threw);
}
{
let threw = false;
try {
class ExtendCustom extends HTMLElement {}
customElements.define('extend-custom', ExtendCustom, { extends: 'fancy-button' });
} catch (e) {
threw = true;
}
testing.expectEqual(true, threw);
}
{
class FancyParagraph extends HTMLElement {}
customElements.define('fancy-paragraph', FancyParagraph, { extends: 'p' });
const definition = customElements.get('fancy-paragraph');
testing.expectEqual(FancyParagraph, definition);
}
{
let constructorCalled = 0;
class ConstructedButton extends HTMLElement {
constructor() {
super();
constructorCalled++;
this.clicked = false;
}
}
customElements.define('constructed-button', ConstructedButton, { extends: 'button' });
const btn = document.createElement('button', { is: 'constructed-button' });
testing.expectEqual(1, constructorCalled);
testing.expectEqual('BUTTON', btn.tagName);
testing.expectEqual(false, btn.clicked);
}
{
class WrongTag extends HTMLElement {}
customElements.define('wrong-tag', WrongTag, { extends: 'button' });
const div = document.createElement('div', { is: 'wrong-tag' });
testing.expectEqual('DIV', div.tagName);
}
{
let connectedCount = 0;
let disconnectedCount = 0;
let attributeChanges = [];
class LifecycleButton extends HTMLElement {
static get observedAttributes() {
return ['data-test'];
}
connectedCallback() {
connectedCount++;
}
disconnectedCallback() {
disconnectedCount++;
}
attributeChangedCallback(name, oldValue, newValue) {
attributeChanges.push({ name, oldValue, newValue });
}
}
customElements.define('lifecycle-button', LifecycleButton, { extends: 'button' });
const btn = document.createElement('button', { is: 'lifecycle-button' });
testing.expectEqual(0, connectedCount);
document.body.appendChild(btn);
testing.expectEqual(1, connectedCount);
btn.setAttribute('data-test', 'value1');
testing.expectEqual(1, attributeChanges.length);
testing.expectEqual('data-test', attributeChanges[0].name);
testing.expectEqual(null, attributeChanges[0].oldValue);
testing.expectEqual('value1', attributeChanges[0].newValue);
btn.remove();
testing.expectEqual(1, disconnectedCount);
}
</script>
<script>
{
let constructorCalled = 0;
let connectedCalled = 0;
class ParsedButton extends HTMLElement {
constructor() {
super();
constructorCalled++;
}
connectedCallback() {
connectedCalled++;
}
}
customElements.define('parsed-button', ParsedButton, { extends: 'button' });
}
</script>
<button is="parsed-button" id="parsed"></button>
<script>
{
const btn = document.getElementById('parsed');
testing.expectEqual('BUTTON', btn.tagName);
}
</script>

View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<body>
<script src="../testing.js"></script>
<script id="connected">
{
let connectedCount = 0;
class MyElement extends HTMLElement {
connectedCallback() {
connectedCount++;
}
}
customElements.define('my-element', MyElement);
const el = document.createElement('my-element');
testing.expectEqual(0, connectedCount);
document.body.appendChild(el);
testing.expectEqual(1, connectedCount);
}
{
let order = [];
class ParentElement extends HTMLElement {
connectedCallback() {
order.push('parent');
}
}
class ChildElement extends HTMLElement {
connectedCallback() {
order.push('child');
}
}
customElements.define('parent-element', ParentElement);
customElements.define('child-element', ChildElement);
const parent = document.createElement('parent-element');
const child = document.createElement('child-element');
parent.appendChild(child);
testing.expectEqual(0, order.length);
document.body.appendChild(parent);
testing.expectEqual(2, order.length);
testing.expectEqual('parent', order[0]);
testing.expectEqual('child', order[1]);
}
{
let connectedCount = 0;
class MoveElement extends HTMLElement {
connectedCallback() {
connectedCount++;
}
}
customElements.define('move-element', MoveElement);
const el = document.createElement('move-element');
document.body.appendChild(el);
testing.expectEqual(1, connectedCount);
const div = document.createElement('div');
document.body.appendChild(div);
div.appendChild(el);
testing.expectEqual(1, connectedCount);
}
{
let connectedCount = 0;
class DetachedElement extends HTMLElement {
connectedCallback() {
connectedCount++;
}
}
customElements.define('detached-element', DetachedElement);
const container = document.createElement('div');
const el = document.createElement('detached-element');
container.appendChild(el);
testing.expectEqual(0, connectedCount);
document.body.appendChild(container);
testing.expectEqual(1, connectedCount);
}
</script>

View File

@@ -0,0 +1,122 @@
<!DOCTYPE html>
<head>
<script src="../testing.js"></script>
<script>
{
// Define the custom element BEFORE the HTML is parsed
window.preParseConnectedCount = 0;
class PreParseElement extends HTMLElement {
connectedCallback() {
window.preParseConnectedCount++;
}
}
customElements.define('pre-parse-element', PreParseElement);
}
</script>
</head>
<body>
<!-- This element is in the HTML and should have connectedCallback invoked -->
<pre-parse-element id="static-element"></pre-parse-element>
<script id="test-static-element">
{
// connectedCallback should have been called for the element in HTML
testing.expectEqual(1, window.preParseConnectedCount);
const el = document.getElementById('static-element');
testing.expectTrue(el !== null);
testing.expectEqual('PRE-PARSE-ELEMENT', el.tagName);
}
</script>
<script id="test-programmatic-still-works">
{
// Reset counter
window.preParseConnectedCount = 0;
// This should still work (programmatic creation)
const el = document.createElement('pre-parse-element');
testing.expectEqual(0, window.preParseConnectedCount);
document.body.appendChild(el);
testing.expectEqual(1, window.preParseConnectedCount);
}
</script>
<script>
{
window.nestedParentCount = 0;
window.nestedChildCount = 0;
class NestedParent extends HTMLElement {
connectedCallback() {
window.nestedParentCount++;
}
}
class NestedChild extends HTMLElement {
connectedCallback() {
window.nestedChildCount++;
}
}
customElements.define('nested-parent', NestedParent);
customElements.define('nested-child', NestedChild);
}
</script>
<nested-parent id="parent-element">
<nested-child id="child-element"></nested-child>
</nested-parent>
<script id="verify-nested">
{
// Both parent and child should have connectedCallback invoked
testing.expectEqual(1, window.nestedParentCount);
testing.expectEqual(1, window.nestedChildCount);
const parent = document.getElementById('parent-element');
const child = document.getElementById('child-element');
testing.expectTrue(parent !== null);
testing.expectTrue(child !== null);
}
</script>
<script>
{
// Test attributeChangedCallback for initial attributes during parsing
window.attrChangedCalls = [];
class AttrElement extends HTMLElement {
static get observedAttributes() {
return ['foo', 'bar'];
}
attributeChangedCallback(name, oldValue, newValue) {
window.attrChangedCalls.push({ name, oldValue, newValue });
}
}
customElements.define('attr-element', AttrElement);
}
</script>
<attr-element foo="value1" bar="value2" ignored="value3"></attr-element>
<script id="verify-attribute-changed">
{
// attributeChangedCallback should have been called for initial attributes
testing.expectEqual(2, window.attrChangedCalls.length);
testing.expectEqual('foo', window.attrChangedCalls[0].name);
testing.expectEqual(null, window.attrChangedCalls[0].oldValue);
testing.expectEqual('value1', window.attrChangedCalls[0].newValue);
testing.expectEqual('bar', window.attrChangedCalls[1].name);
testing.expectEqual(null, window.attrChangedCalls[1].oldValue);
testing.expectEqual('value2', window.attrChangedCalls[1].newValue);
}
</script>
</body>

Some files were not shown because too many files have changed in this diff Show More