2174 Commits

Author SHA1 Message Date
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   - same as chrome
2025-10-15 18:28:45 +08:00
Karl Seguin
b610aa1c0c Encode UTF8 non breakingspace (194, 160) as   - 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
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
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
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
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
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
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
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
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
Francis Bouvier
ed38705efd Basic version using Browser
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-04-19 11:57:44 +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
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
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
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
335 changed files with 57834 additions and 20804 deletions

View File

@@ -5,7 +5,7 @@ inputs:
zig:
description: 'Zig version to install'
required: false
default: '0.13.0'
default: '0.15.2'
arch:
description: 'CPU arch used to select the v8 lib'
required: false
@@ -17,11 +17,11 @@ inputs:
zig-v8:
description: 'zig v8 version to install'
required: false
default: 'v0.1.6'
default: 'v0.1.33'
v8:
description: 'v8 version to install'
required: false
default: '11.1.134'
default: '14.0.365.4'
cache-dir:
description: 'cache dir to use'
required: false
@@ -34,9 +34,11 @@ runs:
- name: Install apt deps
if: ${{ inputs.os == 'linux' }}
shell: bash
run: sudo apt-get install -y wget xz-utils python3 ca-certificates git pkg-config libglib2.0-dev gperf libexpat1-dev cmake clang
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
- uses: mlugg/setup-zig@v1
- uses: mlugg/setup-zig@v2
with:
version: ${{ inputs.zig }}
@@ -47,7 +49,7 @@ runs:
cache-name: cache-v8
with:
path: ${{ inputs.cache-dir }}/v8
key: libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}.a
key: libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}_${{ inputs.zig-v8 }}.a
- if: ${{ steps.cache-v8.outputs.cache-hit != 'true' }}
shell: bash
@@ -59,15 +61,29 @@ runs:
- name: install v8
shell: bash
run: |
mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug/libc_v8.a
mkdir -p v8/out/${{ inputs.os }}/debug/obj/zig/
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a v8/out/${{ inputs.os }}/debug/obj/zig/libc_v8.a
mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release/libc_v8.a
mkdir -p v8/out/${{ inputs.os }}/release/obj/zig/
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a v8/out/${{ inputs.os }}/release/obj/zig/libc_v8.a
- name: libiconv
- name: Cache libiconv
id: cache-libiconv
uses: actions/cache@v4
env:
cache-name: cache-libiconv
with:
path: ${{ inputs.cache-dir }}/libiconv
key: vendor/libiconv/libiconv-1.17
- name: download libiconv
if: ${{ steps.cache-libiconv.outputs.cache-hit != 'true' }}
shell: bash
run: make install-libiconv
run: make download-libiconv
- name: build libiconv
shell: bash
run: make build-libiconv
- name: build mimalloc
shell: bash

View File

@@ -1,5 +1,11 @@
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 }}
on:
schedule:
- cron: "2 2 * * *"
@@ -16,43 +22,13 @@ jobs:
ARCH: x86_64
OS: linux
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8 -Dcpu=x86_64
- name: Rename binary
run: mv zig-out/bin/browsercore-get lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
tag: nightly
build-macos-aarch64:
env:
ARCH: aarch64
OS: macos
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
@@ -62,14 +38,140 @@ jobs:
arch: ${{env.ARCH}}
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8
run: zig build --release=safe -Doptimize=ReleaseSafe -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
- name: Rename binary
run: mv zig-out/bin/browsercore-get lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
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-get-${{ env.ARCH }}-${{ env.OS }}
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
tag: nightly
build-linux-aarch64:
env:
ARCH: aarch64
OS: linux
runs-on: ubuntu-22.04-arm
timeout-minutes: 15
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}}
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -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: nightly
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: 15
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}}
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -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: nightly
build-macos-x86_64:
env:
ARCH: x86_64
OS: macos
# macos-13 runs on x86 CPU. see
# https://github.com/actions/runner-images?tab=readme-ov-file
# If we want to build for macos-14 or superior, we need to switch to
# macos-14-large.
# No need for now, but maybe we will need it in the short term.
runs-on: macos-13
timeout-minutes: 15
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}}
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -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: nightly

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

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

@@ -0,0 +1,232 @@
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@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 -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: 27000
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

View File

@@ -5,66 +5,33 @@ env:
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"
- "tests/wpt/**"
- "vendor/**"
- ".github/**"
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:
- ".github/**"
- "build.zig"
- "src/**/*.zig"
- "src/*.zig"
- "tests/wpt/**"
- "vendor/**"
- ".github/**"
# 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
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- 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
run: zig build wpt -- --json > wpt.json
- name: write commit
run: |
@@ -83,10 +50,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:

View File

@@ -1,7 +1,7 @@
name: zig-fmt
env:
ZIG_VERSION: 0.13.0
ZIG_VERSION: 0.15.2
on:
pull_request:
@@ -29,9 +29,10 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: mlugg/setup-zig@v1
- uses: mlugg/setup-zig@v2
with:
version: ${{ env.ZIG_VERSION }}

View File

@@ -16,6 +16,7 @@ on:
- "src/*.zig"
- "vendor/zig-js-runtime"
- ".github/**"
- "vendor/**"
pull_request:
# By default GH trigger on types opened, synchronize and reopened.
@@ -32,6 +33,7 @@ on:
- "src/*.zig"
- "vendor/**"
- ".github/**"
- "vendor/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -48,38 +50,44 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- name: zig build debug
run: zig build -Dengine=v8
run: zig build
zig-build-release:
name: zig build release
- name: upload artifact
uses: actions/upload-artifact@v4
with:
name: lightpanda-build-dev
path: |
zig-out/bin/lightpanda
retention-days: 1
# Don't run the CI on PR
if: github.event_name != 'pull_request'
browser-fetch:
name: browser fetch
needs: zig-build-dev
# Don't run the CI with draft PR.
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: download artifact
uses: actions/download-artifact@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
name: lightpanda-build-dev
- uses: ./.github/actions/install
- run: chmod a+x ./lightpanda
- name: zig build release
run: zig build -Doptimize=ReleaseSafe -Dengine=v8
- run: ./lightpanda fetch https://httpbin.io/xhr/get
zig-test:
name: zig test
timeout-minutes: 15
# Don't run the CI with draft PR.
if: github.event.pull_request.draft == false
@@ -90,14 +98,13 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- name: zig build test
run: zig build test -Dengine=v8 -- --json > bench.json
run: zig build test -- --json > bench.json
- name: write commit
run: |
@@ -120,6 +127,8 @@ jobs:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: ghcr.io/lightpanda-io/perf-fmt:latest
credentials:

11
.gitignore vendored
View File

@@ -1,7 +1,6 @@
zig-cache
/.zig-cache/
zig-out
/vendor/netsurf/build/
/vendor/netsurf/lib/
/vendor/netsurf/include/
/vendor/libiconv/
/zig-out/
lightpanda.id
/v8/
/build/
src/html5ever/target/

39
.gitmodules vendored
View File

@@ -1,27 +1,18 @@
[submodule "vendor/zig-js-runtime"]
path = vendor/zig-js-runtime
url = git@github.com:lightpanda-io/zig-js-runtime.git
[submodule "vendor/netsurf/libwapcaplet"]
path = vendor/netsurf/libwapcaplet
url = git@github.com:lightpanda-io/libwapcaplet.git
[submodule "vendor/netsurf/libparserutils"]
path = vendor/netsurf/libparserutils
url = git@github.com:lightpanda-io/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 = git@github.com:lightpanda-io/libhubbub.git
[submodule "tests/wpt"]
path = tests/wpt
url = https://github.com/lightpanda-io/wpt
[submodule "vendor/mimalloc"]
path = vendor/mimalloc
url = git@github.com:microsoft/mimalloc.git
[submodule "vendor/tls.zig"]
path = vendor/tls.zig
url = git@github.com:ianic/tls.zig.git
[submodule "vendor/nghttp2"]
path = vendor/nghttp2
url = https://github.com/nghttp2/nghttp2.git
[submodule "vendor/mbedtls"]
path = vendor/mbedtls
url = https://github.com/Mbed-TLS/mbedtls.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).

68
Dockerfile Normal file
View File

@@ -0,0 +1,68 @@
FROM debian:stable
ARG MINISIG=0.12
ARG ZIG=0.15.2
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
ARG V8=14.0.365.4
ARG ZIG_V8=v0.1.33
ARG TARGETPLATFORM
RUN apt-get update -yq && \
apt-get install -yq xz-utils \
python3 ca-certificates git \
pkg-config libglib2.0-dev \
gperf libexpat1-dev \
cmake clang \
curl git
# 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
# install zig
RUN 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
# clone lightpanda
RUN git clone https://github.com/lightpanda-io/browser.git
WORKDIR /browser
# install deps
RUN git submodule init && \
git submodule update --recursive
RUN make install-libiconv && \
make install-netsurf && \
make install-mimalloc
# 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/out/linux/release/obj/zig/ && \
mv libc_v8.a v8/out/linux/release/obj/zig/libc_v8.a
# build release
RUN make build
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
EXPOSE 9222/tcp
CMD ["/bin/lightpanda", "serve", "--host", "0.0.0.0", "--port", "9222"]

22
LICENSING.md Normal file
View File

@@ -0,0 +1,22 @@
# 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).
## MIT
The following files are licensed under MIT:
```
src/polyfill/fetch.js
```
The following directories and their subdirectories are licensed under their
original upstream licenses:
```
vendor/
tests/wpt/
```

216
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
# -----
@@ -23,33 +47,15 @@ help:
# $(ZIG) commands
# ------------
.PHONY: build build-dev run run-release shell test bench download-zig wpt
.PHONY: build build-dev run run-release shell test bench download-zig wpt data get-v8 build-v8 build-v8-dev
.PHONY: end2end
zig_version = $(shell grep 'recommended_zig_version = "' "vendor/zig-js-runtime/build.zig" | cut -d'"' -f2)
kernel = $(shell uname -ms)
## 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")
$(eval url = "https://ziglang.org/download/$(zig_version)/zig-$(OS)-$(ARCH)-$(zig_version).tar.xz")
$(eval dest = "/tmp/zig-$(OS)-$(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"
@@ -57,155 +63,87 @@ endif
## Build in release-safe mode
build:
@printf "\e[36mBuilding (release safe)...\e[0m\n"
@$(ZIG) build -Doptimize=ReleaseSafe -Dengine=v8 || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
$(ZIG) build -Doptimize=ReleaseSafe -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\e[33mBuild OK\e[0m\n"
## Build in debug mode
build-dev:
@printf "\e[36mBuilding (debug)...\e[0m\n"
@$(ZIG) build -Dengine=v8 || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@$(ZIG) build -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
@printf "\e[33mBuild OK\e[0m\n"
## Run the server in debug mode
## 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;)
@./zig-out/bin/lightpanda || (printf "\e[33mRun ERROR\e[0m\n"; exit 1;)
## Run the server in debug mode
run-debug: build-dev
@printf "\e[36mRunning...\e[0m\n"
@./zig-out/bin/lightpanda || (printf "\e[33mRun ERROR\e[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;)
@$(ZIG) build shell || (printf "\e[33mBuild ERROR\e[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;)
@$(ZIG) build wpt -- $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[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;)
@$(ZIG) build wpt -- --summary $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[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 --summary all' 2>&1 \
| grep --line-buffered -v "^/.*zig test -freference-trace"
else
test:
@script -qec 'TEST_FILTER="${F}" $(ZIG) build test -freference-trace --summary all' /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
## v8
get-v8:
@printf "\e[36mGetting v8 source...\e[0m\n"
@$(ZIG) build get-v8
build-v8-dev:
@printf "\e[36mBuilding v8 (dev)...\e[0m\n"
@$(ZIG) build build-v8
build-v8:
@printf "\e[36mBuilding v8...\e[0m\n"
@$(ZIG) build -Doptimize=ReleaseSafe build-v8
# Install and build required dependencies commands
# ------------
.PHONY: install-submodule
.PHONY: install-zig-js-runtime install-zig-js-runtime-dev install-libiconv
.PHONY: _install-netsurf install-netsurf clean-netsurf test-netsurf install-netsurf-dev
.PHONY: install-mimalloc install-mimalloc-dev clean-mimalloc
.PHONY: install-dev install
.PHONY: install-html5ever install-html5ever-dev
.PHONY: install install-dev
## Install and build dependencies for release
install: install-submodule install-zig-js-runtime install-netsurf install-mimalloc
install: install-submodule install-html5ever
## Install and build dependencies for dev
install-dev: install-submodule install-zig-js-runtime-dev install-netsurf-dev install-mimalloc-dev
install-dev: install-submodule install-html5ever-dev
install-netsurf-dev: _install-netsurf
install-netsurf-dev: OPTCFLAGS := -O0 -g -DNDEBUG
install-html5ever:
cd src/html5ever && cargo build --release --target-dir ../../build/html5ever/
install-netsurf: _install-netsurf
install-netsurf: OPTCFLAGS := -DNDEBUG
install-html5ever-dev:
cd src/html5ever && cargo build --target-dir ../../build/html5ever/
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-zig-js-runtime-dev:
@cd vendor/zig-js-runtime && \
make install-dev
install-zig-js-runtime:
@cd vendor/zig-js-runtime && \
make install
.PHONY: _build_mimalloc
_build_mimalloc:
@cd vendor/mimalloc && \
mkdir -p out/include && \
cp include/mimalloc.h out/include/ && \
cd out && \
cmake -DMI_BUILD_SHARED=OFF -DMI_BUILD_OBJECT=OFF -DMI_BUILD_TESTS=OFF -DMI_OVERRIDE=OFF $(OPTS) .. && \
make
install-mimalloc-dev: _build_mimalloc
install-mimalloc-dev: OPTS=-DCMAKE_BUILD_TYPE=Debug
install-mimalloc-dev:
@cd vendor/mimalloc/out && \
mv libmimalloc-debug.a libmimalloc.a
install-mimalloc: _build_mimalloc
clean-mimalloc:
@rm -fr vendor/mimalloc/lib/*
data:
cd src/data && go run public_suffix_list_gen.go > public_suffix_list.zig
## Init and update git submodule
install-submodule:

246
README.md
View File

@@ -2,85 +2,174 @@
<a href="https://lightpanda.io"><img src="https://cdn.lightpanda.io/assets/images/logo/lpd-logo.png" alt="Logo" height=170></a>
</p>
<h1 align="center">Lightpanda</h1>
<h1 align="center">Lightpanda Browser</h1>
<p align="center"><a href="https://lightpanda.io/">lightpanda.io</a></p>
<div align="center">
<br />
[![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 the Web APIs (partial, WIP)
- Compatible with Playwright, Puppeteer through CDP (WIP)
- Support of Web APIs (partial, WIP)
- Compatible with Playwright[^1], Puppeteer, chromedp through CDP
Fast scraping and web automation with minimal memory footprint:
Fast web automation for AI agents, LLM training, scraping and testing:
- Ultra-low memory footprint (12x less than Chrome)
- Blazingly fast & instant startup (64x faster than Chrome)
- Ultra-low memory footprint (9x less than Chrome)
- Exceptionally fast execution (11x faster than Chrome)
- Instant startup
<img width=500px src="https://cdn.lightpanda.io/assets/images/benchmark2.png">
[<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>
See [benchmark details](https://github.com/lightpanda-io/demo).
_Puppeteer requesting 100 pages from a local website on a AWS EC2 m5.large instance.
See [benchmark details](https://github.com/lightpanda-io/demo)._
## Why?
[^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.
### Javascript execution is mandatory for the modern web
## Quick start
Back in the good old times, grabbing a webpage was as easy as making an HTTP request, cURL-like. Its not possible anymore, because Javascript is everywhere, like it or not:
### Install
**Install from the nightly builds**
- Ajax, Single Page App, Infinite loading, “click to display”, instant search, etc.
- JS web frameworks: React, Vue, Angular & others
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.
### Chrome is not the right tool
*For Linux*
```console
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux && \
chmod a+x ./lightpanda
```
So if we need Javascript, why not use a real web browser. Lets take a huge desktop application, hack it, and run it on the server, right? Hundreds of instance of Chrome if you use it at scale. Are you sure its such a good idea?
*For MacOS*
```console
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-aarch64-macos && \
chmod a+x ./lightpanda
```
- 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
*For Windows + WSL2*
### Lightpanda is built for performance
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.
If we want both Javascript and performance, for a real headless browser, we need to start from scratch. Not yet another iteration of Chromium, really from a blank page. Crazy right? But thats we did:
**Install from Docker**
- Not based on Chromium, Blink or WebKit
- Low-level system programming language (Zig) with optimisations in mind
- Opinionated, no rendering
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 still a work in progress and is currently at the Alpha stage.
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 want to implement before releasing a Beta version:
Here are the key features we have implemented:
- [x] Loader
- [x] HTML parser and DOM tree
- [x] Javascript support
- [x] Basic DOM APIs
- [x] HTTP loader (based on Libcurl)
- [x] HTML parser and DOM tree (based on Netsurf libs)
- [x] Javascript support (v8)
- [x] DOM APIs
- [x] Ajax
- [x] XHR API
- [ ] Fetch API
- [x] Fetch API (polyfill)
- [x] DOM dump
- [ ] Basic CDP server
- [x] CDP/websockets server
- [x] Click
- [x] Input form
- [x] Cookies
- [x] Custom HTTP headers
- [x] Proxy support
- [x] Network interception
We will not provide binary versions until we reach at least the Beta stage.
NOTE: There are hundreds of Web APIs. Developing a browser, even just for headless mode, is a huge task. It's more about coverage than a _working/not working_ binary situation.
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
We do not provide yet binary versions of Lightpanda, you have to compile it from source.
### Prerequisites
Lightpanda is written with [Zig](https://ziglang.org/) `0.13.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.
Lightpanda also depends on
[zig-js-runtime](https://github.com/lightpanda-io/zig-js-runtime/) (with v8),
[Libcurl](https://curl.se/libcurl/),
[Netsurf libs](https://www.netsurf-browser.org/) and
[Mimalloc](https://microsoft.github.io/mimalloc).
@@ -92,10 +181,15 @@ For Debian/Ubuntu based Linux:
sudo apt install xz-utils \
python3 ca-certificates git \
pkg-config libglib2.0-dev \
gperf libexpat1-dev \
gperf libexpat1-dev unzip rsync \
cmake clang
```
For systems with [Nix](https://nixos.org/download/), you can use the devShell:
```
nix develop
```
For MacOS, you only need cmake:
```
@@ -108,9 +202,9 @@ brew install cmake
You can run `make install` to install deps all in one (or `make install-dev` if you need the development versions).
Be aware that the build task is very long and cpu consuming, as you will build from sources all dependancies, including the v8 Javascript engine.
Be aware that the build task is very long and cpu consuming, as you will build from sources all dependencies, including the v8 Javascript engine.
#### Step by step build dependancy
#### Step by step build dependency
The project uses git submodules for dependencies.
@@ -120,6 +214,14 @@ To init or update the submodules in the `vendor/` directory:
make install-submodule
```
**iconv**
libiconv is an internationalization library used by Netsurf.
```
make install-libiconv
```
**Netsurf libs**
Netsurf libs are used for HTML parsing and DOM tree generation.
@@ -144,17 +246,21 @@ Note: when Mimalloc is built in dev mode, you can dump memory stats with the
env var `MIMALLOC_SHOW_STATS=1`. See
[https://microsoft.github.io/mimalloc/environment.html](https://microsoft.github.io/mimalloc/environment.html).
**zig-js-runtime**
**v8**
Our own Zig/Javascript runtime, which includes the v8 Javascript engine.
This build task is very long and cpu consuming, as you will build v8 from sources.
First, get the tools necessary for building V8, as well as the V8 source code:
```
make install-zig-js-runtime
make get-v8
```
For dev env, use `make iinstall-zig-js-runtime-dev`.
Next, build v8. This build task is very long and cpu consuming, as you will build v8 from sources.
```
make build-v8
```
For dev env, use `make build-v8-dev`.
## Test
@@ -162,6 +268,20 @@ For dev env, use `make iinstall-zig-js-runtime-dev`.
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
Lightpanda is tested against the standardized [Web Platform
@@ -196,3 +316,35 @@ 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 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

932
build.zig
View File

@@ -17,18 +17,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const builtin = @import("builtin");
const jsruntime_path = "vendor/zig-js-runtime/";
const jsruntime = @import("vendor/zig-js-runtime/build.zig");
const jsruntime_pkgs = jsruntime.packages(jsruntime_path);
const Build = std.Build;
/// 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;
const recommended_zig_version = "0.15.2";
pub fn build(b: *std.Build) !void {
pub fn build(b: *Build) !void {
switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) {
.eq => {},
.lt => {
@@ -42,198 +39,761 @@ pub fn build(b: *std.Build) !void {
},
}
const target = b.standardTargetOptions(.{});
const mode = b.standardOptimizeOption(.{});
const options = try jsruntime.buildOptions(b);
const x86 = b.option(bool, "x86", "Use x86 backend") orelse false;
// browser
// -------
// compile and install
const exe = b.addExecutable(.{
.name = "browsercore",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, exe, options);
b.installArtifact(exe);
// run
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
// step
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// shell
// -----
// compile and install
const shell = b.addExecutable(.{
.name = "browsercore-shell",
.root_source_file = b.path("src/main_shell.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, shell, options);
try jsruntime_pkgs.add_shell(shell);
// run
const shell_cmd = b.addRunArtifact(shell);
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 = b.path("src/run_tests.zig"),
.test_runner = b.path("src/test_runner.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, tests, options);
// add jsruntime pretty deps
tests.root_module.addAnonymousImport("pretty", .{
.root_source_file = b.path("vendor/zig-js-runtime/src/pretty.zig"),
});
const run_tests = b.addRunArtifact(tests);
if (b.args) |args| {
run_tests.addArgs(args);
}
// 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 = b.path("src/main_wpt.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, wpt, options);
// run
const wpt_cmd = b.addRunArtifact(wpt);
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 = b.path("src/main_get.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, get, options);
b.installArtifact(get);
// run
const get_cmd = b.addRunArtifact(get);
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(
b: *std.Build,
step: *std.Build.Step.Compile,
options: jsruntime.Options,
) !void {
const jsruntimemod = try jsruntime_pkgs.module(
b,
options,
step.root_module.optimize.?,
step.root_module.resolved_target.?,
var opts = b.addOptions();
opts.addOption(
[]const u8,
"git_commit",
b.option([]const u8, "git_commit", "Current git commit") orelse "dev",
);
step.root_module.addImport("jsruntime", jsruntimemod);
const netsurf = moduleNetSurf(b);
netsurf.addImport("jsruntime", jsruntimemod);
step.root_module.addImport("netsurf", netsurf);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const tlsmod = b.addModule("tls", .{
.root_source_file = b.path("vendor/tls.zig/src/main.zig"),
});
step.root_module.addImport("tls", tlsmod);
}
const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer");
const enable_csan = b.option(std.zig.SanitizeC, "csan", "Enable C Sanitizers");
fn moduleNetSurf(b: *std.Build) *std.Build.Module {
const mod = b.addModule("netsurf", .{
.root_source_file = b.path("src/netsurf/netsurf.zig"),
});
// iconv
mod.addObjectFile(b.path("vendor/libiconv/lib/libiconv.a"));
mod.addIncludePath(b.path("vendor/libiconv/include"));
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,
});
// mimalloc
mod.addImport("mimalloc", moduleMimalloc(b));
try addDependencies(b, mod, opts);
// netsurf libs
const ns = "vendor/netsurf";
mod.addIncludePath(b.path(ns ++ "/include"));
if (optimize == .ReleaseFast or optimize == .ReleaseSmall) {
mod.addLibraryPath(b.path("build/html5ever/release"));
} else {
mod.addLibraryPath(b.path("build/html5ever/debug"));
}
mod.linkSystemLibrary("litefetch_html5ever", .{});
const libs: [4][]const u8 = .{
"libdom",
"libhubbub",
"libparserutils",
"libwapcaplet",
break :blk mod;
};
inline for (libs) |lib| {
mod.addObjectFile(b.path(ns ++ "/lib/" ++ lib ++ ".a"));
mod.addIncludePath(b.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);
}
return mod;
{
// test
const tests = b.addTest(.{
.root_module = lightpanda_module,
.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);
}
{
// 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);
}
{
// get v8
// -------
const v8 = b.dependency("v8", .{ .target = target, .optimize = optimize });
const get_v8 = b.addRunArtifact(v8.artifact("get-v8"));
const get_step = b.step("get-v8", "Get v8");
get_step.dependOn(&get_v8.step);
}
{
// build v8
// -------
const v8 = b.dependency("v8", .{ .target = target, .optimize = optimize });
const build_v8 = b.addRunArtifact(v8.artifact("build-v8"));
const build_step = b.step("build-v8", "Build v8");
build_step.dependOn(&build_v8.step);
}
}
fn moduleMimalloc(b: *std.Build) *std.Build.Module {
const mod = b.addModule("mimalloc", .{
.root_source_file = b.path("src/mimalloc/mimalloc.zig"),
fn addDependencies(b: *Build, mod: *Build.Module, opts: *Build.Step.Options) !void {
mod.addImport("build_config", opts.createModule());
const target = mod.resolved_target.?;
const dep_opts = .{
.target = target,
.optimize = mod.optimize.?,
};
mod.addIncludePath(b.path("vendor/lightpanda"));
{
// 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);
const release_dir = if (mod.optimize.? == .Debug) "debug" else "release";
const os = switch (target.result.os.tag) {
.linux => "linux",
.macos => "macos",
else => return error.UnsupportedPlatform,
};
var lib_path = try std.fmt.allocPrint(
mod.owner.allocator,
"v8/out/{s}/{s}/obj/zig/libc_v8.a",
.{ os, release_dir },
);
std.fs.cwd().access(lib_path, .{}) catch {
// legacy path
lib_path = try std.fmt.allocPrint(
mod.owner.allocator,
"v8/out/{s}/obj/zig/libc_v8.a",
.{release_dir},
);
};
mod.addObjectFile(mod.owner.path(lib_path));
switch (target.result.os.tag) {
.macos => {
// v8 has a dependency, abseil-cpp, which, on Mac, uses CoreFoundation
mod.addSystemFrameworkPath(.{ .cwd_relative = "/System/Library/Frameworks" });
mod.linkFramework("CoreFoundation", .{});
},
else => {},
}
}
{
//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_MBEDTLS", "1");
mod.addCMacro("USE_THREADS_POSIX", "1");
mod.addCMacro("USE_UNIX_SOCKETS", "1");
}
try buildZlib(b, mod);
try buildBrotli(b, mod);
try buildMbedtls(b, mod);
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,
});
mod.addObjectFile(b.path("vendor/mimalloc/out/libmimalloc.a"));
mod.addIncludePath(b.path("vendor/mimalloc/out/include"));
return mod;
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 buildMbedtls(b: *Build, m: *Build.Module) !void {
const mbedtls = b.addLibrary(.{
.name = "mbedtls",
.root_module = m,
});
const root = "vendor/mbedtls/";
mbedtls.addIncludePath(b.path(root ++ "include"));
mbedtls.addIncludePath(b.path(root ++ "library"));
mbedtls.addCSourceFiles(.{ .flags = &.{}, .files = &.{
root ++ "library/aes.c",
root ++ "library/aesni.c",
root ++ "library/aesce.c",
root ++ "library/aria.c",
root ++ "library/asn1parse.c",
root ++ "library/asn1write.c",
root ++ "library/base64.c",
root ++ "library/bignum.c",
root ++ "library/bignum_core.c",
root ++ "library/bignum_mod.c",
root ++ "library/bignum_mod_raw.c",
root ++ "library/camellia.c",
root ++ "library/ccm.c",
root ++ "library/chacha20.c",
root ++ "library/chachapoly.c",
root ++ "library/cipher.c",
root ++ "library/cipher_wrap.c",
root ++ "library/constant_time.c",
root ++ "library/cmac.c",
root ++ "library/ctr_drbg.c",
root ++ "library/des.c",
root ++ "library/dhm.c",
root ++ "library/ecdh.c",
root ++ "library/ecdsa.c",
root ++ "library/ecjpake.c",
root ++ "library/ecp.c",
root ++ "library/ecp_curves.c",
root ++ "library/entropy.c",
root ++ "library/entropy_poll.c",
root ++ "library/error.c",
root ++ "library/gcm.c",
root ++ "library/hkdf.c",
root ++ "library/hmac_drbg.c",
root ++ "library/lmots.c",
root ++ "library/lms.c",
root ++ "library/md.c",
root ++ "library/md5.c",
root ++ "library/memory_buffer_alloc.c",
root ++ "library/nist_kw.c",
root ++ "library/oid.c",
root ++ "library/padlock.c",
root ++ "library/pem.c",
root ++ "library/pk.c",
root ++ "library/pk_ecc.c",
root ++ "library/pk_wrap.c",
root ++ "library/pkcs12.c",
root ++ "library/pkcs5.c",
root ++ "library/pkparse.c",
root ++ "library/pkwrite.c",
root ++ "library/platform.c",
root ++ "library/platform_util.c",
root ++ "library/poly1305.c",
root ++ "library/psa_crypto.c",
root ++ "library/psa_crypto_aead.c",
root ++ "library/psa_crypto_cipher.c",
root ++ "library/psa_crypto_client.c",
root ++ "library/psa_crypto_ffdh.c",
root ++ "library/psa_crypto_driver_wrappers_no_static.c",
root ++ "library/psa_crypto_ecp.c",
root ++ "library/psa_crypto_hash.c",
root ++ "library/psa_crypto_mac.c",
root ++ "library/psa_crypto_pake.c",
root ++ "library/psa_crypto_rsa.c",
root ++ "library/psa_crypto_se.c",
root ++ "library/psa_crypto_slot_management.c",
root ++ "library/psa_crypto_storage.c",
root ++ "library/psa_its_file.c",
root ++ "library/psa_util.c",
root ++ "library/ripemd160.c",
root ++ "library/rsa.c",
root ++ "library/rsa_alt_helpers.c",
root ++ "library/sha1.c",
root ++ "library/sha3.c",
root ++ "library/sha256.c",
root ++ "library/sha512.c",
root ++ "library/threading.c",
root ++ "library/timing.c",
root ++ "library/version.c",
root ++ "library/version_features.c",
root ++ "library/pkcs7.c",
root ++ "library/x509.c",
root ++ "library/x509_create.c",
root ++ "library/x509_crl.c",
root ++ "library/x509_crt.c",
root ++ "library/x509_csr.c",
root ++ "library/x509write.c",
root ++ "library/x509write_crt.c",
root ++ "library/x509write_csr.c",
root ++ "library/debug.c",
root ++ "library/mps_reader.c",
root ++ "library/mps_trace.c",
root ++ "library/net_sockets.c",
root ++ "library/ssl_cache.c",
root ++ "library/ssl_ciphersuites.c",
root ++ "library/ssl_client.c",
root ++ "library/ssl_cookie.c",
root ++ "library/ssl_debug_helpers_generated.c",
root ++ "library/ssl_msg.c",
root ++ "library/ssl_ticket.c",
root ++ "library/ssl_tls.c",
root ++ "library/ssl_tls12_client.c",
root ++ "library/ssl_tls12_server.c",
root ++ "library/ssl_tls13_keys.c",
root ++ "library/ssl_tls13_server.c",
root ++ "library/ssl_tls13_client.c",
root ++ "library/ssl_tls13_generic.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.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/mbedtls.c",
root ++ "lib/vtls/mbedtls_threadlock.c",
root ++ "lib/vtls/vtls.c",
root ++ "lib/vtls/vtls_scache.c",
root ++ "lib/vtls/x509asn1.c",
},
});
}

13
build.zig.zon Normal file
View File

@@ -0,0 +1,13 @@
.{
.name = .browser,
.paths = .{""},
.version = "0.0.0",
.fingerprint = 0xda130f3af836cea0,
.dependencies = .{
.v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/305bb3706716d32d59b2ffa674731556caa1002b.tar.gz",
.hash = "v8-0.0.0-xddH63bVAwBSEobaUok9J0er1FqsvEujCDDVy6ItqKQ5",
},
//.v8 = .{ .path = "../zig-v8-fork" }
},
}

180
flake.lock generated Normal file
View File

@@ -0,0 +1,180 @@
{
"nodes": {
"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": 1756822655,
"narHash": "sha256-xQAk8xLy7srAkR5NMZFsQFioL02iTHuuEIs3ohGpgdk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4bdac60bfe32c41103ae500ddf894c258291dd61",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"zigPkgs": "zigPkgs",
"zlsPkg": "zlsPkg"
}
},
"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": 1756555914,
"narHash": "sha256-7yoSPIVEuL+3Wzf6e7NHuW3zmruHizRrYhGerjRHTLI=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "d0df3a2fd0f11134409d6d5ea0e510e5e477f7d6",
"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
}

77
flake.nix Normal file
View File

@@ -0,0 +1,77 @@
{
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";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
nixpkgs,
zigPkgs,
zlsPkg,
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;
};
# 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
python3
pkg-config
cmake
gperf
# GCC
gcc
gcc.cc.lib
crtFiles
# Libaries
expat.dev
glib.dev
glibc.dev
zlib
zlib.dev
];
};
in
{
devShells.default = fhs.env;
}
);
}

109
src/App.zig Normal file
View File

@@ -0,0 +1,109 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const log = @import("log.zig");
const Http = @import("http/Http.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,
telemetry: Telemetry,
allocator: Allocator,
app_dir_path: ?[]const u8,
notification: *Notification,
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.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 {
const allocator = self.allocator;
if (self.app_dir_path) |app_dir_path| {
allocator.free(app_dir_path);
}
self.telemetry.deinit();
self.notification.deinit();
self.http.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;
}

386
src/Notification.zig Normal file
View File

@@ -0,0 +1,386 @@
const std = @import("std");
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 {
timestamp: u64,
url: []const u8,
opts: Page.NavigateOpts,
};
pub const PageNavigated = struct {
timestamp: u64,
url: []const u8,
};
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));
std.debug.assert(removed == true);
}
}
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, &.{
.timestamp = 4,
.url = undefined,
.opts = .{},
});
var tc = TestClient{};
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
notifier.dispatch(.page_navigate, &.{
.timestamp = 4,
.url = undefined,
.opts = .{},
});
try testing.expectEqual(4, tc.page_navigate);
notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{
.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, &.{
.timestamp = 10,
.url = undefined,
.opts = .{},
});
notifier.dispatch(.page_navigated, &.{ .timestamp = 6, .url = undefined });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{
.timestamp = 100,
.url = undefined,
.opts = .{},
});
notifier.dispatch(.page_navigated, &.{ .timestamp = 100, .url = undefined });
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, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(1006, tc.page_navigated);
notifier.unregister(.page_navigate, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
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, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
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;
}
};

88
src/Scheduler.zig Normal file
View File

@@ -0,0 +1,88 @@
const std = @import("std");
const log = @import("log.zig");
const timestamp = @import("datetime.zig").milliTimestamp;
const Queue = std.PriorityQueue(Task, void, struct {
fn compare(_: void, a: Task, b: Task) std.math.Order {
return std.math.order(a.run_at, b.run_at);
}
}.compare);
const Scheduler = @This();
low_priority: Queue,
high_priority: Queue,
pub fn init(allocator: std.mem.Allocator) Scheduler {
return .{
.low_priority = Queue.init(allocator, {}),
.high_priority = Queue.init(allocator, {}),
};
}
pub fn reset(self: *Scheduler) void {
self.low_priority.cap = 0;
self.low_priority.items.len = 0;
self.high_priority.cap = 0;
self.high_priority.items.len = 0;
}
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 {
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;
return queue.add(.{
.ctx = ctx,
.callback = cb,
.name = opts.name,
.run_at = timestamp(.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 = timestamp(.monotonic);
while (queue.peek()) |*task_| {
if (task_.run_at > now) {
return @intCast(task_.run_at - now);
}
var task = queue.remove();
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
std.debug.assert(ms != 0);
task.run_at = now + ms;
try self.low_priority.add(task);
}
}
return null;
}
const Task = struct {
run_at: u64,
ctx: *anyopaque,
name: []const u8,
callback: Callback,
};
const Callback = *const fn (ctx: *anyopaque) anyerror!?u32;

1415
src/Server.zig Normal file

File diff suppressed because it is too large Load Diff

119
src/TestHTTPServer.zig Normal file
View File

@@ -0,0 +1,119 @@
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";
}
std.debug.print("TestHTTPServer asked to serve an unknown file type: {s}\n", .{file_path});
return "text/html";
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,133 +0,0 @@
// 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 posix = std.posix;
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: posix.socket_t,
pub fn close(self: Stream) void {
posix.close(self.handle);
self.alloc.destroy(self.conn);
}
pub const ReadError = posix.ReadError;
pub const WriteError = posix.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 posix.iovec) ReadError!usize {
return posix.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 posix.iovec_const) WriteError!usize {
if (iovecs.len == 0) return 0;
const first_buffer = iovecs[0].base[0..iovecs[0].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: []posix.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].len) {
amt -= iovecs[i].len;
i += 1;
if (i >= iovecs.len) return;
}
iovecs[i].base += amt;
iovecs[i].len -= amt;
}
}
};

View File

@@ -1,112 +0,0 @@
// 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 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.posix.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.posix.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.posix.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.posix.ConnectError.ConnectionRefused;
}
pub fn tcpConnectToAddress(alloc: std.mem.Allocator, loop: *Loop, addr: net.Address) !Stream {
const sockfd = try std.posix.socket(addr.any.family, std.posix.SOCK.STREAM, std.posix.IPPROTO.TCP);
errdefer std.posix.close(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,189 +0,0 @@
// 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 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);
const res = try client.fetch(.{
.location = .{ .uri = try std.Uri.parse(url) },
});
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 buf: [2014]u8 = undefined;
var req = try client.open(.GET, try std.Uri.parse(url), .{
.server_header_buffer = &buf,
});
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,
req: ?Request = undefined,
state: State = .new,
impl: YieldImpl,
err: ?anyerror = null,
buf: [2014]u8 = undefined,
pub fn deinit(self: *AsyncRequest) void {
if (self.req) |*r| r.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, .{
.server_header_buffer = &self.buf,
}) 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,
};
}
};
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();
}
}

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

@@ -0,0 +1,115 @@
// 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 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;
const env = try js.Env.init(allocator, &app.platform, .{});
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()) {
log.debug(.browser, "pumpMessageLoop", .{});
}
self.env.runIdleTasks();
}
const testing = @import("../testing.zig");
test "Browser" {
try testing.htmlRunner("browser.html", .{});
}

View File

@@ -0,0 +1,294 @@
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),
lookup: std.AutoHashMapUnmanaged(usize, std.DoublyLinkedList),
pub fn init(page: *Page) EventManager {
return .{
.page = page,
.lookup = .{},
.arena = page.arena,
.listener_pool = std.heap.MemoryPool(Listener).init(page.arena),
};
}
pub const RegisterOptions = struct {
once: bool = false,
capture: bool = false,
passive: bool = false,
signal: ?*@import("webapi/AbortSignal.zig") = null,
};
pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, function: js.Function, opts: RegisterOptions) !void {
if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.register", .{ .type = typ, .capture = opts.capture, .once = opts.once });
}
// 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 functions already registered
var node = gop.value_ptr.first;
while (node) |n| {
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
if (listener.function.eql(function) and listener.capture == opts.capture) {
return;
}
node = n.next;
}
} else {
gop.value_ptr.* = .{};
}
const listener = try self.listener_pool.create();
listener.* = .{
.node = .{},
.once = opts.once,
.capture = opts.capture,
.passive = opts.passive,
.function = .{ .value = function },
.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, function: js.Function, use_capture: bool) void {
const list = self.lookup.getPtr(@intFromPtr(target)) orelse return;
if (findListener(list, typ, function, 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;
switch (target._type) {
.node => |node| try self.dispatchNode(node, event),
.xhr, .window, .abort_signal => {
const list = self.lookup.getPtr(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event);
},
}
}
// 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;
}
if (function_) |func| {
event._current_target = target;
func.call(void, .{event}) catch |err| {
// a non-JS error
log.warn(.event, opts.context, .{ .err = err });
};
}
const list = self.lookup.getPtr(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event);
}
fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
var path_len: usize = 0;
var path_buffer: [128]*EventTarget = undefined;
var node: ?*Node = target;
while (node) |n| : (node = n._parent) {
if (path_len >= path_buffer.len) break;
path_buffer[path_len] = n.asEventTarget();
path_len += 1;
}
// Even though the window isn't part of the DOM, events always propagate
// through it in the capture phase
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.getPtr(@intFromPtr(current_target))) |list| {
try self.dispatchPhase(list, current_target, event, true);
if (event._stop_propagation) {
event._event_phase = .none;
return;
}
}
}
// Phase 2: At target
event._event_phase = .at_target;
const target_et = target.asEventTarget();
if (self.lookup.getPtr(@intFromPtr(target_et))) |list| {
try self.dispatchPhase(list, target_et, event, null);
if (event._stop_propagation) {
event._event_phase = .none;
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.getPtr(@intFromPtr(current_target))) |list| {
try self.dispatchPhase(list, current_target, event, false);
if (event._stop_propagation) {
break;
}
}
}
}
event._event_phase = .none;
}
fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, comptime capture_only: ?bool) !void {
const page = self.page;
const typ = event._type_string;
var node = list.first;
while (node) |n| {
// do this now, in case we need to remove n (once: true or aborted signal)
node = n.next;
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
if (!listener.typ.eql(typ)) {
continue;
}
// Can be null when dispatching to the target itself
if (comptime capture_only) |capture| {
if (listener.capture != capture) {
continue;
}
}
// If the listener has an aborted signal, remove it and skip
if (listener.signal) |signal| {
if (signal.getAborted()) {
self.removeListener(list, listener);
continue;
}
}
event._current_target = current_target;
switch (listener.function) {
.value => |value| try value.call(void, .{event}),
.string => |string| {
const str = try page.call_arena.dupeZ(u8, string.str());
try self.page.js.eval(str, null);
},
}
if (listener.once) {
self.removeListener(list, listener);
}
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) !void {
return self.dispatchPhase(list, current_target, event, null);
}
fn removeListener(self: *EventManager, list: *std.DoublyLinkedList, listener: *Listener) void {
list.remove(&listener.node);
self.listener_pool.destroy(listener);
}
fn findListener(list: *const std.DoublyLinkedList, typ: []const u8, function: js.Function, capture: bool) ?*Listener {
var node = list.first;
while (node) |n| {
node = n.next;
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
if (!listener.function.eql(function)) {
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,
};
const Function = union(enum) {
value: js.Function,
string: String,
fn eql(self: Function, func: js.Function) bool {
return switch (self) {
.string => false,
.value => |v| return v.id == func.id,
};
}
};

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

@@ -0,0 +1,378 @@
const std = @import("std");
const builtin = @import("builtin");
const reflect = @import("reflect.zig");
const IS_DEBUG = builtin.mode == .Debug;
const log = @import("../log.zig");
const String = @import("../string.zig").String;
const Page = @import("Page.zig");
const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.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 MemoryPoolAligned = std.heap.MemoryPoolAligned;
// 1. Generally, wrapping an ArenaAllocator within an ArenaAllocator doesn't make
// much sense. But wrapping a MemoryPool within an Arena does. Specifically, by
// doing so, we solve a major issue with Arena: freed memory can be re-used [for
// more of the same size].
// 2. Normally, you have a MemoryPool(T) where T is a `User` or something. Then
// the MemoryPool can be used for creating users. But in reality, that memory
// created by that pool could be re-used for anything with the same size (or less)
// than a User (and a compatible alignment). So that's what we do - we have size
// (and alignment) based pools.
const Factory = @This();
_page: *Page,
_size_1_8: MemoryPoolAligned([1]u8, .@"8"),
_size_8_8: MemoryPoolAligned([8]u8, .@"8"),
_size_16_8: MemoryPoolAligned([16]u8, .@"8"),
_size_24_8: MemoryPoolAligned([24]u8, .@"8"),
_size_32_8: MemoryPoolAligned([32]u8, .@"8"),
_size_32_16: MemoryPoolAligned([32]u8, .@"16"),
_size_40_8: MemoryPoolAligned([40]u8, .@"8"),
_size_48_16: MemoryPoolAligned([48]u8, .@"16"),
_size_56_8: MemoryPoolAligned([56]u8, .@"8"),
_size_64_16: MemoryPoolAligned([64]u8, .@"16"),
_size_72_8: MemoryPoolAligned([72]u8, .@"8"),
_size_80_16: MemoryPoolAligned([80]u8, .@"16"),
_size_88_8: MemoryPoolAligned([88]u8, .@"8"),
_size_96_16: MemoryPoolAligned([96]u8, .@"16"),
_size_104_8: MemoryPoolAligned([104]u8, .@"8"),
_size_112_8: MemoryPoolAligned([112]u8, .@"8"),
_size_120_8: MemoryPoolAligned([120]u8, .@"8"),
_size_128_8: MemoryPoolAligned([128]u8, .@"8"),
_size_144_8: MemoryPoolAligned([144]u8, .@"8"),
_size_456_8: MemoryPoolAligned([456]u8, .@"8"),
_size_520_8: MemoryPoolAligned([520]u8, .@"8"),
_size_648_8: MemoryPoolAligned([648]u8, .@"8"),
pub fn init(page: *Page) Factory {
return .{
._page = page,
._size_1_8 = MemoryPoolAligned([1]u8, .@"8").init(page.arena),
._size_8_8 = MemoryPoolAligned([8]u8, .@"8").init(page.arena),
._size_16_8 = MemoryPoolAligned([16]u8, .@"8").init(page.arena),
._size_24_8 = MemoryPoolAligned([24]u8, .@"8").init(page.arena),
._size_32_8 = MemoryPoolAligned([32]u8, .@"8").init(page.arena),
._size_32_16 = MemoryPoolAligned([32]u8, .@"16").init(page.arena),
._size_40_8 = MemoryPoolAligned([40]u8, .@"8").init(page.arena),
._size_48_16 = MemoryPoolAligned([48]u8, .@"16").init(page.arena),
._size_56_8 = MemoryPoolAligned([56]u8, .@"8").init(page.arena),
._size_64_16 = MemoryPoolAligned([64]u8, .@"16").init(page.arena),
._size_72_8 = MemoryPoolAligned([72]u8, .@"8").init(page.arena),
._size_80_16 = MemoryPoolAligned([80]u8, .@"16").init(page.arena),
._size_88_8 = MemoryPoolAligned([88]u8, .@"8").init(page.arena),
._size_96_16 = MemoryPoolAligned([96]u8, .@"16").init(page.arena),
._size_104_8 = MemoryPoolAligned([104]u8, .@"8").init(page.arena),
._size_112_8 = MemoryPoolAligned([112]u8, .@"8").init(page.arena),
._size_120_8 = MemoryPoolAligned([120]u8, .@"8").init(page.arena),
._size_128_8 = MemoryPoolAligned([128]u8, .@"8").init(page.arena),
._size_144_8 = MemoryPoolAligned([144]u8, .@"8").init(page.arena),
._size_456_8 = MemoryPoolAligned([456]u8, .@"8").init(page.arena),
._size_520_8 = MemoryPoolAligned([520]u8, .@"8").init(page.arena),
._size_648_8 = MemoryPoolAligned([648]u8, .@"8").init(page.arena),
};
}
// this is a root object
pub fn eventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
const et = try self.createT(EventTarget);
child_ptr._proto = et;
et.* = .{ ._type = unionInit(EventTarget.Type, child_ptr) };
return child_ptr;
}
pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
child_ptr._proto = try self.eventTarget(Node{
._proto = undefined,
._type = unionInit(Node.Type, child_ptr),
});
return child_ptr;
}
pub fn document(self: *Factory, child: anytype) !*@TypeOf(child) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
child_ptr._proto = try self.node(Document{
._proto = undefined,
._type = unionInit(Document.Type, child_ptr),
});
return child_ptr;
}
pub fn element(self: *Factory, child: anytype) !*@TypeOf(child) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
child_ptr._proto = try self.node(Element{
._proto = undefined,
._type = unionInit(Element.Type, child_ptr),
});
return child_ptr;
}
pub fn htmlElement(self: *Factory, child: anytype) !*@TypeOf(child) {
if (comptime fieldIsPointer(Element.Html.Type, @TypeOf(child))) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
child_ptr._proto = try self.element(Element.Html{
._proto = undefined,
._type = unionInit(Element.Html.Type, child_ptr),
});
return child_ptr;
}
// Our union type fields are usually pointers. But, at the leaf, they
// can be struct (if all they contain is the `_proto` field, then we might
// as well store it directly in the struct).
const html = try self.element(Element.Html{
._proto = undefined,
._type = unionInit(Element.Html.Type, child),
});
const field_name = comptime unionFieldName(Element.Html.Type, @TypeOf(child));
var child_ptr = &@field(html._type, field_name);
child_ptr._proto = html;
return child_ptr;
}
pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
if (@TypeOf(child) == Element.Svg) {
return self.element(child);
}
// will never allocate, can't fail
const tag_name_str = String.init(undefined, tag_name, .{}) catch unreachable;
if (comptime fieldIsPointer(Element.Svg.Type, @TypeOf(child))) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
child_ptr._proto = try self.element(Element.Svg{
._proto = undefined,
._tag_name = tag_name_str,
._type = unionInit(Element.Svg.Type, child_ptr),
});
return child_ptr;
}
// Our union type fields are usually pointers. But, at the leaf, they
// can be struct (if all they contain is the `_proto` field, then we might
// as well store it directly in the struct).
const svg = try self.element(Element.Svg{
._proto = undefined,
._tag_name = tag_name_str,
._type = unionInit(Element.Svg.Type, child),
});
const field_name = comptime unionFieldName(Element.Svg.Type, @TypeOf(child));
var child_ptr = &@field(svg._type, field_name);
child_ptr._proto = svg;
return child_ptr;
}
// this is a root object
pub fn event(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
const child_ptr = try self.createT(@TypeOf(child));
child_ptr.* = child;
const e = try self.createT(Event);
child_ptr._proto = e;
e.* = .{
._type = unionInit(Event.Type, child_ptr),
._type_string = try String.init(self._page.arena, typ, .{}),
};
return child_ptr;
}
pub fn xhrEventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
const et = try self.eventTarget(XMLHttpRequestEventTarget{
._proto = undefined,
._type = unionInit(XMLHttpRequestEventTarget.Type, child),
});
const field_name = comptime unionFieldName(XMLHttpRequestEventTarget.Type, @TypeOf(child));
var child_ptr = &@field(et._type, field_name);
child_ptr._proto = et;
return child_ptr;
}
pub fn create(self: *Factory, value: anytype) !*@TypeOf(value) {
const ptr = try self.createT(@TypeOf(value));
ptr.* = value;
return ptr;
}
pub fn createT(self: *Factory, comptime T: type) !*T {
const SO = @sizeOf(T);
if (comptime SO == 1) return @ptrCast(try self._size_1_8.create());
if (comptime SO == 8) return @ptrCast(try self._size_8_8.create());
if (comptime SO == 16) return @ptrCast(try self._size_16_8.create());
if (comptime SO == 24) return @ptrCast(try self._size_24_8.create());
if (comptime SO == 32) {
if (comptime @alignOf(T) == 8) return @ptrCast(try self._size_32_8.create());
if (comptime @alignOf(T) == 16) return @ptrCast(try self._size_32_16.create());
}
if (comptime SO == 40) return @ptrCast(try self._size_40_8.create());
if (comptime SO == 48) return @ptrCast(try self._size_48_16.create());
if (comptime SO == 56) return @ptrCast(try self._size_56_8.create());
if (comptime SO == 64) return @ptrCast(try self._size_64_16.create());
if (comptime SO == 72) return @ptrCast(try self._size_72_8.create());
if (comptime SO == 80) return @ptrCast(try self._size_80_16.create());
if (comptime SO == 88) return @ptrCast(try self._size_88_8.create());
if (comptime SO == 96) return @ptrCast(try self._size_96_16.create());
if (comptime SO == 104) return @ptrCast(try self._size_104_8.create());
if (comptime SO == 112) return @ptrCast(try self._size_112_8.create());
if (comptime SO == 120) return @ptrCast(try self._size_120_8.create());
if (comptime SO == 128) return @ptrCast(try self._size_128_8.create());
if (comptime SO == 144) return @ptrCast(try self._size_144_8.create());
if (comptime SO == 456) return @ptrCast(try self._size_456_8.create());
if (comptime SO == 520) return @ptrCast(try self._size_520_8.create());
if (comptime SO == 648) return @ptrCast(try self._size_648_8.create());
@compileError(std.fmt.comptimePrint("No pool configured for @sizeOf({d}), @alignOf({d}): ({s})", .{ SO, @alignOf(T), @typeName(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 (@hasField(S, "_type") and @typeInfo(@TypeOf(value._type)) == .@"union") {
// 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;
}
}
}
self.destroyChain(value, true);
}
fn destroyChain(self: *Factory, value: anytype, comptime first: bool) void {
const S = reflect.Struct(@TypeOf(value));
// 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);
} else if (@hasDecl(S, "JsApi")) {
// Doesn't have a _proto, but has a JsApi.
if (self._page.js.removeTaggedMapping(@intFromPtr(value))) |tagged| {
self._size_24_8.destroy(@ptrCast(tagged));
}
}
// Leaf types are allowed by be placed directly within their _proto
// (which makes sense when the @sizeOf(Leaf) == 8). These don't need to
// be (cannot be) freed. But we'll still free the chain.
if (comptime wasAllocated(S)) {
switch (@sizeOf(S)) {
1 => self._size_1_8.destroy(@ptrCast(@alignCast(value))),
8 => self._size_8_8.destroy(@ptrCast(@alignCast(value))),
16 => self._size_16_8.destroy(@ptrCast(value)),
24 => self._size_24_8.destroy(@ptrCast(value)),
32 => {
if (comptime @alignOf(S) == 8) {
self._size_32_8.destroy(@ptrCast(value));
} else if (comptime @alignOf(S) == 16) {
self._size_32_16.destroy(@ptrCast(value));
}
},
40 => self._size_40_8.destroy(@ptrCast(value)),
48 => self._size_48_16.destroy(@ptrCast(@alignCast(value))),
56 => self._size_56_8.destroy(@ptrCast(value)),
64 => self._size_64_16.destroy(@ptrCast(@alignCast(value))),
72 => self._size_72_8.destroy(@ptrCast(@alignCast(value))),
80 => self._size_80_16.destroy(@ptrCast(@alignCast(value))),
88 => self._size_88_8.destroy(@ptrCast(@alignCast(value))),
96 => self._size_96_16.destroy(@ptrCast(@alignCast(value))),
104 => self._size_104_8.destroy(@ptrCast(value)),
112 => self._size_112_8.destroy(@ptrCast(value)),
120 => self._size_120_8.destroy(@ptrCast(value)),
128 => self._size_128_8.destroy(@ptrCast(value)),
144 => self._size_144_8.destroy(@ptrCast(value)),
456 => self._size_456_8.destroy(@ptrCast(value)),
520 => self._size_520_8.destroy(@ptrCast(value)),
648 => self._size_648_8.destroy(@ptrCast(value)),
else => |SO| @compileError(std.fmt.comptimePrint("Don't know what I'm being asked to destroy @sizeOf({d}), @alignOf({d}): ({s})", .{ SO, @alignOf(S), @typeName(S) })),
}
}
}
fn wasAllocated(comptime S: type) bool {
// Whether it's heap allocate or not, we should have a pointer.
// (If it isn't heap allocated, it'll be a pointer from the proto's type
// e.g. &html._type.title)
if (!@hasField(S, "_proto")) {
// a root is always on the heap.
return true;
}
// the _proto type
const P = reflect.Struct(std.meta.fieldInfo(S, ._proto).type);
// the _proto._type type (the parent's _type union)
const U = std.meta.fieldInfo(P, ._type).type;
inline for (@typeInfo(U).@"union".fields) |field| {
if (field.type == S) {
// One of the types in the proto's _type union is this non-pointer
// structure, so it isn't heap allocted.
return false;
}
}
return true;
}
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");
}
fn fieldIsPointer(comptime T: type, comptime V: type) bool {
inline for (@typeInfo(T).@"union".fields) |field| {
if (field.type == V) {
return false;
}
if (field.type == *V) {
return true;
}
}
@compileError(@typeName(V) ++ " is not a valid type for " ++ @typeName(T) ++ ".type");
}

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

@@ -0,0 +1,518 @@
// 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,
/// 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 },
};
/// Returns the null-terminated charset value.
pub fn charsetString(mime: *const Mime) [:0]const u8 {
return @ptrCast(&mime.charset);
}
/// 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 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;
},
}
}
return .{
.params = params,
.charset = charset,
.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 T_SPECIAL = blk: {
var v = [_]bool{false} ** 256;
for ("()<>@,;:\\\"/[]?=") |b| {
v[b] = true;
}
break :blk v;
};
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()[0..ec.len]);
} else {
const m: Mime = .unknown;
try testing.expectEqual(m.charsetString(), actual.charsetString());
}
}

1431
src/browser/Page.zig Normal file

File diff suppressed because it is too large Load Diff

109
src/browser/Renderer.zig Normal file
View File

@@ -0,0 +1,109 @@
// 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 parser = @import("netsurf.zig");
const Allocator = std.mem.Allocator;
const Renderer = @This();
allocator: Allocator,
// key is a @ptrFromInt of the element
// value is the index position
positions: std.AutoHashMapUnmanaged(u64, u32),
// given an index, get the element
elements: std.ArrayListUnmanaged(u64),
const Element = @import("dom/element.zig").Element;
// we expect allocator to be an arena
pub fn init(allocator: Allocator) Renderer {
return .{
.elements = .{},
.positions = .{},
.allocator = allocator,
};
}
// The DOMRect is always relative to the viewport, not the document the element belongs to.
// Element that are not part of the main document, either detached or in a shadow DOM should not call this function.
pub fn getRect(self: *Renderer, e: *parser.Element) !Element.DOMRect {
var elements = &self.elements;
const gop = try self.positions.getOrPut(self.allocator, @intFromPtr(e));
var x: u32 = gop.value_ptr.*;
if (gop.found_existing == false) {
x = @intCast(elements.items.len);
try elements.append(self.allocator, @intFromPtr(e));
gop.value_ptr.* = x;
}
const _x: f64 = @floatFromInt(x);
const y: f64 = 0.0;
const w: f64 = 1.0;
const h: f64 = 1.0;
return .{
.x = _x,
.y = y,
.width = w,
.height = h,
.left = _x,
.top = y,
.right = _x + w,
.bottom = y + h,
};
}
pub fn boundingRect(self: *const Renderer) Element.DOMRect {
const x: f64 = 0.0;
const y: f64 = 0.0;
const w: f64 = @floatFromInt(self.width());
const h: f64 = @floatFromInt(self.width());
return .{
.x = x,
.y = y,
.width = w,
.height = h,
.left = x,
.top = y,
.right = x + w,
.bottom = y + h,
};
}
pub fn width(self: *const Renderer) u32 {
return @max(@as(u32, @intCast(self.elements.items.len)), 1); // At least 1 pixel even if empty
}
pub fn height(_: *const Renderer) u32 {
return 1;
}
pub fn getElementAtPosition(self: *const Renderer, x: i32, y: i32) ?*parser.Element {
if (y != 0 or x < 0) {
return null;
}
const elements = self.elements.items;
return if (x < elements.len) @ptrFromInt(elements[@intCast(x)]) else null;
}

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

@@ -0,0 +1,95 @@
const std = @import("std");
const builtin = @import("builtin");
const log = @import("../log.zig");
const timestamp = @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 {
return std.math.order(a.run_at, b.run_at);
}
}.compare);
const Scheduler = @This();
low_priority: Queue,
high_priority: Queue,
pub fn init(allocator: std.mem.Allocator) Scheduler {
return .{
.low_priority = Queue.init(allocator, {}),
.high_priority = Queue.init(allocator, {}),
};
}
pub fn reset(self: *Scheduler) void {
self.low_priority.cap = 0;
self.low_priority.items.len = 0;
self.high_priority.cap = 0;
self.high_priority.items.len = 0;
}
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;
return queue.add(.{
.ctx = ctx,
.callback = cb,
.name = opts.name,
.run_at = timestamp(.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 = timestamp(.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
std.debug.assert(ms != 0);
task.run_at = now + ms;
try self.low_priority.add(task);
}
}
return null;
}
const Task = struct {
run_at: 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

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

@@ -0,0 +1,197 @@
// 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 log = @import("../log.zig");
const js = @import("js/js.zig");
const storage = @import("webapi/storage/storage.zig");
const Page = @import("Page.zig");
const Browser = @import("Browser.zig");
const Allocator = std.mem.Allocator;
const NavigateOpts = Page.NavigateOpts;
// 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.Jar,
storage_shed: storage.Shed,
page: ?*Page = null,
// If the current page want to navigate to a new page
// (form submit, link click, top.location = xxx)
// the details are stored here so that, on the next call to session.wait
// we can destroy the current page and start a new one.
queued_navigation: ?QueuedNavigation,
pub fn init(self: *Session, browser: *Browser) !void {
var executor = try browser.env.newExecutionWorld();
errdefer executor.deinit();
const allocator = browser.app.allocator;
self.* = .{
.browser = browser,
.executor = executor,
.storage_shed = .{},
.queued_navigation = null,
.arena = browser.session_arena.allocator(),
.cookie_jar = storage.Jar.init(allocator),
.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 {
std.debug.assert(self.page == 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.?;
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, .{});
std.debug.assert(self.page != null);
// RemoveJsContext() will execute the destructor of any type that
// registered a destructor (e.g. XMLHttpRequest).
// Should be called before we deinit the page, because these objects
// could be referencing it.
self.executor.removeContext();
self.page.?.deinit();
self.page = null;
log.debug(.browser, "remove page", .{});
}
pub fn currentPage(self: *Session) ?*Page {
return self.page orelse return null;
}
pub const WaitResult = enum {
done,
no_page,
extra_socket,
};
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
_ = self.processQueuedNavigation() catch {
// There was an error processing the queue navigation. This already
// logged the error, just return.
return .done;
};
if (self.page) |page| {
return page.wait(wait_ms);
}
return .no_page;
}
pub fn fetchWait(self: *Session, wait_ms: u32) void {
while (true) {
const page = self.page orelse return;
_ = page.wait(wait_ms);
const navigated = self.processQueuedNavigation() catch {
// There was an error processing the queue navigation. This already
// logged the error, just return.
return;
};
if (navigated == false) {
return;
}
}
}
fn processQueuedNavigation(self: *Session) !bool {
const qn = self.queued_navigation orelse return false;
// 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();
// Page.navigateFromWebAPI terminatedExecution. If we don't resume
// it before doing a shutdown we'll get an error.
self.executor.resumeExecution();
self.removePage();
self.queued_navigation = null;
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;
};
return true;
}
const QueuedNavigation = struct {
url: [:0]const u8,
opts: NavigateOpts,
};

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

@@ -0,0 +1,393 @@
const std = @import("std");
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;
if (std.mem.lastIndexOfScalar(u8, normalized_base[authority_start..], '/')) |pos| {
normalized_base = normalized_base[0 .. pos + authority_start];
}
// 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..], "../")) {
std.debug.assert(out[out_i - 1] == '/');
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 < 6) {
return false;
}
// very common case
if (url[0] == '/') {
return false;
}
return std.ascii.startsWithIgnoreCase(url, "https://") or
std.ascii.startsWithIgnoreCase(url, "http://") or
std.ascii.startsWithIgnoreCase(url, "ftp://");
}
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 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 port = getPort(raw);
const protocol = getProtocol(raw);
const hostname = getHostname(raw);
const p = std.meta.stringToEnum(KnownProtocol, getProtocol(raw)) orelse return null;
const include_port = blk: {
if (port.len == 0) {
break :blk false;
}
if (p == .@"https:" and std.mem.eql(u8, port, "443")) {
break :blk false;
}
if (p == .@"http:" and std.mem.eql(u8, port, "80")) {
break :blk false;
}
break :blk true;
};
if (include_port) {
return try std.fmt.allocPrint(allocator, "{s}//{s}:{s}", .{ protocol, hostname, port });
}
return try std.fmt.allocPrint(allocator, "{s}//{s}", .{ protocol, hostname });
}
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;
}
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];
}
const KnownProtocol = enum {
@"http:",
@"https:",
};
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" {
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);
}
}

View File

@@ -1,571 +0,0 @@
// 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 Types = @import("root").Types;
const parser = @import("netsurf");
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 storage = @import("../storage/storage.zig");
const FetchResult = @import("../http/Client.zig").Client.FetchResult;
const UserContext = @import("../user_context.zig").UserContext;
const HttpClient = @import("../async/Client.zig");
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,
// TODO move the shed to the browser?
storageShed: storage.Shed,
page: ?*Page = null,
httpClient: HttpClient,
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),
.storageShed = storage.Shed.init(alloc),
.httpClient = undefined,
};
self.env = try Env.init(self.arena.allocator(), &self.loop, null);
self.httpClient = .{ .allocator = alloc, .loop = &self.loop };
try self.env.load(&self.jstypes);
return self;
}
fn deinit(self: *Session) void {
if (self.page) |page| page.end();
self.env.deinit();
self.arena.deinit();
self.httpClient.deinit();
self.loader.deinit();
self.storageShed.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,
origin: ?[]const u8 = null,
raw_data: ?[]const u8 = null,
fn init(
alloc: std.mem.Allocator,
session: *Session,
) !Page {
if (session.page != null) return error.SessionPageExists;
var page = Page{
.arena = std.heap.ArenaAllocator.init(alloc),
.session = session,
};
session.page = &page;
return page;
}
// 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
// clear netsurf memory arena.
parser.deinit();
_ = self.arena.reset(.free_all);
}
pub fn deinit(self: *Page) void {
self.arena.deinit();
self.session.page = null;
}
// 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.writeHTML(self.doc.?, out);
}
pub fn wait(self: *Page) !void {
// try catch
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(self.session.env);
defer try_catch.deinit();
self.session.env.wait() catch |err| {
// the js env could not be started if the document wasn't an HTML.
if (err == error.EnvNotStarted) return;
const alloc = self.arena.allocator();
if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
log.info("wait error: {s}", .{msg});
return;
}
};
log.debug("wait: OK", .{});
}
// 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.parseAfterScheme("", self.rawuri.?);
// prepare origin value.
var buf = std.ArrayList(u8).init(alloc);
defer buf.deinit();
try self.uri.writeToStream(.{
.scheme = true,
.authority = true,
}, buf.writer());
self.origin = try buf.toOwnedSlice();
// 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}", .{
req.response.version,
req.response.status,
req.response.reason,
// TODO log headers
});
return error.BadStatusCode;
}
// TODO handle charset
// https://html.spec.whatwg.org/#content-type
var it = req.response.iterateHeaders();
var ct: ?[]const u8 = null;
while (true) {
const h = it.next() orelse break;
if (std.ascii.eqlIgnoreCase(h.name, "Content-Type")) {
ct = try alloc.dupe(u8, h.value);
}
}
if (ct == null) {
// no content type in HTTP headers.
// TODO try to sniff mime type from the body.
log.info("no content-type HTTP header", .{});
return;
}
defer alloc.free(ct.?);
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();
// start netsurf memory arena.
try parser.init();
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
// inject the URL to the document including the fragment.
try parser.documentSetDocumentURI(doc, self.rawuri orelse "about:blank");
// TODO set the referrer to the document.
self.session.window.replaceDocument(html_doc);
self.session.window.setStorageShelf(
try self.session.storageShed.getOrPut(self.origin orelse "null"),
);
// 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();
// replace the user context document with the new one.
try self.session.env.setUserContext(.{
.document = html_doc,
.httpClient = &self.session.httpClient,
});
// add global objects
log.debug("setup global env", .{});
try self.session.env.bindGlobal(&self.session.window);
// 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();
defer parser.eventDestroy(evt);
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();
defer parser.eventDestroy(loadevt);
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;
}
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(self.session.env);
defer try_catch.deinit();
const opt_text = try parser.nodeTextContent(parser.elementToNode(e));
if (opt_text) |text| {
// TODO handle charset attribute
const res = self.session.env.exec(text, "") catch {
if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
log.info("eval inline {s}: {s}", .{ text, msg });
}
return;
};
if (builtin.mode == .Debug) {
const msg = try res.toString(alloc, self.session.env);
defer alloc.free(msg);
log.debug("eval inline {s}", .{msg});
}
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});
var buffer: [1024]u8 = undefined;
var b: []u8 = buffer[0..];
const u = try std.Uri.resolve_inplace(self.uri, src, &b);
var fetchres = try self.session.loader.get(alloc, u);
defer fetchres.deinit();
const resp = fetchres.req.response;
log.info("fech script {any}: {d}", .{ u, resp.status });
if (resp.status != .ok) return FetchError.BadStatusCode;
// TODO check content-type
const body = try fetchres.req.reader().readAllAlloc(alloc, 16 * 1024 * 1024);
defer alloc.free(body);
// check no body
if (body.len == 0) return FetchError.NoBody;
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(self.session.env);
defer try_catch.deinit();
const res = self.session.env.exec(body, src) catch {
if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
log.info("eval remote {s}: {s}", .{ src, msg });
}
return FetchError.JsErr;
};
if (builtin.mode == .Debug) {
const msg = try res.toString(alloc, self.session.env);
defer alloc.free(msg);
log.debug("eval remote {s}: {s}", .{ src, msg });
}
}
// > 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;
}
};

View File

@@ -1,132 +1,86 @@
// 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 File = std.fs.File;
const Node = @import("webapi/Node.zig");
const parser = @import("netsurf");
const Walker = @import("../dom/walker.zig").WalkerChildren;
pub const Opts = struct {
// @ZIGDOM (none of these do anything)
with_base: bool = false,
strip_mode: StripMode = .{},
// writer must be a std.io.Writer
pub fn writeHTML(doc: *parser.Document, writer: anytype) !void {
try writer.writeAll("<!DOCTYPE html>\n");
try writeNode(parser.documentToNode(doc), writer);
try writer.writeAll("\n");
}
pub const StripMode = struct {
js: bool = false,
ui: bool = false,
css: bool = false,
};
};
// writer must be a std.io.Writer
pub fn writeNode(root: *parser.Node, writer: anytype) !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 writer.writeAll("<");
try writer.writeAll(tag);
// 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 writer.writeAll(" ");
try writer.writeAll(try parser.attributeGetName(attr));
try writer.writeAll("=\"");
try writer.writeAll(try parser.attributeGetValue(attr) orelse "");
try writer.writeAll("\"");
i += 1;
}
try writer.writeAll(">");
// void elements can't have any content.
if (try isVoid(parser.nodeToElement(next.?))) continue;
// write the children
// TODO avoid recursion
try writeNode(next.?, writer);
// close the tag
pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer) error{WriteFailed}!void {
switch (node._type) {
.cdata => |cd| try writer.writeAll(cd.getData()),
.element => |el| {
try el.format(writer);
try children(node, opts, writer);
if (!isVoidElement(el)) {
try writer.writeAll("</");
try writer.writeAll(tag);
try writer.writeAll(">");
},
.text => {
const v = try parser.nodeValue(next.?) orelse continue;
try writer.writeAll(v);
},
.cdata_section => {
const v = try parser.nodeValue(next.?) orelse continue;
try writer.writeAll("<![CDATA[");
try writer.writeAll(v);
try writer.writeAll("]]>");
},
.comment => {
const v = try parser.nodeValue(next.?) orelse continue;
try writer.writeAll("<!--");
try writer.writeAll(v);
try writer.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,
}
try writer.writeAll(el.getTagNameDump());
try writer.writeByte('>');
}
},
.document => try children(node, opts, writer),
.document_fragment => try children(node, opts, writer),
.attribute => unreachable,
}
}
// area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr
// https://html.spec.whatwg.org/#void-elements
fn isVoid(elem: *parser.Element) !bool {
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem)));
return switch (tag) {
.area, .base, .br, .col, .embed, .hr, .img, .input, .link => true,
.meta, .source, .track, .wbr => true,
else => false,
pub fn children(parent: *Node, opts: Opts, writer: *std.Io.Writer) !void {
var it = parent.childrenIterator();
while (it.next()) |child| {
try deep(child, opts, writer);
}
}
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");
},
.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 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,
};
}
test "dump.writeHTML" {
const out = try std.fs.openFileAbsolute("/dev/null", .{ .mode = .write_only });
defer out.close();
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 writeHTML(doc, out);
}

492
src/browser/js/Caller.zig Normal file
View File

@@ -0,0 +1,492 @@
// 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 log = @import("../../log.zig");
const js = @import("js.zig");
const v8 = js.v8;
const bridge = @import("bridge.zig");
const Context = @import("Context.zig");
const Page = @import("../Page.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const CALL_ARENA_RETAIN = 1024 * 16;
// Responsible for calling Zig functions from JS invocations. This could
// probably just contained in ExecutionWorld, but having this specific logic, which
// is somewhat repetitive between constructors, functions, getters, etc contained
// here does feel like it makes it cleaner.
const Caller = @This();
context: *Context,
v8_context: v8.Context,
isolate: v8.Isolate,
call_arena: Allocator,
// info is a v8.PropertyCallbackInfo or a v8.FunctionCallback
// All we really want from it is the isolate.
// executor = Isolate -> getCurrentContext -> getEmbedderData()
pub fn init(info: anytype) Caller {
const isolate = info.getIsolate();
const v8_context = isolate.getCurrentContext();
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
context.call_depth += 1;
return .{
.context = context,
.isolate = isolate,
.v8_context = v8_context,
.call_arena = context.call_arena,
};
}
pub fn deinit(self: *Caller) void {
const context = self.context;
const call_depth = context.call_depth - 1;
// Because of callbacks, calls can be nested. Because of this, we
// can't clear the call_arena after _every_ call. Imagine we have
// arr.forEach((i) => { console.log(i); }
//
// First we call forEach. Inside of our forEach call,
// we call console.log. If we reset the call_arena after this call,
// it'll reset it for the `forEach` call after, which might still
// need the data.
//
// Therefore, we keep a call_depth, and only reset the call_arena
// when a top-level (call_depth == 0) function ends.
if (call_depth == 0) {
const arena: *ArenaAllocator = @ptrCast(@alignCast(context.call_arena.ptr));
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
}
// Set this _after_ we've executed the above code, so that if the
// above code executes any callbacks, they aren't being executed
// at scope 0, which would be wrong.
context.call_depth = call_depth;
}
pub const CallOpts = struct {
dom_exception: bool = false,
null_as_undefined: bool = false,
as_typed_array: bool = false,
};
pub fn constructor(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
self._constructor(func, info) catch |err| {
self.handleError(T, @TypeOf(func), err, info, opts);
};
}
pub fn _constructor(self: *Caller, func: anytype, info: v8.FunctionCallbackInfo) !void {
const F = @TypeOf(func);
const args = try self.getArgs(F, 0, info);
const res = @call(.auto, func, args);
const ReturnType = @typeInfo(F).@"fn".return_type orelse {
@compileError(@typeName(F) ++ " has a constructor without a return type");
};
const this = info.getThis();
if (@typeInfo(ReturnType) == .error_union) {
const non_error_res = res catch |err| return err;
_ = try self.context.mapZigInstanceToJs(this, non_error_res);
} else {
_ = try self.context.mapZigInstanceToJs(this, res);
}
info.getReturnValue().set(this);
}
pub fn method(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
self._method(T, func, info, opts) catch |err| {
self.handleError(T, @TypeOf(func), err, info, opts);
};
}
pub fn _method(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) !void {
const F = @TypeOf(func);
var args = try self.getArgs(F, 1, info);
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
const res = @call(.auto, func, args);
info.getReturnValue().set(try self.context.zigValueToJs(res, opts));
}
pub fn function(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
self._function(func, info, opts) catch |err| {
self.handleError(T, @TypeOf(func), err, info, opts);
};
}
pub fn _function(self: *Caller, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) !void {
const F = @TypeOf(func);
const context = self.context;
const args = try self.getArgs(F, 0, info);
const res = @call(.auto, func, args);
info.getReturnValue().set(try context.zigValueToJs(res, opts));
}
pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
return self._getIndex(T, func, idx, info, opts) catch |err| {
self.handleError(T, @TypeOf(func), err, info, opts);
return v8.Intercepted.No;
};
}
pub fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
const F = @TypeOf(func);
var args = try self.getArgs(F, 2, info);
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
@field(args, "1") = idx;
const ret = @call(.auto, func, args);
return self.handleIndexedReturn(T, F, ret, info, opts);
}
pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
return self._getNamedIndex(T, func, name, info, opts) catch |err| {
self.handleError(T, @TypeOf(func), err, info, opts);
return v8.Intercepted.No;
};
}
pub fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: v8.Name, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
const F = @TypeOf(func);
var args = try self.getArgs(F, 2, info);
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
@field(args, "1") = try self.nameToString(name);
const ret = @call(.auto, func, args);
return self.handleIndexedReturn(T, F, ret, info, opts);
}
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, ret: anytype, info: v8.PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
// need to unwrap this error immediately for when opts.null_as_undefined == true
// and we need to compare it to null;
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
.error_union => |eu| blk: {
break :blk ret catch |err| {
// We can't compare err == error.NotHandled if error.NotHandled
// isn't part of the possible error set. So we first need to check
// if error.NotHandled is part of the error set.
if (isInErrorSet(error.NotHandled, eu.error_set)) {
if (err == error.NotHandled) {
return v8.Intercepted.No;
}
}
self.handleError(T, F, err, info, opts);
return v8.Intercepted.No;
};
},
else => ret,
};
info.getReturnValue().set(try self.context.zigValueToJs(non_error_ret, opts));
return v8.Intercepted.Yes;
}
fn isInErrorSet(err: anyerror, comptime T: type) bool {
inline for (@typeInfo(T).error_set.?) |e| {
if (err == @field(anyerror, e.name)) return true;
}
return false;
}
fn namedSetOrDeleteCall(res: anytype, has_value: bool) !u8 {
if (@typeInfo(@TypeOf(res)) == .error_union) {
_ = try res;
}
if (has_value == false) {
return v8.Intercepted.No;
}
return v8.Intercepted.Yes;
}
fn nameToString(self: *Caller, name: v8.Name) ![]const u8 {
return self.context.valueToString(.{ .handle = name.handle }, .{});
}
fn isSelfReceiver(comptime T: type, comptime F: type) bool {
return checkSelfReceiver(T, F, false);
}
fn assertSelfReceiver(comptime T: type, comptime F: type) void {
_ = checkSelfReceiver(T, F, true);
}
fn checkSelfReceiver(comptime T: type, comptime F: type, comptime fail: bool) bool {
const params = @typeInfo(F).@"fn".params;
if (params.len == 0) {
if (fail) {
@compileError(@typeName(F) ++ " must have a self parameter");
}
return false;
}
const first_param = params[0].type.?;
if (first_param != *T and first_param != *const T) {
if (fail) {
@compileError(std.fmt.comptimePrint("The first parameter to {s} must be a *{s} or *const {s}. Got: {s}", .{
@typeName(F),
@typeName(T),
@typeName(T),
@typeName(first_param),
}));
}
return false;
}
return true;
}
fn assertIsPageArg(comptime T: type, comptime F: type, index: comptime_int) void {
const param = @typeInfo(F).@"fn".params[index].type.?;
if (isPage(param)) {
return;
}
@compileError(std.fmt.comptimePrint("The {d} parameter of {s}.{s} must be a *Page or *const Page. Got: {s}", .{ index, @typeName(T), @typeName(F), @typeName(param) }));
}
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
const isolate = self.isolate;
if (comptime @import("builtin").mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
if (log.enabled(.js, .warn)) {
self.logFunctionCallError(@typeName(T), @typeName(F), err, info);
}
}
var js_err: ?v8.Value = switch (err) {
error.InvalidArgument => createTypeException(isolate, "invalid argument"),
error.OutOfMemory => js._createException(isolate, "out of memory"),
error.IllegalConstructor => js._createException(isolate, "Illegal Contructor"),
else => blk: {
if (!comptime opts.dom_exception) {
break :blk null;
}
const DOMException = @import("../webapi/DOMException.zig");
const ex = DOMException.fromError(err) orelse break :blk null;
break :blk self.context.zigValueToJs(ex, .{}) catch js._createException(isolate, "internal error");
},
};
if (js_err == null) {
js_err = js._createException(isolate, @errorName(err));
}
const js_exception = isolate.throwException(js_err.?);
info.getReturnValue().setValueHandle(js_exception.handle);
}
// If we call a method in javascript: cat.lives('nine');
//
// Then we'd expect a Zig function with 2 parameters: a self and the string.
// In this case, offset == 1. Offset is always 1 for setters or methods.
//
// Offset is always 0 for constructors.
//
// For constructors, setters and methods, we can further increase offset + 1
// if the first parameter is an instance of Page.
//
// Finally, if the JS function is called with _more_ parameters and
// the last parameter in Zig is an array, we'll try to slurp the additional
// parameters into the array.
fn getArgs(self: *const Caller, comptime F: type, comptime offset: usize, info: anytype) !ParameterTypes(F) {
const context = self.context;
var args: ParameterTypes(F) = undefined;
const params = @typeInfo(F).@"fn".params[offset..];
// Except for the constructor, the first parameter is always `self`
// This isn't something we'll bind from JS, so skip it.
const params_to_map = blk: {
if (params.len == 0) {
return args;
}
// If the last parameter is the Page, set it, and exclude it
// from our params slice, because we don't want to bind it to
// a JS argument
if (comptime isPage(params[params.len - 1].type.?)) {
@field(args, tupleFieldName(params.len - 1 + offset)) = self.context.page;
break :blk params[0 .. params.len - 1];
}
// If the last parameter is a special JsThis, set it, and exclude it
// from our params slice, because we don't want to bind it to
// a JS argument
if (comptime params[params.len - 1].type.? == js.This) {
@field(args, tupleFieldName(params.len - 1 + offset)) = .{ .obj = .{
.context = context,
.js_obj = info.getThis(),
} };
// AND the 2nd last parameter is state
if (params.len > 1 and comptime isPage(params[params.len - 2].type.?)) {
@field(args, tupleFieldName(params.len - 2 + offset)) = self.context.page;
break :blk params[0 .. params.len - 2];
}
break :blk params[0 .. params.len - 1];
}
// we have neither a Page nor a JsObject. All params must be
// bound to a JavaScript value.
break :blk params;
};
if (params_to_map.len == 0) {
return args;
}
const js_parameter_count = info.length();
const last_js_parameter = params_to_map.len - 1;
var is_variadic = false;
{
// This is going to get complicated. If the last Zig parameter
// is a slice AND the corresponding javascript parameter is
// NOT an an array, then we'll treat it as a variadic.
const last_parameter_type = params_to_map[params_to_map.len - 1].type.?;
const last_parameter_type_info = @typeInfo(last_parameter_type);
if (last_parameter_type_info == .pointer and last_parameter_type_info.pointer.size == .slice) {
const slice_type = last_parameter_type_info.pointer.child;
const corresponding_js_value = info.getArg(@as(u32, @intCast(last_js_parameter)));
if (corresponding_js_value.isArray() == false and corresponding_js_value.isTypedArray() == false and slice_type != u8) {
is_variadic = true;
if (js_parameter_count == 0) {
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
} else if (js_parameter_count >= params_to_map.len) {
const arr = try self.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1);
for (arr, last_js_parameter..) |*a, i| {
const js_value = info.getArg(@as(u32, @intCast(i)));
a.* = try context.jsValueToZig(slice_type, js_value);
}
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = arr;
} else {
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
}
}
}
}
inline for (params_to_map, 0..) |param, i| {
const field_index = comptime i + offset;
if (comptime i == params_to_map.len - 1) {
if (is_variadic) {
break;
}
}
if (comptime isPage(param.type.?)) {
@compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F));
} else if (comptime param.type.? == js.This) {
@compileError("JsThis must be the last parameter: " ++ @typeName(F));
} else if (i >= js_parameter_count) {
if (@typeInfo(param.type.?) != .optional) {
return error.InvalidArgument;
}
@field(args, tupleFieldName(field_index)) = null;
} else {
const js_value = info.getArg(@as(u32, @intCast(i)));
@field(args, tupleFieldName(field_index)) = context.jsValueToZig(param.type.?, js_value) catch {
return error.InvalidArgument;
};
}
}
return args;
}
// This is extracted to speed up compilation. When left inlined in handleError,
// this can add as much as 10 seconds of compilation time.
fn logFunctionCallError(self: *Caller, type_name: []const u8, func: []const u8, err: anyerror, info: v8.FunctionCallbackInfo) void {
const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
log.info(.js, "function call error", .{
.type = type_name,
.func = func,
.err = err,
.args = args_dump,
.stack = self.context.stackTrace() catch |err1| @errorName(err1),
});
}
fn serializeFunctionArgs(self: *Caller, info: v8.FunctionCallbackInfo) ![]const u8 {
const separator = log.separator();
const js_parameter_count = info.length();
const context = self.context;
var arr: std.ArrayListUnmanaged(u8) = .{};
for (0..js_parameter_count) |i| {
const js_value = info.getArg(@intCast(i));
const value_string = try context.valueToDetailString(js_value);
const value_type = try context.jsStringToZig(try js_value.typeOf(self.isolate), .{});
try std.fmt.format(arr.writer(context.call_arena), "{s}{d}: {s} ({s})", .{
separator,
i + 1,
value_string,
value_type,
});
}
return arr.items;
}
// Takes a function, and returns a tuple for its argument. Used when we
// @call a function
fn ParameterTypes(comptime F: type) type {
const params = @typeInfo(F).@"fn".params;
var fields: [params.len]std.builtin.Type.StructField = undefined;
inline for (params, 0..) |param, i| {
fields[i] = .{
.name = tupleFieldName(i),
.type = param.type.?,
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(param.type.?),
};
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.decls = &.{},
.fields = &fields,
.is_tuple = true,
} });
}
fn tupleFieldName(comptime i: usize) [:0]const u8 {
return switch (i) {
0 => "0",
1 => "1",
2 => "2",
3 => "3",
4 => "4",
5 => "5",
6 => "6",
7 => "7",
8 => "8",
9 => "9",
else => std.fmt.comptimePrint("{d}", .{i}),
};
}
fn isPage(comptime T: type) bool {
return T == *Page or T == *const Page;
}
fn createTypeException(isolate: v8.Isolate, msg: []const u8) v8.Value {
return v8.Exception.initTypeError(v8.String.initUtf8(isolate, msg));
}

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

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,356 @@
// 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 Caller = @import("Caller.zig");
const Context = @import("Context.zig");
const Platform = @import("Platform.zig");
const Inspector = @import("Inspector.zig");
const ExecutionWorld = @import("ExecutionWorld.zig");
const NamedFunction = Caller.NamedFunction;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const JsApis = bridge.JsApis;
// 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: v8.Isolate,
// just kept around because we need to free it on deinit
isolate_params: *v8.CreateParams,
// Given a type, we can lookup its index in JS_API_LOOKUP and then have
// access to its TunctionTemplate (the thing we need to create an instance
// of it)
// I.e.:
// const index = @field(JS_API_LOOKUP, @typeName(type_name))
// const template = templates[index];
templates: [JsApis.len]v8.FunctionTemplate,
context_id: usize,
const Opts = struct {};
pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Env {
// var params = v8.initCreateParams();
var params = try allocator.create(v8.CreateParams);
errdefer allocator.destroy(params);
v8.c.v8__Isolate__CreateParams__CONSTRUCT(params);
params.array_buffer_allocator = v8.createDefaultArrayBufferAllocator();
errdefer v8.destroyArrayBufferAllocator(params.array_buffer_allocator.?);
var isolate = v8.Isolate.init(params);
errdefer isolate.deinit();
// This is the callback that runs whenever a module is dynamically imported.
isolate.setHostImportModuleDynamicallyCallback(Context.dynamicModuleCallback);
isolate.setPromiseRejectCallback(promiseRejectCallback);
isolate.setMicrotasksPolicy(v8.c.kExplicit);
isolate.enter();
errdefer isolate.exit();
isolate.setHostInitializeImportMetaObjectCallback(Context.metaObjectCallback);
var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate);
defer temp_scope.deinit();
const env = try allocator.create(Env);
errdefer allocator.destroy(env);
env.* = .{
.context_id = 0,
.platform = platform,
.isolate = isolate,
.templates = undefined,
.allocator = allocator,
.isolate_params = params,
};
// Populate our templates lookup. generateClass creates the
// v8.FunctionTemplate, which we store in our env.templates.
// The ordering doesn't matter. What matters is that, given a type
// we can get its index via: @field(types.LOOKUP, type_name)
const templates = &env.templates;
inline for (JsApis, 0..) |JsApi, i| {
@setEvalBranchQuota(10_000);
JsApi.Meta.class_index = i;
templates[i] = v8.Persistent(v8.FunctionTemplate).init(isolate, generateClass(JsApi, isolate)).castToFunctionTemplate();
}
// Above, we've created all our our FunctionTemplates. Now that we
// have them all, we can hook up the prototypes.
inline for (JsApis, 0..) |JsApi, i| {
if (comptime protoIndexLookup(JsApi)) |proto_index| {
templates[i].inherit(templates[proto_index]);
}
}
return env;
}
pub fn deinit(self: *Env) void {
self.isolate.exit();
self.isolate.deinit();
v8.destroyArrayBufferAllocator(self.isolate_params.array_buffer_allocator.?);
self.allocator.destroy(self.isolate_params);
self.allocator.destroy(self);
}
pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !Inspector {
return Inspector.init(arena, self.isolate, ctx);
}
pub fn runMicrotasks(self: *const Env) void {
self.isolate.performMicrotasksCheckpoint();
}
pub fn pumpMessageLoop(self: *const Env) bool {
return self.platform.inner.pumpMessageLoop(self.isolate, false);
}
pub fn runIdleTasks(self: *const Env) void {
return self.platform.inner.runIdleTasks(self.isolate, 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: v8.HandleScope = undefined;
v8.HandleScope.init(&handle_scope, 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(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
const msg = v8.PromiseRejectMessage.initFromC(v8_msg);
const isolate = msg.getPromise().toObject().getIsolate();
const context = Context.fromIsolate(isolate);
const value =
if (msg.getValue()) |v8_value| context.valueToString(v8_value, .{}) catch |err| @errorName(err) else "no value";
log.debug(.js, "unhandled rejection", .{ .value = value });
}
// Give it a Zig struct, get back a v8.FunctionTemplate.
// The FunctionTemplate is a bit like a struct container - it's where
// we'll attach functions/getters/setters and where we'll "inherit" a
// prototype type (if there is any)
fn generateClass(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTemplate {
const template = generateConstructor(JsApi, isolate);
attachClass(JsApi, isolate, template);
return template;
}
// Normally this is called from generateClass. Where generateClass creates
// the constructor (hence, the FunctionTemplate), attachClass adds all
// of its functions, getters, setters, ...
// But it's extracted from generateClass because we also have 1 global
// object (i.e. the Window), which gets attached not only to the Window
// constructor/FunctionTemplate as normal, but also through the default
// FunctionTemplate of the isolate (in createContext)
pub fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.FunctionTemplate) void {
const template_proto = template.getPrototypeTemplate();
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.String.initUtf8(isolate, name).toName();
const getter_callback = v8.FunctionTemplate.initCallback(isolate, value.getter);
if (value.setter == null) {
template_proto.setAccessorGetter(js_name, getter_callback);
} else {
const setter_callback = v8.FunctionTemplate.initCallback(isolate, value.setter);
template_proto.setAccessorGetterAndSetter(js_name, getter_callback, setter_callback);
}
},
bridge.Function => {
const function_template = v8.FunctionTemplate.initCallback(isolate, value.func);
const js_name: v8.Name = v8.String.initUtf8(isolate, name).toName();
if (value.static) {
template.set(js_name, function_template, v8.PropertyAttribute.None);
} else {
template_proto.set(js_name, function_template, v8.PropertyAttribute.None);
}
},
bridge.Indexed => {
const configuration = v8.IndexedPropertyHandlerConfiguration{
.getter = value.getter,
};
template_proto.setIndexedProperty(configuration, null);
},
bridge.NamedIndexed => {
const configuration = v8.NamedPropertyHandlerConfiguration{
.getter = value.getter,
.flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking,
};
template_proto.setNamedProperty(configuration, null);
},
bridge.Iterator => {
// Same as a function, but with a specific name
const function_template = v8.FunctionTemplate.initCallback(isolate, value.func);
const js_name = v8.Symbol.getIterator(isolate).toName();
template_proto.set(js_name, function_template, v8.PropertyAttribute.None);
},
bridge.Property.Int => {
const js_value = js.simpleZigValueToJs(isolate, value.int, true, false);
const js_name = v8.String.initUtf8(isolate, name).toName();
// apply it both to the type itself
template.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete);
// and to instances of the type
template_proto.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete);
},
bridge.Constructor => {}, // already handled in generateClasss
else => {},
}
}
}
// 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;
}
break :blk struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
const iso = caller.isolate;
log.warn(.js, "Illegal constructor call", .{ .name = @typeName(JsApi) });
const js_exception = iso.throwException(js._createException(iso, "Illegal Constructor"));
info.getReturnValue().set(js_exception);
return;
}
}.wrap;
};
const template = v8.FunctionTemplate.initCallback(isolate, callback);
template.getInstanceTemplate().setInternalFieldCount(1);
const class_name = v8.String.initUtf8(isolate, if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi));
template.setClassName(class_name);
return template;
}
// ZIGDOM (HTMLAllCollection I think)
// fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
// const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
// if (has_js_call_as_function) {
// template.setCallAsFunctionHandler(struct {
// fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
// const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
// var caller = Caller.init(info);
// defer caller.deinit();
// const named_function = comptime NamedFunction.init(Struct, "jsCallAsFunction");
// caller.method(Struct, named_function, info) catch |err| {
// caller.handleError(Struct, named_function, err, info);
// };
// }
// }.callback);
// }
// if (@hasDecl(Struct, "mark_as_undetectable") and Struct.mark_as_undetectable) {
// if (!has_js_call_as_function) {
// @compileError(@typeName(Struct) ++ ": mark_as_undetectable required jsCallAsFunction to be defined. This is a hard-coded requirement in V8, because mark_as_undetectable only exists for HTMLAllCollection which is also callable.");
// }
// template.markAsUndetectable();
// }
// }
pub fn protoIndexLookup(comptime JsApi: type) ?u16 {
@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 @field(bridge.JS_API_LOOKUP, @typeName(F.JsApi));
}
}

View File

@@ -0,0 +1,231 @@
// 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 log = @import("../../log.zig");
const js = @import("js.zig");
const v8 = js.v8;
const bridge = @import("bridge.zig");
const Env = @import("Env.zig");
const Context = @import("Context.zig");
const Page = @import("../Page.zig");
const ScriptManager = @import("../ScriptManager.zig");
const ArenaAllocator = std.heap.ArenaAllocator;
const CONTEXT_ARENA_RETAIN = 1024 * 64;
const JsApis = bridge.JsApis;
// 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,
// 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 v8.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, global_callback: ?js.GlobalMissingCallback) !*Context {
std.debug.assert(self.context == null);
const env = self.env;
const isolate = env.isolate;
const templates = &self.env.templates;
var v8_context: v8.Context = blk: {
var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate);
defer temp_scope.deinit();
const js_global = v8.FunctionTemplate.initDefault(isolate);
js_global.setClassName(v8.String.initUtf8(isolate, "Window"));
Env.attachClass(@TypeOf(page.window.*).JsApi, isolate, js_global);
const global_template = js_global.getInstanceTemplate();
global_template.setInternalFieldCount(1);
// Configure the missing property callback on the global
// object.
if (global_callback != null) {
const configuration = v8.NamedPropertyHandlerConfiguration{
.getter = struct {
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
const context = Context.fromIsolate(info.getIsolate());
const property = context.valueToString(.{ .handle = c_name.? }, .{}) catch "???";
if (context.global_callback.?.missing(property, context)) {
return v8.Intercepted.Yes;
}
return v8.Intercepted.No;
}
}.callback,
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
};
global_template.setNamedProperty(configuration, null);
}
// All the FunctionTemplates that we created and setup in Env.init
// are now going to get associated with our global instance.
inline for (JsApis, 0..) |JsApi, i| {
if (@hasDecl(JsApi.Meta, "name")) {
const class_name = v8.String.initUtf8(isolate, JsApi.Meta.name);
global_template.set(class_name.toName(), templates[i], v8.PropertyAttribute.None);
}
}
// The global object (Window) has already been hooked into the v8
// engine when the Env was initialized - like every other type.
// But the V8 global is its own FunctionTemplate instance so even
// though it's also a Window, we need to set the prototype for this
// specific instance of the the Window.
{
const proto_type = @typeInfo(@TypeOf(page.window._proto)).pointer.child;
const proto_index = @field(bridge.JS_API_LOOKUP, @typeName(proto_type.JsApi));
js_global.inherit(templates[proto_index]);
}
const context_local = v8.Context.init(isolate, global_template, null);
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
v8_context.enter();
errdefer if (enter) v8_context.exit();
defer if (!enter) v8_context.exit();
// 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 Env.protoIndexLookup(JsApi)) |proto_index| {
const proto_obj = templates[proto_index].getFunction(v8_context).toObject();
const self_obj = templates[i].getFunction(v8_context).toObject();
_ = self_obj.setPrototype(v8_context, proto_obj);
}
}
break :blk v8_context;
};
// 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
var handle_scope: ?v8.HandleScope = null;
if (enter) {
handle_scope = @as(v8.HandleScope, undefined);
v8.HandleScope.init(&handle_scope.?, isolate);
}
errdefer if (enter) handle_scope.?.deinit();
{
// If we want to overwrite the built-in console, we have to
// delete the built-in one.
const js_obj = v8_context.getGlobal();
const console_key = v8.String.initUtf8(isolate, "console");
if (js_obj.deleteValue(v8_context, console_key) == false) {
return error.ConsoleDeleteError;
}
}
const context_id = env.context_id;
env.context_id = context_id + 1;
self.context = Context{
.page = page,
.id = context_id,
.isolate = isolate,
.v8_context = v8_context,
.templates = &env.templates,
.handle_scope = handle_scope,
.script_manager = &page._script_manager,
.call_arena = page.call_arena,
.arena = self.context_arena.allocator(),
.global_callback = global_callback,
};
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.initBigIntU64(@intCast(@intFromPtr(context)));
v8_context.setEmbedderData(1, data);
}
// @ZIGDOM
// Custom exception
// NOTE: there is no way in v8 to subclass the Error built-in type
// TODO: this is an horrible hack
// inline for (JsApi) |JsApi| {
// const Struct = s.defaultValue().?;
// if (@hasDecl(Struct, "ErrorSet")) {
// const script = comptime JsApi.Meta.name ++ ".prototype.__proto__ = Error.prototype";
// _ = try context.exec(script, "errorSubclass");
// }
// }
try context.setupGlobal();
return context;
}
pub fn removeContext(self: *ExecutionWorld) void {
// Force running the micro task to drain the queue before reseting the
// context arena.
// Tasks in the queue are relying to the arena memory could be present in
// the queue. Running them later could lead to invalid memory accesses.
self.env.runMicrotasks();
self.context.?.deinit();
self.context = null;
_ = 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();
}

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

@@ -0,0 +1,161 @@
// 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 Caller = @import("Caller.zig");
const Context = @import("Context.zig");
const PersistentFunction = v8.Persistent(v8.Function);
const Allocator = std.mem.Allocator;
const Function = @This();
id: usize,
context: *js.Context,
this: ?v8.Object = null,
func: PersistentFunction,
pub const Result = struct {
stack: ?[]const u8,
exception: []const u8,
};
pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 {
const name = self.func.castToFunction().getName();
return self.context.valueToString(name, .{ .allocator = allocator });
}
pub fn setName(self: *const Function, name: []const u8) void {
const v8_name = v8.String.initUtf8(self.context.isolate, name);
self.func.castToFunction().setName(v8_name);
}
pub fn withThis(self: *const Function, value: anytype) !Function {
const this_obj = if (@TypeOf(value) == js.Object)
value.js_obj
else
(try self.context.zigValueToJs(value, .{})).castTo(v8.Object);
return .{
.id = self.id,
.this = this_obj,
.func = self.func,
.context = self.context,
};
}
pub fn newInstance(self: *const Function, result: *Result) !js.Object {
const context = self.context;
var try_catch: js.TryCatch = undefined;
try_catch.init(context);
defer try_catch.deinit();
// This creates a new instance using this Function as a constructor.
// This returns a generic Object
const js_obj = self.func.castToFunction().initInstance(context.v8_context, &.{}) orelse {
if (try_catch.hasCaught()) {
const allocator = context.call_arena;
result.stack = try_catch.stack(allocator) catch null;
result.exception = (try_catch.exception(allocator) catch "???") orelse "???";
} else {
result.stack = null;
result.exception = "???";
}
return error.JsConstructorFailed;
};
return .{
.context = context,
.js_obj = js_obj,
};
}
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, result: *Result) !T {
return self.tryCallWithThis(T, self.getThis(), args, result);
}
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !T {
var try_catch: js.TryCatch = undefined;
try_catch.init(self.context);
defer try_catch.deinit();
return self.callWithThis(T, this, args) catch |err| {
if (try_catch.hasCaught()) {
const allocator = self.context.call_arena;
result.stack = try_catch.stack(allocator) catch null;
result.exception = (try_catch.exception(allocator) catch @errorName(err)) orelse @errorName(err);
} else {
result.stack = null;
result.exception = @errorName(err);
}
return err;
};
}
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
const context = self.context;
const js_this = try context.valueToExistingObject(this);
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
const js_args: []const v8.Value = switch (@typeInfo(@TypeOf(aargs))) {
.@"struct" => |s| blk: {
const fields = s.fields;
var js_args: [fields.len]v8.Value = undefined;
inline for (fields, 0..) |f, i| {
js_args[i] = try context.zigValueToJs(@field(aargs, f.name), .{});
}
const cargs: [fields.len]v8.Value = js_args;
break :blk &cargs;
},
.pointer => blk: {
var values = try context.call_arena.alloc(v8.Value, args.len);
for (args, 0..) |a, i| {
values[i] = try context.zigValueToJs(a);
}
break :blk values;
},
else => @compileError("JS Function called with invalid paremter type"),
};
const result = self.func.castToFunction().call(context.v8_context, js_this, js_args);
if (result == null) {
return error.JSExecCallback;
}
if (@typeInfo(T) == .void) return {};
return context.jsValueToZig(T, result.?);
}
fn getThis(self: *const Function) v8.Object {
return self.this orelse self.context.v8_context.getGlobal();
}
pub fn src(self: *const Function) ![]const u8 {
const value = self.func.castToFunction().toValue();
return self.context.valueToString(value, .{});
}

View File

@@ -0,0 +1,143 @@
// 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 Inspector = @This();
pub const RemoteObject = v8.RemoteObject;
isolate: v8.Isolate,
inner: *v8.Inspector,
session: v8.InspectorSession,
// We expect allocator to be an arena
pub fn init(allocator: Allocator, isolate: v8.Isolate, ctx: anytype) !Inspector {
const ContextT = @TypeOf(ctx);
const InspectorContainer = 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;
const channel = v8.InspectorChannel.init(safe_context, InspectorContainer.onInspectorResponse, InspectorContainer.onInspectorEvent, isolate);
const client = v8.InspectorClient.init();
const inner = try allocator.create(v8.Inspector);
v8.Inspector.init(inner, client, channel, isolate);
return .{ .inner = inner, .isolate = isolate, .session = inner.connect() };
}
pub fn deinit(self: *const Inspector) void {
self.session.deinit();
self.inner.deinit();
}
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.HandleScope.init(&temp_scope, isolate);
defer temp_scope.deinit();
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: *const Inspector,
context: *const Context,
name: []const u8,
origin: []const u8,
aux_data: ?[]const u8,
is_default_context: bool,
) void {
self.inner.contextCreated(context.v8_context, name, origin, aux_data, is_default_context);
}
// 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 PersistedObject 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,
context.v8_context,
js_value,
group,
generate_preview,
);
}
// Gets a value by object ID regardless of which context it is in.
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 toa = getTaggedAnyOpaque(unwrapped.value) orelse return null;
if (toa.subtype == null or toa.subtype != .node) return error.ObjectIdIsNotANode;
return toa.ptr;
}
const NoopInspector = struct {
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
};
pub fn getTaggedAnyOpaque(value: v8.Value) ?*js.TaggedAnyOpaque {
if (value.isObject() == false) {
return null;
}
const obj = value.castTo(v8.Object);
if (obj.internalFieldCount() == 0) {
return null;
}
const external_data = obj.getInternalField(0).castTo(v8.External).get().?;
return @ptrCast(@alignCast(external_data));
}

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

@@ -0,0 +1,157 @@
// 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 Caller = @import("Caller.zig");
const Context = @import("Context.zig");
const PersistentObject = v8.Persistent(v8.Object);
const Allocator = std.mem.Allocator;
const Object = @This();
js_obj: v8.Object,
context: *js.Context,
pub const SetOpts = packed struct(u32) {
READ_ONLY: bool = false,
DONT_ENUM: bool = false,
DONT_DELETE: bool = false,
_: u29 = 0,
};
pub fn setIndex(self: Object, index: u32, value: anytype, opts: SetOpts) !void {
@setEvalBranchQuota(10000);
const key = switch (index) {
inline 0...20 => |i| std.fmt.comptimePrint("{d}", .{i}),
else => try std.fmt.allocPrint(self.context.arena, "{d}", .{index}),
};
return self.set(key, value, opts);
}
pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) error{ FailedToSet, OutOfMemory }!void {
const context = self.context;
const js_key = v8.String.initUtf8(context.isolate, key);
const js_value = try context.zigValueToJs(value);
const res = self.js_obj.defineOwnProperty(context.v8_context, js_key.toName(), js_value, @bitCast(opts)) orelse false;
if (!res) {
return error.FailedToSet;
}
}
pub fn get(self: Object, key: []const u8) !js.Value {
const context = self.context;
const js_key = v8.String.initUtf8(context.isolate, key);
const js_val = try self.js_obj.getValue(context.v8_context, js_key);
return context.createValue(js_val);
}
pub fn isTruthy(self: Object) bool {
const js_value = self.js_obj.toValue();
return js_value.toBool(self.context.isolate);
}
pub fn toString(self: Object) ![]const u8 {
const js_value = self.js_obj.toValue();
return self.context.valueToString(js_value, .{});
}
pub fn toDetailString(self: Object) ![]const u8 {
const js_value = self.js_obj.toValue();
return self.context.valueToDetailString(js_value);
}
pub fn format(self: Object, writer: *std.Io.Writer) !void {
const str = self.toString() catch return error.WriteFailed;
return writer.writeAll(str);
}
pub fn toJson(self: Object, allocator: Allocator) ![]u8 {
const json_string = try v8.Json.stringify(self.context.v8_context, self.js_obj.toValue(), null);
const str = try self.context.jsStringToZig(json_string, .{ .allocator = allocator });
return str;
}
pub fn persist(self: Object) !Object {
var context = self.context;
const js_obj = self.js_obj;
const persisted = PersistentObject.init(context.isolate, js_obj);
try context.js_object_list.append(context.arena, persisted);
return .{
.context = context,
.js_obj = persisted.castToObject(),
};
}
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
if (self.isNullOrUndefined()) {
return null;
}
const context = self.context;
const js_name = v8.String.initUtf8(context.isolate, name);
const js_value = try self.js_obj.getValue(context.v8_context, js_name.toName());
if (!js_value.isFunction()) {
return null;
}
return try context.createFunction(js_value);
}
pub fn isNull(self: Object) bool {
return self.js_obj.toValue().isNull();
}
pub fn isUndefined(self: Object) bool {
return self.js_obj.toValue().isUndefined();
}
pub fn isNullOrUndefined(self: Object) bool {
return self.js_obj.toValue().isNullOrUndefined();
}
pub fn nameIterator(self: Object) js.ValueIterator {
const context = self.context;
const js_obj = self.js_obj;
const array = js_obj.getPropertyNames(context.v8_context);
const count = array.length();
return .{
.count = count,
.context = context,
.js_obj = array.castTo(v8.Object),
};
}
pub fn toZig(self: Object, comptime T: type) !T {
return self.context.jsValueToZig(T, self.js_obj.toValue());
}
pub fn TriState(comptime T: type) type {
return union(enum) {
null: void,
undefined: void,
value: T,
};
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
@@ -16,28 +16,24 @@
// 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 generate = @import("generate.zig");
const js = @import("js.zig");
const v8 = js.v8;
const Console = @import("jsruntime").Console;
const Platform = @This();
inner: v8.Platform,
const DOM = @import("dom/dom.zig");
const HTML = @import("html/html.zig");
const Events = @import("events/event.zig");
const XHR = @import("xhr/xhr.zig");
const Storage = @import("storage/storage.zig");
const URL = @import("url/url.zig");
pub fn init() !Platform {
if (v8.initV8ICU() == false) {
return error.FailedToInitializeICU;
}
const platform = v8.Platform.initDefault(0, true);
v8.initV8Platform(platform);
v8.initV8();
return .{ .inner = platform };
}
pub const HTMLDocument = @import("html/document.zig").HTMLDocument;
// Interfaces
pub const Interfaces = generate.Tuple(.{
Console,
DOM.Interfaces,
Events.Interfaces,
HTML.Interfaces,
XHR.Interfaces,
Storage.Interfaces,
URL.Interfaces,
});
pub const UserContext = @import("user_context.zig").UserContext;
pub fn deinit(self: Platform) void {
_ = v8.deinitV8();
v8.deinitV8Platform();
self.inner.deinit();
}

43
src/browser/js/This.zig Normal file
View File

@@ -0,0 +1,43 @@
// 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;
// This only exists so that we know whether a function wants the opaque
// JS argument (js.Object), or if it wants the receiver as an opaque
// value.
// js.Object is normally used when a method wants an opaque JS object
// that it'll pass into a callback.
// This is used when the function wants to do advanced manipulation
// of the v8.Object bound to the instance. For example, postAttach is an
// example of using This.
const This = @This();
obj: js.Object,
pub fn setIndex(self: This, index: u32, value: anytype, opts: js.Object.SetOpts) !void {
return self.obj.setIndex(index, value, opts);
}
pub fn set(self: This, key: []const u8, value: anytype, opts: js.Object.SetOpts) !void {
return self.obj.set(key, value, opts);
}

View File

@@ -0,0 +1,82 @@
// 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 TryCatch = @This();
inner: v8.TryCatch,
context: *const js.Context,
pub fn init(self: *TryCatch, context: *const js.Context) void {
self.context = context;
self.inner.init(context.isolate);
}
pub fn hasCaught(self: TryCatch) bool {
return self.inner.hasCaught();
}
// the caller needs to deinit the string returned
pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 {
const msg = self.inner.getException() orelse return null;
return try self.context.valueToString(msg, .{ .allocator = allocator });
}
// the caller needs to deinit the string returned
pub fn stack(self: TryCatch, allocator: Allocator) !?[]const u8 {
const context = self.context;
const s = self.inner.getStackTrace(context.v8_context) orelse return null;
return try context.valueToString(s, .{ .allocator = allocator });
}
// the caller needs to deinit the string returned
pub fn sourceLine(self: TryCatch, allocator: Allocator) !?[]const u8 {
const context = self.context;
const msg = self.inner.getMessage() orelse return null;
const sl = msg.getSourceLine(context.v8_context) orelse return null;
return try context.jsStringToZig(sl, .{ .allocator = allocator });
}
pub fn sourceLineNumber(self: TryCatch) ?u32 {
const context = self.context;
const msg = self.inner.getMessage() orelse return null;
return msg.getLineNumber(context.v8_context);
}
// a shorthand method to return either the entire stack message
// or just the exception message
// - in Debug mode return the stack if available
// - otherwise return the exception if available
// the caller needs to deinit the string returned
pub fn err(self: TryCatch, allocator: Allocator) !?[]const u8 {
if (comptime @import("builtin").mode == .Debug) {
if (try self.stack(allocator)) |msg| {
return msg;
}
}
return try self.exception(allocator);
}
pub fn deinit(self: *TryCatch) void {
self.inner.deinit();
}

476
src/browser/js/bridge.zig Normal file
View File

@@ -0,0 +1,476 @@
// 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 log = @import("../../log.zig");
const v8 = js.v8;
const Caller = @import("Caller.zig");
pub fn Builder(comptime T: type) type {
return struct {
pub const @"type" = T;
pub fn constructor(comptime func: anytype, comptime opts: Constructor.Opts) Constructor {
return Constructor.init(T, func, opts);
}
pub fn accessor(comptime getter: anytype, comptime setter: anytype, comptime opts: Accessor.Opts) Accessor {
return Accessor.init(T, getter, setter, opts);
}
pub fn function(comptime func: anytype, comptime opts: Function.Opts) Function {
return Function.init(T, func, opts);
}
pub fn indexed(comptime getter_func: anytype, comptime opts: Indexed.Opts) Indexed {
return Indexed.init(T, getter_func, opts);
}
pub fn namedIndexed(comptime getter_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed {
return NamedIndexed.init(T, getter_func, opts);
}
pub fn iterator(comptime func: anytype, comptime opts: Iterator.Opts) Iterator {
return Iterator.init(T, func, opts);
}
pub fn property(value: anytype) Property.GetType(@TypeOf(value)) {
return Property.GetType(@TypeOf(value)).init(value);
}
pub fn prototypeChain() [prototypeChainLength(T)]js.PrototypeChainEntry {
var entries: [prototypeChainLength(T)]js.PrototypeChainEntry = undefined;
entries[0] = .{
.offset = 0,
.index = @field(JS_API_LOOKUP, @typeName(T.JsApi)),
};
if (entries.len == 1) {
return entries;
}
var Prototype = T;
for (entries[1..]) |*entry| {
const Next = PrototypeType(Prototype).?;
entry.* = .{
.index = @field(JS_API_LOOKUP, @typeName(Next.JsApi)),
.offset = @offsetOf(Prototype, "_proto"),
};
Prototype = Next;
}
return entries;
}
};
}
pub const Constructor = struct {
func: *const fn (?*const v8.C_FunctionCallbackInfo) callconv(.c) void,
const Opts = struct {
dom_exception: bool = false,
};
fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Constructor {
return .{ .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
caller.constructor(T, func, info, .{
.dom_exception = opts.dom_exception,
});
}
}.wrap };
}
};
pub const Function = struct {
static: bool,
func: *const fn (?*const v8.C_FunctionCallbackInfo) callconv(.c) void,
const Opts = struct {
static: bool = false,
dom_exception: bool = false,
as_typed_array: bool = false,
null_as_undefined: bool = false,
};
fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Function {
return .{
.static = opts.static,
.func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
if (comptime opts.static) {
caller.function(T, func, info, .{
.dom_exception = opts.dom_exception,
.as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined,
});
} else {
caller.method(T, func, info, .{
.dom_exception = opts.dom_exception,
.as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined,
});
}
}
}.wrap,
};
}
};
pub const Accessor = struct {
getter: ?*const fn (?*const v8.C_FunctionCallbackInfo) callconv(.c) void = null,
setter: ?*const fn (?*const v8.C_FunctionCallbackInfo) callconv(.c) void = null,
const Opts = struct {
cache: ?[]const u8 = null, // @ZIGDOM
as_typed_array: bool = false,
null_as_undefined: bool = false,
};
fn init(comptime T: type, comptime getter: anytype, comptime setter: anytype, comptime opts: Opts) Accessor {
var accessor = Accessor{};
if (@typeInfo(@TypeOf(getter)) != .null) {
accessor.getter = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
caller.method(T, getter, info, .{
.as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined,
});
}
}.wrap;
}
if (@typeInfo(@TypeOf(setter)) != .null) {
accessor.setter = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
std.debug.assert(info.length() == 1);
var caller = Caller.init(info);
defer caller.deinit();
caller.method(T, setter, info, .{
.as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined,
});
}
}.wrap;
}
return accessor;
}
};
pub const Indexed = struct {
getter: *const fn (idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8,
const Opts = struct {
as_typed_array: bool = false,
null_as_undefined: bool = false,
};
fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) Indexed {
return .{ .getter = struct {
fn wrap(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
return caller.getIndex(T, getter, idx, info, .{
.as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined,
});
}
}.wrap };
}
};
pub const NamedIndexed = struct {
getter: *const fn (c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8,
const Opts = struct {
as_typed_array: bool = false,
null_as_undefined: bool = false,
};
fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) NamedIndexed {
return .{ .getter = struct {
fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{
.as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined,
});
}
}.wrap };
}
};
pub const Iterator = struct {
func: *const fn (?*const v8.C_FunctionCallbackInfo) callconv(.c) void,
const Opts = struct {};
fn init(comptime T: type, comptime struct_or_func: anytype, comptime opts: Opts) Iterator {
_ = opts;
if (@typeInfo(@TypeOf(struct_or_func)) == .type) {
return .{ .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
info.getReturnValue().set(info.getThis());
}
}.wrap };
}
return .{ .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
var caller = Caller.init(info);
defer caller.deinit();
caller.method(T, struct_or_func, info, .{});
}
}.wrap };
}
};
pub const Property = struct {
fn GetType(comptime T: type) type {
switch (@typeInfo(T)) {
.comptime_int, .int => return Int,
else => @compileError("Property for " ++ @typeName(T) ++ " hasn't been defined yet"),
}
}
pub const Int = struct {
int: i64,
pub fn init(value: i64) Int {
return .{ .int = value };
}
};
};
// Given a Type, returns the length of the prototype chain, including self
fn prototypeChainLength(comptime T: type) usize {
var l: usize = 1;
var Next = T;
while (PrototypeType(Next)) |N| {
Next = N;
l += 1;
}
return l;
}
// Given a Type, gets its prototype Type (if any)
fn PrototypeType(comptime T: type) ?type {
if (!@hasField(T, "_proto")) {
return null;
}
return Struct(std.meta.fieldInfo(T, ._proto).type);
}
fn flattenTypes(comptime Types: []const type) [countFlattenedTypes(Types)]type {
var index: usize = 0;
var flat: [countFlattenedTypes(Types)]type = undefined;
for (Types) |T| {
if (@hasDecl(T, "registerTypes")) {
for (T.registerTypes()) |TT| {
flat[index] = TT.JsApi;
index += 1;
}
} else {
flat[index] = T.JsApi;
index += 1;
}
}
return flat;
}
fn countFlattenedTypes(comptime Types: []const type) usize {
var c: usize = 0;
for (Types) |T| {
c += if (@hasDecl(T, "registerTypes")) T.registerTypes().len else 1;
}
return c;
}
// T => T
// *T => T
pub fn Struct(comptime T: type) type {
return switch (@typeInfo(T)) {
.@"struct" => T,
.pointer => |ptr| ptr.child,
else => @compileError("Expecting Struct or *Struct, got: " ++ @typeName(T)),
};
}
// Imagine we have a type Cat which has a getter:
//
// fn getOwner(self: *Cat) *Owner {
// return self.owner;
// }
//
// When we execute caller.getter, we'll end up doing something like:
// const res = @call(.auto, Cat.getOwner, .{cat_instance});
//
// How do we turn `res`, which is an *Owner, into something we can return
// to v8? We need the ObjectTemplate associated with Owner. How do we
// get that? Well, we store all the ObjectTemplates in an array that's
// tied to env. So we do something like:
//
// env.templates[index_of_owner].initInstance(...);
//
// But how do we get that `index_of_owner`? `Lookup` is a struct
// that looks like:
//
// const Lookup = struct {
// comptime cat: usize = 0,
// comptime owner: usize = 1,
// ...
// }
//
// So to get the template index of `owner`, we can do:
//
// const index_id = @field(type_lookup, @typeName(@TypeOf(res));
//
pub const JsApiLookup = blk: {
var fields: [JsApis.len]std.builtin.Type.StructField = undefined;
for (JsApis, 0..) |JsApi, i| {
fields[i] = .{
.name = @typeName(JsApi),
.type = u16,
.is_comptime = true,
.alignment = @alignOf(u16),
.default_value_ptr = @ptrCast(&i),
};
}
break :blk @Type(.{ .@"struct" = .{
.layout = .auto,
.decls = &.{},
.is_tuple = false,
.fields = &fields,
} });
};
pub const JS_API_LOOKUP = JsApiLookup{};
pub const SubType = enum {
@"error",
array,
arraybuffer,
dataview,
date,
generator,
iterator,
map,
node,
promise,
proxy,
regexp,
set,
typedarray,
wasmvalue,
weakmap,
weakset,
webassemblymemory,
};
pub const JsApis = flattenTypes(&.{
@import("../webapi/AbortController.zig"),
@import("../webapi/AbortSignal.zig"),
@import("../webapi/CData.zig"),
@import("../webapi/cdata/Comment.zig"),
@import("../webapi/cdata/Text.zig"),
@import("../webapi/collections.zig"),
@import("../webapi/Console.zig"),
@import("../webapi/Crypto.zig"),
@import("../webapi/css/CSSStyleDeclaration.zig"),
@import("../webapi/css/CSSStyleProperties.zig"),
@import("../webapi/Document.zig"),
@import("../webapi/HTMLDocument.zig"),
@import("../webapi/DocumentFragment.zig"),
@import("../webapi/DOMException.zig"),
@import("../webapi/DOMTreeWalker.zig"),
@import("../webapi/DOMNodeIterator.zig"),
@import("../webapi/NodeFilter.zig"),
@import("../webapi/Element.zig"),
@import("../webapi/element/Attribute.zig"),
@import("../webapi/element/Html.zig"),
@import("../webapi/element/html/Anchor.zig"),
@import("../webapi/element/html/Body.zig"),
@import("../webapi/element/html/BR.zig"),
@import("../webapi/element/html/Button.zig"),
@import("../webapi/element/html/Custom.zig"),
@import("../webapi/element/html/Div.zig"),
@import("../webapi/element/html/Form.zig"),
@import("../webapi/element/html/Generic.zig"),
@import("../webapi/element/html/Head.zig"),
@import("../webapi/element/html/Heading.zig"),
@import("../webapi/element/html/HR.zig"),
@import("../webapi/element/html/Html.zig"),
@import("../webapi/element/html/Image.zig"),
@import("../webapi/element/html/Input.zig"),
@import("../webapi/element/html/LI.zig"),
@import("../webapi/element/html/Link.zig"),
@import("../webapi/element/html/Meta.zig"),
@import("../webapi/element/html/OL.zig"),
@import("../webapi/element/html/Option.zig"),
@import("../webapi/element/html/Paragraph.zig"),
@import("../webapi/element/html/Script.zig"),
@import("../webapi/element/html/Select.zig"),
@import("../webapi/element/html/Style.zig"),
@import("../webapi/element/html/TextArea.zig"),
@import("../webapi/element/html/Title.zig"),
@import("../webapi/element/html/UL.zig"),
@import("../webapi/element/html/Unknown.zig"),
@import("../webapi/element/Svg.zig"),
@import("../webapi/element/svg/Generic.zig"),
@import("../webapi/encoding/TextDecoder.zig"),
@import("../webapi/encoding/TextEncoder.zig"),
@import("../webapi/Event.zig"),
@import("../webapi/event/ErrorEvent.zig"),
@import("../webapi/event/ProgressEvent.zig"),
@import("../webapi/EventTarget.zig"),
@import("../webapi/Location.zig"),
@import("../webapi/Navigator.zig"),
@import("../webapi/net/Request.zig"),
@import("../webapi/net/Response.zig"),
@import("../webapi/net/URLSearchParams.zig"),
@import("../webapi/net/XMLHttpRequest.zig"),
@import("../webapi/net/XMLHttpRequestEventTarget.zig"),
@import("../webapi/Node.zig"),
@import("../webapi/storage/storage.zig"),
@import("../webapi/URL.zig"),
@import("../webapi/Window.zig"),
@import("../webapi/MutationObserver.zig"),
});

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

@@ -0,0 +1,502 @@
// 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");
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");
// TODO: Is "This" really necessary?
pub const This = @import("This.zig");
pub const Object = @import("Object.zig");
pub const TryCatch = @import("TryCatch.zig");
pub const Function = @import("Function.zig");
const Caller = @import("Caller.zig");
const Page = @import("../Page.zig");
const Allocator = std.mem.Allocator;
const NamedFunction = Context.NamedFunction;
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 {
pub const _TYPED_ARRAY_ID_KLUDGE = true;
values: []const T,
pub fn dupe(self: TypedArray(T), allocator: Allocator) !TypedArray(T) {
return .{ .values = try allocator.dupe(T, self.values) };
}
};
}
pub const PromiseResolver = struct {
context: *Context,
resolver: v8.PromiseResolver,
pub fn promise(self: PromiseResolver) Promise {
return self.resolver.getPromise();
}
pub fn resolve(self: PromiseResolver, value: anytype) !void {
const context = self.context;
const js_value = try context.zigValueToJs(value);
// resolver.resolve will return null if the promise isn't pending
const ok = self.resolver.resolve(context.v8_context, js_value) orelse return;
if (!ok) {
return error.FailedToResolvePromise;
}
}
pub fn reject(self: PromiseResolver, value: anytype) !void {
const context = self.context;
const js_value = try context.zigValueToJs(value);
// resolver.reject will return null if the promise isn't pending
const ok = self.resolver.reject(context.v8_context, js_value) orelse return;
if (!ok) {
return error.FailedToRejectPromise;
}
}
};
pub const PersistentPromiseResolver = struct {
context: *Context,
resolver: v8.Persistent(v8.PromiseResolver),
pub fn deinit(self: *PersistentPromiseResolver) void {
self.resolver.deinit();
}
pub fn promise(self: PersistentPromiseResolver) Promise {
return self.resolver.castToPromiseResolver().getPromise();
}
pub fn resolve(self: PersistentPromiseResolver, value: anytype) !void {
const context = self.context;
const js_value = try context.zigValueToJs(value, .{});
// resolver.resolve will return null if the promise isn't pending
const ok = self.resolver.castToPromiseResolver().resolve(context.v8_context, js_value) orelse return;
if (!ok) {
return error.FailedToResolvePromise;
}
}
pub fn reject(self: PersistentPromiseResolver, value: anytype) !void {
const context = self.context;
const js_value = try context.zigValueToJs(value, .{});
// resolver.reject will return null if the promise isn't pending
const ok = self.resolver.castToPromiseResolver().reject(context.v8_context, js_value) orelse return;
if (!ok) {
return error.FailedToRejectPromise;
}
}
};
pub const Promise = v8.Promise;
// When doing jsValueToZig, string ([]const u8) are managed by the
// call_arena. That means that if the API wants to persist the string
// (which is relatively common), it needs to dupe it again.
// If the parameter is an Env.String rather than a []const u8, then
// the page's arena will be used (rather than the call arena).
pub const String = struct {
string: []const u8,
};
pub const Exception = struct {
inner: v8.Value,
context: *const Context,
// the caller needs to deinit the string returned
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
return self.context.valueToString(self.inner, .{ .allocator = allocator });
}
};
pub const Value = struct {
value: v8.Value,
context: *const Context,
// the caller needs to deinit the string returned
pub fn toString(self: Value, allocator: Allocator) ![]const u8 {
return self.context.valueToString(self.value, .{ .allocator = allocator });
}
pub fn fromJson(ctx: *Context, json: []const u8) !Value {
const json_string = v8.String.initUtf8(ctx.isolate, json);
const value = try v8.Json.parse(ctx.v8_context, json_string);
return Value{ .context = ctx, .value = value };
}
};
pub const ValueIterator = struct {
count: u32,
idx: u32 = 0,
js_obj: v8.Object,
context: *const Context,
pub fn next(self: *ValueIterator) !?Value {
const idx = self.idx;
if (idx == self.count) {
return null;
}
self.idx += 1;
const context = self.context;
const js_val = try self.js_obj.getAtIndex(context.v8_context, idx);
return context.createValue(js_val);
}
};
pub fn UndefinedOr(comptime T: type) type {
return union(enum) {
undefined: void,
value: T,
};
}
// An interface for types that want to have their jsScopeEnd function be
// called when the call context ends
const CallScopeEndCallback = struct {
ptr: *anyopaque,
callScopeEndFn: *const fn (ptr: *anyopaque) void,
fn init(ptr: anytype) CallScopeEndCallback {
const T = @TypeOf(ptr);
const ptr_info = @typeInfo(T);
const gen = struct {
pub fn callScopeEnd(pointer: *anyopaque) void {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.jsCallScopeEnd(self);
}
};
return .{
.ptr = ptr,
.callScopeEndFn = gen.callScopeEnd,
};
}
pub fn callScopeEnd(self: CallScopeEndCallback) void {
self.callScopeEndFn(self.ptr);
}
};
// Callback called on global's property missing.
// Return true to intercept the execution or false to let the call
// continue the chain.
pub const GlobalMissingCallback = struct {
ptr: *anyopaque,
missingFn: *const fn (ptr: *anyopaque, name: []const u8, ctx: *Context) bool,
pub fn init(ptr: anytype) GlobalMissingCallback {
const T = @TypeOf(ptr);
const ptr_info = @typeInfo(T);
const gen = struct {
pub fn missing(pointer: *anyopaque, name: []const u8, ctx: *Context) bool {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.missing(self, name, ctx);
}
};
return .{
.ptr = ptr,
.missingFn = gen.missing,
};
}
pub fn missing(self: GlobalMissingCallback, name: []const u8, ctx: *Context) bool {
return self.missingFn(self.ptr, name, ctx);
}
};
// Attributes that return a primitive type are setup directly on the
// FunctionTemplate when the Env is setup. More complex types need a v8.Context
// and cannot be set directly on the FunctionTemplate.
// We default to saying types are primitives because that's mostly what
// we have. If we add a new complex type that isn't explictly handled here,
// we'll get a compiler error in simpleZigValueToJs, and can then explicitly
// add the type here.
pub fn isComplexAttributeType(ti: std.builtin.Type) bool {
return switch (ti) {
.array => true,
else => false,
};
}
// 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: v8.Isolate, value: anytype, comptime fail: bool, comptime null_as_undefined: bool) if (fail) v8.Value else ?v8.Value {
switch (@typeInfo(@TypeOf(value))) {
.void => return v8.initUndefined(isolate).toValue(),
.null => if (comptime null_as_undefined) return v8.initUndefined(isolate).toValue() else return v8.initNull(isolate).toValue(),
.bool => return v8.getValue(if (value) v8.initTrue(isolate) else v8.initFalse(isolate)),
.int => |n| switch (n.signedness) {
.signed => {
if (value >= -2_147_483_648 and value <= 2_147_483_647) {
return v8.Integer.initI32(isolate, @intCast(value)).toValue();
}
if (comptime n.bits <= 64) {
return v8.getValue(v8.BigInt.initI64(isolate, @intCast(value)));
}
@compileError(@typeName(value) ++ " is not supported");
},
.unsigned => {
if (value <= 4_294_967_295) {
return v8.Integer.initU32(isolate, @intCast(value)).toValue();
}
if (comptime n.bits <= 64) {
return v8.getValue(v8.BigInt.initU64(isolate, @intCast(value)));
}
@compileError(@typeName(value) ++ " is not supported");
},
},
.comptime_int => {
if (value >= 0) {
if (value <= 4_294_967_295) {
return v8.Integer.initU32(isolate, @intCast(value)).toValue();
}
return v8.BigInt.initU64(isolate, @intCast(value)).toValue();
}
if (value >= -2_147_483_648) {
return v8.Integer.initI32(isolate, @intCast(value)).toValue();
}
return v8.BigInt.initI64(isolate, @intCast(value)).toValue();
},
.comptime_float => return v8.Number.init(isolate, value).toValue(),
.float => |f| switch (f.bits) {
64 => return v8.Number.init(isolate, value).toValue(),
32 => return v8.Number.init(isolate, @floatCast(value)).toValue(),
else => @compileError(@typeName(value) ++ " is not supported"),
},
.pointer => |ptr| {
if (ptr.size == .slice and ptr.child == u8) {
return v8.String.initUtf8(isolate, value).toValue();
}
if (ptr.size == .one) {
const one_info = @typeInfo(ptr.child);
if (one_info == .array and one_info.array.child == u8) {
return v8.String.initUtf8(isolate, value).toValue();
}
}
},
.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 v8.initUndefined(isolate).toValue();
}
return v8.initNull(isolate).toValue();
},
.@"struct" => {
const T = @TypeOf(value);
if (@hasDecl(T, "_TYPED_ARRAY_ID_KLUDGE")) {
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: v8.ArrayBuffer = undefined;
if (len == 0) {
array_buffer = v8.ArrayBuffer.init(isolate, 0);
} else {
const buffer_len = len * bits / 8;
const backing_store = v8.BackingStore.init(isolate, buffer_len);
const data: [*]u8 = @ptrCast(@alignCast(backing_store.getData()));
@memcpy(data[0..buffer_len], @as([]const u8, @ptrCast(values))[0..buffer_len]);
array_buffer = v8.ArrayBuffer.initWithBackingStore(isolate, &backing_store.toSharedPtr());
}
switch (@typeInfo(value_type)) {
.int => |n| switch (n.signedness) {
.unsigned => switch (n.bits) {
8 => return v8.Uint8Array.init(array_buffer, 0, len).toValue(),
16 => return v8.Uint16Array.init(array_buffer, 0, len).toValue(),
32 => return v8.Uint32Array.init(array_buffer, 0, len).toValue(),
64 => return v8.BigUint64Array.init(array_buffer, 0, len).toValue(),
else => {},
},
.signed => switch (n.bits) {
8 => return v8.Int8Array.init(array_buffer, 0, len).toValue(),
16 => return v8.Int16Array.init(array_buffer, 0, len).toValue(),
32 => return v8.Int32Array.init(array_buffer, 0, len).toValue(),
64 => return v8.BigInt64Array.init(array_buffer, 0, len).toValue(),
else => {},
},
},
.float => |f| switch (f.bits) {
32 => return v8.Float32Array.init(array_buffer, 0, len).toValue(),
64 => return v8.Float64Array.init(array_buffer, 0, len).toValue(),
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));
}
},
.@"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;
}
pub fn _createException(isolate: v8.Isolate, msg: []const u8) v8.Value {
return v8.Exception.initError(v8.String.initUtf8(isolate, msg));
}
pub fn classNameForStruct(comptime Struct: type) []const u8 {
if (@hasDecl(Struct, "js_name")) {
return Struct.js_name;
}
@setEvalBranchQuota(10_000);
const full_name = @typeName(Struct);
const last = std.mem.lastIndexOfScalar(u8, full_name, '.') orelse return full_name;
return full_name[last + 1 ..];
}
// 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 v8.Value we
// can get a v8.Object, and from the v8.Object, we can get out TaggedAnyOpaque
// which is where we store the subtype.
subtype: ?bridge.SubType,
};
pub const PrototypeChainEntry = struct {
index: u16,
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.c.InspectorClientImpl,
c_value: *const v8.C_Value,
) callconv(.c) [*c]const u8 {
const external_entry = Inspector.getTaggedAnyOpaque(.{ .handle = 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.c.InspectorClientImpl,
v8_context: *const v8.C_Context,
c_value: *const v8.C_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(.{ .handle = 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,91 +0,0 @@
// 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 Client = @import("../http/Client.zig");
const user_agent = "Lightpanda.io/1.0";
pub const Loader = struct {
client: Client,
// use 64KB for headers buffer size.
server_header_buffer: [1024 * 64]u8 = undefined,
pub const Response = struct {
alloc: std.mem.Allocator,
req: *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 = Client{
.allocator = alloc,
},
};
}
pub fn deinit(self: *Loader) void {
self.client.deinit();
}
// 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 resp = Response{
.alloc = alloc,
.req = try alloc.create(Client.Request),
};
errdefer alloc.destroy(resp.req);
resp.req.* = try self.client.open(.GET, uri, .{
.headers = .{
.user_agent = .{ .override = user_agent },
},
.extra_headers = &.{
.{ .name = "Accept", .value = "*/*" },
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
},
.server_header_buffer = &self.server_header_buffer,
});
errdefer resp.req.deinit();
try resp.req.send();
try resp.req.finish();
try resp.req.wait();
return resp;
}
};
test "basic url get" {
const alloc = std.testing.allocator;
var loader = Loader.init(alloc);
defer loader.deinit();
var result = try loader.get(alloc, "https://en.wikipedia.org/wiki/Main_Page");
defer result.deinit();
try std.testing.expect(result.req.response.status == std.http.Status.ok);
}

View File

@@ -1,157 +0,0 @@
// 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 testing = std.testing;
const strparser = @import("../str/parser.zig");
const Reader = strparser.Reader;
const trim = strparser.trim;
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" };
// 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,243 @@
const std = @import("std");
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,
};
};
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,
appendDoctypeToDocument,
);
}
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,
appendDoctypeToDocument,
);
}
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 {
std.debug.assert(self.handle == null);
self.handle = h5e.html5ever_streaming_parser_create(
&self.parser.container,
&self.parser,
createElementCallback,
getDataCallback,
appendCallback,
parseErrorCallback,
popCallback,
createCommentCallback,
appendDoctypeToDocument,
) orelse return error.ParserCreationFailed;
}
pub fn read(self: *Streaming, data: []const u8) void {
h5e.html5ever_streaming_parser_feed(
self.handle.?,
data.ptr,
data.len,
);
}
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 {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._createElementCallback(data, qname, attributes) catch |err| {
self.err = .{ .err = err, .source = .create_element };
return null;
};
}
fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) !*anyopaque {
const page = self.page;
const name = qname.local.slice();
const namespace = qname.ns.slice();
const node = try page.createElement(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 appendDoctypeToDocument(ctx: *anyopaque, name: h5e.StringSlice, public_id: h5e.StringSlice, system_id: h5e.StringSlice) callconv(.c) void {
_ = public_id;
_ = system_id;
const self: *Parser = @ptrCast(@alignCast(ctx));
self._appendDoctypeToDocument(name.slice()) catch |err| {
self.err = .{ .err = err, .source = .append_doctype_to_document };
};
}
fn _appendDoctypeToDocument(self: *Parser, name: []const u8) !void {
_ = self;
_ = name;
}
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.
std.debug.assert(pn.data != null);
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 {
switch (node_or_text.toUnion()) {
.node => |cpn| {
const child = getNode(cpn);
// child node is guaranteed not to belong to another parent
try self.page.appendNew(parent, .{ .node = child });
},
.text => |txt| {
try self.page.appendNew(parent, .{ .text = txt });
},
}
}
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,134 @@
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,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) 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,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) 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,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
) ?*anyopaque;
pub extern "c" fn html5ever_streaming_parser_feed(
parser: *anyopaque,
html: [*c]const u8,
len: usize,
) void;
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,
};
};

View File

@@ -0,0 +1,100 @@
// 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 js = @import("../js/js.zig");
const log = @import("../../log.zig");
const Allocator = std.mem.Allocator;
pub const Loader = struct {
state: enum { empty, loading } = .empty,
done: struct {
webcomponents: bool = false,
} = .{},
fn load(self: *Loader, comptime name: []const u8, source: []const u8, js_context: *js.Context) void {
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
self.state = .loading;
defer self.state = .empty;
log.debug(.js, "polyfill load", .{ .name = name });
_ = js_context.exec(source, name) catch |err| {
log.fatal(.app, "polyfill error", .{
.name = name,
.err = try_catch.err(js_context.call_arena) catch @errorName(err) orelse @errorName(err),
});
};
@field(self.done, name) = true;
}
pub fn missing(self: *Loader, name: []const u8, js_context: *js.Context) bool {
// Avoid recursive calls during polyfill loading.
if (self.state == .loading) {
return false;
}
if (!self.done.webcomponents and isWebcomponents(name)) {
const source = @import("webcomponents.zig").source;
self.load("webcomponents", source, js_context);
// We return false here: We want v8 to continue the calling chain
// to finally find the polyfill we just inserted. If we want to
// return false and stops the call chain, we have to use
// `info.GetReturnValue.Set()` function, or `undefined` will be
// returned immediately.
return false;
}
if (comptime builtin.mode == .Debug) {
log.debug(.unknown_prop, "unkown global property", .{
.info = "but the property can exist in pure JS",
.stack = js_context.stackTrace() catch "???",
.property = name,
});
}
return false;
}
fn isWebcomponents(name: []const u8) bool {
if (std.mem.eql(u8, name, "customElements")) return true;
return false;
}
};
pub fn preload(allocator: Allocator, js_context: *js.Context) !void {
var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
const name = "webcomponents-pre";
const source = @import("webcomponents.zig").pre;
_ = js_context.exec(source, name) catch |err| {
if (try try_catch.err(allocator)) |msg| {
defer allocator.free(msg);
log.fatal(.app, "polyfill error", .{ .name = name, .err = msg });
}
return err;
};
}

View File

@@ -0,0 +1,61 @@
/**
@license @nocompile
Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function(){/*
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt The complete set of authors may be found
at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
Google as part of the polymer project is also subject to an additional IP
rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';var n=window.Document.prototype.createElement,p=window.Document.prototype.createElementNS,aa=window.Document.prototype.importNode,ba=window.Document.prototype.prepend,ca=window.Document.prototype.append,da=window.DocumentFragment.prototype.prepend,ea=window.DocumentFragment.prototype.append,q=window.Node.prototype.cloneNode,r=window.Node.prototype.appendChild,t=window.Node.prototype.insertBefore,u=window.Node.prototype.removeChild,v=window.Node.prototype.replaceChild,w=Object.getOwnPropertyDescriptor(window.Node.prototype,
"textContent"),y=window.Element.prototype.attachShadow,z=Object.getOwnPropertyDescriptor(window.Element.prototype,"innerHTML"),A=window.Element.prototype.getAttribute,B=window.Element.prototype.setAttribute,C=window.Element.prototype.removeAttribute,D=window.Element.prototype.toggleAttribute,E=window.Element.prototype.getAttributeNS,F=window.Element.prototype.setAttributeNS,G=window.Element.prototype.removeAttributeNS,H=window.Element.prototype.insertAdjacentElement,fa=window.Element.prototype.insertAdjacentHTML,
ha=window.Element.prototype.prepend,ia=window.Element.prototype.append,ja=window.Element.prototype.before,ka=window.Element.prototype.after,la=window.Element.prototype.replaceWith,ma=window.Element.prototype.remove,na=window.HTMLElement,I=Object.getOwnPropertyDescriptor(window.HTMLElement.prototype,"innerHTML"),oa=window.HTMLElement.prototype.insertAdjacentElement,pa=window.HTMLElement.prototype.insertAdjacentHTML;var qa=new Set;"annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" ").forEach(function(a){return qa.add(a)});function ra(a){var b=qa.has(a);a=/^[a-z][.0-9_a-z]*-[-.0-9_a-z]*$/.test(a);return!b&&a}var sa=document.contains?document.contains.bind(document):document.documentElement.contains.bind(document.documentElement);
function J(a){var b=a.isConnected;if(void 0!==b)return b;if(sa(a))return!0;for(;a&&!(a.__CE_isImportDocument||a instanceof Document);)a=a.parentNode||(window.ShadowRoot&&a instanceof ShadowRoot?a.host:void 0);return!(!a||!(a.__CE_isImportDocument||a instanceof Document))}function K(a){var b=a.children;if(b)return Array.prototype.slice.call(b);b=[];for(a=a.firstChild;a;a=a.nextSibling)a.nodeType===Node.ELEMENT_NODE&&b.push(a);return b}
function L(a,b){for(;b&&b!==a&&!b.nextSibling;)b=b.parentNode;return b&&b!==a?b.nextSibling:null}
function M(a,b,d){for(var f=a;f;){if(f.nodeType===Node.ELEMENT_NODE){var c=f;b(c);var e=c.localName;if("link"===e&&"import"===c.getAttribute("rel")){f=c.import;void 0===d&&(d=new Set);if(f instanceof Node&&!d.has(f))for(d.add(f),f=f.firstChild;f;f=f.nextSibling)M(f,b,d);f=L(a,c);continue}else if("template"===e){f=L(a,c);continue}if(c=c.__CE_shadowRoot)for(c=c.firstChild;c;c=c.nextSibling)M(c,b,d)}f=f.firstChild?f.firstChild:L(a,f)}};function N(){var a=!(null===O||void 0===O||!O.noDocumentConstructionObserver),b=!(null===O||void 0===O||!O.shadyDomFastWalk);this.m=[];this.g=[];this.j=!1;this.shadyDomFastWalk=b;this.I=!a}function P(a,b,d,f){var c=window.ShadyDOM;if(a.shadyDomFastWalk&&c&&c.inUse){if(b.nodeType===Node.ELEMENT_NODE&&d(b),b.querySelectorAll)for(a=c.nativeMethods.querySelectorAll.call(b,"*"),b=0;b<a.length;b++)d(a[b])}else M(b,d,f)}function ta(a,b){a.j=!0;a.m.push(b)}function ua(a,b){a.j=!0;a.g.push(b)}
function Q(a,b){a.j&&P(a,b,function(d){return R(a,d)})}function R(a,b){if(a.j&&!b.__CE_patched){b.__CE_patched=!0;for(var d=0;d<a.m.length;d++)a.m[d](b);for(d=0;d<a.g.length;d++)a.g[d](b)}}function S(a,b){var d=[];P(a,b,function(c){return d.push(c)});for(b=0;b<d.length;b++){var f=d[b];1===f.__CE_state?a.connectedCallback(f):T(a,f)}}function U(a,b){var d=[];P(a,b,function(c){return d.push(c)});for(b=0;b<d.length;b++){var f=d[b];1===f.__CE_state&&a.disconnectedCallback(f)}}
function V(a,b,d){d=void 0===d?{}:d;var f=d.J,c=d.upgrade||function(g){return T(a,g)},e=[];P(a,b,function(g){a.j&&R(a,g);if("link"===g.localName&&"import"===g.getAttribute("rel")){var h=g.import;h instanceof Node&&(h.__CE_isImportDocument=!0,h.__CE_registry=document.__CE_registry);h&&"complete"===h.readyState?h.__CE_documentLoadHandled=!0:g.addEventListener("load",function(){var k=g.import;if(!k.__CE_documentLoadHandled){k.__CE_documentLoadHandled=!0;var l=new Set;f&&(f.forEach(function(m){return l.add(m)}),
l.delete(k));V(a,k,{J:l,upgrade:c})}})}else e.push(g)},f);for(b=0;b<e.length;b++)c(e[b])}
function T(a,b){try{var d=b.ownerDocument,f=d.__CE_registry;var c=f&&(d.defaultView||d.__CE_isImportDocument)?W(f,b.localName):void 0;if(c&&void 0===b.__CE_state){c.constructionStack.push(b);try{try{if(new c.constructorFunction!==b)throw Error("The custom element constructor did not produce the element being upgraded.");}finally{c.constructionStack.pop()}}catch(k){throw b.__CE_state=2,k;}b.__CE_state=1;b.__CE_definition=c;if(c.attributeChangedCallback&&b.hasAttributes()){var e=c.observedAttributes;
for(c=0;c<e.length;c++){var g=e[c],h=b.getAttribute(g);null!==h&&a.attributeChangedCallback(b,g,null,h,null)}}J(b)&&a.connectedCallback(b)}}catch(k){X(k)}}N.prototype.connectedCallback=function(a){var b=a.__CE_definition;if(b.connectedCallback)try{b.connectedCallback.call(a)}catch(d){X(d)}};N.prototype.disconnectedCallback=function(a){var b=a.__CE_definition;if(b.disconnectedCallback)try{b.disconnectedCallback.call(a)}catch(d){X(d)}};
N.prototype.attributeChangedCallback=function(a,b,d,f,c){var e=a.__CE_definition;if(e.attributeChangedCallback&&-1<e.observedAttributes.indexOf(b))try{e.attributeChangedCallback.call(a,b,d,f,c)}catch(g){X(g)}};
function va(a,b,d,f){var c=b.__CE_registry;if(c&&(null===f||"http://www.w3.org/1999/xhtml"===f)&&(c=W(c,d)))try{var e=new c.constructorFunction;if(void 0===e.__CE_state||void 0===e.__CE_definition)throw Error("Failed to construct '"+d+"': The returned value was not constructed with the HTMLElement constructor.");if("http://www.w3.org/1999/xhtml"!==e.namespaceURI)throw Error("Failed to construct '"+d+"': The constructed element's namespace must be the HTML namespace.");if(e.hasAttributes())throw Error("Failed to construct '"+
d+"': The constructed element must not have any attributes.");if(null!==e.firstChild)throw Error("Failed to construct '"+d+"': The constructed element must not have any children.");if(null!==e.parentNode)throw Error("Failed to construct '"+d+"': The constructed element must not have a parent node.");if(e.ownerDocument!==b)throw Error("Failed to construct '"+d+"': The constructed element's owner document is incorrect.");if(e.localName!==d)throw Error("Failed to construct '"+d+"': The constructed element's local name is incorrect.");
return e}catch(g){return X(g),b=null===f?n.call(b,d):p.call(b,f,d),Object.setPrototypeOf(b,HTMLUnknownElement.prototype),b.__CE_state=2,b.__CE_definition=void 0,R(a,b),b}b=null===f?n.call(b,d):p.call(b,f,d);R(a,b);return b}
function X(a){var b="",d="",f=0,c=0;a instanceof Error?(b=a.message,d=a.sourceURL||a.fileName||"",f=a.line||a.lineNumber||0,c=a.column||a.columnNumber||0):b="Uncaught "+String(a);var e=void 0;void 0===ErrorEvent.prototype.initErrorEvent?e=new ErrorEvent("error",{cancelable:!0,message:b,filename:d,lineno:f,colno:c,error:a}):(e=document.createEvent("ErrorEvent"),e.initErrorEvent("error",!1,!0,b,d,f),e.preventDefault=function(){Object.defineProperty(this,"defaultPrevented",{configurable:!0,get:function(){return!0}})});
void 0===e.error&&Object.defineProperty(e,"error",{configurable:!0,enumerable:!0,get:function(){return a}});window.dispatchEvent(e);e.defaultPrevented||console.error(a)};function wa(){var a=this;this.g=void 0;this.F=new Promise(function(b){a.l=b})}wa.prototype.resolve=function(a){if(this.g)throw Error("Already resolved.");this.g=a;this.l(a)};function xa(a){var b=document;this.l=void 0;this.h=a;this.g=b;V(this.h,this.g);"loading"===this.g.readyState&&(this.l=new MutationObserver(this.G.bind(this)),this.l.observe(this.g,{childList:!0,subtree:!0}))}function ya(a){a.l&&a.l.disconnect()}xa.prototype.G=function(a){var b=this.g.readyState;"interactive"!==b&&"complete"!==b||ya(this);for(b=0;b<a.length;b++)for(var d=a[b].addedNodes,f=0;f<d.length;f++)V(this.h,d[f])};function Y(a){this.s=new Map;this.u=new Map;this.C=new Map;this.A=!1;this.B=new Map;this.o=function(b){return b()};this.i=!1;this.v=[];this.h=a;this.D=a.I?new xa(a):void 0}Y.prototype.H=function(a,b){var d=this;if(!(b instanceof Function))throw new TypeError("Custom element constructor getters must be functions.");za(this,a);this.s.set(a,b);this.v.push(a);this.i||(this.i=!0,this.o(function(){return Aa(d)}))};
Y.prototype.define=function(a,b){var d=this;if(!(b instanceof Function))throw new TypeError("Custom element constructors must be functions.");za(this,a);Ba(this,a,b);this.v.push(a);this.i||(this.i=!0,this.o(function(){return Aa(d)}))};function za(a,b){if(!ra(b))throw new SyntaxError("The element name '"+b+"' is not valid.");if(W(a,b))throw Error("A custom element with name '"+(b+"' has already been defined."));if(a.A)throw Error("A custom element is already being defined.");}
function Ba(a,b,d){a.A=!0;var f;try{var c=d.prototype;if(!(c instanceof Object))throw new TypeError("The custom element constructor's prototype is not an object.");var e=function(m){var x=c[m];if(void 0!==x&&!(x instanceof Function))throw Error("The '"+m+"' callback must be a function.");return x};var g=e("connectedCallback");var h=e("disconnectedCallback");var k=e("adoptedCallback");var l=(f=e("attributeChangedCallback"))&&d.observedAttributes||[]}catch(m){throw m;}finally{a.A=!1}d={localName:b,
constructorFunction:d,connectedCallback:g,disconnectedCallback:h,adoptedCallback:k,attributeChangedCallback:f,observedAttributes:l,constructionStack:[]};a.u.set(b,d);a.C.set(d.constructorFunction,d);return d}Y.prototype.upgrade=function(a){V(this.h,a)};
function Aa(a){if(!1!==a.i){a.i=!1;for(var b=[],d=a.v,f=new Map,c=0;c<d.length;c++)f.set(d[c],[]);V(a.h,document,{upgrade:function(k){if(void 0===k.__CE_state){var l=k.localName,m=f.get(l);m?m.push(k):a.u.has(l)&&b.push(k)}}});for(c=0;c<b.length;c++)T(a.h,b[c]);for(c=0;c<d.length;c++){for(var e=d[c],g=f.get(e),h=0;h<g.length;h++)T(a.h,g[h]);(e=a.B.get(e))&&e.resolve(void 0)}d.length=0}}Y.prototype.get=function(a){if(a=W(this,a))return a.constructorFunction};
Y.prototype.whenDefined=function(a){if(!ra(a))return Promise.reject(new SyntaxError("'"+a+"' is not a valid custom element name."));var b=this.B.get(a);if(b)return b.F;b=new wa;this.B.set(a,b);var d=this.u.has(a)||this.s.has(a);a=-1===this.v.indexOf(a);d&&a&&b.resolve(void 0);return b.F};Y.prototype.polyfillWrapFlushCallback=function(a){this.D&&ya(this.D);var b=this.o;this.o=function(d){return a(function(){return b(d)})}};
function W(a,b){var d=a.u.get(b);if(d)return d;if(d=a.s.get(b)){a.s.delete(b);try{return Ba(a,b,d())}catch(f){X(f)}}}Y.prototype.define=Y.prototype.define;Y.prototype.upgrade=Y.prototype.upgrade;Y.prototype.get=Y.prototype.get;Y.prototype.whenDefined=Y.prototype.whenDefined;Y.prototype.polyfillDefineLazy=Y.prototype.H;Y.prototype.polyfillWrapFlushCallback=Y.prototype.polyfillWrapFlushCallback;function Z(a,b,d){function f(c){return function(e){for(var g=[],h=0;h<arguments.length;++h)g[h]=arguments[h];h=[];for(var k=[],l=0;l<g.length;l++){var m=g[l];m instanceof Element&&J(m)&&k.push(m);if(m instanceof DocumentFragment)for(m=m.firstChild;m;m=m.nextSibling)h.push(m);else h.push(m)}c.apply(this,g);for(g=0;g<k.length;g++)U(a,k[g]);if(J(this))for(g=0;g<h.length;g++)k=h[g],k instanceof Element&&S(a,k)}}void 0!==d.prepend&&(b.prepend=f(d.prepend));void 0!==d.append&&(b.append=f(d.append))};function Ca(a){Document.prototype.createElement=function(b){return va(a,this,b,null)};Document.prototype.importNode=function(b,d){b=aa.call(this,b,!!d);this.__CE_registry?V(a,b):Q(a,b);return b};Document.prototype.createElementNS=function(b,d){return va(a,this,d,b)};Z(a,Document.prototype,{prepend:ba,append:ca})};function Da(a){function b(f){return function(c){for(var e=[],g=0;g<arguments.length;++g)e[g]=arguments[g];g=[];for(var h=[],k=0;k<e.length;k++){var l=e[k];l instanceof Element&&J(l)&&h.push(l);if(l instanceof DocumentFragment)for(l=l.firstChild;l;l=l.nextSibling)g.push(l);else g.push(l)}f.apply(this,e);for(e=0;e<h.length;e++)U(a,h[e]);if(J(this))for(e=0;e<g.length;e++)h=g[e],h instanceof Element&&S(a,h)}}var d=Element.prototype;void 0!==ja&&(d.before=b(ja));void 0!==ka&&(d.after=b(ka));void 0!==la&&
(d.replaceWith=function(f){for(var c=[],e=0;e<arguments.length;++e)c[e]=arguments[e];e=[];for(var g=[],h=0;h<c.length;h++){var k=c[h];k instanceof Element&&J(k)&&g.push(k);if(k instanceof DocumentFragment)for(k=k.firstChild;k;k=k.nextSibling)e.push(k);else e.push(k)}h=J(this);la.apply(this,c);for(c=0;c<g.length;c++)U(a,g[c]);if(h)for(U(a,this),c=0;c<e.length;c++)g=e[c],g instanceof Element&&S(a,g)});void 0!==ma&&(d.remove=function(){var f=J(this);ma.call(this);f&&U(a,this)})};function Ea(a){function b(c,e){Object.defineProperty(c,"innerHTML",{enumerable:e.enumerable,configurable:!0,get:e.get,set:function(g){var h=this,k=void 0;J(this)&&(k=[],P(a,this,function(x){x!==h&&k.push(x)}));e.set.call(this,g);if(k)for(var l=0;l<k.length;l++){var m=k[l];1===m.__CE_state&&a.disconnectedCallback(m)}this.ownerDocument.__CE_registry?V(a,this):Q(a,this);return g}})}function d(c,e){c.insertAdjacentElement=function(g,h){var k=J(h);g=e.call(this,g,h);k&&U(a,h);J(g)&&S(a,h);return g}}function f(c,
e){function g(h,k){for(var l=[];h!==k;h=h.nextSibling)l.push(h);for(k=0;k<l.length;k++)V(a,l[k])}c.insertAdjacentHTML=function(h,k){h=h.toLowerCase();if("beforebegin"===h){var l=this.previousSibling;e.call(this,h,k);g(l||this.parentNode.firstChild,this)}else if("afterbegin"===h)l=this.firstChild,e.call(this,h,k),g(this.firstChild,l);else if("beforeend"===h)l=this.lastChild,e.call(this,h,k),g(l||this.firstChild,null);else if("afterend"===h)l=this.nextSibling,e.call(this,h,k),g(this.nextSibling,l);
else throw new SyntaxError("The value provided ("+String(h)+") is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'.");}}y&&(Element.prototype.attachShadow=function(c){c=y.call(this,c);if(a.j&&!c.__CE_patched){c.__CE_patched=!0;for(var e=0;e<a.m.length;e++)a.m[e](c)}return this.__CE_shadowRoot=c});z&&z.get?b(Element.prototype,z):I&&I.get?b(HTMLElement.prototype,I):ua(a,function(c){b(c,{enumerable:!0,configurable:!0,get:function(){return q.call(this,!0).innerHTML},set:function(e){var g=
"template"===this.localName,h=g?this.content:this,k=p.call(document,this.namespaceURI,this.localName);for(k.innerHTML=e;0<h.childNodes.length;)u.call(h,h.childNodes[0]);for(e=g?k.content:k;0<e.childNodes.length;)r.call(h,e.childNodes[0])}})});Element.prototype.setAttribute=function(c,e){if(1!==this.__CE_state)return B.call(this,c,e);var g=A.call(this,c);B.call(this,c,e);e=A.call(this,c);a.attributeChangedCallback(this,c,g,e,null)};Element.prototype.setAttributeNS=function(c,e,g){if(1!==this.__CE_state)return F.call(this,
c,e,g);var h=E.call(this,c,e);F.call(this,c,e,g);g=E.call(this,c,e);a.attributeChangedCallback(this,e,h,g,c)};Element.prototype.removeAttribute=function(c){if(1!==this.__CE_state)return C.call(this,c);var e=A.call(this,c);C.call(this,c);null!==e&&a.attributeChangedCallback(this,c,e,null,null)};D&&(Element.prototype.toggleAttribute=function(c,e){if(1!==this.__CE_state)return D.call(this,c,e);var g=A.call(this,c),h=null!==g;e=D.call(this,c,e);h!==e&&a.attributeChangedCallback(this,c,g,e?"":null,null);
return e});Element.prototype.removeAttributeNS=function(c,e){if(1!==this.__CE_state)return G.call(this,c,e);var g=E.call(this,c,e);G.call(this,c,e);var h=E.call(this,c,e);g!==h&&a.attributeChangedCallback(this,e,g,h,c)};oa?d(HTMLElement.prototype,oa):H&&d(Element.prototype,H);pa?f(HTMLElement.prototype,pa):fa&&f(Element.prototype,fa);Z(a,Element.prototype,{prepend:ha,append:ia});Da(a)};var Fa={};function Ga(a){function b(){var d=this.constructor;var f=document.__CE_registry.C.get(d);if(!f)throw Error("Failed to construct a custom element: The constructor was not registered with `customElements`.");var c=f.constructionStack;if(0===c.length)return c=n.call(document,f.localName),Object.setPrototypeOf(c,d.prototype),c.__CE_state=1,c.__CE_definition=f,R(a,c),c;var e=c.length-1,g=c[e];if(g===Fa)throw Error("Failed to construct '"+f.localName+"': This element was already constructed.");c[e]=Fa;
Object.setPrototypeOf(g,d.prototype);R(a,g);return g}b.prototype=na.prototype;Object.defineProperty(HTMLElement.prototype,"constructor",{writable:!0,configurable:!0,enumerable:!1,value:b});window.HTMLElement=b};function Ha(a){function b(d,f){Object.defineProperty(d,"textContent",{enumerable:f.enumerable,configurable:!0,get:f.get,set:function(c){if(this.nodeType===Node.TEXT_NODE)f.set.call(this,c);else{var e=void 0;if(this.firstChild){var g=this.childNodes,h=g.length;if(0<h&&J(this)){e=Array(h);for(var k=0;k<h;k++)e[k]=g[k]}}f.set.call(this,c);if(e)for(c=0;c<e.length;c++)U(a,e[c])}}})}Node.prototype.insertBefore=function(d,f){if(d instanceof DocumentFragment){var c=K(d);d=t.call(this,d,f);if(J(this))for(f=
0;f<c.length;f++)S(a,c[f]);return d}c=d instanceof Element&&J(d);f=t.call(this,d,f);c&&U(a,d);J(this)&&S(a,d);return f};Node.prototype.appendChild=function(d){if(d instanceof DocumentFragment){var f=K(d);d=r.call(this,d);if(J(this))for(var c=0;c<f.length;c++)S(a,f[c]);return d}f=d instanceof Element&&J(d);c=r.call(this,d);f&&U(a,d);J(this)&&S(a,d);return c};Node.prototype.cloneNode=function(d){d=q.call(this,!!d);this.ownerDocument.__CE_registry?V(a,d):Q(a,d);return d};Node.prototype.removeChild=function(d){var f=
d instanceof Element&&J(d),c=u.call(this,d);f&&U(a,d);return c};Node.prototype.replaceChild=function(d,f){if(d instanceof DocumentFragment){var c=K(d);d=v.call(this,d,f);if(J(this))for(U(a,f),f=0;f<c.length;f++)S(a,c[f]);return d}c=d instanceof Element&&J(d);var e=v.call(this,d,f),g=J(this);g&&U(a,f);c&&U(a,d);g&&S(a,d);return e};w&&w.get?b(Node.prototype,w):ta(a,function(d){b(d,{enumerable:!0,configurable:!0,get:function(){for(var f=[],c=this.firstChild;c;c=c.nextSibling)c.nodeType!==Node.COMMENT_NODE&&
f.push(c.textContent);return f.join("")},set:function(f){for(;this.firstChild;)u.call(this,this.firstChild);null!=f&&""!==f&&r.call(this,document.createTextNode(f))}})})};var O=window.customElements;function Ia(){var a=new N;Ga(a);Ca(a);Z(a,DocumentFragment.prototype,{prepend:da,append:ea});Ha(a);Ea(a);window.CustomElementRegistry=Y;a=new Y(a);document.__CE_registry=a;Object.defineProperty(window,"customElements",{configurable:!0,enumerable:!0,value:a})}O&&!O.forcePolyfill&&"function"==typeof O.define&&"function"==typeof O.get||Ia();window.__CE_installPolyfill=Ia;/*
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
}).call(this);

View File

@@ -0,0 +1,42 @@
// webcomponents.js code comes from
// https://github.com/webcomponents/polyfills/tree/master/packages/webcomponentsjs
//
// The original code source is available in a "BSD style license".
//
// This is the `webcomponents-ce.js` bundle
pub const source = @embedFile("webcomponents.js");
// The main webcomponents.js is lazilly loaded when window.customElements is
// called. But, if you look at the test below, you'll notice that we declare
// our custom element (LightPanda) before we call `customElements.define`. We
// _have_ to declare it before we can register it.
// That causes an issue, because the LightPanda class extends HTMLElement, which
// hasn't been monkeypatched by the polyfill yet. If you were to try it as-is
// you'd get an "Illegal Constructor", because that's what the Zig HTMLElement
// constructor does (and that's correct).
// However, once HTMLElement is monkeypatched, it'll work. One simple solution
// is to run the webcomponents.js polyfill proactively on each page, ensuring
// that HTMLElement is monkeypatched before any other JavaScript is run. But
// that adds _a lot_ of overhead.
// So instead of always running the [large and intrusive] webcomponents.js
// polyfill, we'll always run this little snippet. It wraps the HTMLElement
// constructor. When the Lightpanda class is created, it'll extend our little
// wrapper. But, unlike the Zig default constructor which throws, our code
// calls the "real" constructor. That might seem like the same thing, but by the
// time our wrapper is called, the webcomponents.js polyfill will have been
// loaded and the "real" constructor will be the monkeypatched version.
// TL;DR creates a layer of indirection for the constructor, so that, when it's
// actually instantiated, the webcomponents.js polyfill will have been loaded.
pub const pre =
\\ (() => {
\\ const HE = window.HTMLElement;
\\ const b = function() { return HE.prototype.constructor.call(this); }
\\ b.prototype = HE.prototype;
\\ window.HTMLElement = b;
\\ })();
;
const testing = @import("../../testing.zig");
test "Browser: Polyfill.WebComponents" {
try testing.htmlRunner("polyfill/webcomponents.html", .{});
}

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

@@ -0,0 +1,46 @@
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,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,58 @@
<!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>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<form id="form1"></form>
<img id="img1" src="about:blank">
<script></script>
<a id="link1" href="#"></a>
<a id="link2"></a>
<script id=collections>
testing.expectEqual(1, document.forms.length);
testing.expectEqual($('#form1'), document.forms[0]);
testing.expectEqual(1, document.images.length);
testing.expectEqual($('#img1'), document.images[0]);
testing.expectEqual(3, document.scripts.length);
testing.expectEqual(true, document.scripts[0].src.endsWith('testing.js'));
testing.expectEqual(document.scripts[1].src, '');
testing.expectEqual($('#collections'), document.scripts[2]);
testing.expectEqual(2, document.links.length);
testing.expectEqual($('#link1'), document.links[0]);
testing.expectEqual($('#link2'), document.links[1]);
</script>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<body></body>
<script src="../testing.js"></script>
<script id=createElement>
const div = document.createElement('div');
testing.expectEqual("DIV", div.tagName);
div.id = "hello";
testing.expectEqual(null, $('#hello'));
document.getElementsByTagName('body')[0].appendChild(div);
testing.expectEqual(div, $('#hello'));
</script>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<body></body>
<script src="../testing.js"></script>
<script id=createElementNS>
const htmlDiv = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
testing.expectEqual('DIV', htmlDiv.tagName);
testing.expectEqual('http://www.w3.org/1999/xhtml', htmlDiv.namespaceURI);
const svgRect = document.createElementNS('http://www.w3.org/2000/svg', 'RecT');
testing.expectEqual('RecT', svgRect.tagName);
testing.expectEqual('http://www.w3.org/2000/svg', svgRect.namespaceURI);
const mathElement = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'math');
testing.expectEqual('math', mathElement.tagName);
testing.expectEqual('http://www.w3.org/1998/Math/MathML', mathElement.namespaceURI);
const xmlElement = document.createElementNS('http://www.w3.org/XML/1998/namespace', 'TEst');
testing.expectEqual('TEst', xmlElement.tagName);
testing.expectEqual('http://www.w3.org/XML/1998/namespace', xmlElement.namespaceURI);
const nullNsElement = document.createElementNS(null, 'span');
testing.expectEqual('SPAN', nullNsElement.tagName);
testing.expectEqual('http://www.w3.org/1999/xhtml', nullNsElement.namespaceURI);
const unknownNsElement = document.createElementNS('http://example.com/unknown', 'custom');
testing.expectEqual('CUSTOM', unknownNsElement.tagName);
testing.expectEqual('http://www.w3.org/1999/xhtml', unknownNsElement.namespaceURI);
const regularDiv = document.createElement('div');
testing.expectEqual('DIV', regularDiv.tagName);
testing.expectEqual('http://www.w3.org/1999/xhtml', regularDiv.namespaceURI);
</script>

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<head id="the_head">
<title>Test Document Title</title>
<script src="../testing.js"></script>
</head>
<body id=the_body>
</body>
<script id=document>
testing.expectEqual(null, document.parentNode);
testing.expectEqual(undefined, document.getCurrentScript);
testing.expectEqual("http://127.0.0.1:9582/src/browser/tests/document/document.html", document.URL);
</script>
<script id=headAndbody>
testing.expectEqual($('#the_head'), document.head);
testing.expectEqual($('#the_body'), document.body);
</script>
<script id=documentElement>
testing.expectEqual($('#the_body').parentNode, document.documentElement);
</script>
<script id=title>
testing.expectEqual('Test Document Title', document.title);
document.title = 'New Title';
testing.expectEqual('New Title', document.title);
document.title = '';
testing.expectEqual('', document.title);
</script>
<script id=createTextNode>
const textNode = document.createTextNode('Hello World');
testing.expectEqual(3, textNode.nodeType);
testing.expectEqual('Hello World', textNode.nodeValue);
testing.expectEqual('Hello World', textNode.textContent);
const emptyText = document.createTextNode('');
testing.expectEqual('', emptyText.nodeValue);
</script>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<body>
<script src="../testing.js"></script>
<div id="div-1">x: <p id=p1>d1</p></div>
</body>
<script id=getElementById>
testing.expectEqual(null, document.getElementById(null));
testing.expectEqual(null, document.getElementById(23));
testing.expectEqual(null, document.getElementById('value'));
testing.expectEqual('x: d1', document.getElementById('div-1').textContent);
testing.expectEqual('d1', document.getElementById('p1').textContent);
document.getElementById('p1').id = 'p2'
testing.expectEqual(null, document.getElementById('p1'));
testing.expectEqual('d1', document.getElementById('p2').textContent);
const div = document.createElement('div');
div.id = 'hello';
testing.expectEqual(null, document.getElementById('hello'));
const body = document.getElementsByTagName('body')[0];
body.appendChild(div);
testing.expectEqual('hello', document.getElementById('hello').id);
div.setAttribute('ID', 'world')
testing.expectEqual(null, document.getElementById('hello'));
testing.expectEqual('world', document.getElementById('world').id);
body.replaceChildren(div);
testing.expectEqual('world', document.getElementById('world').id);
testing.expectEqual(null, document.getElementById('div-1'));
testing.expectEqual(null, document.getElementById('p1'));
div.removeAttribute('id');
testing.expectEqual(null, document.getElementById('world'));
</script>

View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div class="foo">foo1</div>
<span class="foo">foo2</span>
<p class="foo" id="foo3">foo3</p>
<div class="bar">bar1</div>
<div class="baz">baz1</div>
<div class="multi class names">multi1</div>
<span class="class multi">multi2</span>
<div id="container">
<div class="nested">nested1</div>
<div class="nested">nested2</div>
</div>
<script>
const foos = document.getElementsByClassName('foo');
</script>
<script id=basic>
testing.expectEqual(0, document.getElementsByClassName('nonexistent').length);
testing.expectEqual(0, document.getElementsByClassName('unknown').length);
testing.expectEqual(true, foos instanceof HTMLCollection);
testing.expectEqual(3, foos.length);
testing.expectEqual(3, foos.length); // cache test
testing.expectEqual('foo1', foos[0].textContent);
testing.expectEqual('foo2', foos[1].textContent);
testing.expectEqual('foo3', foos[2].textContent);
testing.expectEqual('foo1', foos[0].textContent); // cache test
testing.expectEqual('foo3', foos[2].textContent);
testing.expectEqual('foo2', foos[1].textContent);
testing.expectEqual(undefined, foos[-1]);
testing.expectEqual(undefined, foos[3]);
testing.expectEqual(undefined, foos[100]);
const bars = document.getElementsByClassName('bar');
testing.expectEqual(1, bars.length);
testing.expectEqual('bar1', bars[0].textContent);
const bazs = document.getElementsByClassName('baz');
testing.expectEqual(1, bazs.length);
testing.expectEqual('baz1', bazs[0].textContent);
</script>
<script id=item>
testing.expectEqual('foo2', foos.item(1).textContent);
testing.expectEqual(null, foos.item(-1));
testing.expectEqual('foo1', foos.item(0).textContent);
testing.expectEqual(null, foos.item(-100));
testing.expectEqual(null, foos.item(3));
testing.expectEqual(null, foos.item(100));
</script>
<script id=namedItem>
testing.expectEqual('foo3', foos.namedItem('foo3').id);
testing.expectEqual(null, foos.namedItem('foo1'));
testing.expectEqual(null, foos.namedItem('bar'));
testing.expectEqual(null, foos.namedItem('nonexistent'));
testing.expectEqual('foo3', foos['foo3'].id);
</script>
<script id=iterator>
let acc = [];
for (let x of foos) {
acc.push(x.textContent);
}
testing.expectEqual(['foo1', 'foo2', 'foo3'], acc);
</script>
<script id=emptyString>
const empty = document.getElementsByClassName('');
testing.expectEqual(0, empty.length);
</script>
<script id=multipleClasses>
const multi = document.getElementsByClassName('multi');
testing.expectEqual(2, multi.length);
testing.expectEqual('multi1', multi[0].textContent);
testing.expectEqual('multi2', multi[1].textContent);
const classCol = document.getElementsByClassName('class');
testing.expectEqual(2, classCol.length);
const names = document.getElementsByClassName('names');
testing.expectEqual(1, names.length);
testing.expectEqual('multi1', names[0].textContent);
</script>
<script id=nested>
const nested = document.getElementsByClassName('nested');
testing.expectEqual(2, nested.length);
testing.expectEqual('nested1', nested[0].textContent);
testing.expectEqual('nested2', nested[1].textContent);
</script>

View File

@@ -0,0 +1,155 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div>0</div>
<div>1</div>
<div>2</div>
<p id=p1></p>
<p id=p2></p>
<p id=p3></p>
<h1>H1</h1>
<h2>H2</h2>
<h3>H3</h3>
<h4>H4</h4>
<h5>H5</h5>
<h6>H6</h6>
<b>Bold</b>
<i>Italic</i>
<em>Emphasized</em>
<strong>Strong</strong>
<header>Header</header>
<nav>Nav</nav>
<main>Main</main>
<script id=basic>
testing.expectEqual(0, document.getElementsByTagName('a').length);
testing.expectEqual(0, document.getElementsByTagName('unknown').length);
const divs = document.getElementsByTagName('div');
testing.expectEqual(true, divs instanceof HTMLCollection);
testing.expectEqual(3, divs.length);
testing.expectEqual(3, divs.length);
testing.expectEqual('0', divs[0].textContent);
testing.expectEqual('0', divs[0].textContent);
testing.expectEqual('0', divs[0].textContent);
testing.expectEqual('1', divs[1].textContent);
testing.expectEqual('2', divs[2].textContent);
testing.expectEqual('2', divs[2].textContent);
testing.expectEqual('1', divs[1].textContent);
testing.expectEqual('1', divs[1].textContent);
testing.expectEqual('2', divs[2].textContent);
testing.expectEqual('0', divs[0].textContent);
testing.expectEqual(undefined, divs[-1]);
testing.expectEqual(undefined, divs[4]);
testing.expectEqual('2', divs.item(2).textContent);
testing.expectEqual(null, divs.item(-3));
testing.expectEqual('0', divs.item(0).textContent);
testing.expectEqual(null, divs.item(-100));
</script>
<script id=namedItem>
testing.expectEqual(null, divs.namedItem('d1'));
const ps = document.getElementsByTagName('p');
testing.expectEqual('p1', ps.namedItem('p1').id);
testing.expectEqual('p2', ps.namedItem('p2').id);
testing.expectEqual('p3', ps.namedItem('p3').id);
testing.expectEqual(null, ps.namedItem('p4'));
testing.expectEqual('p1', ps['p1'].id);
</script>
<script id=iterator>
let acc = [];
for (let x of ps) {
acc.push(x.id);
}
testing.expectEqual(['p1', 'p2', 'p3'], acc);
</script>
<script id=extendedTags>
// Test headings
testing.expectEqual(1, document.getElementsByTagName('h1').length);
testing.expectEqual('H1', document.getElementsByTagName('h1')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('h2').length);
testing.expectEqual('H2', document.getElementsByTagName('h2')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('h3').length);
testing.expectEqual(1, document.getElementsByTagName('h4').length);
testing.expectEqual(1, document.getElementsByTagName('h5').length);
testing.expectEqual(1, document.getElementsByTagName('h6').length);
// Test case insensitivity
testing.expectEqual(1, document.getElementsByTagName('H1').length);
testing.expectEqual('H1', document.getElementsByTagName('H1')[0].textContent);
// Test text formatting elements
testing.expectEqual(1, document.getElementsByTagName('b').length);
testing.expectEqual('Bold', document.getElementsByTagName('b')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('i').length);
testing.expectEqual('Italic', document.getElementsByTagName('i')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('em').length);
testing.expectEqual('Emphasized', document.getElementsByTagName('em')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('strong').length);
testing.expectEqual('Strong', document.getElementsByTagName('strong')[0].textContent);
// Test structural elements
testing.expectEqual(1, document.getElementsByTagName('header').length);
testing.expectEqual('Header', document.getElementsByTagName('header')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('nav').length);
testing.expectEqual('Nav', document.getElementsByTagName('nav')[0].textContent);
testing.expectEqual(1, document.getElementsByTagName('main').length);
testing.expectEqual('Main', document.getElementsByTagName('main')[0].textContent);
</script>
<script id=multipleIterators>
{
const list = document.getElementsByTagName('p');
const iter1 = list[Symbol.iterator]();
const iter2 = list[Symbol.iterator]();
const val1_iter1 = iter1.next();
testing.expectEqual(false, val1_iter1.done);
testing.expectEqual('p1', val1_iter1.value.id);
const val1_iter2 = iter2.next();
testing.expectEqual(false, val1_iter2.done);
testing.expectEqual('p1', val1_iter2.value.id);
const val2_iter1 = iter1.next();
testing.expectEqual(false, val2_iter1.done);
testing.expectEqual('p2', val2_iter1.value.id);
const val2_iter2 = iter2.next();
testing.expectEqual(false, val2_iter2.done);
testing.expectEqual('p2', val2_iter2.value.id);
}
</script>
<script id=iteratorLifetime>
{
let iter;
{
const list = document.getElementsByTagName('p');
iter = list[Symbol.iterator]();
}
const val1 = iter.next();
testing.expectEqual(false, val1.done);
testing.expectEqual('p1', val1.value.id);
const val2 = iter.next();
testing.expectEqual(false, val2.done);
testing.expectEqual('p2', val2.value.id);
const val3 = iter.next();
testing.expectEqual(false, val3.done);
testing.expectEqual('p3', val3.value.id);
const val4 = iter.next();
testing.expectEqual(true, val4.done);
}
</script>

View File

@@ -0,0 +1,271 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div class="test-class">div1</div>
<span class="test-class">span1</span>
<div class="multiple classes here">multi1</div>
<div class="classes">multi2</div>
<div class="">empty-class</div>
<div>no-class</div>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
<b>Bold text</b>
<i>Italic text</i>
<em>Emphasized text</em>
<strong>Strong text</strong>
<header>Header element</header>
<nav>Navigation</nav>
<main>Main content</main>
<script id=byId name="test1">
testing.expectError("Syntax Error", () => document.querySelector(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);
testing.expectEqual("Syntax Error", err.message);
}, () => document.querySelector(''));
testing.expectEqual('test1', document.querySelector('#byId').getAttribute('name'));
</script>
<script id=byClass>
{
const result = document.querySelector('.test-class');
testing.expectEqual('div1', result.textContent);
testing.expectEqual('multi1', document.querySelector('.multiple').textContent);
testing.expectEqual('multi1', document.querySelector('.classes').textContent);
testing.expectEqual('multi1', document.querySelector('.here').textContent);
testing.expectEqual(null, document.querySelector('.nonexistent'));
}
</script>
<script id=byTag>
{
const result = document.querySelector('div');
testing.expectEqual('div1', result.textContent);
testing.expectEqual('span1', document.querySelector('span').textContent);
// Test that script elements can be found
const firstScript = document.querySelector('script');
testing.expectEqual('SCRIPT', firstScript.tagName);
testing.expectEqual(null, document.querySelector('article'));
testing.expectEqual(null, document.querySelector('another'));
}
</script>
<script id=byTagExtended>
{
// Test headings (h1-h6)
testing.expectEqual('Heading 1', document.querySelector('h1').textContent);
testing.expectEqual('Heading 2', document.querySelector('h2').textContent);
testing.expectEqual('Heading 3', document.querySelector('h3').textContent);
testing.expectEqual('Heading 4', document.querySelector('h4').textContent);
testing.expectEqual('Heading 5', document.querySelector('h5').textContent);
testing.expectEqual('Heading 6', document.querySelector('h6').textContent);
// Test case insensitivity
testing.expectEqual('Heading 1', document.querySelector('H1').textContent);
testing.expectEqual('Heading 2', document.querySelector('H2').textContent);
// Test text formatting elements
testing.expectEqual('Bold text', document.querySelector('b').textContent);
testing.expectEqual('Italic text', document.querySelector('i').textContent);
testing.expectEqual('Emphasized text', document.querySelector('em').textContent);
testing.expectEqual('Strong text', document.querySelector('strong').textContent);
// Test structural elements
testing.expectEqual('Header element', document.querySelector('header').textContent);
testing.expectEqual('Navigation', document.querySelector('nav').textContent);
testing.expectEqual('Main content', document.querySelector('main').textContent);
// Test case insensitivity for these too
testing.expectEqual('Navigation', document.querySelector('NAV').textContent);
testing.expectEqual('Main content', document.querySelector('Main').textContent);
}
</script>
<div id="compound-test" class="container active">Compound 1</div>
<div class="container">Compound 2</div>
<span class="container active">Compound 3</span>
<p id="compound-test2">Compound 4</p>
<script id=compoundSelectors>
{
testing.expectEqual('Compound 1', document.querySelector('div.container').textContent);
testing.expectEqual('Compound 1', document.querySelector('div.active').textContent);
testing.expectEqual('Compound 3', document.querySelector('span.active').textContent);
testing.expectEqual('Compound 1', document.querySelector('div.container.active').textContent);
testing.expectEqual('Compound 1', document.querySelector('div#compound-test').textContent);
testing.expectEqual('Compound 1', document.querySelector('.container#compound-test').textContent);
testing.expectEqual('Compound 1', document.querySelector('#compound-test.active').textContent);
testing.expectEqual('Compound 1', document.querySelector('.container.active').textContent);
testing.expectEqual('Compound 1', document.querySelector('.active.container').textContent);
testing.expectEqual('Compound 1', document.querySelector(' div.container').textContent);
testing.expectEqual('Compound 1', document.querySelector(' .active').textContent);
testing.expectEqual('Compound 1', document.querySelector('div.container ').textContent);
testing.expectEqual('Compound 4', document.querySelector('#compound-test2 ').textContent);
testing.expectEqual('Compound 3', document.querySelector(' span.active ').textContent);
testing.expectEqual(null, document.querySelector('span#compound-test'));
testing.expectEqual(null, document.querySelector('div.nonexistent'));
testing.expectEqual(null, document.querySelector('p.container'));
}
</script>
<script id=universalSelector>
{
const result = document.querySelector('*');
testing.expectEqual('HTML', result.tagName);
testing.expectEqual('div1', document.querySelector('*.test-class').textContent);
testing.expectEqual('test1', document.querySelector('*#byId').getAttribute('name'));
testing.expectEqual('Compound 1', document.querySelector('*.container.active').textContent);
}
</script>
<div id="descendant-container">
<p class="desc-text">Direct child paragraph</p>
<div class="nested">
<p class="desc-text">Nested paragraph</p>
<span>
<p class="deep">Deeply nested paragraph</p>
</span>
</div>
<article>
<p class="desc-text">Article paragraph</p>
</article>
</div>
<p class="desc-text">Outside paragraph</p>
<div id="complex">
<div class="outer">
<div class="middle">
<div class="inner">
<span id="target">Target span</span>
</div>
</div>
</div>
</div>
<script id=descendantSelectors>
{
testing.expectEqual('Direct child paragraph', document.querySelector('div p').textContent);
testing.expectEqual('Direct child paragraph', document.querySelector('#descendant-container p').textContent);
testing.expectEqual('Nested paragraph', document.querySelector('div div p').textContent);
testing.expectEqual('target', document.querySelector('div div div span').id);
testing.expectEqual('Nested paragraph', document.querySelector('div.nested p.desc-text').textContent);
testing.expectEqual('target', document.querySelector('div #target').id);
testing.expectEqual('Nested paragraph', document.querySelector('.nested p').textContent);
testing.expectEqual('Deeply nested paragraph', document.querySelector('div span p').textContent);
testing.expectEqual(null, document.querySelector('article div p'));
}
</script>
<script id=descendantWithWhitespace>
{
testing.expectEqual('Direct child paragraph', document.querySelector(' div p ').textContent);
testing.expectEqual('Nested paragraph', document.querySelector(' div.nested p.desc-text ').textContent);
}
</script>
<div id="compound-id-test" class="special">
<p class="text">Paragraph in compound ID div</p>
<div class="inner">
<span id="nested-compound" class="highlight">Nested span</span>
</div>
</div>
<script id=compoundIdSelectors>
{
testing.expectEqual('Paragraph in compound ID div', document.querySelector('div#compound-id-test p').textContent);
testing.expectEqual('Paragraph in compound ID div', document.querySelector('#compound-id-test.special p').textContent);
testing.expectEqual('Nested span', document.querySelector('div #nested-compound.highlight').textContent);
testing.expectEqual('Nested span', document.querySelector('#compound-id-test span#nested-compound').textContent);
testing.expectEqual('Nested span', document.querySelector('div.special #nested-compound.highlight').textContent);
}
</script>
<script id=idOptimizationEdgeCases>
{
testing.expectEqual(null, document.querySelector('article #compound-id-test p'));
testing.expectEqual(null, document.querySelector('#compound-id-test.wrong-class p'));
testing.expectEqual(null, document.querySelector('#compound-id-test.container p'));
testing.expectEqual(null, document.querySelector('#compound-id-test article'));
testing.expectEqual(null, document.querySelector('#compound-id-test nav'));
testing.expectEqual(null, document.querySelector('#nonexistent-id p'));
testing.expectEqual(null, document.querySelector('div #nonexistent-id'));
testing.expectEqual(null, document.querySelector('#nonexistent-id.some-class span'));
testing.expectEqual(null, document.querySelector('span#compound-id-test p'));
testing.expectEqual(null, document.querySelector('article#nested-compound'));
}
</script>
<script id=universalDescendantSelectors>
{
testing.expectEqual('div1', document.querySelector('* div').textContent);
testing.expectEqual('span1', document.querySelector('* span').textContent);
testing.expectEqual('Direct child paragraph', document.querySelector('div *').textContent);
testing.expectEqual('Nested paragraph', document.querySelector('.nested *').textContent);
testing.expectEqual('div1', document.querySelector('* .test-class').textContent);
testing.expectEqual('multi1', document.querySelector('* .multiple').textContent);
testing.expectEqual('Nested paragraph', document.querySelector('.nested *').textContent);
testing.expectEqual('Paragraph in compound ID div', document.querySelector('.special *').textContent);
testing.expectEqual('test1', document.querySelector('* #byId').getAttribute('name'));
testing.expectEqual('Compound 1', document.querySelector('* #compound-test').textContent);
testing.expectEqual('Direct child paragraph', document.querySelector('#descendant-container *').textContent);
testing.expectEqual('Paragraph in compound ID div', document.querySelector('#compound-id-test *').textContent);
testing.expectEqual('div1', document.querySelector('* * div').textContent);
testing.expectEqual(document.querySelector('p').textContent, document.querySelector('* * p').textContent);
testing.expectEqual('Nested paragraph', document.querySelector('div * p').textContent);
testing.expectEqual('Nested paragraph', document.querySelector('#descendant-container * p').textContent);
testing.expectEqual('Deeply nested paragraph', document.querySelector('.nested * p').textContent);
testing.expectEqual('target', document.querySelector('#complex * span').id);
testing.expectEqual('Nested span', document.querySelector('#compound-id-test * span').textContent);
testing.expectEqual('Nested paragraph', document.querySelector('* div.nested p').textContent);
testing.expectEqual('Paragraph in compound ID div', document.querySelector('* #compound-id-test.special p').textContent);
}
</script>
<svg id="test-svg-selector" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"/>
<text x="10" y="20">SVG Text Content</text>
<g id="svg-group">
<rect x="5" y="5" width="20" height="20"/>
</g>
</svg>
<script id=svgSelectors>
{
testing.expectEqual('svg', document.querySelector('svg').tagName);
testing.expectEqual('circle', document.querySelector('circle').tagName);
testing.expectEqual('text', document.querySelector('text').tagName);
testing.expectEqual('circle', document.querySelector('svg circle').tagName);
testing.expectEqual('text', document.querySelector('svg text').tagName);
testing.expectEqual('rect', document.querySelector('svg rect').tagName);
testing.expectEqual('rect', document.querySelector('#svg-group rect').tagName);
testing.expectEqual('rect', document.querySelector('svg #svg-group rect').tagName);
testing.expectEqual('rect', document.querySelector('g rect').tagName);
testing.expectEqual('rect', document.querySelector('svg g rect').tagName);
}
</script>

View File

@@ -0,0 +1,378 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div class="test-class">div1</div>
<span class="test-class">span1</span>
<div class="multiple classes here">multi1</div>
<div class="classes">multi2</div>
<div class="">empty-class</div>
<div>no-class</div>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
<b>Bold text</b>
<i>Italic text</i>
<em>Emphasized text</em>
<strong>Strong text</strong>
<header>Header element</header>
<nav>Navigation</nav>
<main>Main content</main>
<script>
function assertList(expected, result) {
testing.expectEqual(expected.length, result.length);
testing.expectEqual(expected, Array.from(result).map((e) => e.textContent));
testing.expectEqual(expected, Array.from(result.values()).map((e) => e.textContent));
testing.expectEqual(expected.map((e, i) => i), Array.from(result.keys()));
}
</script>
<script id=script1 name="test1">
testing.expectError("Syntax Error", () => document.querySelectorAll(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);
testing.expectEqual("Syntax Error", err.message);
}, () => document.querySelectorAll(''));
</script>
<script id=byId>
{
const result = document.querySelectorAll('#script1');
testing.expectEqual(true, result instanceof NodeList);
testing.expectEqual(1, result.length);
testing.expectEqual('test1', result[0].getAttribute('name'));
assertList([], document.querySelectorAll('#nonexistent'));
}
</script>
<script id=byClass>
assertList([], document.querySelectorAll('.nope'));
assertList(['div1', 'span1'], document.querySelectorAll('.test-class'));
assertList(['multi1', 'multi2'], document.querySelectorAll('.classes'));
assertList(['multi1'], document.querySelectorAll('.multiple'));
assertList(['multi1'], document.querySelectorAll('.here'));
</script>
<script id=byTag>
{
const divs = document.querySelectorAll('div');
testing.expectEqual(true, divs.length >= 5);
testing.expectEqual('div1', divs[0].textContent);
const scripts = document.querySelectorAll('script');
testing.expectEqual(true, scripts.length >= 5);
assertList(['span1'], document.querySelectorAll('span'));
assertList([], document.querySelectorAll('article'));
assertList([], document.querySelectorAll('section'));
assertList(['Heading 1'], document.querySelectorAll('h1'));
assertList(['Heading 2'], document.querySelectorAll('h2'));
assertList(['Heading 3'], document.querySelectorAll('h3'));
assertList(['Heading 1'], document.querySelectorAll('H1'));
assertList(['Navigation'], document.querySelectorAll('NAV'));
}
</script>
<div id="compound-test" class="container active">Compound 1</div>
<div class="container">Compound 2</div>
<span class="container active">Compound 3</span>
<div class="active">Compound 4</div>
<p id="compound-test2">Compound 5</p>
<script id=compoundSelectors>
{
assertList(['Compound 1', 'Compound 2'], document.querySelectorAll('div.container'));
assertList(['Compound 1', 'Compound 4'], document.querySelectorAll('div.active'));
assertList(['Compound 3'], document.querySelectorAll('span.active'));
assertList(['Compound 1', 'Compound 3'], document.querySelectorAll('.container.active'));
assertList(['Compound 1', 'Compound 3'], document.querySelectorAll('.active.container'));
assertList(['Compound 1'], document.querySelectorAll('div.container.active'));
assertList(['Compound 1'], document.querySelectorAll('div#compound-test'));
assertList(['Compound 1'], document.querySelectorAll('.container#compound-test'));
assertList(['Compound 1'], document.querySelectorAll('#compound-test.active'));
assertList(['Compound 1', 'Compound 2'], document.querySelectorAll(' div.container'));
assertList(['Compound 1', 'Compound 3', 'Compound 4'], document.querySelectorAll(' .active'));
assertList(['Compound 1', 'Compound 2'], document.querySelectorAll('div.container '));
assertList(['Compound 5'], document.querySelectorAll('#compound-test2 '));
assertList(['Compound 3'], document.querySelectorAll(' span.active '));
assertList([], document.querySelectorAll('span#compound-test'));
assertList([], document.querySelectorAll('div.nonexistent'));
assertList([], document.querySelectorAll('p.container'));
}
</script>
<script id=universalSelector>
{
const all = document.querySelectorAll('*');
testing.expectEqual(true, all.length > 20);
testing.expectEqual('HTML', all[0].tagName);
assertList(['div1', 'span1'], document.querySelectorAll('*.test-class'));
const scriptResult = document.querySelectorAll('*#script1');
testing.expectEqual(1, scriptResult.length);
testing.expectEqual('test1', scriptResult[0].getAttribute('name'));
assertList(['Compound 1', 'Compound 3'], document.querySelectorAll('*.container.active'));
}
</script>
<script id=iteratorTests>
{
const list = document.querySelectorAll('.test-class');
const entries = Array.from(list.entries());
testing.expectEqual(2, entries.length);
testing.expectEqual(0, entries[0][0]);
testing.expectEqual('div1', entries[0][1].textContent);
testing.expectEqual(1, entries[1][0]);
testing.expectEqual('span1', entries[1][1].textContent);
const keys = Array.from(list.keys());
testing.expectEqual([0, 1], keys);
const values = Array.from(list.values());
testing.expectEqual(['div1', 'span1'], values.map((e) => e.textContent));
const defaultIter = Array.from(list);
testing.expectEqual(['div1', 'span1'], defaultIter.map((e) => e.textContent));
}
</script>
<script id=multipleIterators>
{
const list = document.querySelectorAll('.test-class');
const keysIter = list.keys();
const valuesIter = list.values();
const key1 = keysIter.next();
testing.expectEqual(false, key1.done);
testing.expectEqual(0, key1.value);
const val1 = valuesIter.next();
testing.expectEqual(false, val1.done);
testing.expectEqual('div1', val1.value.textContent);
const key2 = keysIter.next();
testing.expectEqual(false, key2.done);
testing.expectEqual(1, key2.value);
const val2 = valuesIter.next();
testing.expectEqual(false, val2.done);
testing.expectEqual('span1', val2.value.textContent);
const key3 = keysIter.next();
testing.expectEqual(true, key3.done);
const val3 = valuesIter.next();
testing.expectEqual(true, val3.done);
}
</script>
<script id=iteratorLifetime>
{
let iter;
{
const list = document.querySelectorAll('.test-class');
iter = list.values();
}
const val1 = iter.next();
testing.expectEqual(false, val1.done);
testing.expectEqual('div1', val1.value.textContent);
const val2 = iter.next();
testing.expectEqual(false, val2.done);
testing.expectEqual('span1', val2.value.textContent);
const val3 = iter.next();
testing.expectEqual(true, val3.done);
}
</script>
<div id="descendant-container">
<p class="desc-text">Direct child paragraph</p>
<div class="nested">
<p class="desc-text">Nested paragraph</p>
<span>
<p class="deep">Deeply nested paragraph</p>
</span>
</div>
<article>
<p class="desc-text">Article paragraph</p>
</article>
</div>
<p class="desc-text">Outside paragraph</p>
<div id="complex">
<div class="outer">
<div class="middle">
<div class="inner">
<span id="target">Target span</span>
</div>
</div>
</div>
</div>
<script>
function assertList(expected, result) {
testing.expectEqual(expected.length, result.length);
testing.expectEqual(expected, Array.from(result).map((e) => e.textContent));
}
</script>
<script id=descendantSelectors>
{
assertList(['Direct child paragraph', 'Nested paragraph', 'Deeply nested paragraph', 'Article paragraph'], document.querySelectorAll('div p'));
assertList(['Direct child paragraph', 'Nested paragraph', 'Article paragraph'], document.querySelectorAll('div .desc-text'));
assertList(['Direct child paragraph', 'Nested paragraph', 'Deeply nested paragraph', 'Article paragraph'], document.querySelectorAll('#descendant-container p'));
assertList(['Nested paragraph', 'Deeply nested paragraph'], document.querySelectorAll('div div p'));
assertList(['Target span'], document.querySelectorAll('div div div span'));
assertList(['Nested paragraph'], document.querySelectorAll('div.nested p.desc-text'));
assertList([], document.querySelectorAll('article div p'));
}
</script>
<script id=descendantWithWhitespace>
{
assertList(['Direct child paragraph', 'Nested paragraph', 'Deeply nested paragraph', 'Article paragraph'], document.querySelectorAll(' div p '));
assertList(['Nested paragraph'], document.querySelectorAll(' div.nested p.desc-text '));
}
</script>
<script id=multiLevelDescendant>
{
assertList(['Nested paragraph', 'Deeply nested paragraph'], document.querySelectorAll('body div div p'));
assertList(['Target span'], document.querySelectorAll('body div div div div span'));
}
</script>
<script id=descendantCombinedWithCompound>
{
assertList(['Nested paragraph', 'Deeply nested paragraph'], document.querySelectorAll('.nested p'));
const bodyPs = document.querySelectorAll('body p');
testing.expectEqual(true, bodyPs.length >= 5);
assertList(['Target span'], document.querySelectorAll('#complex span'));
assertList(['Target span'], document.querySelectorAll('div.outer span'));
assertList(['Target span'], document.querySelectorAll('.middle .inner span'));
}
</script>
<div id="combinator-test">
<h1>Title</h1>
<p class="intro">First paragraph</p>
<p>Second paragraph</p>
<div class="content">
<span>Span 1</span>
<span>Span 2</span>
<span>Span 3</span>
</div>
<p>Third paragraph</p>
</div>
<script id=childCombinator>
{
assertList(['First paragraph', 'Second paragraph', 'Third paragraph'], document.querySelectorAll('#combinator-test > p'));
assertList(['Span 1', 'Span 2', 'Span 3'], document.querySelectorAll('div.content > span'));
assertList([], document.querySelectorAll('#combinator-test > span'));
assertList(['Title', 'First paragraph', 'Second paragraph', 'Third paragraph'], document.querySelectorAll('#combinator-test > *:not(div)'));
}
</script>
<script id=adjacentSiblingCombinator>
{
assertList(['First paragraph'], document.querySelectorAll('h1 + p'));
assertList(['Second paragraph'], document.querySelectorAll('p.intro + p'));
assertList([], document.querySelectorAll('#combinator-test div + h1'));
}
</script>
<script id=generalSiblingCombinator>
{
assertList(['First paragraph', 'Second paragraph', 'Third paragraph'], document.querySelectorAll('#combinator-test h1 ~ p'));
assertList(['Second paragraph', 'Third paragraph'], document.querySelectorAll('p.intro ~ p'));
assertList([], document.querySelectorAll('#combinator-test p ~ h1'));
}
</script>
<script id=combinedCombinators>
{
assertList(['Span 1'], document.querySelectorAll('#combinator-test > div > span:nth-child(1)'));
assertList(['Second paragraph'], document.querySelectorAll('#combinator-test h1 + p + p'));
assertList(['Second paragraph', 'Third paragraph'], document.querySelectorAll('#combinator-test h1 + p ~ p'));
}
</script>
<div id="pseudo-test">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
<div>
<p>Para 1</p>
<span>Span</span>
<p>Para 2</p>
</div>
</div>
<script id=firstChildPseudo>
{
assertList(['Item 1'], document.querySelectorAll('#pseudo-test li:first-child'));
assertList(['Para 1'], document.querySelectorAll('#pseudo-test p:first-child'));
assertList([], document.querySelectorAll('#pseudo-test span:first-child'));
}
</script>
<script id=lastChildPseudo>
{
assertList(['Item 5'], document.querySelectorAll('#pseudo-test li:last-child'));
assertList(['Para 2'], document.querySelectorAll('#pseudo-test p:last-child'));
}
</script>
<script id=nthChildPseudo>
{
assertList(['Item 2'], document.querySelectorAll('#pseudo-test li:nth-child(2)'));
assertList(['Item 1', 'Item 3', 'Item 5'], document.querySelectorAll('#pseudo-test li:nth-child(odd)'));
assertList(['Item 2', 'Item 4'], document.querySelectorAll('#pseudo-test li:nth-child(even)'));
assertList(['Item 3', 'Item 5'], document.querySelectorAll('#pseudo-test li:nth-child(2n+3)'));
}
</script>
<script id=firstOfTypePseudo>
{
assertList(['Para 1'], document.querySelectorAll('#pseudo-test div > p:first-of-type'));
assertList(['Span'], document.querySelectorAll('#pseudo-test div > span:first-of-type'));
}
</script>
<script id=lastOfTypePseudo>
{
assertList(['Para 2'], document.querySelectorAll('#pseudo-test div > p:last-of-type'));
assertList(['Span'], document.querySelectorAll('#pseudo-test div > span:last-of-type'));
}
</script>

View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="container">
<div data-test="value1">First</div>
<div data-test="value2">Second</div>
<input type="text" name="username">
<input type="password" name="password">
<input type="checkbox" checked>
<a href="https://example.com">Link 1</a>
<a href="/relative">Link 2</a>
<a href="https://example.com/page">Link 3</a>
<button disabled>Disabled Button</button>
<button>Enabled Button</button>
<span class="foo bar">Span 1</span>
<span class="foo">Span 2</span>
<span class="bar">Span 3</span>
<div lang="en">English</div>
<div lang="en-US">American English</div>
<div lang="en-GB">British English</div>
<div lang="fr">French</div>
<img src="image.png" alt="Test image">
<img src="photo.jpg">
</div>
<script id="presence">
testing.expectEqual(2, document.querySelectorAll('[data-test]').length);
testing.expectEqual(3, document.querySelectorAll('[type]').length);
testing.expectEqual(1, document.querySelectorAll('[checked]').length);
testing.expectEqual(1, document.querySelectorAll('[disabled]').length);
testing.expectEqual(1, document.querySelectorAll('[alt]').length);
const firstDataTest = document.querySelector('[data-test]');
testing.expectEqual('First', firstDataTest.innerText);
</script>
<script id="exact">
testing.expectEqual(1, document.querySelectorAll('[data-test="value1"]').length);
testing.expectEqual(1, document.querySelectorAll('[data-test="value2"]').length);
testing.expectEqual(0, document.querySelectorAll('[data-test="value3"]').length);
testing.expectEqual(1, document.querySelectorAll('[type="text"]').length);
testing.expectEqual(1, document.querySelectorAll('[type="password"]').length);
testing.expectEqual(1, document.querySelectorAll('[type="checkbox"]').length);
const textInput = document.querySelector('[type="text"]');
testing.expectEqual('username', textInput.getAttribute('name'));
</script>
<script id="word">
testing.expectEqual(2, document.querySelectorAll('[class~="foo"]').length);
testing.expectEqual(2, document.querySelectorAll('[class~="bar"]').length);
testing.expectEqual(0, document.querySelectorAll('[class~="baz"]').length);
const fooEl = document.querySelector('[class~="foo"]');
testing.expectEqual('Span 1', fooEl.innerText);
</script>
<script id="prefixDash">
testing.expectEqual(3, document.querySelectorAll('[lang|="en"]').length);
testing.expectEqual(1, document.querySelectorAll('[lang|="fr"]').length);
testing.expectEqual(0, document.querySelectorAll('[lang|="es"]').length);
const enEl = document.querySelector('[lang|="en"]');
testing.expectEqual('English', enEl.innerText);
</script>
<script id="startsWith">
testing.expectEqual(2, document.querySelectorAll('[href^="https://"]').length);
testing.expectEqual(1, document.querySelectorAll('[href^="/"]').length);
testing.expectEqual(2, document.querySelectorAll('[href^="https://example.com"]').length);
const httpsLink = document.querySelector('[href^="https://"]');
testing.expectEqual('Link 1', httpsLink.innerText);
</script>
<script id="endsWith">
testing.expectEqual(1, document.querySelectorAll('[href$=".com"]').length);
testing.expectEqual(1, document.querySelectorAll('[href$="/page"]').length);
testing.expectEqual(1, document.querySelectorAll('[href$="relative"]').length);
const comLink = document.querySelector('[href$=".com"]');
testing.expectEqual('https://example.com', comLink.getAttribute('href'));
</script>
<script id="substring">
testing.expectEqual(2, document.querySelectorAll('[href*="example"]').length);
testing.expectEqual(1, document.querySelectorAll('[href*="relative"]').length);
testing.expectEqual(1, document.querySelectorAll('[src*="image"]').length);
testing.expectEqual(1, document.querySelectorAll('[src*="photo"]').length);
const exampleLink = document.querySelector('[href*="example"]');
testing.expectEqual('Link 1', exampleLink.innerText);
</script>
<script id="compound">
testing.expectEqual(1, document.querySelectorAll('input[type="text"]').length);
testing.expectEqual(1, document.querySelectorAll('input[type="password"]').length);
testing.expectEqual(1, document.querySelectorAll('button[disabled]').length);
testing.expectEqual(1, document.querySelectorAll('div[lang="en"]').length);
testing.expectEqual(1, document.querySelectorAll('div[data-test="value1"]').length);
const compoundInput = document.querySelector('input[type="text"][name="username"]');
testing.expectEqual('username', compoundInput.getAttribute('name'));
</script>
<script id="descendant">
testing.expectEqual(2, document.querySelectorAll('#container [data-test]').length);
testing.expectEqual(3, document.querySelectorAll('#container input[type]').length);
const containerDataTest = document.querySelector('#container [data-test]');
testing.expectEqual('First', containerDataTest.innerText);
</script>

View File

@@ -0,0 +1,202 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="root">
<div id="a" class="x">
<p id="b" class="y">Para 1</p>
<div id="c" class="x y">
<span id="d">Span 1</span>
<p id="e" class="z">Para 2</p>
</div>
</div>
<article id="f">
<div id="g">
<p id="h">Para 3</p>
</div>
</article>
</div>
<script>
function assertList(expected, result) {
testing.expectEqual(expected.length, result.length);
testing.expectEqual(expected, Array.from(result).map((e) => e.textContent));
}
</script>
<script id=redundantUniversal>
{
// Universal with ID
assertList(['Para 1'], document.querySelectorAll('#b'));
assertList(['Span 1'], document.querySelectorAll('#d'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#c *'));
// Universal with combinators
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#c > *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#b + * *'));
}
</script>
<script id=multipleClasses>
{
// Multiple class combinations
const yClass = document.querySelectorAll('.y');
testing.expectEqual(true, yClass.length >= 1);
assertList(['Span 1', 'Para 2'], document.querySelectorAll('.x.y *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('.y.x *'));
}
</script>
<script id=idEdgeCases>
{
// ID with universal
assertList(['Para 1'], document.querySelectorAll('*#b'));
assertList(['Para 1'], document.querySelectorAll('#b'));
// ID with tag mismatch (should return empty)
assertList([], document.querySelectorAll('div#b'));
assertList([], document.querySelectorAll('span#a'));
}
</script>
<script id=complexCombinatorChains>
{
// Long combinator chains
assertList(['Span 1'], document.querySelectorAll('div > div > div > span'));
// Mixed combinators
assertList(['Para 2'], document.querySelectorAll('#b + div > p'));
assertList(['Para 2'], document.querySelectorAll('#b ~ * > p'));
assertList(['Para 2'], document.querySelectorAll('p + div > p'));
}
</script>
<script id=descendantWithSiblings>
{
// Descendant followed by sibling
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#a div *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#a p ~ div *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#a p + div *'));
}
</script>
<script id=notEdgeCases>
{
// :not() with different selectors
const notDivArticle = document.querySelectorAll('*:not(div):not(article)');
testing.expectEqual(true, notDivArticle.length >= 3);
assertList(['Para 3'], document.querySelectorAll('#root p:not(.y):not(.z)'));
// :not() with universal
const allButDiv = document.querySelectorAll('#root *:not(div)');
testing.expectEqual(true, allButDiv.length >= 3);
// :not() with ID
const allP = document.querySelectorAll('p:not(#nonexistent)');
testing.expectEqual(true, allP.length >= 3);
assertList(['Para 1', 'Para 3'], document.querySelectorAll('p:not(#e)'));
// :not() with class
const notClasses = document.querySelectorAll('#root *:not(.x):not(.y):not(.z)');
testing.expectEqual(true, notClasses.length >= 2);
}
</script>
<script id=pseudoClassCombinations>
{
// First/last child combinations
assertList(['Para 1'], document.querySelectorAll('#a > p:first-child'));
assertList(['Span 1'], document.querySelectorAll('#c > :first-child'));
assertList(['Para 2'], document.querySelectorAll('#c > :last-child'));
// nth-child with combinators
assertList(['Span 1'], document.querySelectorAll('#c > :nth-child(1)'));
assertList(['Para 2'], document.querySelectorAll('#c > :nth-child(2)'));
assertList(['Span 1'], document.querySelectorAll('#c > :nth-child(odd)'));
assertList(['Para 2'], document.querySelectorAll('#c > :nth-child(even)'));
}
</script>
<script id=whitespaceMadness>
{
// Excessive whitespace
assertList(['Para 2'], document.querySelectorAll(' #a > div > p '));
assertList(['Para 2'], document.querySelectorAll(' p + div > p '));
assertList(['Span 1', 'Para 2'], document.querySelectorAll(' #b ~ div * '));
}
</script>
<script id=compoundSelectorMadness>
{
// Very long compound selectors
assertList(['Para 1'], document.querySelectorAll('p#b.y'));
assertList(['Para 1'], document.querySelectorAll('#b.y'));
assertList(['Para 1'], document.querySelectorAll('.y#b'));
assertList(['Para 1'], document.querySelectorAll('p.y#b'));
assertList(['Para 1'], document.querySelectorAll('*.y#b'));
assertList(['Para 1'], document.querySelectorAll('*#b.y'));
assertList(['Para 1'], document.querySelectorAll('#b.y'));
// Multiple classes in different orders
assertList(['Span 1', 'Para 2'], document.querySelectorAll('.x.y *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('.y.x *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('div.x.y *'));
assertList(['Span 1', 'Para 2'], document.querySelectorAll('div.y.x *'));
}
</script>
<script id=rootBoundaries>
{
// Root itself can match in ancestor searches
const a = document.getElementById('a');
const divChildren = a.querySelectorAll('div *');
testing.expectEqual(true, divChildren.length >= 2);
const c = document.getElementById('c');
const cDivChildren = c.querySelectorAll('div *');
// c itself is a div, so its children match
testing.expectEqual(2, cDivChildren.length);
}
</script>
<script id=siblingEdgeCases>
{
// Sibling selector with descendants
assertList(['Span 1', 'Para 2'], document.querySelectorAll('#b + div *'));
// Multiple sibling hops
const root = document.getElementById('root');
assertList(['Para 3'], root.querySelectorAll('div ~ article p'));
assertList(['Para 3'], root.querySelectorAll('div + article p'));
}
</script>
<script id=nonExistentCombinations>
{
// Valid selectors that don't match anything
assertList([], document.querySelectorAll('p > div'));
assertList([], document.querySelectorAll('span > p'));
assertList([], document.querySelectorAll('#a + #b'));
assertList([], document.querySelectorAll('#b + #a'));
assertList([], document.querySelectorAll('article > article'));
assertList([], document.querySelectorAll('span.x'));
assertList([], document.querySelectorAll('p#a'));
assertList([], document.querySelectorAll('.nonexistent'));
assertList([], document.querySelectorAll('#nonexistent'));
assertList([], document.querySelectorAll('video'));
}
</script>
<script id=universalCombinations>
{
// Universal in different positions
const universalDesc = document.querySelectorAll('* * * p');
testing.expectEqual(true, universalDesc.length >= 2);
const universalChild = document.querySelectorAll('* > * > * > *');
testing.expectEqual(true, universalChild.length >= 1);
const mixedUniversal = document.querySelectorAll('* > * > p');
testing.expectEqual(true, mixedUniversal.length >= 1);
}
</script>

View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="root">
<div id="container" class="parent">
<p id="p1" class="text">Paragraph 1</p>
<p id="p2" class="text highlight">Paragraph 2</p>
<div id="nested">
<span id="s1" class="item">Span 1</span>
<span id="s2" class="item active">Span 2</span>
<a id="link1" href="#">Link 1</a>
</div>
<article id="article1">
<p id="p3">Article paragraph</p>
<span id="s3">Article span</span>
</article>
</div>
</div>
<script id=basicNot>
{
const notDiv = document.querySelectorAll('#root *:not(div)');
testing.expectEqual(true, notDiv.length >= 7);
const notClass = document.querySelectorAll('#container *:not(.text)');
testing.expectEqual(true, notClass.length >= 5);
const notId = document.querySelectorAll('#container p:not(#p2)');
testing.expectEqual(2, notId.length);
testing.expectEqual('p1', notId[0].id);
testing.expectEqual('p3', notId[1].id);
}
</script>
<script id=notWithCombinators>
{
const notDirectChild = document.querySelectorAll('span:not(#nested > span)');
testing.expectEqual(1, notDirectChild.length);
testing.expectEqual('s3', notDirectChild[0].id);
const notDescendant = document.querySelectorAll('#container > *:not(div)');
testing.expectEqual(3, notDescendant.length);
}
</script>
<script id=notWithMultipleSelectors>
{
const notDivOrP = document.querySelectorAll('#container *:not(div, p)');
testing.expectEqual(5, notDivOrP.length);
const notTextOrItem = document.querySelectorAll('#container *:not(.text, .item)');
testing.expectEqual(true, notTextOrItem.length >= 3);
const notIdOrClass = document.querySelectorAll('p:not(#p1, .highlight)');
testing.expectEqual(1, notIdOrClass.length);
testing.expectEqual('p3', notIdOrClass[0].id);
}
</script>
<script id=notWithAttributes>
{
const notWithHref = document.querySelectorAll('#nested *:not([href])');
testing.expectEqual(2, notWithHref.length);
const notLinkWithHref = document.querySelectorAll('#nested *:not(a[href])');
testing.expectEqual(2, notLinkWithHref.length);
}
</script>
<script id=notWithPseudoClasses>
{
const notFirstChild = document.querySelectorAll('#nested *:not(:first-child)');
testing.expectEqual(2, notFirstChild.length);
const notLastChild = document.querySelectorAll('#nested *:not(:last-child)');
testing.expectEqual(2, notLastChild.length);
}
</script>
<script id=nestedNot>
{
// :not(:not(...)) matches elements that DO match the inner selector
const items = document.querySelectorAll('.item:not(:not(.active))');
testing.expectEqual(1, items.length);
testing.expectEqual('s2', items[0].id);
}
</script>
<script id=complexCombinations>
{
const complex1 = document.querySelectorAll('p:not(#nested p, .highlight)');
testing.expectEqual(2, complex1.length);
testing.expectEqual('p1', complex1[0].id);
testing.expectEqual('p3', complex1[1].id);
const complex2 = document.querySelectorAll('#container > *:not(div)');
testing.expectEqual(3, complex2.length);
}
</script>
<script id=notWithUniversal>
{
const notUniversal = document.querySelectorAll('#nested :not(*)');
testing.expectEqual(0, notUniversal.length);
}
</script>
<script id=edgeCases>
{
const multiNot = document.querySelectorAll('*:not(div):not(p):not(article)');
testing.expectEqual(true, multiNot.length >= 3);
const notAtEnd = document.querySelectorAll('#container p:not(.highlight)');
testing.expectEqual(2, notAtEnd.length);
const notAtStart = document.querySelectorAll(':not(div) > span');
testing.expectEqual(1, notAtStart.length);
}
</script>

View File

@@ -0,0 +1,102 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<body></body>
<script id=document_fragment>
{
const df = new DocumentFragment();
testing.expectEqual("DocumentFragment", df.constructor.name);
testing.expectEqual("#document-fragment", df.nodeName);
}
{
const df = document.createDocumentFragment();
testing.expectEqual("DocumentFragment", df.constructor.name);
testing.expectEqual("#document-fragment", df.nodeName);
}
{
const df = document.createDocumentFragment();
const div1 = document.createElement("div");
div1.textContent = "First";
const div2 = document.createElement("div");
div2.textContent = "Second";
df.appendChild(div1);
df.appendChild(div2);
testing.expectEqual(2, df.childElementCount);
testing.expectEqual(div1, df.firstElementChild);
testing.expectEqual(div2, df.lastElementChild);
}
{
const df = document.createDocumentFragment();
df.append("text1", document.createElement("span"), "text2");
testing.expectEqual(3, df.childNodes.length);
testing.expectEqual(1, df.childElementCount);
}
{
const df = document.createDocumentFragment();
df.append("last");
df.prepend("first");
testing.expectEqual("#text", df.firstChild.nodeName);
testing.expectEqual("first", df.firstChild.textContent);
}
{
const df = document.createDocumentFragment();
const p = document.createElement("p");
p.className = "test";
p.textContent = "Hello";
df.appendChild(p);
const found = df.querySelector(".test");
testing.expectEqual(p, found);
testing.expectEqual("Hello", found.textContent);
}
{
const df = document.createDocumentFragment();
df.appendChild(document.createElement("div"));
df.appendChild(document.createElement("div"));
testing.expectEqual(2, df.childElementCount);
df.replaceChildren(document.createElement("span"));
testing.expectEqual(1, df.childElementCount);
testing.expectEqual("SPAN", df.firstElementChild.tagName);
}
{
const df = document.createDocumentFragment();
const outer = document.createElement("div");
outer.id = "outer";
const inner = document.createElement("div");
inner.id = "inner";
outer.appendChild(inner);
df.appendChild(outer);
testing.expectEqual(outer, df.getElementById("outer"));
testing.expectEqual(inner, df.getElementById("inner"));
testing.expectEqual(null, df.getElementById("notfound"));
}
{
const df = document.createDocumentFragment();
const test1 = document.createElement("div");
test1.id = "test1";
const test2 = document.createElement("div");
test2.id = "test2";
df.appendChild(test1);
df.appendChild(test2);
testing.expectEqual(2, df.childElementCount);
document.body.appendChild(df);
testing.expectEqual(0, df.childElementCount);
testing.expectEqual(test1, document.getElementById("test1"));
testing.expectEqual(test2, document.getElementById("test2"));
}
</script>

View File

@@ -0,0 +1,9 @@
<head id="the_head">
<script src="testing.js"></script>
</head>
<body id="the_body">
<script id="test-document-head-body">
testing.expectEqual(document.getElementById('the_head'), document.head);
testing.expectEqual(document.getElementById('the_body'), document.body);
</script>
</body>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="test-container"></div>
<script id="append-prepend-tests">
const container = $('#test-container');
// Test append
const p1 = document.createElement('p');
p1.textContent = 'p1';
container.append(p1);
testing.expectEqual('<p>p1</p>', container.innerHTML, "append single element");
const p2 = document.createElement('p');
p2.textContent = 'p2';
container.append(p2, ' some text');
testing.expectEqual('<p>p1</p><p>p2</p> some text', container.innerHTML, "append multiple nodes");
// Test prepend
container.innerHTML = '';
const p3 = document.createElement('p');
p3.textContent = 'p3';
container.prepend(p3);
testing.expectEqual('<p>p3</p>', container.innerHTML, "prepend single element");
const p4 = document.createElement('p');
p4.textContent = 'p4';
container.prepend(p4, 'some text ');
testing.expectEqual('<p>p4</p>some text <p>p3</p>', container.innerHTML, "prepend multiple nodes");
</script>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=attr1 ClasS="sHow"></div>
<script id=attributes>
const el1 = $('#attr1');
testing.expectEqual(null, el1.getAttribute('other'));
testing.expectEqual('attr1', el1.id);
testing.expectEqual('attr1', el1.getAttribute('id'));
testing.expectEqual('attr1', el1.getAttribute('Id'));
testing.expectEqual('sHow', el1.getAttribute('CLASS'));
testing.expectEqual('sHow', el1.getAttribute('class'));
testing.expectEqual(['id', 'class'], el1.getAttributeNames());
el1.setAttribute('other', 'attr-1')
testing.expectEqual('attr-1', el1.getAttribute('other'));
testing.expectEqual(['id', 'class', 'other'], el1.getAttributeNames());
el1.removeAttribute('other');
testing.expectEqual(null, el1.getAttribute('other'));
testing.expectEqual(['id', 'class'], el1.getAttributeNames());
testing.expectEqual(null, el1.getAttributeNode('unknown'));
let an1 = el1.getAttributeNode('class');
testing.expectEqual(an1 , el1.setAttributeNode(an1));
testing.expectEqual(el1, an1.ownerElement);
testing.expectEqual('class', an1.name);
testing.expectEqual('class', an1.localName);
testing.expectEqual('sHow', an1.value);
testing.expectEqual(el1.getAttributeNode('class'), an1);
testing.expectEqual(null, an1.namespaceURI);
const script_id_node = $('#attributes').getAttributeNode('id')
testing.withError((err) => {
testing.expectEqual(8, err.code);
testing.expectEqual("NotFoundError", err.name);
testing.expectEqual("Not Found", err.message);
}, () => el1.removeAttributeNode(script_id_node));
testing.expectEqual(an1, el1.removeAttributeNode(an1));
testing.expectEqual(null, an1.ownerElement);
testing.expectEqual(null, el1.getAttributeNode('class'));
testing.expectEqual(null, el1.setAttributeNode(an1))
testing.expectEqual(el1, an1.ownerElement);
</script>
<script id=namednodemap>
testing.expectEqual(true, el1.attributes instanceof NamedNodeMap);
testing.expectEqual(el1.attributes, el1.attributes);
function assertAttributes(expected) {
const attributes = el1.attributes;
testing.expectEqual(expected.length, attributes.length);
for (let i = 0; i < expected.length; i++) {
const ex = expected[i];
let attribute = attributes[ex.name];
testing.expectEqual(ex.name, attribute.name)
testing.expectEqual(ex.value, attribute.value)
attribute = attributes.getNamedItem(ex.name);
testing.expectEqual(ex.name, attribute.name)
testing.expectEqual(ex.value, attribute.value)
attribute = attributes.item(i);
testing.expectEqual(ex.name, attribute.name)
testing.expectEqual(ex.value, attribute.value)
}
testing.expectEqual(undefined, attributes["nope"]);
testing.expectEqual('attr1', attributes.id.value);
testing.expectEqual(null, attributes.getNamedItem("nope"));
testing.expectEqual(null, attributes.item(100));
testing.expectEqual(null, attributes.item(-3));
let acc = [];
for (let attribute of attributes) {
acc.push({name: attribute.name, value: attribute.value});
}
testing.expectEqual(expected, acc);
}
assertAttributes([{name: 'id', value: 'attr1'}, {name: 'class', value: 'sHow'}]);
</script>

View File

@@ -0,0 +1,334 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=test1></div>
<div id=test2 class="foo bar"></div>
<script id=basic>
{
const div = $('#test1');
testing.expectEqual(0, div.classList.length);
div.classList.add('foo');
testing.expectEqual('foo', div.className);
testing.expectEqual(1, div.classList.length);
div.classList.add('bar', 'baz');
testing.expectEqual('foo bar baz', div.className);
testing.expectEqual(3, div.classList.length);
div.classList.add('bar');
testing.expectEqual('foo bar baz', div.className);
testing.expectEqual(3, div.classList.length);
}
</script>
<script id=contains>
{
const div = $('#test2');
testing.expectEqual(false, div.classList.contains(''));
testing.expectEqual(true, div.classList.contains('foo'));
testing.expectEqual(true, div.classList.contains('bar'));
testing.expectEqual(false, div.classList.contains('baz'));
}
</script>
<script id=remove>
{
const div = $('#test2');
div.classList.remove('foo');
testing.expectEqual('bar', div.className);
testing.expectEqual(1, div.classList.length);
div.classList.remove('nonexistent');
testing.expectEqual('bar', div.className);
div.classList.remove('bar');
testing.expectEqual('', div.className);
testing.expectEqual(0, div.classList.length);
}
</script>
<script id=toggle>
{
const div = document.createElement('div');
let result = div.classList.toggle('foo');
testing.expectEqual(true, result);
testing.expectEqual('foo', div.className);
result = div.classList.toggle('foo');
testing.expectEqual(false, result);
testing.expectEqual('', div.className);
result = div.classList.toggle('bar', true);
testing.expectEqual(true, result);
testing.expectEqual('bar', div.className);
result = div.classList.toggle('bar', true);
testing.expectEqual(true, result);
testing.expectEqual('bar', div.className);
result = div.classList.toggle('baz', false);
testing.expectEqual(false, result);
testing.expectEqual('bar', div.className);
}
</script>
<script id=replace>
{
const div = document.createElement('div');
div.className = 'foo bar baz';
let result = div.classList.replace('bar', 'qux');
testing.expectEqual(true, result);
testing.expectEqual('foo qux baz', div.className);
result = div.classList.replace('nonexistent', 'other');
testing.expectEqual(false, result);
testing.expectEqual('foo qux baz', div.className);
}
</script>
<script id=item>
{
const div = document.createElement('div');
div.className = 'alpha beta gamma';
testing.expectEqual('alpha', div.classList.item(0));
testing.expectEqual('beta', div.classList.item(1));
testing.expectEqual('gamma', div.classList.item(2));
testing.expectEqual(null, div.classList.item(3));
testing.expectEqual(null, div.classList.item(-1));
testing.expectEqual('alpha', div.classList[0]);
testing.expectEqual('beta', div.classList[1]);
testing.expectEqual('gamma', div.classList[2]);
testing.expectEqual(undefined, div.classList[3]);
}
</script>
<script id=iteration>
{
const div = document.createElement('div');
div.className = 'one two three';
const result = [];
for (const cls of div.classList) {
result.push(cls);
}
testing.expectEqual(['one', 'two', 'three'], result);
}
</script>
<script id=sync>
{
const div = document.createElement('div');
div.setAttribute('class', 'alpha beta');
testing.expectEqual(2, div.classList.length);
testing.expectEqual(true, div.classList.contains('alpha'));
testing.expectEqual(true, div.classList.contains('beta'));
div.className = 'gamma delta';
testing.expectEqual(2, div.classList.length);
testing.expectEqual(true, div.classList.contains('gamma'));
testing.expectEqual(false, div.classList.contains('alpha'));
div.classList.add('epsilon');
testing.expectEqual('gamma delta epsilon', div.className);
}
</script>
<script id=whitespace>
{
const div = document.createElement('div');
div.className = ' foo bar \t\n baz ';
testing.expectEqual(3, div.classList.length);
testing.expectEqual('foo', div.classList[0]);
testing.expectEqual('bar', div.classList[1]);
testing.expectEqual('baz', div.classList[2]);
}
</script>
<script id=value>
{
const div = document.createElement('div');
div.classList.value = 'test1 test2';
testing.expectEqual('test1 test2', div.classList.value);
testing.expectEqual('test1 test2', div.className);
testing.expectEqual(2, div.classList.length);
}
</script>
<script id=errors>
{
const div = document.createElement('div');
testing.withError((err) => {
testing.expectEqual('SyntaxError', err.name);
}, () => div.classList.add(''));
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => div.classList.add('foo bar'));
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => div.classList.add('foo\tbar'));
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => div.classList.add('foo\nbar'));
testing.withError((err) => {
testing.expectEqual('SyntaxError', err.name);
}, () => div.classList.toggle(''));
testing.withError((err) => {
testing.expectEqual('InvalidCharacterError', err.name);
}, () => div.classList.remove('has space'));
}
</script>
<script id=identity>
{
const div = document.createElement('div');
testing.expectEqual(div.classList, div.classList);
const classList = div.classList;
div.className = 'changed';
testing.expectEqual(classList, div.classList);
}
</script>
<script id=attribute_lifecycle>
{
const div = document.createElement('div');
testing.expectEqual(null, div.getAttribute('class'));
testing.expectEqual(0, div.classList.length);
div.classList.add('foo');
testing.expectEqual('foo', div.getAttribute('class'));
div.classList.remove('foo');
testing.expectEqual('', div.getAttribute('class'));
testing.expectEqual(0, div.classList.length);
div.className = 'bar';
testing.expectEqual('bar', div.getAttribute('class'));
div.className = '';
testing.expectEqual('', div.getAttribute('class'));
}
</script>
<script id=duplicates>
{
const div = document.createElement('div');
div.className = 'foo foo bar';
testing.expectEqual(2, div.classList.length);
testing.expectEqual('foo', div.classList[0]);
testing.expectEqual('bar', div.classList[1]);
div.classList.add('foo');
testing.expectEqual('foo bar', div.className);
div.classList.remove('foo');
testing.expectEqual('bar', div.className);
}
</script>
<script id=replace_edge_cases>
{
const div = document.createElement('div');
div.className = 'foo bar';
let result = div.classList.replace('foo', 'foo');
testing.expectEqual(true, result);
testing.expectEqual('foo bar', div.className);
result = div.classList.replace('foo', 'bar');
testing.expectEqual(true, result);
testing.expectEqual('bar', div.className);
div.className = 'alpha beta gamma';
result = div.classList.replace('beta', 'gamma');
testing.expectEqual(true, result);
testing.expectEqual('alpha gamma', div.className);
}
</script>
<script id=empty_calls>
{
const div = document.createElement('div');
div.className = 'foo bar';
div.classList.add();
testing.expectEqual('foo bar', div.className);
div.classList.remove();
testing.expectEqual('foo bar', div.className);
}
</script>
<script id=case_sensitivity>
{
const div = document.createElement('div');
div.classList.add('Foo');
div.classList.add('foo');
div.classList.add('FOO');
testing.expectEqual('Foo foo FOO', div.className);
testing.expectEqual(3, div.classList.length);
testing.expectEqual(true, div.classList.contains('Foo'));
testing.expectEqual(true, div.classList.contains('foo'));
testing.expectEqual(true, div.classList.contains('FOO'));
testing.expectEqual(false, div.classList.contains('fOO'));
}
</script>
<script id=special_characters>
{
const div = document.createElement('div');
div.classList.add('class-name');
div.classList.add('class_name');
div.classList.add('class123');
div.classList.add('123class');
div.classList.add('über');
div.classList.add('日本語');
testing.expectEqual(6, div.classList.length);
testing.expectEqual(true, div.classList.contains('class-name'));
testing.expectEqual(true, div.classList.contains('class_name'));
testing.expectEqual(true, div.classList.contains('class123'));
testing.expectEqual(true, div.classList.contains('123class'));
testing.expectEqual(true, div.classList.contains('über'));
testing.expectEqual(true, div.classList.contains('日本語'));
}
</script>
<script id=multiple_operations>
{
const div = document.createElement('div');
div.classList.add('a', 'b', 'c', 'd', 'e');
testing.expectEqual('a b c d e', div.className);
div.classList.remove('b', 'd');
testing.expectEqual('a c e', div.className);
div.classList.add('b', 'c', 'f');
testing.expectEqual('a c e b f', div.className);
}
</script>

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="test-div"></div>
<script id="camelCaseAccess">
{
const div = $('#test-div');
div.style.cssText = '';
testing.expectEqual('', div.style.backgroundColor);
testing.expectEqual('', div.style.fontSize);
testing.expectEqual('', div.style.marginTop);
div.style.setProperty('background-color', 'blue');
div.style.setProperty('font-size', '14px');
div.style.setProperty('margin-top', '10px');
testing.expectEqual('blue', div.style.backgroundColor);
testing.expectEqual('14px', div.style.fontSize);
testing.expectEqual('10px', div.style.marginTop);
}
</script>
<script id="dashCaseAccess">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('background-color', 'red');
div.style.setProperty('font-size', '16px');
testing.expectEqual('red', div.style['background-color']);
testing.expectEqual('16px', div.style['font-size']);
}
</script>
<script id="mixedAccess">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('color', 'green');
testing.expectEqual('green', div.style.color);
testing.expectEqual('green', div.style['color']);
testing.expectEqual('green', div.style.getPropertyValue('color'));
}
</script>
<script id="cssFloat">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('float', 'left');
testing.expectEqual('left', div.style.cssFloat);
testing.expectEqual('left', div.style['float']);
testing.expectEqual('left', div.style.getPropertyValue('float'));
div.style.setProperty('float', 'right');
testing.expectEqual('right', div.style.cssFloat);
}
</script>
<script id="vendorPrefixes">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('-webkit-transform', 'rotate(45deg)');
div.style.setProperty('-moz-user-select', 'none');
div.style.setProperty('-ms-filter', 'blur(5px)');
div.style.setProperty('-o-transition', 'all');
testing.expectEqual('rotate(45deg)', div.style.webkitTransform);
testing.expectEqual(undefined, div.style.mozUserSelect);
testing.expectEqual(undefined, div.style.msFilter);
testing.expectEqual(undefined, div.style.oTransition);
testing.expectEqual('rotate(45deg)', div.style['-webkit-transform']);
testing.expectEqual('none', div.style['-moz-user-select']);
}
</script>
<script id="nonExistentProperties">
{
const div = $('#test-div');
div.style.cssText = '';
testing.expectEqual(undefined, div.style.notARealProperty);
testing.expectEqual(undefined, div.style['not-a-real-property']);
testing.expectEqual(undefined, div.style.somethingTotallyMadeUp);
}
</script>
<script id="edgeCaseCamelConversion">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('border-top-left-radius', '5px');
testing.expectEqual('5px', div.style.borderTopLeftRadius);
div.style.setProperty('z-index', '100');
testing.expectEqual('100', div.style.zIndex);
}
</script>
<script id="vendorPrefixEdgeCases">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('-webkit-border-radius', '5px');
testing.expectEqual('5px', div.style.webkitBorderRadius);
testing.expectEqual(undefined, div.style.webkitNotSet);
testing.expectEqual(undefined, div.style.mozNotSet);
}
</script>
<script id="prototypeChainCheck">
{
const div = $('#test-div');
testing.expectEqual(true, typeof div.style.getPropertyValue === 'function');
testing.expectEqual(true, typeof div.style.setProperty === 'function');
testing.expectEqual(true, typeof div.style.removeProperty === 'function');
testing.expectEqual(true, typeof div.style.getPropertyPriority === 'function');
}
</script>

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="container">
<!-- comment -->
<p id="p1">Paragraph 1</p>
<div id="div1">Div 1</div>
<!-- comment -->
<span id="span1">Span 1</span>
<p id="p2">Paragraph 2</p>
</div>
<div id="empty"></div>
<script id="relatedElements">
const container = $('#container');
const p1 = $('#p1');
const div1 = $('#div1');
const span1 = $('#span1');
const p2 = $('#p2');
const empty = $('#empty');
testing.expectEqual(p1, container.firstElementChild);
testing.expectEqual(p2, container.lastElementChild);
testing.expectEqual(div1, p1.nextElementSibling);
testing.expectEqual(span1, div1.nextElementSibling);
testing.expectEqual(p2, span1.nextElementSibling);
testing.expectEqual(null, p2.nextElementSibling);
testing.expectEqual(span1, p2.previousElementSibling);
testing.expectEqual(div1, span1.previousElementSibling);
testing.expectEqual(p1, div1.previousElementSibling);
testing.expectEqual(null, p1.previousElementSibling);
testing.expectEqual(null, empty.firstElementChild);
testing.expectEqual(null, empty.lastElementChild);
</script>
<script id=children>
const cc = container.children;
testing.expectEqual(4, cc.length);
testing.expectEqual(p1, cc[0]);
testing.expectEqual(p1, cc.item(0));
testing.expectEqual(div1, cc[1]);
testing.expectEqual(div1, cc.item(1));
testing.expectEqual(span1, cc[2]);
testing.expectEqual(span1, cc.item(2));
testing.expectEqual(p2, cc[3]);
testing.expectEqual(p2, cc.item(3));
const ec = empty.children;
testing.expectEqual(0, ec.length);
testing.expectEqual(undefined, ec[0]);
</script>

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="container" class="container-class">
<div class="foo" id="foo1">foo1</div>
<span class="foo" id="foo2">foo2</span>
<p class="foo" id="foo3">foo3</p>
<div class="bar" id="bar1">bar1</div>
<div class="baz" id="baz1">baz1</div>
</div>
<div id="nested" class="nested-root">
<div class="outer" id="outer">
<div class="inner" id="inner1">inner1</div>
<div class="inner" id="inner2">inner2</div>
<p class="inner" id="inner3">inner3</p>
</div>
</div>
<div id="empty"></div>
<div id="multi" class="multi class names">
<div class="multi" id="multi1">multi1</div>
<span class="class" id="multi2">multi2</span>
<p class="names" id="multi3">multi3</p>
</div>
<div id="classMatch" class="test-class">
<span class="other">span1</span>
<p class="other">p1</p>
</div>
<script id=basic>
{
const container = $('#container');
testing.expectEqual(0, container.getElementsByClassName('nonexistent').length);
testing.expectEqual(0, container.getElementsByClassName('unknown').length);
const foos = container.getElementsByClassName('foo');
testing.expectEqual(true, foos instanceof HTMLCollection);
testing.expectEqual(3, foos.length);
testing.expectEqual(3, foos.length); // cache test
testing.expectEqual('foo1', foos[0].id);
testing.expectEqual('foo2', foos[1].id);
testing.expectEqual('foo3', foos[2].id);
testing.expectEqual('foo1', foos[0].id); // cache test
testing.expectEqual('foo3', foos[2].id);
testing.expectEqual('foo2', foos[1].id);
testing.expectEqual(undefined, foos[-1]);
testing.expectEqual(undefined, foos[3]);
const bars = container.getElementsByClassName('bar');
testing.expectEqual(1, bars.length);
testing.expectEqual('bar1', bars[0].id);
const bazs = container.getElementsByClassName('baz');
testing.expectEqual(1, bazs.length);
testing.expectEqual('baz1', bazs[0].id);
}
</script>
<script id=nestedSearch>
{
const nested = $('#nested');
const inners = nested.getElementsByClassName('inner');
testing.expectEqual(3, inners.length);
testing.expectEqual('inner1', inners[0].id);
testing.expectEqual('inner2', inners[1].id);
testing.expectEqual('inner3', inners[2].id);
const outer = $('#outer');
const outerInners = outer.getElementsByClassName('inner');
testing.expectEqual(3, outerInners.length);
testing.expectEqual('inner1', outerInners[0].id);
testing.expectEqual('inner2', outerInners[1].id);
testing.expectEqual('inner3', outerInners[2].id);
}
</script>
<script id=emptyResult>
{
const empty = $('#empty');
testing.expectEqual(0, empty.getElementsByClassName('foo').length);
testing.expectEqual(0, empty.getElementsByClassName('bar').length);
testing.expectEqual(0, empty.getElementsByClassName('').length);
}
</script>
<script id=excludeSelf>
{
// Element itself should NOT be included in results, even if it matches
const container = $('#container');
const containerClass = container.getElementsByClassName('container-class');
testing.expectEqual(0, containerClass.length); // container has 'container-class' but should not be included
const classMatch = $('#classMatch');
const testClass = classMatch.getElementsByClassName('test-class');
testing.expectEqual(0, testClass.length); // classMatch itself has 'test-class' but should not be included
const others = classMatch.getElementsByClassName('other');
testing.expectEqual(2, others.length); // Only the child elements
}
</script>
<script id=item>
{
const container = $('#container');
const foos = container.getElementsByClassName('foo');
testing.expectEqual('foo1', foos.item(0).id);
testing.expectEqual('foo2', foos.item(1).id);
testing.expectEqual('foo3', foos.item(2).id);
testing.expectEqual(null, foos.item(3));
testing.expectEqual(null, foos.item(-1));
testing.expectEqual(null, foos.item(100));
}
</script>
<script id=namedItem>
{
const container = $('#container');
const foos = container.getElementsByClassName('foo');
testing.expectEqual('foo1', foos.namedItem('foo1').id);
testing.expectEqual('foo2', foos.namedItem('foo2').id);
testing.expectEqual('foo3', foos.namedItem('foo3').id);
testing.expectEqual(null, foos.namedItem('foo4'));
testing.expectEqual(null, foos.namedItem('container'));
testing.expectEqual('foo1', foos['foo1'].id);
testing.expectEqual('foo2', foos['foo2'].id);
}
</script>
<script id=iterator>
{
const container = $('#container');
const foos = container.getElementsByClassName('foo');
let acc = [];
for (let el of foos) {
acc.push(el.id);
}
testing.expectEqual(['foo1', 'foo2', 'foo3'], acc);
}
</script>
<script id=multipleClasses>
{
const multi = $('#multi');
const multiClass = multi.getElementsByClassName('multi');
testing.expectEqual(1, multiClass.length);
testing.expectEqual('multi1', multiClass[0].id);
const classClass = multi.getElementsByClassName('class');
testing.expectEqual(1, classClass.length);
testing.expectEqual('multi2', classClass[0].id);
const namesClass = multi.getElementsByClassName('names');
testing.expectEqual(1, namesClass.length);
testing.expectEqual('multi3', namesClass[0].id);
}
</script>
<script id=arrayIndexing>
{
const container = $('#container');
const foos = container.getElementsByClassName('foo');
testing.expectEqual('foo1', foos[0].id);
testing.expectEqual('foo2', foos[1].id);
testing.expectEqual('foo3', foos[2].id);
testing.expectEqual(undefined, foos[3]);
testing.expectEqual(undefined, foos[-1]);
testing.expectEqual(undefined, foos[100]);
}
</script>
<script id=emptyString>
{
const container = $('#container');
const empty = container.getElementsByClassName('');
testing.expectEqual(0, empty.length);
}
</script>

View File

@@ -0,0 +1,186 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="container">
<div id="div1">div1</div>
<p id="p1">p1</p>
<div id="div2">div2</div>
<span id="span1">span1</span>
<p id="p2">p2</p>
</div>
<div id="nested">
<div id="outer">
<div id="inner1">inner1</div>
<p id="innerP">innerP</p>
<div id="inner2">inner2</div>
</div>
</div>
<div id="empty"></div>
<div id="divMatch" class="test">
<span>span1</span>
<p>p1</p>
<span>span2</span>
</div>
<h1>H1</h1>
<h2>H2</h2>
<h3>H3</h3>
<section id="headings">
<h1>Section H1</h1>
<h2>Section H2</h2>
<h3>Section H3</h3>
<h4>Section H4</h4>
<h5>Section H5</h5>
<h6>Section H6</h6>
</section>
<script id=basic>
{
const container = $('#container');
testing.expectEqual(0, container.getElementsByTagName('a').length);
testing.expectEqual(0, container.getElementsByTagName('unknown').length);
const divs = container.getElementsByTagName('div');
testing.expectEqual(true, divs instanceof HTMLCollection);
testing.expectEqual(2, divs.length);
testing.expectEqual('div1', divs[0].id);
testing.expectEqual('div2', divs[1].id);
testing.expectEqual('div1', divs[0].id); // cache test
const ps = container.getElementsByTagName('p');
testing.expectEqual(2, ps.length);
testing.expectEqual('p1', ps[0].id);
testing.expectEqual('p2', ps[1].id);
const spans = container.getElementsByTagName('span');
testing.expectEqual(1, spans.length);
testing.expectEqual('span1', spans[0].id);
}
</script>
<script id=nestedSearch>
{
const nested = $('#nested');
const nestedDivs = nested.getElementsByTagName('div');
testing.expectEqual(3, nestedDivs.length);
testing.expectEqual('outer', nestedDivs[0].id);
testing.expectEqual('inner1', nestedDivs[1].id);
testing.expectEqual('inner2', nestedDivs[2].id);
const outer = $('#outer');
const outerDivs = outer.getElementsByTagName('div');
testing.expectEqual(2, outerDivs.length);
testing.expectEqual('inner1', outerDivs[0].id);
testing.expectEqual('inner2', outerDivs[1].id);
}
</script>
<script id=emptyResult>
{
const empty = $('#empty');
testing.expectEqual(0, empty.getElementsByTagName('div').length);
testing.expectEqual(0, empty.getElementsByTagName('p').length);
testing.expectEqual(0, empty.getElementsByTagName('span').length);
}
</script>
<script id=excludeSelf>
{
// Element itself should NOT be included in results, even if it matches
const divMatch = $('#divMatch');
const divsInDiv = divMatch.getElementsByTagName('div');
testing.expectEqual(0, divsInDiv.length); // divMatch itself is a div but should not be included
const spansInDiv = divMatch.getElementsByTagName('span');
testing.expectEqual(2, spansInDiv.length); // Only the child spans
}
</script>
<script id=caseInsensitive>
{
const container = $('#container');
testing.expectEqual(2, container.getElementsByTagName('DIV').length);
testing.expectEqual(2, container.getElementsByTagName('Div').length);
testing.expectEqual(2, container.getElementsByTagName('div').length);
testing.expectEqual(2, container.getElementsByTagName('P').length);
testing.expectEqual(1, container.getElementsByTagName('SPAN').length);
}
</script>
<script id=item>
{
const container = $('#container');
const divs = container.getElementsByTagName('div');
testing.expectEqual('div1', divs.item(0).id);
testing.expectEqual('div2', divs.item(1).id);
testing.expectEqual(null, divs.item(2));
testing.expectEqual(null, divs.item(-1));
testing.expectEqual(null, divs.item(100));
}
</script>
<script id=namedItem>
{
const container = $('#container');
const divs = container.getElementsByTagName('div');
testing.expectEqual('div1', divs.namedItem('div1').id);
testing.expectEqual('div2', divs.namedItem('div2').id);
testing.expectEqual(null, divs.namedItem('div3'));
testing.expectEqual(null, divs.namedItem('container'));
testing.expectEqual('div1', divs['div1'].id);
testing.expectEqual('div2', divs['div2'].id);
}
</script>
<script id=iterator>
{
const container = $('#container');
const ps = container.getElementsByTagName('p');
let acc = [];
for (let p of ps) {
acc.push(p.id);
}
testing.expectEqual(['p1', 'p2'], acc);
}
</script>
<script id=headings>
{
const headings = $('#headings');
testing.expectEqual(1, headings.getElementsByTagName('h1').length);
testing.expectEqual('Section H1', headings.getElementsByTagName('h1')[0].textContent);
testing.expectEqual(1, headings.getElementsByTagName('h2').length);
testing.expectEqual('Section H2', headings.getElementsByTagName('h2')[0].textContent);
testing.expectEqual(1, headings.getElementsByTagName('h3').length);
testing.expectEqual(1, headings.getElementsByTagName('h4').length);
testing.expectEqual(1, headings.getElementsByTagName('h5').length);
testing.expectEqual(1, headings.getElementsByTagName('h6').length);
// Case insensitive
testing.expectEqual(1, headings.getElementsByTagName('H1').length);
testing.expectEqual('Section H1', headings.getElementsByTagName('H1')[0].textContent);
}
</script>
<script id=arrayIndexing>
{
const container = $('#container');
const divs = container.getElementsByTagName('div');
testing.expectEqual('div1', divs[0].id);
testing.expectEqual('div2', divs[1].id);
testing.expectEqual(undefined, divs[2]);
testing.expectEqual(undefined, divs[-1]);
testing.expectEqual(undefined, divs[100]);
}
</script>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<a id=a0></a>
<a href="../anchor1.html" id=a1></a>
<a href="/hello/world/anchor2.html" id=a2></a>
<a href="https://www.openmymind.net/Elixirs-With-Statement/" id=a3></a>
<script id=href>
testing.expectEqual('http://127.0.0.1:9582/src/browser/tests/element/html/anchor.html', $('#a0').href)
testing.expectEqual('http://127.0.0.1:9582/src/browser/tests/element/anchor1.html', $('#a1').href)
testing.expectEqual('http://127.0.0.1:9582/hello/world/anchor2.html', $('#a2').href)
testing.expectEqual('https://www.openmymind.net/Elixirs-With-Statement/', $('#a3').href)
</script>

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<!-- Button elements -->
<button id="button1">Click me</button>
<button id="button2" disabled>Disabled button</button>
<!-- Form association tests -->
<form id="form1">
<button id="button_in_form">In form</button>
</form>
<form id="form2"></form>
<button id="button_with_form_attr" form="form2">With form attr</button>
<button id="button_no_form">No form</button>
<form id="form3">
<button id="button_invalid_form_attr" form="nonexistent">Invalid form</button>
</form>
<script id="disabled_initial">
testing.expectEqual(false, $('#button1').disabled)
testing.expectEqual(true, $('#button2').disabled)
</script>
<script id="disabled_set">
$('#button1').disabled = true
testing.expectEqual(true, $('#button1').disabled)
$('#button2').disabled = false
testing.expectEqual(false, $('#button2').disabled)
</script>
<script id="form_ancestor">
const buttonInForm = $('#button_in_form')
testing.expectEqual('FORM', buttonInForm.form.tagName)
testing.expectEqual('form1', buttonInForm.form.id)
</script>
<script id="form_attribute">
const buttonWithFormAttr = $('#button_with_form_attr')
testing.expectEqual('FORM', buttonWithFormAttr.form.tagName)
testing.expectEqual('form2', buttonWithFormAttr.form.id)
</script>
<script id="form_null">
const buttonNoForm = $('#button_no_form')
testing.expectEqual(null, buttonNoForm.form)
</script>
<script id="form_invalid_attribute">
const buttonInvalidFormAttr = $('#button_invalid_form_attr')
testing.expectEqual(null, buttonInvalidFormAttr.form)
</script>

View File

@@ -0,0 +1,246 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<input id="text1" type="text" value="initial">
<input id="text2" type="text">
<input id="check1" type="checkbox" checked>
<input id="check2" type="checkbox">
<input id="radio1" type="radio" name="group1" checked>
<input id="radio2" type="radio" name="group1">
<input id="radio3" type="radio" name="group1">
<input id="radio4" type="radio" name="group2">
<input id="disabled1" type="text" disabled>
<input id="disabled2" type="checkbox">
<script id="type">
testing.expectEqual('text', $('#text1').type)
testing.expectEqual('text', $('#text2').type)
testing.expectEqual('checkbox', $('#check1').type)
testing.expectEqual('radio', $('#radio1').type)
</script>
<script id="type_case_insensitive">
{
const input = document.createElement('input')
input.setAttribute('type', 'TEXT')
testing.expectEqual('text', input.type)
input.setAttribute('type', 'ChEcKbOx')
testing.expectEqual('checkbox', input.type)
input.setAttribute('type', 'EMAIL')
testing.expectEqual('email', input.type)
}
</script>
<script id="type_attribute_name_case_insensitive">
{
const input = document.createElement('input')
input.setAttribute('TYPE', 'checkbox')
testing.expectEqual('checkbox', input.type)
testing.expectEqual('checkbox', input.getAttribute('type'))
input.setAttribute('TyPe', 'radio')
testing.expectEqual('radio', input.type)
testing.expectEqual('radio', input.getAttribute('type'))
}
</script>
<script id="type_invalid_defaults_to_text">
{
const input = document.createElement('input')
input.setAttribute('type', 'invalid')
testing.expectEqual('text', input.type)
input.setAttribute('type', 'notavalidtype')
testing.expectEqual('text', input.type)
input.setAttribute('type', 'x'.repeat(50))
testing.expectEqual('text', input.type)
}
</script>
<script id="type_set">
const input = document.createElement('input')
testing.expectEqual('text', input.type)
input.type = 'password'
testing.expectEqual('password', input.type)
input.type = 'EMAIL'
testing.expectEqual('email', input.type)
input.type = 'invalid'
testing.expectEqual('text', input.type)
input.type = 'ChEcKbOx'
testing.expectEqual('checkbox', input.type)
</script>
<script id="value_initial">
testing.expectEqual('initial', $('#text1').value)
testing.expectEqual('', $('#text2').value)
</script>
<script id="value_set">
$('#text1').value = 'changed'
testing.expectEqual('changed', $('#text1').value)
$('#text2').value = 'new value'
testing.expectEqual('new value', $('#text2').value)
</script>
<script id="checked_initial">
testing.expectEqual(true, $('#check1').checked)
testing.expectEqual(false, $('#check2').checked)
testing.expectEqual(true, $('#radio1').checked)
testing.expectEqual(false, $('#radio2').checked)
</script>
<script id="checked_set">
$('#check1').checked = false
testing.expectEqual(false, $('#check1').checked)
$('#check2').checked = true
testing.expectEqual(true, $('#check2').checked)
$('#radio2').checked = true
testing.expectEqual(true, $('#radio2').checked)
</script>
<script id="defaultValue">
testing.expectEqual('initial', $('#text1').defaultValue)
testing.expectEqual('', $('#text2').defaultValue)
$('#text1').value = 'changed'
testing.expectEqual('initial', $('#text1').defaultValue)
</script>
<script id="defaultChecked">
testing.expectEqual(true, $('#check1').defaultChecked)
testing.expectEqual(false, $('#check2').defaultChecked)
testing.expectEqual(true, $('#radio1').defaultChecked)
testing.expectEqual(false, $('#radio2').defaultChecked)
$('#check1').checked = false
testing.expectEqual(true, $('#check1').defaultChecked)
</script>
<script id="disabled_initial">
testing.expectEqual(true, $('#disabled1').disabled)
testing.expectEqual(false, $('#disabled2').disabled)
</script>
<script id="disabled_set">
$('#disabled1').disabled = false
testing.expectEqual(false, $('#disabled1').disabled)
$('#disabled2').disabled = true
testing.expectEqual(true, $('#disabled2').disabled)
</script>
<form id="form1">
<input id="input_in_form" type="text">
</form>
<form id="form2"></form>
<input id="input_with_form_attr" type="text" form="form2">
<input id="input_no_form" type="text">
<form id="form3">
<input id="input_invalid_form_attr" type="text" form="nonexistent">
</form>
<script id="form_ancestor">
const inputInForm = $('#input_in_form')
testing.expectEqual('FORM', inputInForm.form.tagName)
testing.expectEqual('form1', inputInForm.form.id)
</script>
<script id="form_attribute">
const inputWithFormAttr = $('#input_with_form_attr')
testing.expectEqual('FORM', inputWithFormAttr.form.tagName)
testing.expectEqual('form2', inputWithFormAttr.form.id)
</script>
<script id="form_null">
const inputNoForm = $('#input_no_form')
testing.expectEqual(null, inputNoForm.form)
</script>
<script id="form_invalid_attribute">
const inputInvalidFormAttr = $('#input_invalid_form_attr')
testing.expectEqual(null, inputInvalidFormAttr.form)
</script>
<script id="input_clone">
{
const input = document.createElement('input');
input.type = 'image';
const clone = input.cloneNode();
testing.expectEqual('image', clone.type);
}
</script>
<script id="type_reflects_to_attribute">
{
const input = document.createElement('input')
input.type = 'checkbox'
testing.expectEqual('checkbox', input.getAttribute('type'))
testing.expectTrue(input.outerHTML.includes('type="checkbox"'))
input.type = 'radio'
testing.expectEqual('radio', input.getAttribute('type'))
testing.expectTrue(input.outerHTML.includes('type="radio"'))
}
</script>
<script id="disabled_reflects_to_attribute">
{
const input = document.createElement('input')
testing.expectEqual(null, input.getAttribute('disabled'))
testing.expectFalse(input.outerHTML.includes('disabled'))
input.disabled = true
testing.expectEqual('', input.getAttribute('disabled'))
testing.expectTrue(input.outerHTML.includes('disabled'))
input.disabled = false
testing.expectEqual(null, input.getAttribute('disabled'))
testing.expectFalse(input.outerHTML.includes('disabled'))
}
</script>
<script id="value_does_not_reflect_to_attribute">
{
const input = document.createElement('input')
input.setAttribute('value', 'initial')
testing.expectEqual('initial', input.getAttribute('value'))
input.value = 'changed'
testing.expectEqual('changed', input.value)
testing.expectEqual('initial', input.getAttribute('value'))
testing.expectTrue(input.outerHTML.includes('value="initial"'))
testing.expectFalse(input.outerHTML.includes('value="changed"'))
}
</script>
<script id="checked_does_not_reflect_to_attribute">
{
const input = document.createElement('input')
input.type = 'checkbox'
input.setAttribute('checked', '')
testing.expectEqual('', input.getAttribute('checked'))
testing.expectTrue(input.outerHTML.includes('checked'))
input.checked = false
testing.expectEqual(false, input.checked)
testing.expectEqual('', input.getAttribute('checked'))
testing.expectTrue(input.outerHTML.includes('checked'))
}
</script>

View File

@@ -0,0 +1,140 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<input id="radio1" type="radio" name="group1" checked>
<input id="radio2" type="radio" name="group1">
<input id="radio3" type="radio" name="group1">
<input id="radio4" type="radio" name="group2">
<script id="radio_grouping_basic">
testing.expectEqual(true, $('#radio1').checked)
testing.expectEqual(false, $('#radio2').checked)
testing.expectEqual(false, $('#radio3').checked)
$('#radio2').checked = true
testing.expectEqual(false, $('#radio1').checked)
testing.expectEqual(true, $('#radio2').checked)
testing.expectEqual(false, $('#radio3').checked)
$('#radio3').checked = true
testing.expectEqual(false, $('#radio1').checked)
testing.expectEqual(false, $('#radio2').checked)
testing.expectEqual(true, $('#radio3').checked)
</script>
<script id="radio_grouping_different_groups">
$('#radio1').checked = true
$('#radio4').checked = true
testing.expectEqual(true, $('#radio1').checked)
testing.expectEqual(true, $('#radio4').checked)
$('#radio2').checked = true
testing.expectEqual(false, $('#radio1').checked)
testing.expectEqual(true, $('#radio2').checked)
testing.expectEqual(true, $('#radio4').checked)
</script>
<script id="radio_grouping_unchecking">
$('#radio1').checked = true
testing.expectEqual(true, $('#radio1').checked)
$('#radio1').checked = false
testing.expectEqual(false, $('#radio1').checked)
testing.expectEqual(false, $('#radio2').checked)
testing.expectEqual(false, $('#radio3').checked)
</script>
<form id="radio_form1">
<input id="radio_form1_a" type="radio" name="formgroup">
<input id="radio_form1_b" type="radio" name="formgroup">
</form>
<form id="radio_form2">
<input id="radio_form2_a" type="radio" name="formgroup">
<input id="radio_form2_b" type="radio" name="formgroup">
</form>
<input id="radio_no_form_a" type="radio" name="formgroup">
<input id="radio_no_form_b" type="radio" name="formgroup">
<script id="radio_grouping_same_name_different_forms">
$('#radio_form1_a').checked = true
$('#radio_form2_a').checked = true
$('#radio_no_form_a').checked = true
testing.expectEqual(true, $('#radio_form1_a').checked)
testing.expectEqual(true, $('#radio_form2_a').checked)
testing.expectEqual(true, $('#radio_no_form_a').checked)
$('#radio_form1_b').checked = true
testing.expectEqual(false, $('#radio_form1_a').checked)
testing.expectEqual(true, $('#radio_form1_b').checked)
testing.expectEqual(true, $('#radio_form2_a').checked)
testing.expectEqual(true, $('#radio_no_form_a').checked)
$('#radio_no_form_b').checked = true
testing.expectEqual(false, $('#radio_form1_a').checked)
testing.expectEqual(true, $('#radio_form1_b').checked)
testing.expectEqual(true, $('#radio_form2_a').checked)
testing.expectEqual(false, $('#radio_no_form_a').checked)
testing.expectEqual(true, $('#radio_no_form_b').checked)
</script>
<form id="radio_form3"></form>
<input id="radio_form_attr_a" type="radio" name="attrgroup" form="radio_form3">
<input id="radio_form_attr_b" type="radio" name="attrgroup" form="radio_form3">
<input id="radio_form_attr_c" type="radio" name="attrgroup">
<script id="radio_grouping_form_attribute">
$('#radio_form_attr_a').checked = true
$('#radio_form_attr_c').checked = true
testing.expectEqual(true, $('#radio_form_attr_a').checked)
testing.expectEqual(false, $('#radio_form_attr_b').checked)
testing.expectEqual(true, $('#radio_form_attr_c').checked)
$('#radio_form_attr_b').checked = true
testing.expectEqual(false, $('#radio_form_attr_a').checked)
testing.expectEqual(true, $('#radio_form_attr_b').checked)
testing.expectEqual(true, $('#radio_form_attr_c').checked)
</script>
<input id="radio_no_name_a" type="radio">
<input id="radio_no_name_b" type="radio">
<script id="radio_no_name_no_grouping">
$('#radio_no_name_a').checked = true
$('#radio_no_name_b').checked = true
testing.expectEqual(true, $('#radio_no_name_a').checked)
testing.expectEqual(true, $('#radio_no_name_b').checked)
</script>
<input id="radio_empty_name_a" type="radio" name="">
<input id="radio_empty_name_b" type="radio" name="">
<script id="radio_empty_name_no_grouping">
$('#radio_empty_name_a').checked = true
$('#radio_empty_name_b').checked = true
testing.expectEqual(true, $('#radio_empty_name_a').checked)
testing.expectEqual(true, $('#radio_empty_name_b').checked)
</script>
<input id="radio_case_a" type="radio" name="CaseSensitive">
<input id="radio_case_b" type="radio" name="casesensitive">
<input id="radio_case_c" type="radio" name="CaseSensitive">
<script id="radio_name_case_sensitive">
$('#radio_case_a').checked = true
$('#radio_case_b').checked = true
testing.expectEqual(true, $('#radio_case_a').checked)
testing.expectEqual(true, $('#radio_case_b').checked)
$('#radio_case_c').checked = true
testing.expectEqual(false, $('#radio_case_a').checked)
testing.expectEqual(true, $('#radio_case_b').checked)
testing.expectEqual(true, $('#radio_case_c').checked)
</script>

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<select id="select1">
<option id="opt1" value="val1">Text 1</option>
<option id="opt2" value="val2" selected>Text 2</option>
<option id="opt3">Text 3</option>
<option id="opt4" disabled>Text 4</option>
</select>
<script id="value_with_attribute">
testing.expectEqual('val1', $('#opt1').value)
testing.expectEqual('val2', $('#opt2').value)
</script>
<script id="value_without_attribute">
// When value attribute is not present, value should be text content
testing.expectEqual('Text 3', $('#opt3').value)
</script>
<script id="value_set">
$('#opt1').value = 'changed'
testing.expectEqual('changed', $('#opt1').value)
</script>
<script id="text">
testing.expectEqual('Text 1', $('#opt1').text)
testing.expectEqual('Text 2', $('#opt2').text)
testing.expectEqual('Text 3', $('#opt3').text)
</script>
<script id="selected">
testing.expectEqual(false, $('#opt1').selected)
testing.expectEqual(true, $('#opt2').selected)
testing.expectEqual(false, $('#opt3').selected)
</script>
<script id="selected_set">
$('#opt1').selected = true
testing.expectEqual(true, $('#opt1').selected)
$('#opt2').selected = false
testing.expectEqual(false, $('#opt2').selected)
</script>
<script id="defaultSelected">
testing.expectEqual(false, $('#opt1').defaultSelected)
testing.expectEqual(true, $('#opt2').defaultSelected)
testing.expectEqual(false, $('#opt3').defaultSelected)
// Setting selected shouldn't change defaultSelected
$('#opt1').selected = true
testing.expectEqual(false, $('#opt1').defaultSelected)
</script>
<script id="disabled">
testing.expectEqual(false, $('#opt1').disabled)
testing.expectEqual(true, $('#opt4').disabled)
</script>
<script id="disabled_set">
$('#opt1').disabled = true
testing.expectEqual(true, $('#opt1').disabled)
$('#opt4').disabled = false
testing.expectEqual(false, $('#opt4').disabled)
</script>

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<head></head>
<script src="../../../testing.js"></script>
<script id=append_before_src>
loaded1 = 0;
const script1 = document.createElement('script');
script1.src = "dynamic1.js";
document.getElementsByTagName('head')[0].appendChild(script1);
testing.eventually(() => {
testing.expectEqual(1, loaded1);
});
</script>
<script id=no_double_execute>
document.getElementsByTagName('head')[0].appendChild(script1);
testing.eventually(() => {
testing.expectEqual(1, loaded1);
});
</script>
<script id=append_before_src>
loaded2 = 0;
const script2a = document.createElement('script');
script2a.src = "dynamic2.js";
document.getElementsByTagName('head')[0].appendChild(script2a);
testing.eventually(() => {
testing.expectEqual(2, loaded2);
});
</script>
<script>
const script2b = document.createElement('script');
document.getElementsByTagName('head')[0].appendChild(script2b);
script2b.src = "dynamic2.js";
script2b.src = "dynamic2.js";
</script>
<script id=src_after_append>
testing.eventually(() => {
testing.expectEqual(2, loaded2);
});
</script>

View File

@@ -0,0 +1 @@
loaded1 += 1;

View File

@@ -0,0 +1 @@
loaded2 += 1;

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<select id="select1">
<option value="val1">Option 1</option>
<option value="val2" selected>Option 2</option>
<option value="val3">Option 3</option>
</select>
<select id="select2">
<option value="a">A</option>
<option value="b">B</option>
</select>
<select id="select3" disabled>
<option value="x">X</option>
</select>
<form id="form1">
<select id="select_in_form">
<option value="f1">Form option</option>
</select>
</form>
<form id="form2"></form>
<select id="select_with_form_attr" form="form2">
<option value="f2">Form attr option</option>
</select>
<select id="select_no_form">
<option value="nf">No form option</option>
</select>
<script id="value_initial">
// Should return value of selected option
testing.expectEqual('val2', $('#select1').value)
// If no option is explicitly selected, first option is implicitly selected
testing.expectEqual('a', $('#select2').value)
</script>
<script id="value_set">
$('#select1').value = 'val3'
testing.expectEqual('val3', $('#select1').value)
// The option should now be selected
const opt3 = $('#select1').querySelector('option[value="val3"]')
testing.expectEqual(true, opt3.selected)
// Other options should be unselected
const opt2 = $('#select1').querySelector('option[value="val2"]')
testing.expectEqual(false, opt2.selected)
</script>
<script id="disabled_initial">
testing.expectEqual(false, $('#select1').disabled)
testing.expectEqual(true, $('#select3').disabled)
</script>
<script id="disabled_set">
$('#select1').disabled = true
testing.expectEqual(true, $('#select1').disabled)
$('#select3').disabled = false
testing.expectEqual(false, $('#select3').disabled)
</script>
<script id="form_ancestor">
const selectInForm = $('#select_in_form')
testing.expectEqual('FORM', selectInForm.form.tagName)
testing.expectEqual('form1', selectInForm.form.id)
</script>
<script id="form_attribute">
const selectWithFormAttr = $('#select_with_form_attr')
testing.expectEqual('FORM', selectWithFormAttr.form.tagName)
testing.expectEqual('form2', selectWithFormAttr.form.id)
</script>
<script id="form_null">
const selectNoForm = $('#select_no_form')
testing.expectEqual(null, selectNoForm.form)
</script>

View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<!-- TextArea elements -->
<textarea id="textarea1">initial text</textarea>
<textarea id="textarea2"></textarea>
<textarea id="textarea3" disabled>disabled text</textarea>
<!-- Form association tests -->
<form id="form1">
<textarea id="textarea_in_form">in form</textarea>
</form>
<form id="form2"></form>
<textarea id="textarea_with_form_attr" form="form2">with form attr</textarea>
<textarea id="textarea_no_form">no form</textarea>
<form id="form3">
<textarea id="textarea_invalid_form_attr" form="nonexistent">invalid form</textarea>
</form>
<script id="value_initial">
testing.expectEqual('initial text', $('#textarea1').value)
testing.expectEqual('', $('#textarea2').value)
</script>
<script id="value_set">
$('#textarea1').value = 'changed'
testing.expectEqual('changed', $('#textarea1').value)
$('#textarea2').value = 'new value'
testing.expectEqual('new value', $('#textarea2').value)
</script>
<script id="defaultValue">
testing.expectEqual('initial text', $('#textarea1').defaultValue)
testing.expectEqual('', $('#textarea2').defaultValue)
// Setting value shouldn't change defaultValue
$('#textarea1').value = 'changed'
testing.expectEqual('initial text', $('#textarea1').defaultValue)
</script>
<script id="disabled_initial">
testing.expectEqual(false, $('#textarea1').disabled)
testing.expectEqual(true, $('#textarea3').disabled)
</script>
<script id="disabled_set">
$('#textarea1').disabled = true
testing.expectEqual(true, $('#textarea1').disabled)
$('#textarea3').disabled = false
testing.expectEqual(false, $('#textarea3').disabled)
</script>
<script id="form_ancestor">
const textareaInForm = $('#textarea_in_form')
testing.expectEqual('FORM', textareaInForm.form.tagName)
testing.expectEqual('form1', textareaInForm.form.id)
</script>
<script id="form_attribute">
const textareaWithFormAttr = $('#textarea_with_form_attr')
testing.expectEqual('FORM', textareaWithFormAttr.form.tagName)
testing.expectEqual('form2', textareaWithFormAttr.form.id)
</script>
<script id="form_null">
const textareaNoForm = $('#textarea_no_form')
testing.expectEqual(null, textareaNoForm.form)
</script>
<script id="form_invalid_attribute">
const textareaInvalidFormAttr = $('#textarea_invalid_form_attr')
testing.expectEqual(null, textareaInvalidFormAttr.form)
</script>

View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=d1>hello <em>world</em></div>
<script id=innerHTML>
const d1 = $('#d1');
testing.expectEqual('hello <em>world</em>', d1.innerHTML);
d1.innerHTML = 'only text';
testing.expectEqual('only text', d1.innerHTML);
d1.innerHTML = 'hello <div>world</div><b>!!</b>';
testing.expectEqual('hello <div>world</div><b>!!</b>', d1.innerHTML);
for (let child of d1.childNodes) {
testing.expectEqual(d1, child.parentNode);
}
// doesn't run JavaScript, important!
let inner_loaded = false;
d1.innerHTML = '<script src=inner.js>';
testing.expectEqual('<script src="inner.js"><\/script>', d1.innerHTML);
testing.expectEqual(false, inner_loaded);
</script>
<script id=ids>
d1.innerHTML = '<a id=link>home</a>';
testing.expectEqual('home', $('#link').innerText);
d1.innerHTML = '';
testing.expectEqual(null, $('#link'));
d1.innerHTML = '<div><p><a id=link>hi</a></p></div>';
testing.expectEqual('hi', $('#link').innerText);
d1.innerHTML = '';
testing.todo(null, $('#link'));
</script>
<script id=attributeSerialization>
d1.innerHTML = '<div class="test">text</div>';
testing.expectEqual('<div class="test">text</div>', d1.innerHTML);
d1.innerHTML = '<div id=simple>text</div>';
testing.expectEqual('<div id="simple">text</div>', d1.innerHTML);
d1.innerHTML = '<a href="/?foo=1&bar=2">link</a>';
testing.expectEqual('<a href="/?foo=1&amp;bar=2">link</a>', d1.innerHTML);
d1.innerHTML = '<div title="She said &quot;hello&quot;">text</div>';
testing.expectEqual('<div title="She said &quot;hello&quot;">text</div>', d1.innerHTML);
d1.innerHTML = '<div data-value="<tag>">text</div>';
testing.expectEqual('<div data-value="&lt;tag&gt;">text</div>', d1.innerHTML);
const div1 = document.createElement('div');
div1.setAttribute('title', 'line1\nline2');
d1.innerHTML = '';
d1.appendChild(div1);
testing.expectEqual('<div title="line1\nline2"></div>', d1.innerHTML);
const div2 = document.createElement('div');
div2.setAttribute('title', 'line1\rline2');
d1.innerHTML = '';
d1.appendChild(div2);
testing.expectEqual('<div title="line1\rline2"></div>', d1.innerHTML);
const div3 = document.createElement('div');
div3.setAttribute('title', 'col1\tcol2');
d1.innerHTML = '';
d1.appendChild(div3);
testing.expectEqual('<div title="col1\tcol2"></div>', d1.innerHTML);
d1.innerHTML = '<div data-test="&quot;&amp;&lt;&gt;">text</div>';
testing.expectEqual('<div data-test="&quot;&amp;&lt;&gt;">text</div>', d1.innerHTML);
d1.innerHTML = '<input type="text" value="">';
testing.expectEqual('<input type="text" value="">', d1.innerHTML);
d1.innerHTML = '<div class="foo bar">text</div>';
testing.expectEqual('<div class="foo bar">text</div>', d1.innerHTML);
d1.innerHTML = '<div data-expr="x=5">text</div>';
testing.expectEqual('<div data-expr="x=5">text</div>', d1.innerHTML);
const div4 = document.createElement('div');
div4.setAttribute('title', "it's working");
d1.innerHTML = '';
d1.appendChild(div4);
testing.expectEqual('<div title="it\'s working"></div>', d1.innerHTML);
const div5 = document.createElement('div');
div5.setAttribute('data-code', '`template`');
d1.innerHTML = '';
d1.appendChild(div5);
testing.expectEqual('<div data-code="`template`"></div>', d1.innerHTML);
d1.innerHTML = '<a href="/search?q=test&lang=en" title="Search: &quot;test&quot;">Search</a>';
testing.expectEqual('<a href="/search?q=test&amp;lang=en" title="Search: &quot;test&quot;">Search</a>', d1.innerHTML);
d1.innerHTML = '<div data-start="&start">text</div>';
testing.expectEqual('<div data-start="&amp;start">text</div>', d1.innerHTML);
d1.innerHTML = '<div data-end="end&">text</div>';
testing.expectEqual('<div data-end="end&amp;">text</div>', d1.innerHTML);
d1.innerHTML = '<div data-middle="mid&dle">text</div>';
testing.expectEqual('<div data-middle="mid&amp;dle">text</div>', d1.innerHTML);
</script>
<script id=voidElements>
d1.innerHTML = '<br>';
testing.expectEqual('<br>', d1.innerHTML);
d1.innerHTML = '<hr>';
testing.expectEqual('<hr>', d1.innerHTML);
d1.innerHTML = '<img src="test.png" alt="test">';
testing.expectEqual('<img src="test.png" alt="test">', d1.innerHTML);
d1.innerHTML = '<input type="text" name="field">';
testing.expectEqual('<input type="text" name="field">', d1.innerHTML);
d1.innerHTML = '<link rel="stylesheet" href="style.css">';
testing.expectEqual('<link rel="stylesheet" href="style.css">', d1.innerHTML);
d1.innerHTML = '<meta charset="utf-8">';
testing.expectEqual('<meta charset="utf-8">', d1.innerHTML);
d1.innerHTML = '<div><br><hr><img src="x.png"></div>';
testing.expectEqual('<div><br><hr><img src="x.png"></div>', d1.innerHTML);
</script>

View File

@@ -0,0 +1 @@
inner_loaded = true;

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=p1>
<div id=c2>
<div id=c3></div>
</div>
</div>
<div id=other></div>
<!-- <script id=querySelector">
const p1 = $('#p1');
testing.expectError("Syntax Error", () => p1.querySelector(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);
testing.expectEqual("Syntax Error", err.message);
}, () => p1.querySelector(''));
testing.expectEqual($('#c2'), p1.querySelector('#c2'));
testing.expectEqual($('#c3'), p1.querySelector('#c3'));
testing.expectEqual(null, p1.querySelector('#nope'));
testing.expectEqual(null, p1.querySelector('#other'));
testing.expectEqual($('#c2'), p1.querySelector('*'));
testing.expectEqual($('#c3'), p1.querySelector('*#c3'));
</script> -->
<div id="desc-container">
<p class="text">Direct child paragraph</p>
<div class="nested">
<p class="text">Nested paragraph</p>
<span class="wrapper">
<p class="deep">Deeply nested paragraph</p>
</span>
</div>
<article>
<p class="text">Article paragraph</p>
</article>
</div>
<div id="outer-div">
<div class="level1">
<div class="level2">
<span id="deep-span">Deep span</span>
</div>
</div>
</div>
<script id=descendantSelectors>
{
const container = $('#desc-container');
// testing.expectEqual('Nested paragraph', container.querySelector('div p').textContent);
// testing.expectEqual('Nested paragraph', container.querySelector('div.nested p').textContent);
testing.expectEqual('Deeply nested paragraph', container.querySelector('div span p').textContent);
// testing.expectEqual('Nested paragraph', container.querySelector('.nested .text').textContent);
// testing.expectEqual(null, container.querySelector('article div p'));
// const outerDiv = $('#outer-div');
// testing.expectEqual('deep-span', outerDiv.querySelector('div div span').id);
// testing.expectEqual('deep-span', outerDiv.querySelector('.level1 span').id);
// testing.expectEqual('deep-span', outerDiv.querySelector('.level1 .level2 span').id);
}
</script>

View File

@@ -0,0 +1,188 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=root>
<div class="item">Item 1</div>
<span class="item">Item 2</span>
<div class="item special">Item 3</div>
<div id=nested>
<div class="item">Item 4</div>
<span class="special">Item 5</span>
</div>
</div>
<div id=outside class="item">Outside</div>
<script>
function assertList(expected, result) {
testing.expectEqual(expected.length, result.length);
testing.expectEqual(expected, Array.from(result).map((e) => e.textContent));
testing.expectEqual(expected, Array.from(result.values()).map((e) => e.textContent));
testing.expectEqual(expected.map((e, i) => i), Array.from(result.keys()));
}
</script>
<script id=errors>
{
const root = $('#root');
testing.expectError("Syntax Error", () => root.querySelectorAll(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);
testing.expectEqual("Syntax Error", err.message);
}, () => root.querySelectorAll(''));
}
</script>
<script id=byId>
{
const root = $('#root');
const nested = root.querySelectorAll('#nested');
testing.expectEqual(true, nested instanceof NodeList);
testing.expectEqual(1, nested.length);
testing.expectEqual('nested', nested[0].getAttribute('id'));
assertList([], root.querySelectorAll('#outside'));
assertList([], root.querySelectorAll('#nope'));
}
</script>
<script id=byClass>
{
const root = $('#root');
assertList(['Item 1', 'Item 2', 'Item 3', 'Item 4'], root.querySelectorAll('.item'));
assertList(['Item 3', 'Item 5'], root.querySelectorAll('.special'));
assertList([], root.querySelectorAll('.nope'));
}
</script>
<script id=byTag>
{
const root = $('#root');
const divs = root.querySelectorAll('div');
testing.expectEqual(4, divs.length);
assertList(['Item 2', 'Item 5'], root.querySelectorAll('span'));
assertList([], root.querySelectorAll('article'));
}
</script>
<script id=compound>
{
const root = $('#root');
const divItems = root.querySelectorAll('div.item');
testing.expectEqual(3, divItems.length);
assertList(['Item 2'], root.querySelectorAll('span.item'));
assertList(['Item 3'], root.querySelectorAll('div.item.special'));
assertList(['Item 3'], root.querySelectorAll('.item.special'));
assertList(['Item 3'], root.querySelectorAll('.special.item'));
}
</script>
<script id=universal>
{
const root = $('#root');
const all = root.querySelectorAll('*');
testing.expectEqual(true, all.length >= 6);
testing.expectEqual('Item 1', all[0].textContent);
const items = root.querySelectorAll('*.item');
testing.expectEqual(4, items.length);
assertList(['Item 3', 'Item 5'], root.querySelectorAll('*.special'));
}
</script>
<script id=nested>
{
const nested = $('#nested');
assertList(['Item 4'], nested.querySelectorAll('.item'));
assertList(['Item 5'], nested.querySelectorAll('.special'));
assertList(['Item 4'], nested.querySelectorAll('div'));
assertList(['Item 5'], nested.querySelectorAll('span'));
}
</script>
<script id=iterators>
{
const root = $('#root');
const list = root.querySelectorAll('.special');
const entries = Array.from(list.entries());
testing.expectEqual(2, entries.length);
testing.expectEqual(0, entries[0][0]);
testing.expectEqual('Item 3', entries[0][1].textContent);
testing.expectEqual(1, entries[1][0]);
testing.expectEqual('Item 5', entries[1][1].textContent);
const keys = Array.from(list.keys());
testing.expectEqual(2, keys.length);
testing.expectEqual(0, keys[0]);
testing.expectEqual(1, keys[1]);
const values = Array.from(list.values());
testing.expectEqual(['Item 3', 'Item 5'], values.map((e) => e.textContent));
const defaultIter = Array.from(list);
testing.expectEqual(['Item 3', 'Item 5'], defaultIter.map((e) => e.textContent));
}
</script>
<div id="desc-root">
<p class="text">Direct child paragraph</p>
<div class="nested">
<p class="text">Nested paragraph</p>
<span class="wrapper">
<p class="deep">Deeply nested paragraph</p>
</span>
</div>
<article>
<p class="text">Article paragraph</p>
<div class="inner">
<span>Article span</span>
</div>
</article>
</div>
<div id="multi-level">
<div class="level1">
<div class="level2">
<span class="target">Target 1</span>
</div>
</div>
<div class="level1">
<span class="target">Target 2</span>
</div>
</div>
<script id=descendantSelectors>
{
const root = $('#desc-root');
assertList(['Direct child paragraph', 'Nested paragraph', 'Deeply nested paragraph', 'Article paragraph'], root.querySelectorAll('div p'));
assertList(['Direct child paragraph', 'Nested paragraph', 'Article paragraph'], root.querySelectorAll('div .text'));
assertList(['Nested paragraph', 'Deeply nested paragraph'], root.querySelectorAll('.nested p'));
assertList(['Deeply nested paragraph'], root.querySelectorAll('div span p'));
assertList(['Nested paragraph'], root.querySelectorAll('div.nested p.text'));
assertList([], root.querySelectorAll('article div p'));
assertList(['Article span'], root.querySelectorAll('article span'));
assertList(['Article span'], root.querySelectorAll('article div span'));
}
</script>
<script id=multiLevelDescendant>
{
const root = $('#multi-level');
assertList(['Target 1', 'Target 2'], root.querySelectorAll('div span'));
assertList(['Target 1', 'Target 2'], root.querySelectorAll('div div span'));
assertList(['Target 1'], root.querySelectorAll('.level1 .level2 .target'));
assertList(['Target 1', 'Target 2'], root.querySelectorAll('.level1 .target'));
}
</script>
<script id=descendantWithWhitespace>
{
const root = $('#desc-root');
assertList(['Direct child paragraph', 'Nested paragraph', 'Deeply nested paragraph', 'Article paragraph'], root.querySelectorAll(' div p '));
assertList(['Nested paragraph'], root.querySelectorAll(' div.nested p.text '));
}
</script>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=d1>
<div id=d2>
<div id=d3>
<div id=d4>
<div id=d5>
</div>
</div>
</div>
</div>
</div>
<script id=remove>
testing.expectEqual(1, $('#d4').childElementCount);
$('#d5').remove();
testing.expectEqual(null, document.getElementById('#d5'));
testing.expectEqual(0, $('#d4').childElementCount);
testing.expectEqual(1, $('#d1').childElementCount);
$('#d2').remove();
testing.expectEqual(null, document.getElementById('#d2'));
testing.expectEqual(null, document.getElementById('#d3'));
testing.expectEqual(null, document.getElementById('#d4'));
testing.expectEqual(0, $('#d1').childElementCount);
</script>

View File

@@ -0,0 +1,129 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id="test-div"></div>
<script id="styles">
{
const div = $('#test-div');
div.style.cssText = '';
testing.expectEqual(0, div.style.length);
testing.expectEqual('', div.style.cssText);
div.style.setProperty('color', 'red');
testing.expectEqual('red', div.style.getPropertyValue('color'));
testing.expectEqual('', div.style.getPropertyPriority('color'));
testing.expectEqual(1, div.style.length);
div.style.setProperty('background-color', 'blue', 'important');
testing.expectEqual('blue', div.style.getPropertyValue('background-color'));
testing.expectEqual('important', div.style.getPropertyPriority('background-color'));
testing.expectEqual(2, div.style.length);
testing.expectEqual('blue', div.style.getPropertyValue('BACKGROUND-COLOR'));
testing.expectEqual('blue', div.style.getPropertyValue('Background-Color'));
div.style.setProperty('font-size', '16px');
testing.expectEqual('16px', div.style.getPropertyValue('font-size'));
testing.expectEqual(3, div.style.length);
const cssText = div.style.cssText;
testing.expectEqual(true, cssText.includes('color: red;'));
testing.expectEqual(true, cssText.includes('background-color: blue !important;'));
testing.expectEqual(true, cssText.includes('font-size: 16px;'));
const removedValue = div.style.removeProperty('color');
testing.expectEqual('red', removedValue);
testing.expectEqual('', div.style.getPropertyValue('color'));
testing.expectEqual(2, div.style.length);
const notFound = div.style.removeProperty('non-existent');
testing.expectEqual('', notFound);
testing.expectEqual(2, div.style.length);
const prop0 = div.style.item(0);
const prop1 = div.style.item(1);
testing.expectEqual(true, prop0 === 'background-color' || prop0 === 'font-size');
testing.expectEqual(true, prop1 === 'background-color' || prop1 === 'font-size');
testing.expectEqual(true, prop0 !== prop1);
testing.expectEqual('', div.style.item(2));
testing.expectEqual('', div.style.item(-1));
}
</script>
<script id="cssText">
{
const div = $('#test-div');
div.style.cssText = '';
testing.expectEqual(0, div.style.length);
testing.expectEqual('', div.style.cssText);
div.style.cssText = 'width: 100px; height: 50px !important; margin-top: 10px';
testing.expectEqual(3, div.style.length);
testing.expectEqual('100px', div.style.getPropertyValue('width'));
testing.expectEqual('50px', div.style.getPropertyValue('height'));
testing.expectEqual('10px', div.style.getPropertyValue('margin-top'));
testing.expectEqual('', div.style.getPropertyPriority('width'));
testing.expectEqual('important', div.style.getPropertyPriority('height'));
testing.expectEqual('', div.style.getPropertyPriority('margin-top'));
div.style.cssText = ' padding-left: 5px ; border-top-width: 1px ';
testing.expectEqual(2, div.style.length);
testing.expectEqual('5px', div.style.getPropertyValue('padding-left'));
testing.expectEqual('1px', div.style.getPropertyValue('border-top-width'));
div.style.cssText = '';
testing.expectEqual(0, div.style.length);
testing.expectEqual('', div.style.cssText);
div.style.cssText = 'color: red;';
testing.expectEqual(1, div.style.length);
testing.expectEqual('red', div.style.getPropertyValue('color'));
}
</script>
<script id="propertyUpdates">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('color', 'red');
testing.expectEqual('red', div.style.getPropertyValue('color'));
testing.expectEqual(1, div.style.length);
div.style.setProperty('color', 'blue');
testing.expectEqual('blue', div.style.getPropertyValue('color'));
testing.expectEqual(1, div.style.length);
div.style.setProperty('color', 'green', 'important');
testing.expectEqual('green', div.style.getPropertyValue('color'));
testing.expectEqual('important', div.style.getPropertyPriority('color'));
testing.expectEqual(1, div.style.length);
div.style.setProperty('color', 'yellow');
testing.expectEqual('yellow', div.style.getPropertyValue('color'));
testing.expectEqual('', div.style.getPropertyPriority('color'));
testing.expectEqual(1, div.style.length);
div.style.setProperty('color', '');
testing.expectEqual('', div.style.getPropertyValue('color'));
testing.expectEqual(0, div.style.length);
}
</script>
<script id="invalidPriority">
{
const div = $('#test-div');
div.style.cssText = '';
div.style.setProperty('color', 'red', 'invalid');
testing.expectEqual('', div.style.getPropertyValue('color'));
testing.expectEqual(0, div.style.length);
div.style.setProperty('color', 'red', 'important');
testing.expectEqual('red', div.style.getPropertyValue('color'));
testing.expectEqual('important', div.style.getPropertyPriority('color'));
}
</script>

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<svg id=svg1></svg>
<sVg id=svg2></sVg>
<SVG id=svg3></SVG>
<script id=svg>
let svg1 = $('#svg1');
testing.expectEqual('svg', svg1.tagName);
testing.expectEqual('http://www.w3.org/2000/svg', svg1.namespaceURI);
let svg2 = $('#svg2');
testing.expectEqual('svg', svg2.tagName);
testing.expectEqual('http://www.w3.org/2000/svg', svg2.namespaceURI);
let svg3 = $('#svg3');
testing.expectEqual('svg', svg3.tagName);
testing.expectEqual('http://www.w3.org/2000/svg', svg3.namespaceURI);
const svg4 = document.createElementNS('http://www.w3.org/2000/svg', 'SvG');
testing.expectEqual('SvG', svg4.tagName);
testing.expectEqual('http://www.w3.org/2000/svg', svg4.namespaceURI);
const svg5 = document.createElement('SvG');
testing.expectEqual('SVG', svg5.tagName);
testing.expectEqual('http://www.w3.org/1999/xhtml', svg5.namespaceURI);
</script>

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