mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
Merge pull request #506 from lightpanda-io/jsruntime
replace zig-js-runtime
This commit is contained in:
8
.github/actions/install/action.yml
vendored
8
.github/actions/install/action.yml
vendored
@@ -59,11 +59,11 @@ runs:
|
|||||||
- name: install v8
|
- name: install v8
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug
|
mkdir -p v8/build/${{inputs.arch}}-${{inputs.os}}/debug/ninja/obj/zig/
|
||||||
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug/libc_v8.a
|
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a v8/build/${{inputs.arch}}-${{inputs.os}}/debug/ninja/obj/zig/libc_v8.a
|
||||||
|
|
||||||
mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release
|
mkdir -p v8/build/${{inputs.arch}}-${{inputs.os}}/release/ninja/obj/zig/
|
||||||
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release/libc_v8.a
|
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a v8/build/${{inputs.arch}}-${{inputs.os}}/release/ninja/obj/zig/libc_v8.a
|
||||||
|
|
||||||
- name: libiconv
|
- name: libiconv
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8 -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build --release=safe -Doptimize=ReleaseSafe -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build --release=safe -Doptimize=ReleaseSafe -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build --release=safe -Doptimize=ReleaseSafe -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build --release=safe -Doptimize=ReleaseSafe -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
|
|||||||
4
.github/workflows/e2e-test.yml
vendored
4
.github/workflows/e2e-test.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
- uses: ./.github/actions/install
|
- uses: ./.github/actions/install
|
||||||
|
|
||||||
- name: zig build release
|
- name: zig build release
|
||||||
run: zig build -Doptimize=ReleaseSafe -Dengine=v8
|
run: zig build -Doptimize=ReleaseSafe
|
||||||
|
|
||||||
- name: upload artifact
|
- name: upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
- name: run puppeteer
|
- name: run puppeteer
|
||||||
run: |
|
run: |
|
||||||
python3 -m http.server 1234 -d ./public & echo $! > PYTHON.pid
|
python3 -m http.server 1234 -d ./public & echo $! > PYTHON.pid
|
||||||
./lightpanda serve & echo $! > LPD.pid
|
./lightpanda serve --gc_hints & echo $! > LPD.pid
|
||||||
RUNS=100 npm run bench-puppeteer-cdp > puppeteer.out || exit 1
|
RUNS=100 npm run bench-puppeteer-cdp > puppeteer.out || exit 1
|
||||||
cat /proc/`cat LPD.pid`/status |grep VmHWM|grep -oP '\d+' > LPD.VmHWM
|
cat /proc/`cat LPD.pid`/status |grep VmHWM|grep -oP '\d+' > LPD.VmHWM
|
||||||
kill `cat LPD.pid` `cat PYTHON.pid`
|
kill `cat LPD.pid` `cat PYTHON.pid`
|
||||||
|
|||||||
4
.github/workflows/wpt.yml
vendored
4
.github/workflows/wpt.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/install
|
- uses: ./.github/actions/install
|
||||||
|
|
||||||
- run: zig build wpt -Dengine=v8 -- --safe --summary
|
- run: zig build wpt -- --safe --summary
|
||||||
|
|
||||||
# For now WPT tests doesn't pass at all.
|
# For now WPT tests doesn't pass at all.
|
||||||
# We accept then to continue the job on failure.
|
# We accept then to continue the job on failure.
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
- uses: ./.github/actions/install
|
- uses: ./.github/actions/install
|
||||||
|
|
||||||
- name: json output
|
- name: json output
|
||||||
run: zig build wpt -Dengine=v8 -- --safe --json > wpt.json
|
run: zig build wpt -- --safe --json > wpt.json
|
||||||
|
|
||||||
- name: write commit
|
- name: write commit
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
7
.github/workflows/zig-test.yml
vendored
7
.github/workflows/zig-test.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
|||||||
- uses: ./.github/actions/install
|
- uses: ./.github/actions/install
|
||||||
|
|
||||||
- name: zig build debug
|
- name: zig build debug
|
||||||
run: zig build -Dengine=v8
|
run: zig build
|
||||||
|
|
||||||
- name: upload artifact
|
- name: upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -102,11 +102,8 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/install
|
- uses: ./.github/actions/install
|
||||||
|
|
||||||
- name: zig build unittest
|
|
||||||
run: zig build unittest -freference-trace --summary all
|
|
||||||
|
|
||||||
- name: zig build test
|
- name: zig build test
|
||||||
run: zig build test -Dengine=v8 -- --json > bench.json
|
run: zig build test -- --json > bench.json
|
||||||
|
|
||||||
- name: write commit
|
- name: write commit
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ zig-out
|
|||||||
/vendor/netsurf/out
|
/vendor/netsurf/out
|
||||||
/vendor/libiconv/
|
/vendor/libiconv/
|
||||||
lightpanda.id
|
lightpanda.id
|
||||||
|
/v8/
|
||||||
|
|||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,7 +1,3 @@
|
|||||||
[submodule "vendor/zig-js-runtime"]
|
|
||||||
path = vendor/zig-js-runtime
|
|
||||||
url = https://github.com/lightpanda-io/zig-js-runtime.git/
|
|
||||||
branch = zig-0.14
|
|
||||||
[submodule "vendor/netsurf/libwapcaplet"]
|
[submodule "vendor/netsurf/libwapcaplet"]
|
||||||
path = vendor/netsurf/libwapcaplet
|
path = vendor/netsurf/libwapcaplet
|
||||||
url = https://github.com/lightpanda-io/libwapcaplet.git/
|
url = https://github.com/lightpanda-io/libwapcaplet.git/
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ RUN make install-libiconv && \
|
|||||||
|
|
||||||
# download and install v8
|
# download and install v8
|
||||||
RUN 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 && \
|
RUN 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 vendor/zig-js-runtime/vendor/v8/${ARCH}-linux/release && \
|
mkdir -p v8/build/${ARCH}-linux/release/ninja/obj/zig/ && \
|
||||||
mv libc_v8.a vendor/zig-js-runtime/vendor/v8/${ARCH}-linux/release/libc_v8.a
|
mv libc_v8.a v8/build/${ARCH}-linux/release/ninja/obj/zig/libc_v8.a
|
||||||
|
|
||||||
# build release
|
# build release
|
||||||
RUN make build
|
RUN make build
|
||||||
|
|||||||
46
Makefile
46
Makefile
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
ZIG := zig
|
ZIG := zig
|
||||||
BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
# option test filter make unittest F="server"
|
# option test filter make test F="server"
|
||||||
F=
|
F=
|
||||||
|
|
||||||
# OS and ARCH
|
# OS and ARCH
|
||||||
@@ -47,7 +47,7 @@ help:
|
|||||||
|
|
||||||
# $(ZIG) commands
|
# $(ZIG) commands
|
||||||
# ------------
|
# ------------
|
||||||
.PHONY: build build-dev run run-release shell test bench download-zig wpt unittest data
|
.PHONY: build build-dev run run-release shell test bench download-zig wpt data get-v8 build-v8 build-v8-dev
|
||||||
|
|
||||||
zig_version = $(shell grep 'recommended_zig_version = "' "vendor/zig-js-runtime/build.zig" | cut -d'"' -f2)
|
zig_version = $(shell grep 'recommended_zig_version = "' "vendor/zig-js-runtime/build.zig" | cut -d'"' -f2)
|
||||||
|
|
||||||
@@ -62,13 +62,13 @@ download-zig:
|
|||||||
## Build in release-safe mode
|
## Build in release-safe mode
|
||||||
build:
|
build:
|
||||||
@printf "\e[36mBuilding (release safe)...\e[0m\n"
|
@printf "\e[36mBuilding (release safe)...\e[0m\n"
|
||||||
$(ZIG) build -Doptimize=ReleaseSafe -Dengine=v8 -Dgit_commit=$$(git rev-parse --short HEAD) || (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"
|
@printf "\e[33mBuild OK\e[0m\n"
|
||||||
|
|
||||||
## Build in debug mode
|
## Build in debug mode
|
||||||
build-dev:
|
build-dev:
|
||||||
@printf "\e[36mBuilding (debug)...\e[0m\n"
|
@printf "\e[36mBuilding (debug)...\e[0m\n"
|
||||||
@$(ZIG) build -Dengine=v8 -Dgit_commit=$$(git rev-parse --short HEAD) || (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"
|
@printf "\e[33mBuild OK\e[0m\n"
|
||||||
|
|
||||||
## Run the server in debug mode
|
## Run the server in debug mode
|
||||||
@@ -79,39 +79,47 @@ run: build
|
|||||||
## Run a JS shell in debug mode
|
## Run a JS shell in debug mode
|
||||||
shell:
|
shell:
|
||||||
@printf "\e[36mBuilding shell...\e[0m\n"
|
@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
|
## Run WPT tests
|
||||||
wpt:
|
wpt:
|
||||||
@printf "\e[36mBuilding wpt...\e[0m\n"
|
@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 -- --safe $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
|
||||||
|
|
||||||
wpt-summary:
|
wpt-summary:
|
||||||
@printf "\e[36mBuilding wpt...\e[0m\n"
|
@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 -- --safe --summary $(filter-out $@,$(MAKECMDGOALS)) || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
test:
|
test:
|
||||||
@printf "\e[36mTesting...\e[0m\n"
|
@TEST_FILTER='${F}' $(ZIG) build test -freference-trace --summary all
|
||||||
@$(ZIG) build test -Dengine=v8 || (printf "\e[33mTest ERROR\e[0m\n"; exit 1;)
|
|
||||||
@printf "\e[33mTest OK\e[0m\n"
|
|
||||||
|
|
||||||
unittest:
|
## v8
|
||||||
@TEST_FILTER='${F}' $(ZIG) build unittest -freference-trace --summary all
|
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
|
# Install and build required dependencies commands
|
||||||
# ------------
|
# ------------
|
||||||
.PHONY: install-submodule
|
.PHONY: install-submodule
|
||||||
.PHONY: install-zig-js-runtime install-zig-js-runtime-dev install-libiconv
|
.PHONY: install-libiconv
|
||||||
.PHONY: _install-netsurf install-netsurf clean-netsurf test-netsurf install-netsurf-dev
|
.PHONY: _install-netsurf install-netsurf clean-netsurf test-netsurf install-netsurf-dev
|
||||||
.PHONY: install-mimalloc install-mimalloc-dev clean-mimalloc
|
.PHONY: install-mimalloc install-mimalloc-dev clean-mimalloc
|
||||||
.PHONY: install-dev install
|
.PHONY: install-dev install
|
||||||
|
|
||||||
## Install and build dependencies for release
|
## Install and build dependencies for release
|
||||||
install: install-submodule install-zig-js-runtime install-libiconv install-netsurf install-mimalloc
|
install: install-submodule install-libiconv install-netsurf install-mimalloc
|
||||||
|
|
||||||
## Install and build dependencies for dev
|
## Install and build dependencies for dev
|
||||||
install-dev: install-submodule install-zig-js-runtime-dev install-libiconv install-netsurf-dev install-mimalloc-dev
|
install-dev: install-submodule install-libiconv install-netsurf-dev install-mimalloc-dev
|
||||||
|
|
||||||
install-netsurf-dev: _install-netsurf
|
install-netsurf-dev: _install-netsurf
|
||||||
install-netsurf-dev: OPTCFLAGS := -O0 -g -DNDEBUG
|
install-netsurf-dev: OPTCFLAGS := -O0 -g -DNDEBUG
|
||||||
@@ -194,14 +202,6 @@ ifneq ("$(wildcard vendor/libiconv/libiconv-1.17/Makefile)","")
|
|||||||
make clean
|
make clean
|
||||||
endif
|
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
|
|
||||||
|
|
||||||
data:
|
data:
|
||||||
cd src/data && go run public_suffix_list_gen.go > public_suffix_list.zig
|
cd src/data && go run public_suffix_list_gen.go > public_suffix_list.zig
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -221,17 +221,21 @@ Note: when Mimalloc is built in dev mode, you can dump memory stats with the
|
|||||||
env var `MIMALLOC_SHOW_STATS=1`. See
|
env var `MIMALLOC_SHOW_STATS=1`. See
|
||||||
[https://microsoft.github.io/mimalloc/environment.html](https://microsoft.github.io/mimalloc/environment.html).
|
[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.
|
First, get the tools necessary for building V8, as well as the V8 source code:
|
||||||
|
|
||||||
This build task is very long and cpu consuming, as you will build v8 from sources.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
make install-zig-js-runtime
|
make get-v8
|
||||||
```
|
```
|
||||||
|
|
||||||
For dev env, use `make install-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
|
## Test
|
||||||
|
|
||||||
|
|||||||
317
build.zig
317
build.zig
@@ -17,16 +17,11 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const builtin = @import("builtin");
|
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);
|
|
||||||
|
|
||||||
/// Do not rename this constant. It is scanned by some scripts to determine
|
/// Do not rename this constant. It is scanned by some scripts to determine
|
||||||
/// which zig version to install.
|
/// which zig version to install.
|
||||||
const recommended_zig_version = jsruntime.recommended_zig_version;
|
const recommended_zig_version = "0.14.0";
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) {
|
switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) {
|
||||||
@@ -42,193 +37,190 @@ pub fn build(b: *std.Build) !void {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var opts = b.addOptions();
|
||||||
|
opts.addOption(
|
||||||
|
[]const u8,
|
||||||
|
"git_commit",
|
||||||
|
b.option([]const u8, "git_commit", "Current git commit") orelse "dev",
|
||||||
|
);
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const mode = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const options = jsruntime.buildOptions(b);
|
|
||||||
|
|
||||||
// browser
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// compile and install
|
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = "lightpanda",
|
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = mode,
|
|
||||||
});
|
|
||||||
try common(b, exe, options);
|
|
||||||
{
|
{
|
||||||
var opt = b.addOptions();
|
// browser
|
||||||
opt.addOption(
|
// -------
|
||||||
[]const u8,
|
|
||||||
"git_commit",
|
|
||||||
b.option([]const u8, "git_commit", "Current git commit") orelse "dev",
|
|
||||||
);
|
|
||||||
exe.root_module.addImport("build_info", opt.createModule());
|
|
||||||
}
|
|
||||||
b.installArtifact(exe);
|
|
||||||
|
|
||||||
// run
|
// compile and install
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
const exe = b.addExecutable(.{
|
||||||
if (b.args) |args| {
|
.name = "lightpanda",
|
||||||
run_cmd.addArgs(args);
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
});
|
||||||
|
|
||||||
|
try common(b, opts, exe);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step
|
{
|
||||||
const run_step = b.step("run", "Run the app");
|
// get v8
|
||||||
run_step.dependOn(&run_cmd.step);
|
// -------
|
||||||
|
const v8 = b.dependency("v8", .{ .target = target, .optimize = optimize });
|
||||||
// shell
|
const get_v8 = b.addRunArtifact(v8.artifact("get-v8"));
|
||||||
// -----
|
const get_step = b.step("get-v8", "Get v8");
|
||||||
|
get_step.dependOn(&get_v8.step);
|
||||||
// compile and install
|
|
||||||
const shell = b.addExecutable(.{
|
|
||||||
.name = "lightpanda-shell",
|
|
||||||
.root_source_file = b.path("src/main_shell.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = mode,
|
|
||||||
});
|
|
||||||
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");
|
// build v8
|
||||||
shell_step.dependOn(&shell_cmd.step);
|
// -------
|
||||||
|
const v8 = b.dependency("v8", .{ .target = target, .optimize = optimize });
|
||||||
// test
|
const build_v8 = b.addRunArtifact(v8.artifact("build-v8"));
|
||||||
// ----
|
const build_step = b.step("build-v8", "Build v8");
|
||||||
|
build_step.dependOn(&build_v8.step);
|
||||||
// compile
|
|
||||||
const tests = b.addTest(.{
|
|
||||||
.root_source_file = b.path("src/main_tests.zig"),
|
|
||||||
.test_runner = .{ .path = b.path("src/main_tests.zig"), .mode = .simple },
|
|
||||||
.target = target,
|
|
||||||
.optimize = mode,
|
|
||||||
});
|
|
||||||
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");
|
// tests
|
||||||
test_step.dependOn(&run_tests.step);
|
// ----
|
||||||
|
|
||||||
// unittest
|
// compile
|
||||||
// ----
|
const tests = b.addTest(.{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.test_runner = .{ .path = b.path("src/test_runner.zig"), .mode = .simple },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
try common(b, opts, tests);
|
||||||
|
|
||||||
// compile
|
const run_tests = b.addRunArtifact(tests);
|
||||||
const unit_tests = b.addTest(.{
|
if (b.args) |args| {
|
||||||
.root_source_file = b.path("src/main_unit_tests.zig"),
|
run_tests.addArgs(args);
|
||||||
.test_runner = .{ .path = b.path("src/test_runner.zig"), .mode = .simple },
|
}
|
||||||
.target = target,
|
|
||||||
.optimize = mode,
|
|
||||||
});
|
|
||||||
try common(b, unit_tests, options);
|
|
||||||
|
|
||||||
const run_unit_tests = b.addRunArtifact(unit_tests);
|
// step
|
||||||
if (b.args) |args| {
|
const tests_step = b.step("test", "Run unit tests");
|
||||||
run_unit_tests.addArgs(args);
|
tests_step.dependOn(&run_tests.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step
|
{
|
||||||
const unit_test_step = b.step("unittest", "Run unit tests");
|
// wpt
|
||||||
unit_test_step.dependOn(&run_unit_tests.step);
|
// -----
|
||||||
|
|
||||||
// wpt
|
// compile and install
|
||||||
// -----
|
const wpt = b.addExecutable(.{
|
||||||
|
.name = "lightpanda-wpt",
|
||||||
|
.root_source_file = b.path("src/main_wpt.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
try common(b, opts, wpt);
|
||||||
|
|
||||||
// compile and install
|
// run
|
||||||
const wpt = b.addExecutable(.{
|
const wpt_cmd = b.addRunArtifact(wpt);
|
||||||
.name = "lightpanda-wpt",
|
if (b.args) |args| {
|
||||||
.root_source_file = b.path("src/main_wpt.zig"),
|
wpt_cmd.addArgs(args);
|
||||||
.target = target,
|
}
|
||||||
.optimize = mode,
|
// step
|
||||||
});
|
const wpt_step = b.step("wpt", "WPT tests");
|
||||||
try common(b, wpt, options);
|
wpt_step.dependOn(&wpt_cmd.step);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn common(
|
fn common(b: *std.Build, opts: *std.Build.Step.Options, step: *std.Build.Step.Compile) !void {
|
||||||
b: *std.Build,
|
const mod = step.root_module;
|
||||||
step: *std.Build.Step.Compile,
|
const target = mod.resolved_target.?;
|
||||||
options: jsruntime.Options,
|
const optimize = mod.optimize.?;
|
||||||
) !void {
|
|
||||||
const target = step.root_module.resolved_target.?;
|
|
||||||
const optimize = step.root_module.optimize.?;
|
|
||||||
const dep_opts = .{ .target = target, .optimize = optimize };
|
const dep_opts = .{ .target = target, .optimize = optimize };
|
||||||
|
|
||||||
const jsruntimemod = try jsruntime_pkgs.module(
|
try moduleNetSurf(b, step, target);
|
||||||
b,
|
mod.addImport("tls", b.dependency("tls", dep_opts).module("tls"));
|
||||||
options,
|
mod.addImport("tigerbeetle-io", b.dependency("tigerbeetle_io", .{}).module("tigerbeetle_io"));
|
||||||
step.root_module.optimize.?,
|
|
||||||
target,
|
{
|
||||||
|
// 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 mode_str: []const u8 = if (mod.optimize.? == .Debug) "debug" else "release";
|
||||||
|
|
||||||
|
// FIXME: we are tied to native v8 builds, currently:
|
||||||
|
// - aarch64-macos
|
||||||
|
// - x86_64-linux
|
||||||
|
const os = target.result.os.tag;
|
||||||
|
const arch = target.result.cpu.arch;
|
||||||
|
switch (os) {
|
||||||
|
.macos => {},
|
||||||
|
.linux => {
|
||||||
|
// TODO: why do we need it? It should be linked already when we built v8
|
||||||
|
mod.link_libcpp = true;
|
||||||
|
},
|
||||||
|
else => return error.OsNotSupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
const lib_path = try std.fmt.allocPrint(
|
||||||
|
mod.owner.allocator,
|
||||||
|
"v8/build/{s}-{s}/{s}/ninja/obj/zig/libc_v8.a",
|
||||||
|
.{ @tagName(arch), @tagName(os), mode_str },
|
||||||
);
|
);
|
||||||
step.root_module.addImport("jsruntime", jsruntimemod);
|
mod.addObjectFile(mod.owner.path(lib_path));
|
||||||
|
mod.addImport("build_info", opts.createModule());
|
||||||
const netsurf = try moduleNetSurf(b, target);
|
|
||||||
netsurf.addImport("jsruntime", jsruntimemod);
|
|
||||||
step.root_module.addImport("netsurf", netsurf);
|
|
||||||
|
|
||||||
step.root_module.addImport("tls", b.dependency("tls", dep_opts).module("tls"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moduleNetSurf(b: *std.Build, target: std.Build.ResolvedTarget) !*std.Build.Module {
|
fn moduleNetSurf(b: *std.Build, step: *std.Build.Step.Compile, target: std.Build.ResolvedTarget) !void {
|
||||||
const mod = b.addModule("netsurf", .{
|
|
||||||
.root_source_file = b.path("src/netsurf/netsurf.zig"),
|
|
||||||
.target = target,
|
|
||||||
});
|
|
||||||
|
|
||||||
const os = target.result.os.tag;
|
const os = target.result.os.tag;
|
||||||
const arch = target.result.cpu.arch;
|
const arch = target.result.cpu.arch;
|
||||||
|
|
||||||
// iconv
|
// iconv
|
||||||
const libiconv_lib_path = try std.fmt.allocPrint(
|
const libiconv_lib_path = try std.fmt.allocPrint(
|
||||||
mod.owner.allocator,
|
b.allocator,
|
||||||
"vendor/libiconv/out/{s}-{s}/lib/libiconv.a",
|
"vendor/libiconv/out/{s}-{s}/lib/libiconv.a",
|
||||||
.{ @tagName(os), @tagName(arch) },
|
.{ @tagName(os), @tagName(arch) },
|
||||||
);
|
);
|
||||||
const libiconv_include_path = try std.fmt.allocPrint(
|
const libiconv_include_path = try std.fmt.allocPrint(
|
||||||
mod.owner.allocator,
|
b.allocator,
|
||||||
"vendor/libiconv/out/{s}-{s}/lib/libiconv.a",
|
"vendor/libiconv/out/{s}-{s}/lib/libiconv.a",
|
||||||
.{ @tagName(os), @tagName(arch) },
|
.{ @tagName(os), @tagName(arch) },
|
||||||
);
|
);
|
||||||
mod.addObjectFile(b.path(libiconv_lib_path));
|
step.addObjectFile(b.path(libiconv_lib_path));
|
||||||
mod.addIncludePath(b.path(libiconv_include_path));
|
step.addIncludePath(b.path(libiconv_include_path));
|
||||||
|
|
||||||
// mimalloc
|
{
|
||||||
mod.addImport("mimalloc", (try moduleMimalloc(b, target)));
|
// mimalloc
|
||||||
|
const mimalloc = "vendor/mimalloc";
|
||||||
|
const lib_path = try std.fmt.allocPrint(
|
||||||
|
b.allocator,
|
||||||
|
mimalloc ++ "/out/{s}-{s}/lib/libmimalloc.a",
|
||||||
|
.{ @tagName(os), @tagName(arch) },
|
||||||
|
);
|
||||||
|
step.addObjectFile(b.path(lib_path));
|
||||||
|
step.addIncludePath(b.path(mimalloc ++ "/include"));
|
||||||
|
}
|
||||||
|
|
||||||
// netsurf libs
|
// netsurf libs
|
||||||
const ns = "vendor/netsurf";
|
const ns = "vendor/netsurf";
|
||||||
const ns_include_path = try std.fmt.allocPrint(
|
const ns_include_path = try std.fmt.allocPrint(
|
||||||
mod.owner.allocator,
|
b.allocator,
|
||||||
ns ++ "/out/{s}-{s}/include",
|
ns ++ "/out/{s}-{s}/include",
|
||||||
.{ @tagName(os), @tagName(arch) },
|
.{ @tagName(os), @tagName(arch) },
|
||||||
);
|
);
|
||||||
mod.addIncludePath(b.path(ns_include_path));
|
step.addIncludePath(b.path(ns_include_path));
|
||||||
|
|
||||||
const libs: [4][]const u8 = .{
|
const libs: [4][]const u8 = .{
|
||||||
"libdom",
|
"libdom",
|
||||||
@@ -238,34 +230,11 @@ fn moduleNetSurf(b: *std.Build, target: std.Build.ResolvedTarget) !*std.Build.Mo
|
|||||||
};
|
};
|
||||||
inline for (libs) |lib| {
|
inline for (libs) |lib| {
|
||||||
const ns_lib_path = try std.fmt.allocPrint(
|
const ns_lib_path = try std.fmt.allocPrint(
|
||||||
mod.owner.allocator,
|
b.allocator,
|
||||||
ns ++ "/out/{s}-{s}/lib/" ++ lib ++ ".a",
|
ns ++ "/out/{s}-{s}/lib/" ++ lib ++ ".a",
|
||||||
.{ @tagName(os), @tagName(arch) },
|
.{ @tagName(os), @tagName(arch) },
|
||||||
);
|
);
|
||||||
mod.addObjectFile(b.path(ns_lib_path));
|
step.addObjectFile(b.path(ns_lib_path));
|
||||||
mod.addIncludePath(b.path(ns ++ "/" ++ lib ++ "/src"));
|
step.addIncludePath(b.path(ns ++ "/" ++ lib ++ "/src"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moduleMimalloc(b: *std.Build, target: std.Build.ResolvedTarget) !*std.Build.Module {
|
|
||||||
const mod = b.addModule("mimalloc", .{
|
|
||||||
.root_source_file = b.path("src/mimalloc/mimalloc.zig"),
|
|
||||||
.target = target,
|
|
||||||
});
|
|
||||||
|
|
||||||
const os = target.result.os.tag;
|
|
||||||
const arch = target.result.cpu.arch;
|
|
||||||
|
|
||||||
const mimalloc = "vendor/mimalloc";
|
|
||||||
const lib_path = try std.fmt.allocPrint(
|
|
||||||
mod.owner.allocator,
|
|
||||||
mimalloc ++ "/out/{s}-{s}/lib/libmimalloc.a",
|
|
||||||
.{ @tagName(os), @tagName(arch) },
|
|
||||||
);
|
|
||||||
mod.addObjectFile(b.path(lib_path));
|
|
||||||
mod.addIncludePath(b.path(mimalloc ++ "/include"));
|
|
||||||
|
|
||||||
return mod;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,17 @@
|
|||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.tls = .{
|
.tls = .{
|
||||||
.url = "https://github.com/ianic/tls.zig/archive/b29a8b45fc59fc2d202769c4f54509bb9e17d0a2.tar.gz",
|
.url = "https://github.com/ianic/tls.zig/archive/b29a8b45fc59fc2d202769c4f54509bb9e17d0a2.tar.gz",
|
||||||
.hash = "1220e6fd39920dd6e28b2bc06688787a39430f8856f0597cd77c44ca868c6c54fb86",
|
.hash = "tls-0.1.0-ER2e0uAxBQDm_TmSDdbiiyvAZoh4ejlDD4hW8Fl813xE",
|
||||||
},
|
},
|
||||||
|
.tigerbeetle_io = .{
|
||||||
|
.url = "https://github.com/lightpanda-io/tigerbeetle-io/archive/61d9652f1a957b7f4db723ea6aa0ce9635e840ce.tar.gz",
|
||||||
|
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
|
||||||
|
},
|
||||||
|
.v8 = .{
|
||||||
|
.url = "https://github.com/karlseguin/zig-v8-fork/archive/e5f1c0c9f1ed147617427f22cdaf11df4ab60b79.tar.gz",
|
||||||
|
.hash = "v8-0.0.0-xddH61vYIACI2pT1t-dUbXm18cHAKy-KWT_Qft4sBwam",
|
||||||
|
},
|
||||||
|
//.v8 = .{ .path = "../zig-v8-fork" },
|
||||||
|
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +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 generate = @import("generate.zig");
|
|
||||||
|
|
||||||
const Console = @import("jsruntime").Console;
|
|
||||||
|
|
||||||
const DOM = @import("dom/dom.zig");
|
|
||||||
const HTML = @import("html/html.zig");
|
|
||||||
const Events = @import("events/event.zig");
|
|
||||||
const XHR = @import("xhr/xhr.zig");
|
|
||||||
const Storage = @import("storage/storage.zig");
|
|
||||||
const URL = @import("url/url.zig");
|
|
||||||
const Iterators = @import("iterator/iterator.zig");
|
|
||||||
const XMLSerializer = @import("xmlserializer/xmlserializer.zig");
|
|
||||||
|
|
||||||
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,
|
|
||||||
Iterators.Interfaces,
|
|
||||||
XMLSerializer.Interfaces,
|
|
||||||
}){};
|
|
||||||
|
|
||||||
pub const UserContext = @import("user_context.zig").UserContext;
|
|
||||||
10
src/app.zig
10
src/app.zig
@@ -1,7 +1,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const Loop = @import("jsruntime").Loop;
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const js = @import("runtime/js.zig");
|
||||||
|
const Loop = @import("runtime/loop.zig").Loop;
|
||||||
const HttpClient = @import("http/client.zig").Client;
|
const HttpClient = @import("http/client.zig").Client;
|
||||||
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ const log = std.log.scoped(.app);
|
|||||||
// might need.
|
// might need.
|
||||||
pub const App = struct {
|
pub const App = struct {
|
||||||
loop: *Loop,
|
loop: *Loop,
|
||||||
|
config: Config,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
telemetry: Telemetry,
|
telemetry: Telemetry,
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
@@ -24,8 +26,9 @@ pub const App = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Config = struct {
|
pub const Config = struct {
|
||||||
tls_verify_host: bool = true,
|
|
||||||
run_mode: RunMode,
|
run_mode: RunMode,
|
||||||
|
gc_hints: bool = false,
|
||||||
|
tls_verify_host: bool = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, config: Config) !*App {
|
pub fn init(allocator: Allocator, config: Config) !*App {
|
||||||
@@ -48,6 +51,7 @@ pub const App = struct {
|
|||||||
.http_client = try HttpClient.init(allocator, 5, .{
|
.http_client = try HttpClient.init(allocator, 5, .{
|
||||||
.tls_verify_host = config.tls_verify_host,
|
.tls_verify_host = config.tls_verify_host,
|
||||||
}),
|
}),
|
||||||
|
.config = config,
|
||||||
};
|
};
|
||||||
app.telemetry = Telemetry.init(app, config.run_mode);
|
app.telemetry = Telemetry.init(app, config.run_mode);
|
||||||
|
|
||||||
|
|||||||
@@ -21,31 +21,26 @@ const builtin = @import("builtin");
|
|||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Types = @import("root").Types;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const Dump = @import("dump.zig");
|
const Dump = @import("dump.zig");
|
||||||
const Mime = @import("mime.zig").Mime;
|
const Mime = @import("mime.zig").Mime;
|
||||||
|
const parser = @import("netsurf.zig");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const Window = @import("html/window.zig").Window;
|
||||||
const Loop = jsruntime.Loop;
|
const Walker = @import("dom/walker.zig").WalkerDepthFirst;
|
||||||
const Env = jsruntime.Env;
|
|
||||||
const Module = jsruntime.Module;
|
|
||||||
|
|
||||||
|
const Env = @import("env.zig").Env;
|
||||||
const App = @import("../app.zig").App;
|
const App = @import("../app.zig").App;
|
||||||
const apiweb = @import("../apiweb.zig");
|
|
||||||
|
|
||||||
const Window = @import("../html/window.zig").Window;
|
|
||||||
const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
|
||||||
|
|
||||||
const URL = @import("../url.zig").URL;
|
const URL = @import("../url.zig").URL;
|
||||||
const storage = @import("../storage/storage.zig");
|
|
||||||
const Notification = @import("../notification.zig").Notification;
|
|
||||||
|
|
||||||
const http = @import("../http/client.zig");
|
const http = @import("../http/client.zig");
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
const storage = @import("storage/storage.zig");
|
||||||
|
const Loop = @import("../runtime/loop.zig").Loop;
|
||||||
|
const SessionState = @import("env.zig").SessionState;
|
||||||
|
const HttpClient = @import("../http/client.zig").Client;
|
||||||
|
const Notification = @import("../notification.zig").Notification;
|
||||||
|
|
||||||
const polyfill = @import("../polyfill/polyfill.zig");
|
const polyfill = @import("polyfill/polyfill.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.browser);
|
const log = std.log.scoped(.browser);
|
||||||
|
|
||||||
@@ -56,6 +51,7 @@ pub const user_agent = "Lightpanda/1.0";
|
|||||||
// A browser contains only one session.
|
// A browser contains only one session.
|
||||||
// TODO allow multiple sessions per browser.
|
// TODO allow multiple sessions per browser.
|
||||||
pub const Browser = struct {
|
pub const Browser = struct {
|
||||||
|
env: *Env,
|
||||||
app: *App,
|
app: *App,
|
||||||
session: ?*Session,
|
session: ?*Session,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
@@ -65,10 +61,17 @@ pub const Browser = struct {
|
|||||||
|
|
||||||
const SessionPool = std.heap.MemoryPool(Session);
|
const SessionPool = std.heap.MemoryPool(Session);
|
||||||
|
|
||||||
pub fn init(app: *App) Browser {
|
pub fn init(app: *App) !Browser {
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
|
|
||||||
|
const env = try Env.init(allocator, .{
|
||||||
|
.gc_hints = app.config.gc_hints,
|
||||||
|
});
|
||||||
|
errdefer env.deinit();
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.app = app,
|
.app = app,
|
||||||
|
.env = env,
|
||||||
.session = null,
|
.session = null,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.http_client = &app.http_client,
|
.http_client = &app.http_client,
|
||||||
@@ -79,6 +82,7 @@ pub const Browser = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Browser) void {
|
pub fn deinit(self: *Browser) void {
|
||||||
self.closeSession();
|
self.closeSession();
|
||||||
|
self.env.deinit();
|
||||||
self.session_pool.deinit();
|
self.session_pool.deinit();
|
||||||
self.page_arena.deinit();
|
self.page_arena.deinit();
|
||||||
}
|
}
|
||||||
@@ -101,10 +105,7 @@ pub const Browser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn runMicrotasks(self: *const Browser) void {
|
pub fn runMicrotasks(self: *const Browser) void {
|
||||||
// if no session exists, there is nothing to do.
|
return self.env.runMicrotasks();
|
||||||
if (self.session == null) return;
|
|
||||||
|
|
||||||
return self.session.?.env.runMicrotasks();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,8 +114,11 @@ pub const Browser = struct {
|
|||||||
// You can create successively multiple pages for a session, but you must
|
// You can create successively multiple pages for a session, but you must
|
||||||
// deinit a page before running another one.
|
// deinit a page before running another one.
|
||||||
pub const Session = struct {
|
pub const Session = struct {
|
||||||
app: *App,
|
state: SessionState,
|
||||||
|
executor: *Env.Executor,
|
||||||
|
inspector: Env.Inspector,
|
||||||
|
|
||||||
|
app: *App,
|
||||||
browser: *Browser,
|
browser: *Browser,
|
||||||
|
|
||||||
// The arena is used only to bound the js env init b/c it leaks memory.
|
// The arena is used only to bound the js env init b/c it leaks memory.
|
||||||
@@ -124,9 +128,6 @@ pub const Session = struct {
|
|||||||
// all others Session deps use directly self.alloc and not the arena.
|
// all others Session deps use directly self.alloc and not the arena.
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
env: Env,
|
|
||||||
inspector: jsruntime.Inspector,
|
|
||||||
|
|
||||||
window: Window,
|
window: Window,
|
||||||
|
|
||||||
// TODO move the shed/jar to the browser?
|
// TODO move the shed/jar to the browser?
|
||||||
@@ -136,8 +137,6 @@ pub const Session = struct {
|
|||||||
page: ?Page = null,
|
page: ?Page = null,
|
||||||
http_client: *http.Client,
|
http_client: *http.Client,
|
||||||
|
|
||||||
jstypes: [Types.len]usize = undefined,
|
|
||||||
|
|
||||||
// recipient of notification, passed as the first parameter to notify
|
// recipient of notification, passed as the first parameter to notify
|
||||||
notify_ctx: *anyopaque,
|
notify_ctx: *anyopaque,
|
||||||
notify_func: *const fn (ctx: *anyopaque, notification: *const Notification) anyerror!void,
|
notify_func: *const fn (ctx: *anyopaque, notification: *const Notification) anyerror!void,
|
||||||
@@ -159,48 +158,59 @@ pub const Session = struct {
|
|||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.app = app,
|
.app = app,
|
||||||
.env = undefined,
|
|
||||||
.browser = browser,
|
.browser = browser,
|
||||||
.notify_ctx = any_ctx,
|
.notify_ctx = any_ctx,
|
||||||
.inspector = undefined,
|
.inspector = undefined,
|
||||||
.notify_func = ContextStruct.notify,
|
.notify_func = ContextStruct.notify,
|
||||||
.http_client = browser.http_client,
|
.http_client = browser.http_client,
|
||||||
|
.executor = undefined,
|
||||||
.storage_shed = storage.Shed.init(allocator),
|
.storage_shed = storage.Shed.init(allocator),
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.cookie_jar = storage.CookieJar.init(allocator),
|
.cookie_jar = storage.CookieJar.init(allocator),
|
||||||
.window = Window.create(null, .{ .agent = user_agent }),
|
.window = Window.create(null, .{ .agent = user_agent }),
|
||||||
|
.state = .{
|
||||||
|
.loop = app.loop,
|
||||||
|
.document = null,
|
||||||
|
.http_client = browser.http_client,
|
||||||
|
|
||||||
|
// we'll set this immediately after
|
||||||
|
.cookie_jar = undefined,
|
||||||
|
|
||||||
|
// nothing should be used on the state until we have a page
|
||||||
|
// at which point we'll set these fields
|
||||||
|
.renderer = undefined,
|
||||||
|
.url = undefined,
|
||||||
|
.arena = undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
self.state.cookie_jar = &self.cookie_jar;
|
||||||
|
errdefer self.arena.deinit();
|
||||||
|
|
||||||
const arena = self.arena.allocator();
|
self.executor = try browser.env.startExecutor(Window, &self.state, self);
|
||||||
Env.init(&self.env, arena, app.loop, null);
|
errdefer browser.env.stopExecutor(self.executor);
|
||||||
errdefer self.env.deinit();
|
self.inspector = try Env.Inspector.init(self.arena.allocator(), self.executor, ctx);
|
||||||
try self.env.load(&self.jstypes);
|
|
||||||
|
|
||||||
// const ctx_opaque = @as(*anyopaque, @ptrCast(ctx));
|
self.microtaskLoop();
|
||||||
self.inspector = try jsruntime.Inspector.init(
|
|
||||||
arena,
|
|
||||||
&self.env,
|
|
||||||
any_ctx,
|
|
||||||
ContextStruct.onInspectorResponse,
|
|
||||||
ContextStruct.onInspectorEvent,
|
|
||||||
);
|
|
||||||
self.env.setInspector(self.inspector);
|
|
||||||
try self.env.setModuleLoadFn(self, Session.fetchModule);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Session) void {
|
fn deinit(self: *Session) void {
|
||||||
|
self.app.loop.resetZig();
|
||||||
if (self.page != null) {
|
if (self.page != null) {
|
||||||
self.removePage();
|
self.removePage();
|
||||||
}
|
}
|
||||||
self.env.deinit();
|
self.inspector.deinit();
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.cookie_jar.deinit();
|
self.cookie_jar.deinit();
|
||||||
self.storage_shed.deinit();
|
self.storage_shed.deinit();
|
||||||
|
self.browser.env.stopExecutor(self.executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetchModule(ctx: *anyopaque, referrer: ?jsruntime.Module, specifier: []const u8) !jsruntime.Module {
|
fn microtaskLoop(self: *Session) void {
|
||||||
_ = referrer;
|
self.browser.runMicrotasks();
|
||||||
|
self.app.loop.zigTimeout(1 * std.time.ns_per_ms, *Session, self, microtaskLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) ![]const u8 {
|
||||||
const self: *Session = @ptrCast(@alignCast(ctx));
|
const self: *Session = @ptrCast(@alignCast(ctx));
|
||||||
const page = &(self.page orelse return error.NoPage);
|
const page = &(self.page orelse return error.NoPage);
|
||||||
|
|
||||||
@@ -209,16 +219,15 @@ pub const Session = struct {
|
|||||||
// Use the page_arena for this, which has a more appropriate lifetime
|
// Use the page_arena for this, which has a more appropriate lifetime
|
||||||
// and which has more retained memory between sessions and pages.
|
// and which has more retained memory between sessions and pages.
|
||||||
const arena = self.browser.page_arena.allocator();
|
const arena = self.browser.page_arena.allocator();
|
||||||
const body = try page.fetchData(
|
return try page.fetchData(
|
||||||
arena,
|
arena,
|
||||||
specifier,
|
specifier,
|
||||||
if (page.current_script) |s| s.src else null,
|
if (page.current_script) |s| s.src else null,
|
||||||
);
|
);
|
||||||
return self.env.compileModule(body, specifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn callInspector(self: *Session, msg: []const u8) void {
|
pub fn callInspector(self: *const Session, msg: []const u8) void {
|
||||||
self.inspector.send(self.env, msg);
|
self.inspector.send(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: the caller is not the owner of the returned value,
|
// NOTE: the caller is not the owner of the returned value,
|
||||||
@@ -232,19 +241,14 @@ pub const Session = struct {
|
|||||||
const page = &self.page.?;
|
const page = &self.page.?;
|
||||||
|
|
||||||
// start JS env
|
// start JS env
|
||||||
log.debug("start js env", .{});
|
log.debug("start new js scope", .{});
|
||||||
try self.env.start();
|
self.state.arena = self.browser.page_arena.allocator();
|
||||||
|
errdefer self.state.arena = undefined;
|
||||||
|
|
||||||
if (comptime builtin.is_test == false) {
|
try self.executor.startScope(&self.window);
|
||||||
// By not loading this during tests, we aren't required to load
|
|
||||||
// all of the interfaces into zig-js-runtime.
|
|
||||||
log.debug("setup global env", .{});
|
|
||||||
try self.env.bindGlobal(&self.window);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load polyfills
|
// load polyfills
|
||||||
// TODO: change to 'env' when https://github.com/lightpanda-io/zig-js-runtime/pull/285 lands
|
try polyfill.load(self.arena.allocator(), self.executor);
|
||||||
try polyfill.load(self.arena.allocator(), &self.env);
|
|
||||||
|
|
||||||
// inspector
|
// inspector
|
||||||
self.contextCreated(page, aux_data);
|
self.contextCreated(page, aux_data);
|
||||||
@@ -254,11 +258,10 @@ pub const Session = struct {
|
|||||||
|
|
||||||
pub fn removePage(self: *Session) void {
|
pub fn removePage(self: *Session) void {
|
||||||
std.debug.assert(self.page != null);
|
std.debug.assert(self.page != null);
|
||||||
|
|
||||||
// Reset all existing callbacks.
|
// Reset all existing callbacks.
|
||||||
self.app.loop.resetJS();
|
self.app.loop.resetJS();
|
||||||
|
self.executor.endScope();
|
||||||
|
|
||||||
self.env.stop();
|
|
||||||
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
|
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
|
||||||
|
|
||||||
self.window.replaceLocation(.{ .url = null }) catch |e| {
|
self.window.replaceLocation(.{ .url = null }) catch |e| {
|
||||||
@@ -267,6 +270,7 @@ pub const Session = struct {
|
|||||||
|
|
||||||
// clear netsurf memory arena.
|
// clear netsurf memory arena.
|
||||||
parser.deinit();
|
parser.deinit();
|
||||||
|
self.state.arena = undefined;
|
||||||
|
|
||||||
self.page = null;
|
self.page = null;
|
||||||
}
|
}
|
||||||
@@ -277,7 +281,7 @@ pub const Session = struct {
|
|||||||
|
|
||||||
fn contextCreated(self: *Session, page: *Page, aux_data: ?[]const u8) void {
|
fn contextCreated(self: *Session, page: *Page, aux_data: ?[]const u8) void {
|
||||||
log.debug("inspector context created", .{});
|
log.debug("inspector context created", .{});
|
||||||
self.inspector.contextCreated(&self.env, "", (page.origin() catch "://") orelse "://", aux_data);
|
self.inspector.contextCreated(self.executor, "", (page.origin() catch "://") orelse "://", aux_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notify(self: *const Session, notification: *const Notification) void {
|
fn notify(self: *const Session, notification: *const Notification) void {
|
||||||
@@ -332,19 +336,16 @@ pub const Page = struct {
|
|||||||
|
|
||||||
pub fn wait(self: *Page) !void {
|
pub fn wait(self: *Page) !void {
|
||||||
// try catch
|
// try catch
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(&self.session.env);
|
try_catch.init(self.session.executor);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
self.session.env.wait() catch |err| {
|
self.session.app.loop.run() catch |err| {
|
||||||
// the js env could not be started if the document wasn't an HTML.
|
if (try try_catch.err(self.arena)) |msg| {
|
||||||
if (err == error.EnvNotStarted) return;
|
|
||||||
|
|
||||||
const arena = self.arena;
|
|
||||||
if (try try_catch.err(arena, &self.session.env)) |msg| {
|
|
||||||
defer arena.free(msg);
|
|
||||||
log.info("wait error: {s}", .{msg});
|
log.info("wait error: {s}", .{msg});
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
log.info("wait error: {any}", .{err});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
log.debug("wait: OK", .{});
|
log.debug("wait: OK", .{});
|
||||||
@@ -397,7 +398,7 @@ pub const Page = struct {
|
|||||||
try session.cookie_jar.populateFromResponse(&url.uri, &header);
|
try session.cookie_jar.populateFromResponse(&url.uri, &header);
|
||||||
|
|
||||||
// TODO handle fragment in url.
|
// TODO handle fragment in url.
|
||||||
try self.session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
|
try session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
|
||||||
|
|
||||||
log.info("GET {any} {d}", .{ url, header.status });
|
log.info("GET {any} {d}", .{ url, header.status });
|
||||||
|
|
||||||
@@ -414,7 +415,6 @@ pub const Page = struct {
|
|||||||
|
|
||||||
log.debug("header content-type: {s}", .{ct});
|
log.debug("header content-type: {s}", .{ct});
|
||||||
var mime = try Mime.parse(arena, ct);
|
var mime = try Mime.parse(arena, ct);
|
||||||
defer mime.deinit();
|
|
||||||
|
|
||||||
if (mime.isHTML()) {
|
if (mime.isHTML()) {
|
||||||
try self.loadHTMLDoc(&response, mime.charset orelse "utf-8", aux_data);
|
try self.loadHTMLDoc(&response, mime.charset orelse "utf-8", aux_data);
|
||||||
@@ -485,7 +485,7 @@ pub const Page = struct {
|
|||||||
// https://html.spec.whatwg.org/#reporting-document-loading-status
|
// https://html.spec.whatwg.org/#reporting-document-loading-status
|
||||||
|
|
||||||
// inject the URL to the document including the fragment.
|
// inject the URL to the document including the fragment.
|
||||||
try parser.documentSetDocumentURI(doc, if (self.url) |*url| url.raw else "about:blank");
|
try parser.documentSetDocumentURI(doc, self.url.?.raw);
|
||||||
|
|
||||||
const session = self.session;
|
const session = self.session;
|
||||||
// TODO set the referrer to the document.
|
// TODO set the referrer to the document.
|
||||||
@@ -499,14 +499,13 @@ pub const Page = struct {
|
|||||||
// inspector
|
// inspector
|
||||||
session.contextCreated(self, aux_data);
|
session.contextCreated(self, aux_data);
|
||||||
|
|
||||||
// replace the user context document with the new one.
|
{
|
||||||
try session.env.setUserContext(.{
|
// update the sessions state
|
||||||
.url = @ptrCast(&self.url.?),
|
const state = &session.state;
|
||||||
.document = html_doc,
|
state.url = &self.url.?;
|
||||||
.renderer = @ptrCast(&self.renderer),
|
state.document = html_doc;
|
||||||
.cookie_jar = @ptrCast(&self.session.cookie_jar),
|
state.renderer = &self.renderer;
|
||||||
.http_client = @ptrCast(self.session.http_client),
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// browse the DOM tree to retrieve scripts
|
// browse the DOM tree to retrieve scripts
|
||||||
// TODO execute the synchronous scripts during the HTL parsing.
|
// TODO execute the synchronous scripts during the HTL parsing.
|
||||||
@@ -643,7 +642,7 @@ pub const Page = struct {
|
|||||||
// TODO handle charset attribute
|
// TODO handle charset attribute
|
||||||
const opt_text = try parser.nodeTextContent(parser.elementToNode(s.element));
|
const opt_text = try parser.nodeTextContent(parser.elementToNode(s.element));
|
||||||
if (opt_text) |text| {
|
if (opt_text) |text| {
|
||||||
try s.eval(self.arena, &self.session.env, text);
|
try s.eval(self.arena, self.session, text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +710,7 @@ pub const Page = struct {
|
|||||||
fn fetchScript(self: *const Page, s: *const Script) !void {
|
fn fetchScript(self: *const Page, s: *const Script) !void {
|
||||||
const arena = self.arena;
|
const arena = self.arena;
|
||||||
const body = try self.fetchData(arena, s.src, null);
|
const body = try self.fetchData(arena, s.src, null);
|
||||||
try s.eval(arena, &self.session.env, body);
|
try s.eval(arena, self.session, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newHTTPRequest(self: *const Page, method: http.Request.Method, url: *const URL, opts: storage.cookie.LookupOpts) !http.Request {
|
fn newHTTPRequest(self: *const Page, method: http.Request.Method, url: *const URL, opts: storage.cookie.LookupOpts) !http.Request {
|
||||||
@@ -769,24 +768,24 @@ pub const Page = struct {
|
|||||||
return .unknown;
|
return .unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval(self: Script, arena: Allocator, env: *const Env, body: []const u8) !void {
|
fn eval(self: Script, arena: Allocator, session: *Session, body: []const u8) !void {
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(env);
|
try_catch.init(session.executor);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const res = switch (self.kind) {
|
const res = switch (self.kind) {
|
||||||
.unknown => return error.UnknownScript,
|
.unknown => return error.UnknownScript,
|
||||||
.javascript => env.exec(body, self.src),
|
.javascript => session.executor.exec(body, self.src),
|
||||||
.module => env.module(body, self.src),
|
.module => session.executor.module(body, self.src),
|
||||||
} catch {
|
} catch {
|
||||||
if (try try_catch.err(arena, env)) |msg| {
|
if (try try_catch.err(arena)) |msg| {
|
||||||
log.info("eval script {s}: {s}", .{ self.src, msg });
|
log.info("eval script {s}: {s}", .{ self.src, msg });
|
||||||
}
|
}
|
||||||
return FetchError.JsErr;
|
return FetchError.JsErr;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
if (builtin.mode == .Debug) {
|
||||||
const msg = try res.toString(arena, env);
|
const msg = try res.toString(arena);
|
||||||
log.debug("eval script {s}: {s}", .{ self.src, msg });
|
log.debug("eval script {s}: {s}", .{ self.src, msg });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -810,7 +809,7 @@ const FlatRenderer = struct {
|
|||||||
// given an index, get the element
|
// given an index, get the element
|
||||||
elements: std.ArrayListUnmanaged(u64),
|
elements: std.ArrayListUnmanaged(u64),
|
||||||
|
|
||||||
const Element = @import("../dom/element.zig").Element;
|
const Element = @import("dom/element.zig").Element;
|
||||||
|
|
||||||
// we expect allocator to be an arena
|
// we expect allocator to be an arena
|
||||||
pub fn init(allocator: Allocator) FlatRenderer {
|
pub fn init(allocator: Allocator) FlatRenderer {
|
||||||
|
|||||||
28
src/browser/console/console.zig
Normal file
28
src/browser/console/console.zig
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// 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 log = std.log.scoped(.console);
|
||||||
|
|
||||||
|
pub const Console = struct {
|
||||||
|
// TODO: configurable writer
|
||||||
|
|
||||||
|
pub fn _log(_: *const Console, str: []const u8) void {
|
||||||
|
log.debug("{s}\n", .{str});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
// Node implementation with Netsurf Libdom C lib.
|
// Node implementation with Netsurf Libdom C lib.
|
||||||
pub const Node = struct {
|
pub const Node = struct {
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const css = @import("css.zig");
|
const css = @import("css.zig");
|
||||||
const Node = @import("libdom.zig").Node;
|
const Node = @import("libdom.zig").Node;
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const Matcher = struct {
|
const Matcher = struct {
|
||||||
const Nodes = std.ArrayList(Node);
|
const Nodes = std.ArrayList(Node);
|
||||||
@@ -18,11 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
const DOMException = @import("exceptions.zig").DOMException;
|
const DOMException = @import("exceptions.zig").DOMException;
|
||||||
@@ -31,7 +27,6 @@ const DOMException = @import("exceptions.zig").DOMException;
|
|||||||
pub const Attr = struct {
|
pub const Attr = struct {
|
||||||
pub const Self = parser.Attribute;
|
pub const Self = parser.Attribute;
|
||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_namespaceURI(self: *parser.Attribute) !?[]const u8 {
|
pub fn get_namespaceURI(self: *parser.Attribute) !?[]const u8 {
|
||||||
return try parser.nodeGetNamespace(parser.attributeToNode(self));
|
return try parser.nodeGetNamespace(parser.attributeToNode(self));
|
||||||
@@ -70,34 +65,33 @@ pub const Attr = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.Attribute" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var getters = [_]Case{
|
|
||||||
.{ .src = "let a = document.createAttributeNS('foo', 'bar')", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "a.namespaceURI", .ex = "foo" },
|
.{ "let a = document.createAttributeNS('foo', 'bar')", "undefined" },
|
||||||
.{ .src = "a.prefix", .ex = "null" },
|
.{ "a.namespaceURI", "foo" },
|
||||||
.{ .src = "a.localName", .ex = "bar" },
|
.{ "a.prefix", "null" },
|
||||||
.{ .src = "a.name", .ex = "bar" },
|
.{ "a.localName", "bar" },
|
||||||
.{ .src = "a.value", .ex = "" },
|
.{ "a.name", "bar" },
|
||||||
|
.{ "a.value", "" },
|
||||||
// TODO: libdom has a bug here: the created attr has no parent, it
|
// TODO: libdom has a bug here: the created attr has no parent, it
|
||||||
// causes a panic w/ libdom when setting the value.
|
// causes a panic w/ libdom when setting the value.
|
||||||
//.{ .src = "a.value = 'nok'", .ex = "nok" },
|
//.{ "a.value = 'nok'", "nok" },
|
||||||
.{ .src = "a.ownerElement", .ex = "null" },
|
.{ "a.ownerElement", "null" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &getters);
|
|
||||||
|
|
||||||
var attr = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let b = document.getElementById('link').getAttributeNode('class')", .ex = "undefined" },
|
.{ "let b = document.getElementById('link').getAttributeNode('class')", "undefined" },
|
||||||
.{ .src = "b.name", .ex = "class" },
|
.{ "b.name", "class" },
|
||||||
.{ .src = "b.value", .ex = "ok" },
|
.{ "b.value", "ok" },
|
||||||
.{ .src = "b.value = 'nok'", .ex = "nok" },
|
.{ "b.value = 'nok'", "nok" },
|
||||||
.{ .src = "b.value", .ex = "nok" },
|
.{ "b.value", "nok" },
|
||||||
.{ .src = "b.value = null", .ex = "null" },
|
.{ "b.value = null", "null" },
|
||||||
.{ .src = "b.value", .ex = "null" },
|
.{ "b.value", "null" },
|
||||||
.{ .src = "b.value = 'ok'", .ex = "ok" },
|
.{ "b.value = 'ok'", "ok" },
|
||||||
.{ .src = "b.ownerElement.id", .ex = "link" },
|
.{ "b.ownerElement.id", "link" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &attr);
|
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const Text = @import("text.zig").Text;
|
const Text = @import("text.zig").Text;
|
||||||
|
|
||||||
@@ -26,5 +26,4 @@ const Text = @import("text.zig").Text;
|
|||||||
pub const CDATASection = struct {
|
pub const CDATASection = struct {
|
||||||
pub const Self = parser.CDATASection;
|
pub const Self = parser.CDATASection;
|
||||||
pub const prototype = *Text;
|
pub const prototype = *Text;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
@@ -18,11 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
const Comment = @import("comment.zig").Comment;
|
const Comment = @import("comment.zig").Comment;
|
||||||
@@ -42,7 +38,6 @@ pub const Interfaces = .{
|
|||||||
pub const CharacterData = struct {
|
pub const CharacterData = struct {
|
||||||
pub const Self = parser.CharacterData;
|
pub const Self = parser.CharacterData;
|
||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
// JS funcs
|
// JS funcs
|
||||||
// --------
|
// --------
|
||||||
@@ -106,74 +101,65 @@ pub const CharacterData = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.CharacterData" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var get_data = [_]Case{
|
|
||||||
.{ .src = "let link = document.getElementById('link')", .ex = "undefined" },
|
|
||||||
.{ .src = "let cdata = link.firstChild", .ex = "undefined" },
|
|
||||||
.{ .src = "cdata.data", .ex = "OK" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &get_data);
|
|
||||||
|
|
||||||
var set_data = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.data = 'OK modified'", .ex = "OK modified" },
|
.{ "let link = document.getElementById('link')", "undefined" },
|
||||||
.{ .src = "cdata.data === 'OK modified'", .ex = "true" },
|
.{ "let cdata = link.firstChild", "undefined" },
|
||||||
.{ .src = "cdata.data = 'OK'", .ex = "OK" },
|
.{ "cdata.data", "OK" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &set_data);
|
|
||||||
|
|
||||||
var get_length = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.length === 2", .ex = "true" },
|
.{ "cdata.data = 'OK modified'", "OK modified" },
|
||||||
};
|
.{ "cdata.data === 'OK modified'", "true" },
|
||||||
try checkCases(js_env, &get_length);
|
.{ "cdata.data = 'OK'", "OK" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
var get_next_elem_sibling = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.nextElementSibling === null", .ex = "true" },
|
.{ "cdata.length === 2", "true" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "cdata.nextElementSibling === null", "true" },
|
||||||
// create a next element
|
// create a next element
|
||||||
.{ .src = "let next = document.createElement('a')", .ex = "undefined" },
|
.{ "let next = document.createElement('a')", "undefined" },
|
||||||
.{ .src = "link.appendChild(next, cdata) !== undefined", .ex = "true" },
|
.{ "link.appendChild(next, cdata) !== undefined", "true" },
|
||||||
.{ .src = "cdata.nextElementSibling.localName === 'a' ", .ex = "true" },
|
.{ "cdata.nextElementSibling.localName === 'a' ", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &get_next_elem_sibling);
|
|
||||||
|
|
||||||
var get_prev_elem_sibling = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.previousElementSibling === null", .ex = "true" },
|
.{ "cdata.previousElementSibling === null", "true" },
|
||||||
// create a prev element
|
// create a prev element
|
||||||
.{ .src = "let prev = document.createElement('div')", .ex = "undefined" },
|
.{ "let prev = document.createElement('div')", "undefined" },
|
||||||
.{ .src = "link.insertBefore(prev, cdata) !== undefined", .ex = "true" },
|
.{ "link.insertBefore(prev, cdata) !== undefined", "true" },
|
||||||
.{ .src = "cdata.previousElementSibling.localName === 'div' ", .ex = "true" },
|
.{ "cdata.previousElementSibling.localName === 'div' ", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &get_prev_elem_sibling);
|
|
||||||
|
|
||||||
var append_data = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.appendData(' modified')", .ex = "undefined" },
|
.{ "cdata.appendData(' modified')", "undefined" },
|
||||||
.{ .src = "cdata.data === 'OK modified' ", .ex = "true" },
|
.{ "cdata.data === 'OK modified' ", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &append_data);
|
|
||||||
|
|
||||||
var delete_data = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.deleteData('OK'.length, ' modified'.length)", .ex = "undefined" },
|
.{ "cdata.deleteData('OK'.length, ' modified'.length)", "undefined" },
|
||||||
.{ .src = "cdata.data == 'OK'", .ex = "true" },
|
.{ "cdata.data == 'OK'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &delete_data);
|
|
||||||
|
|
||||||
var insert_data = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.insertData('OK'.length-1, 'modified')", .ex = "undefined" },
|
.{ "cdata.insertData('OK'.length-1, 'modified')", "undefined" },
|
||||||
.{ .src = "cdata.data == 'OmodifiedK'", .ex = "true" },
|
.{ "cdata.data == 'OmodifiedK'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &insert_data);
|
|
||||||
|
|
||||||
var replace_data = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.replaceData('OK'.length-1, 'modified'.length, 'replaced')", .ex = "undefined" },
|
.{ "cdata.replaceData('OK'.length-1, 'modified'.length, 'replaced')", "undefined" },
|
||||||
.{ .src = "cdata.data == 'OreplacedK'", .ex = "true" },
|
.{ "cdata.data == 'OreplacedK'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &replace_data);
|
|
||||||
|
|
||||||
var substring_data = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "cdata.substringData('OK'.length-1, 'replaced'.length) == 'replaced'", .ex = "true" },
|
.{ "cdata.substringData('OK'.length-1, 'replaced'.length) == 'replaced'", "true" },
|
||||||
.{ .src = "cdata.substringData('OK'.length-1, 0) == ''", .ex = "true" },
|
.{ "cdata.substringData('OK'.length-1, 0) == ''", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &substring_data);
|
|
||||||
}
|
}
|
||||||
@@ -17,25 +17,20 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const CharacterData = @import("character_data.zig").CharacterData;
|
const CharacterData = @import("character_data.zig").CharacterData;
|
||||||
|
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-comment
|
// https://dom.spec.whatwg.org/#interface-comment
|
||||||
pub const Comment = struct {
|
pub const Comment = struct {
|
||||||
pub const Self = parser.Comment;
|
pub const Self = parser.Comment;
|
||||||
pub const prototype = *CharacterData;
|
pub const prototype = *CharacterData;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Comment {
|
pub fn constructor(data: ?[]const u8, state: *const SessionState) !*parser.Comment {
|
||||||
return parser.documentCreateComment(
|
return parser.documentCreateComment(
|
||||||
parser.documentHTMLToDocument(userctx.document),
|
parser.documentHTMLToDocument(state.document.?),
|
||||||
data orelse "",
|
data orelse "",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,16 +39,16 @@ pub const Comment = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.Comment" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var constructor = [_]Case{
|
|
||||||
.{ .src = "let comment = new Comment('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "comment.data", .ex = "foo" },
|
|
||||||
|
|
||||||
.{ .src = "let emptycomment = new Comment()", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "emptycomment.data", .ex = "" },
|
.{ "let comment = new Comment('foo')", "undefined" },
|
||||||
};
|
.{ "comment.data", "foo" },
|
||||||
try checkCases(js_env, &constructor);
|
|
||||||
|
.{ "let emptycomment = new Comment()", "undefined" },
|
||||||
|
.{ "emptycomment.data", "" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const css = @import("../css/css.zig");
|
const css = @import("../css/css.zig");
|
||||||
const Node = @import("../css/libdom.zig").Node;
|
const Node = @import("../css/libdom.zig").Node;
|
||||||
444
src/browser/dom/document.zig
Normal file
444
src/browser/dom/document.zig
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
// 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 parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
|
const Node = @import("node.zig").Node;
|
||||||
|
const NodeList = @import("nodelist.zig").NodeList;
|
||||||
|
const NodeUnion = @import("node.zig").Union;
|
||||||
|
|
||||||
|
const collection = @import("html_collection.zig");
|
||||||
|
const css = @import("css.zig");
|
||||||
|
|
||||||
|
const Element = @import("element.zig").Element;
|
||||||
|
const ElementUnion = @import("element.zig").Union;
|
||||||
|
|
||||||
|
const DocumentType = @import("document_type.zig").DocumentType;
|
||||||
|
const DocumentFragment = @import("document_fragment.zig").DocumentFragment;
|
||||||
|
const DOMImplementation = @import("implementation.zig").DOMImplementation;
|
||||||
|
|
||||||
|
// WEB IDL https://dom.spec.whatwg.org/#document
|
||||||
|
pub const Document = struct {
|
||||||
|
pub const Self = parser.Document;
|
||||||
|
pub const prototype = *Node;
|
||||||
|
|
||||||
|
pub fn constructor(state: *const SessionState) !*parser.DocumentHTML {
|
||||||
|
const doc = try parser.documentCreateDocument(
|
||||||
|
try parser.documentHTMLGetTitle(state.document.?),
|
||||||
|
);
|
||||||
|
|
||||||
|
// we have to work w/ document instead of html document.
|
||||||
|
const ddoc = parser.documentHTMLToDocument(doc);
|
||||||
|
const ccur = parser.documentHTMLToDocument(state.document.?);
|
||||||
|
try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur));
|
||||||
|
try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur));
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JS funcs
|
||||||
|
// --------
|
||||||
|
pub fn get_implementation(_: *parser.Document) DOMImplementation {
|
||||||
|
return DOMImplementation{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_documentElement(self: *parser.Document) !?ElementUnion {
|
||||||
|
const e = try parser.documentGetDocumentElement(self);
|
||||||
|
if (e == null) return null;
|
||||||
|
return try Element.toInterface(e.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_documentURI(self: *parser.Document) ![]const u8 {
|
||||||
|
return try parser.documentGetDocumentURI(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_URL(self: *parser.Document) ![]const u8 {
|
||||||
|
return try get_documentURI(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO implement contentType
|
||||||
|
pub fn get_contentType(self: *parser.Document) []const u8 {
|
||||||
|
_ = self;
|
||||||
|
return "text/html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO implement compactMode
|
||||||
|
pub fn get_compatMode(self: *parser.Document) []const u8 {
|
||||||
|
_ = self;
|
||||||
|
return "CSS1Compat";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_characterSet(self: *parser.Document) ![]const u8 {
|
||||||
|
return try parser.documentGetInputEncoding(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// alias of get_characterSet
|
||||||
|
pub fn get_charset(self: *parser.Document) ![]const u8 {
|
||||||
|
return try get_characterSet(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// alias of get_characterSet
|
||||||
|
pub fn get_inputEncoding(self: *parser.Document) ![]const u8 {
|
||||||
|
return try get_characterSet(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_doctype(self: *parser.Document) !?*parser.DocumentType {
|
||||||
|
return try parser.documentGetDoctype(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createEvent(_: *parser.Document, eventCstr: []const u8) !*parser.Event {
|
||||||
|
// TODO: for now only "Event" constructor is supported
|
||||||
|
// see table on https://dom.spec.whatwg.org/#dom-document-createevent $2
|
||||||
|
if (std.ascii.eqlIgnoreCase(eventCstr, "Event") or std.ascii.eqlIgnoreCase(eventCstr, "Events")) {
|
||||||
|
return try parser.eventCreate();
|
||||||
|
}
|
||||||
|
return parser.DOMError.NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _getElementById(self: *parser.Document, id: []const u8) !?ElementUnion {
|
||||||
|
const e = try parser.documentGetElementById(self, id) orelse return null;
|
||||||
|
return try Element.toInterface(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createElement(self: *parser.Document, tag_name: []const u8) !ElementUnion {
|
||||||
|
const e = try parser.documentCreateElement(self, tag_name);
|
||||||
|
return try Element.toInterface(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion {
|
||||||
|
const e = try parser.documentCreateElementNS(self, ns, tag_name);
|
||||||
|
return try Element.toInterface(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't simply use libdom dom_document_get_elements_by_tag_name here.
|
||||||
|
// Indeed, netsurf implemented a previous dom spec when
|
||||||
|
// getElementsByTagName returned a NodeList.
|
||||||
|
// But since
|
||||||
|
// https://github.com/whatwg/dom/commit/190700b7c12ecfd3b5ebdb359ab1d6ea9cbf7749
|
||||||
|
// the spec changed to return an HTMLCollection instead.
|
||||||
|
// That's why we reimplemented getElementsByTagName by using an
|
||||||
|
// HTMLCollection in zig here.
|
||||||
|
pub fn _getElementsByTagName(
|
||||||
|
self: *parser.Document,
|
||||||
|
tag_name: []const u8,
|
||||||
|
state: *SessionState,
|
||||||
|
) !collection.HTMLCollection {
|
||||||
|
return try collection.HTMLCollectionByTagName(state.arena, parser.documentToNode(self), tag_name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _getElementsByClassName(
|
||||||
|
self: *parser.Document,
|
||||||
|
classNames: []const u8,
|
||||||
|
state: *SessionState,
|
||||||
|
) !collection.HTMLCollection {
|
||||||
|
const allocator = state.arena;
|
||||||
|
return try collection.HTMLCollectionByClassName(allocator, parser.documentToNode(self), classNames, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createDocumentFragment(self: *parser.Document) !*parser.DocumentFragment {
|
||||||
|
return try parser.documentCreateDocumentFragment(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createTextNode(self: *parser.Document, data: []const u8) !*parser.Text {
|
||||||
|
return try parser.documentCreateTextNode(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createCDATASection(self: *parser.Document, data: []const u8) !*parser.CDATASection {
|
||||||
|
return try parser.documentCreateCDATASection(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createComment(self: *parser.Document, data: []const u8) !*parser.Comment {
|
||||||
|
return try parser.documentCreateComment(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createProcessingInstruction(self: *parser.Document, target: []const u8, data: []const u8) !*parser.ProcessingInstruction {
|
||||||
|
return try parser.documentCreateProcessingInstruction(self, target, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _importNode(self: *parser.Document, node: *parser.Node, deep: ?bool) !NodeUnion {
|
||||||
|
const n = try parser.documentImportNode(self, node, deep orelse false);
|
||||||
|
return try Node.toInterface(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _adoptNode(self: *parser.Document, node: *parser.Node) !NodeUnion {
|
||||||
|
const n = try parser.documentAdoptNode(self, node);
|
||||||
|
return try Node.toInterface(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createAttribute(self: *parser.Document, name: []const u8) !*parser.Attribute {
|
||||||
|
return try parser.documentCreateAttribute(self, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _createAttributeNS(self: *parser.Document, ns: []const u8, qname: []const u8) !*parser.Attribute {
|
||||||
|
return try parser.documentCreateAttributeNS(self, ns, qname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentNode
|
||||||
|
// https://dom.spec.whatwg.org/#parentnode
|
||||||
|
pub fn get_children(self: *parser.Document) !collection.HTMLCollection {
|
||||||
|
return try collection.HTMLCollectionChildren(parser.documentToNode(self), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_firstElementChild(self: *parser.Document) !?ElementUnion {
|
||||||
|
const elt = try parser.documentGetDocumentElement(self) orelse return null;
|
||||||
|
return try Element.toInterface(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lastElementChild(self: *parser.Document) !?ElementUnion {
|
||||||
|
const elt = try parser.documentGetDocumentElement(self) orelse return null;
|
||||||
|
return try Element.toInterface(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_childElementCount(self: *parser.Document) !u32 {
|
||||||
|
_ = try parser.documentGetDocumentElement(self) orelse return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _querySelector(self: *parser.Document, selector: []const u8, state: *SessionState) !?ElementUnion {
|
||||||
|
if (selector.len == 0) return null;
|
||||||
|
|
||||||
|
const allocator = state.arena;
|
||||||
|
const n = try css.querySelector(allocator, parser.documentToNode(self), selector);
|
||||||
|
|
||||||
|
if (n == null) return null;
|
||||||
|
|
||||||
|
return try Element.toInterface(parser.nodeToElement(n.?));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _querySelectorAll(self: *parser.Document, selector: []const u8, state: *SessionState) !NodeList {
|
||||||
|
const allocator = state.arena;
|
||||||
|
return css.querySelectorAll(allocator, parser.documentToNode(self), selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
|
// function must accept either node or string.
|
||||||
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
|
pub fn _prepend(self: *parser.Document, nodes: []const *parser.Node) !void {
|
||||||
|
return Node.prepend(parser.documentToNode(self), nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
|
// function must accept either node or string.
|
||||||
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
|
pub fn _append(self: *parser.Document, nodes: []const *parser.Node) !void {
|
||||||
|
return Node.append(parser.documentToNode(self), nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
|
// function must accept either node or string.
|
||||||
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
|
pub fn _replaceChildren(self: *parser.Document, nodes: []const *parser.Node) !void {
|
||||||
|
return Node.replaceChildren(parser.documentToNode(self), nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(_: *parser.Document, _: std.mem.Allocator) void {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser.DOM.Document" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.__proto__.__proto__.constructor.name", "Document" },
|
||||||
|
.{ "document.__proto__.__proto__.__proto__.constructor.name", "Node" },
|
||||||
|
.{ "document.__proto__.__proto__.__proto__.__proto__.constructor.name", "EventTarget" },
|
||||||
|
|
||||||
|
.{ "let newdoc = new Document()", "undefined" },
|
||||||
|
.{ "newdoc.documentElement", "null" },
|
||||||
|
.{ "newdoc.children.length", "0" },
|
||||||
|
.{ "newdoc.getElementsByTagName('*').length", "0" },
|
||||||
|
.{ "newdoc.getElementsByTagName('*').item(0)", "null" },
|
||||||
|
.{ "newdoc.inputEncoding === document.inputEncoding", "true" },
|
||||||
|
.{ "newdoc.documentURI === document.documentURI", "true" },
|
||||||
|
.{ "newdoc.URL === document.URL", "true" },
|
||||||
|
.{ "newdoc.compatMode === document.compatMode", "true" },
|
||||||
|
.{ "newdoc.characterSet === document.characterSet", "true" },
|
||||||
|
.{ "newdoc.charset === document.charset", "true" },
|
||||||
|
.{ "newdoc.contentType === document.contentType", "true" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let getElementById = document.getElementById('content')", "undefined" },
|
||||||
|
.{ "getElementById.constructor.name", "HTMLDivElement" },
|
||||||
|
.{ "getElementById.localName", "div" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let getElementsByTagName = document.getElementsByTagName('p')", "undefined" },
|
||||||
|
.{ "getElementsByTagName.length", "2" },
|
||||||
|
.{ "getElementsByTagName.item(0).localName", "p" },
|
||||||
|
.{ "getElementsByTagName.item(1).localName", "p" },
|
||||||
|
.{ "let getElementsByTagNameAll = document.getElementsByTagName('*')", "undefined" },
|
||||||
|
.{ "getElementsByTagNameAll.length", "8" },
|
||||||
|
.{ "getElementsByTagNameAll.item(0).localName", "html" },
|
||||||
|
.{ "getElementsByTagNameAll.item(7).localName", "p" },
|
||||||
|
.{ "getElementsByTagNameAll.namedItem('para-empty-child').localName", "span" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let ok = document.getElementsByClassName('ok')", "undefined" },
|
||||||
|
.{ "ok.length", "2" },
|
||||||
|
.{ "let empty = document.getElementsByClassName('empty')", "undefined" },
|
||||||
|
.{ "empty.length", "1" },
|
||||||
|
.{ "let emptyok = document.getElementsByClassName('empty ok')", "undefined" },
|
||||||
|
.{ "emptyok.length", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let e = document.documentElement", "undefined" },
|
||||||
|
.{ "e.localName", "html" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.characterSet", "UTF-8" },
|
||||||
|
.{ "document.charset", "UTF-8" },
|
||||||
|
.{ "document.inputEncoding", "UTF-8" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.compatMode", "CSS1Compat" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.contentType", "text/html" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.documentURI", "about:blank" },
|
||||||
|
.{ "document.URL", "about:blank" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let impl = document.implementation", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let d = new Document()", "undefined" },
|
||||||
|
.{ "d.characterSet", "UTF-8" },
|
||||||
|
.{ "d.URL", "about:blank" },
|
||||||
|
.{ "d.documentURI", "about:blank" },
|
||||||
|
.{ "d.compatMode", "CSS1Compat" },
|
||||||
|
.{ "d.contentType", "text/html" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var v = document.createDocumentFragment()", "undefined" },
|
||||||
|
.{ "v.nodeName", "#document-fragment" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var v = document.createTextNode('foo')", "undefined" },
|
||||||
|
.{ "v.nodeName", "#text" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var v = document.createCDATASection('foo')", "undefined" },
|
||||||
|
.{ "v.nodeName", "#cdata-section" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var v = document.createComment('foo')", "undefined" },
|
||||||
|
.{ "v.nodeName", "#comment" },
|
||||||
|
.{ "let v2 = v.cloneNode()", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let pi = document.createProcessingInstruction('foo', 'bar')", "undefined" },
|
||||||
|
.{ "pi.target", "foo" },
|
||||||
|
.{ "let pi2 = pi.cloneNode()", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let nimp = document.getElementById('content')", "undefined" },
|
||||||
|
.{ "var v = document.importNode(nimp)", "undefined" },
|
||||||
|
.{ "v.nodeName", "DIV" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var v = document.createAttribute('foo')", "undefined" },
|
||||||
|
.{ "v.nodeName", "foo" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.children.length", "1" },
|
||||||
|
.{ "document.children.item(0).nodeName", "HTML" },
|
||||||
|
.{ "document.firstElementChild.nodeName", "HTML" },
|
||||||
|
.{ "document.lastElementChild.nodeName", "HTML" },
|
||||||
|
.{ "document.childElementCount", "1" },
|
||||||
|
|
||||||
|
.{ "let nd = new Document()", "undefined" },
|
||||||
|
.{ "nd.children.length", "0" },
|
||||||
|
.{ "nd.children.item(0)", "null" },
|
||||||
|
.{ "nd.firstElementChild", "null" },
|
||||||
|
.{ "nd.lastElementChild", "null" },
|
||||||
|
.{ "nd.childElementCount", "0" },
|
||||||
|
|
||||||
|
.{ "let emptydoc = document.createElement('html')", "undefined" },
|
||||||
|
.{ "emptydoc.prepend(document.createElement('html'))", "undefined" },
|
||||||
|
|
||||||
|
.{ "let emptydoc2 = document.createElement('html')", "undefined" },
|
||||||
|
.{ "emptydoc2.append(document.createElement('html'))", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.querySelector('')", "null" },
|
||||||
|
.{ "document.querySelector('*').nodeName", "HTML" },
|
||||||
|
.{ "document.querySelector('#content').id", "content" },
|
||||||
|
.{ "document.querySelector('#para').id", "para" },
|
||||||
|
.{ "document.querySelector('.ok').id", "link" },
|
||||||
|
.{ "document.querySelector('a ~ p').id", "para-empty" },
|
||||||
|
.{ "document.querySelector(':root').nodeName", "HTML" },
|
||||||
|
|
||||||
|
.{ "document.querySelectorAll('p').length", "2" },
|
||||||
|
.{
|
||||||
|
\\ Array.from(document.querySelectorAll('#content > p#para-empty'))
|
||||||
|
\\ .map(row => row.querySelector('span').textContent)
|
||||||
|
\\ .length;
|
||||||
|
,
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
// this test breaks the doc structure, keep it at the end of the test
|
||||||
|
// suite.
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let nadop = document.getElementById('content')", "undefined" },
|
||||||
|
.{ "var v = document.adoptNode(nadop)", "undefined" },
|
||||||
|
.{ "v.nodeName", "DIV" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
const Case = testing.JsRunner.Case;
|
||||||
|
const tags = comptime parser.Tag.all();
|
||||||
|
var createElements: [(tags.len) * 2]Case = undefined;
|
||||||
|
inline for (tags, 0..) |tag, i| {
|
||||||
|
const tag_name = @tagName(tag);
|
||||||
|
createElements[i * 2] = Case{
|
||||||
|
"var " ++ tag_name ++ "Elem = document.createElement('" ++ tag_name ++ "')",
|
||||||
|
"undefined",
|
||||||
|
};
|
||||||
|
createElements[(i * 2) + 1] = Case{
|
||||||
|
tag_name ++ "Elem.localName",
|
||||||
|
tag_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try runner.testCases(&createElements, .{});
|
||||||
|
}
|
||||||
@@ -18,39 +18,30 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
|
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
|
||||||
|
|
||||||
// WEB IDL https://dom.spec.whatwg.org/#documentfragment
|
// WEB IDL https://dom.spec.whatwg.org/#documentfragment
|
||||||
pub const DocumentFragment = struct {
|
pub const DocumentFragment = struct {
|
||||||
pub const Self = parser.DocumentFragment;
|
pub const Self = parser.DocumentFragment;
|
||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn constructor(userctx: UserContext) !*parser.DocumentFragment {
|
pub fn constructor(state: *const SessionState) !*parser.DocumentFragment {
|
||||||
return parser.documentCreateDocumentFragment(
|
return parser.documentCreateDocumentFragment(
|
||||||
parser.documentHTMLToDocument(userctx.document),
|
parser.documentHTMLToDocument(state.document.?),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.DOM.DocumentFragment" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
try runner.testCases(&.{
|
||||||
_: std.mem.Allocator,
|
.{ "const dc = new DocumentFragment()", "undefined" },
|
||||||
js_env: *jsruntime.Env,
|
.{ "dc.constructor.name", "DocumentFragment" },
|
||||||
) anyerror!void {
|
}, .{});
|
||||||
var constructor = [_]Case{
|
|
||||||
.{ .src = "const dc = new DocumentFragment()", .ex = "undefined" },
|
|
||||||
.{ .src = "dc.constructor.name", .ex = "DocumentFragment" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &constructor);
|
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ const Node = @import("node.zig").Node;
|
|||||||
pub const DocumentType = struct {
|
pub const DocumentType = struct {
|
||||||
pub const Self = parser.DocumentType;
|
pub const Self = parser.DocumentType;
|
||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_name(self: *parser.DocumentType) ![]const u8 {
|
pub fn get_name(self: *parser.DocumentType) ![]const u8 {
|
||||||
return try parser.documentTypeGetName(self);
|
return try parser.documentTypeGetName(self);
|
||||||
@@ -22,7 +22,7 @@ const DOMImplementation = @import("implementation.zig").DOMImplementation;
|
|||||||
const NamedNodeMap = @import("namednodemap.zig").NamedNodeMap;
|
const NamedNodeMap = @import("namednodemap.zig").NamedNodeMap;
|
||||||
const DOMTokenList = @import("token_list.zig").DOMTokenList;
|
const DOMTokenList = @import("token_list.zig").DOMTokenList;
|
||||||
const NodeList = @import("nodelist.zig");
|
const NodeList = @import("nodelist.zig");
|
||||||
const Nod = @import("node.zig");
|
const Node = @import("node.zig");
|
||||||
const MutationObserver = @import("mutation_observer.zig");
|
const MutationObserver = @import("mutation_observer.zig");
|
||||||
|
|
||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
@@ -32,7 +32,7 @@ pub const Interfaces = .{
|
|||||||
NamedNodeMap,
|
NamedNodeMap,
|
||||||
DOMTokenList,
|
DOMTokenList,
|
||||||
NodeList.Interfaces,
|
NodeList.Interfaces,
|
||||||
Nod.Node,
|
Node.Node,
|
||||||
Nod.Interfaces,
|
Node.Interfaces,
|
||||||
MutationObserver.Interfaces,
|
MutationObserver.Interfaces,
|
||||||
};
|
};
|
||||||
@@ -18,22 +18,17 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
const Variadic = jsruntime.Variadic;
|
|
||||||
|
|
||||||
const collection = @import("html_collection.zig");
|
const collection = @import("html_collection.zig");
|
||||||
const dump = @import("../browser/dump.zig");
|
const dump = @import("../dump.zig");
|
||||||
const css = @import("css.zig");
|
const css = @import("css.zig");
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
const Walker = @import("walker.zig").WalkerDepthFirst;
|
const Walker = @import("walker.zig").WalkerDepthFirst;
|
||||||
const NodeList = @import("nodelist.zig").NodeList;
|
const NodeList = @import("nodelist.zig").NodeList;
|
||||||
const HTMLElem = @import("../html/elements.zig");
|
const HTMLElem = @import("../html/elements.zig");
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
|
||||||
pub const Union = @import("../html/elements.zig").Union;
|
pub const Union = @import("../html/elements.zig").Union;
|
||||||
|
|
||||||
const DOMException = @import("exceptions.zig").DOMException;
|
const DOMException = @import("exceptions.zig").DOMException;
|
||||||
@@ -42,7 +37,6 @@ const DOMException = @import("exceptions.zig").DOMException;
|
|||||||
pub const Element = struct {
|
pub const Element = struct {
|
||||||
pub const Self = parser.Element;
|
pub const Self = parser.Element;
|
||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const DOMRect = struct {
|
pub const DOMRect = struct {
|
||||||
x: f64,
|
x: f64,
|
||||||
@@ -106,8 +100,8 @@ pub const Element = struct {
|
|||||||
return try parser.nodeGetAttributes(parser.elementToNode(self));
|
return try parser.nodeGetAttributes(parser.elementToNode(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_innerHTML(self: *parser.Element, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_innerHTML(self: *parser.Element, state: *SessionState) ![]const u8 {
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
var buf = std.ArrayList(u8).init(state.arena);
|
||||||
defer buf.deinit();
|
defer buf.deinit();
|
||||||
|
|
||||||
try dump.writeChildren(parser.elementToNode(self), buf.writer());
|
try dump.writeChildren(parser.elementToNode(self), buf.writer());
|
||||||
@@ -116,8 +110,8 @@ pub const Element = struct {
|
|||||||
return buf.toOwnedSlice();
|
return buf.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outerHTML(self: *parser.Element, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_outerHTML(self: *parser.Element, state: *SessionState) ![]const u8 {
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
var buf = std.ArrayList(u8).init(state.arena);
|
||||||
defer buf.deinit();
|
defer buf.deinit();
|
||||||
|
|
||||||
try dump.writeNode(parser.elementToNode(self), buf.writer());
|
try dump.writeNode(parser.elementToNode(self), buf.writer());
|
||||||
@@ -232,11 +226,11 @@ pub const Element = struct {
|
|||||||
|
|
||||||
pub fn _getElementsByTagName(
|
pub fn _getElementsByTagName(
|
||||||
self: *parser.Element,
|
self: *parser.Element,
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
tag_name: []const u8,
|
tag_name: []const u8,
|
||||||
|
state: *SessionState,
|
||||||
) !collection.HTMLCollection {
|
) !collection.HTMLCollection {
|
||||||
return try collection.HTMLCollectionByTagName(
|
return try collection.HTMLCollectionByTagName(
|
||||||
alloc,
|
state.arena,
|
||||||
parser.elementToNode(self),
|
parser.elementToNode(self),
|
||||||
tag_name,
|
tag_name,
|
||||||
false,
|
false,
|
||||||
@@ -245,11 +239,11 @@ pub const Element = struct {
|
|||||||
|
|
||||||
pub fn _getElementsByClassName(
|
pub fn _getElementsByClassName(
|
||||||
self: *parser.Element,
|
self: *parser.Element,
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
classNames: []const u8,
|
classNames: []const u8,
|
||||||
|
state: *SessionState,
|
||||||
) !collection.HTMLCollection {
|
) !collection.HTMLCollection {
|
||||||
return try collection.HTMLCollectionByClassName(
|
return try collection.HTMLCollectionByClassName(
|
||||||
alloc,
|
state.arena,
|
||||||
parser.elementToNode(self),
|
parser.elementToNode(self),
|
||||||
classNames,
|
classNames,
|
||||||
false,
|
false,
|
||||||
@@ -312,51 +306,51 @@ pub const Element = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _querySelector(self: *parser.Element, alloc: std.mem.Allocator, selector: []const u8) !?Union {
|
pub fn _querySelector(self: *parser.Element, selector: []const u8, state: *SessionState) !?Union {
|
||||||
if (selector.len == 0) return null;
|
if (selector.len == 0) return null;
|
||||||
|
|
||||||
const n = try css.querySelector(alloc, parser.elementToNode(self), selector);
|
const n = try css.querySelector(state.arena, parser.elementToNode(self), selector);
|
||||||
|
|
||||||
if (n == null) return null;
|
if (n == null) return null;
|
||||||
|
|
||||||
return try toInterface(parser.nodeToElement(n.?));
|
return try toInterface(parser.nodeToElement(n.?));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _querySelectorAll(self: *parser.Element, alloc: std.mem.Allocator, selector: []const u8) !NodeList {
|
pub fn _querySelectorAll(self: *parser.Element, selector: []const u8, state: *SessionState) !NodeList {
|
||||||
return css.querySelectorAll(alloc, parser.elementToNode(self), selector);
|
return css.querySelectorAll(state.arena, parser.elementToNode(self), selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn _prepend(self: *parser.Element, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn _prepend(self: *parser.Element, nodes: []const *parser.Node) !void {
|
||||||
return Node.prepend(parser.elementToNode(self), nodes);
|
return Node.prepend(parser.elementToNode(self), nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn _append(self: *parser.Element, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn _append(self: *parser.Element, nodes: []const *parser.Node) !void {
|
||||||
return Node.append(parser.elementToNode(self), nodes);
|
return Node.append(parser.elementToNode(self), nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn _replaceChildren(self: *parser.Element, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn _replaceChildren(self: *parser.Element, nodes: []const *parser.Node) !void {
|
||||||
return Node.replaceChildren(parser.elementToNode(self), nodes);
|
return Node.replaceChildren(parser.elementToNode(self), nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _getBoundingClientRect(self: *parser.Element, user_context: UserContext) !DOMRect {
|
pub fn _getBoundingClientRect(self: *parser.Element, state: *SessionState) !DOMRect {
|
||||||
return user_context.renderer.getRect(self);
|
return state.renderer.getRect(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clientWidth(_: *parser.Element, user_context: UserContext) u32 {
|
pub fn get_clientWidth(_: *parser.Element, state: *SessionState) u32 {
|
||||||
return user_context.renderer.width();
|
return state.renderer.width();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_clientHeight(_: *parser.Element, user_context: UserContext) u32 {
|
pub fn get_clientHeight(_: *parser.Element, state: *SessionState) u32 {
|
||||||
return user_context.renderer.height();
|
return state.renderer.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *parser.Element, _: std.mem.Allocator) void {}
|
pub fn deinit(_: *parser.Element, _: std.mem.Allocator) void {}
|
||||||
@@ -365,172 +359,161 @@ pub const Element = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.Element" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var getters = [_]Case{
|
|
||||||
.{ .src = "let g = document.getElementById('content')", .ex = "undefined" },
|
|
||||||
.{ .src = "g.namespaceURI", .ex = "http://www.w3.org/1999/xhtml" },
|
|
||||||
.{ .src = "g.prefix", .ex = "null" },
|
|
||||||
.{ .src = "g.localName", .ex = "div" },
|
|
||||||
.{ .src = "g.tagName", .ex = "DIV" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getters);
|
|
||||||
|
|
||||||
var gettersetters = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let gs = document.getElementById('content')", .ex = "undefined" },
|
.{ "let g = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "gs.id", .ex = "content" },
|
.{ "g.namespaceURI", "http://www.w3.org/1999/xhtml" },
|
||||||
.{ .src = "gs.id = 'foo'", .ex = "foo" },
|
.{ "g.prefix", "null" },
|
||||||
.{ .src = "gs.id", .ex = "foo" },
|
.{ "g.localName", "div" },
|
||||||
.{ .src = "gs.id = 'content'", .ex = "content" },
|
.{ "g.tagName", "DIV" },
|
||||||
.{ .src = "gs.className", .ex = "" },
|
}, .{});
|
||||||
.{ .src = "let gs2 = document.getElementById('para-empty')", .ex = "undefined" },
|
|
||||||
.{ .src = "gs2.className", .ex = "ok empty" },
|
|
||||||
.{ .src = "gs2.className = 'foo bar baz'", .ex = "foo bar baz" },
|
|
||||||
.{ .src = "gs2.className", .ex = "foo bar baz" },
|
|
||||||
.{ .src = "gs2.className = 'ok empty'", .ex = "ok empty" },
|
|
||||||
.{ .src = "let cl = gs2.classList", .ex = "undefined" },
|
|
||||||
.{ .src = "cl.length", .ex = "2" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &gettersetters);
|
|
||||||
|
|
||||||
var attribute = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let a = document.getElementById('content')", .ex = "undefined" },
|
.{ "let gs = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "a.hasAttributes()", .ex = "true" },
|
.{ "gs.id", "content" },
|
||||||
.{ .src = "a.attributes.length", .ex = "1" },
|
.{ "gs.id = 'foo'", "foo" },
|
||||||
|
.{ "gs.id", "foo" },
|
||||||
|
.{ "gs.id = 'content'", "content" },
|
||||||
|
.{ "gs.className", "" },
|
||||||
|
.{ "let gs2 = document.getElementById('para-empty')", "undefined" },
|
||||||
|
.{ "gs2.className", "ok empty" },
|
||||||
|
.{ "gs2.className = 'foo bar baz'", "foo bar baz" },
|
||||||
|
.{ "gs2.className", "foo bar baz" },
|
||||||
|
.{ "gs2.className = 'ok empty'", "ok empty" },
|
||||||
|
.{ "let cl = gs2.classList", "undefined" },
|
||||||
|
.{ "cl.length", "2" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
.{ .src = "a.getAttribute('id')", .ex = "content" },
|
try runner.testCases(&.{
|
||||||
|
.{ "let a = document.getElementById('content')", "undefined" },
|
||||||
|
.{ "a.hasAttributes()", "true" },
|
||||||
|
.{ "a.attributes.length", "1" },
|
||||||
|
|
||||||
.{ .src = "a.hasAttribute('foo')", .ex = "false" },
|
.{ "a.getAttribute('id')", "content" },
|
||||||
.{ .src = "a.getAttribute('foo')", .ex = "null" },
|
|
||||||
|
|
||||||
.{ .src = "a.setAttribute('foo', 'bar')", .ex = "undefined" },
|
.{ "a.hasAttribute('foo')", "false" },
|
||||||
.{ .src = "a.hasAttribute('foo')", .ex = "true" },
|
.{ "a.getAttribute('foo')", "null" },
|
||||||
.{ .src = "a.getAttribute('foo')", .ex = "bar" },
|
|
||||||
|
|
||||||
.{ .src = "a.setAttribute('foo', 'baz')", .ex = "undefined" },
|
.{ "a.setAttribute('foo', 'bar')", "undefined" },
|
||||||
.{ .src = "a.hasAttribute('foo')", .ex = "true" },
|
.{ "a.hasAttribute('foo')", "true" },
|
||||||
.{ .src = "a.getAttribute('foo')", .ex = "baz" },
|
.{ "a.getAttribute('foo')", "bar" },
|
||||||
|
|
||||||
.{ .src = "a.removeAttribute('foo')", .ex = "undefined" },
|
.{ "a.setAttribute('foo', 'baz')", "undefined" },
|
||||||
.{ .src = "a.hasAttribute('foo')", .ex = "false" },
|
.{ "a.hasAttribute('foo')", "true" },
|
||||||
.{ .src = "a.getAttribute('foo')", .ex = "null" },
|
.{ "a.getAttribute('foo')", "baz" },
|
||||||
};
|
|
||||||
try checkCases(js_env, &attribute);
|
|
||||||
|
|
||||||
var toggleAttr = [_]Case{
|
.{ "a.removeAttribute('foo')", "undefined" },
|
||||||
.{ .src = "let b = document.getElementById('content')", .ex = "undefined" },
|
.{ "a.hasAttribute('foo')", "false" },
|
||||||
.{ .src = "b.toggleAttribute('foo')", .ex = "true" },
|
.{ "a.getAttribute('foo')", "null" },
|
||||||
.{ .src = "b.hasAttribute('foo')", .ex = "true" },
|
}, .{});
|
||||||
.{ .src = "b.getAttribute('foo')", .ex = "" },
|
|
||||||
|
|
||||||
.{ .src = "b.toggleAttribute('foo')", .ex = "false" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "b.hasAttribute('foo')", .ex = "false" },
|
.{ "let b = document.getElementById('content')", "undefined" },
|
||||||
};
|
.{ "b.toggleAttribute('foo')", "true" },
|
||||||
try checkCases(js_env, &toggleAttr);
|
.{ "b.hasAttribute('foo')", "true" },
|
||||||
|
.{ "b.getAttribute('foo')", "" },
|
||||||
|
|
||||||
var parentNode = [_]Case{
|
.{ "b.toggleAttribute('foo')", "false" },
|
||||||
.{ .src = "let c = document.getElementById('content')", .ex = "undefined" },
|
.{ "b.hasAttribute('foo')", "false" },
|
||||||
.{ .src = "c.children.length", .ex = "3" },
|
}, .{});
|
||||||
.{ .src = "c.firstElementChild.nodeName", .ex = "A" },
|
|
||||||
.{ .src = "c.lastElementChild.nodeName", .ex = "P" },
|
|
||||||
.{ .src = "c.childElementCount", .ex = "3" },
|
|
||||||
|
|
||||||
.{ .src = "c.prepend(document.createTextNode('foo'))", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "c.append(document.createTextNode('bar'))", .ex = "undefined" },
|
.{ "let c = document.getElementById('content')", "undefined" },
|
||||||
};
|
.{ "c.children.length", "3" },
|
||||||
try checkCases(js_env, &parentNode);
|
.{ "c.firstElementChild.nodeName", "A" },
|
||||||
|
.{ "c.lastElementChild.nodeName", "P" },
|
||||||
|
.{ "c.childElementCount", "3" },
|
||||||
|
|
||||||
var elementSibling = [_]Case{
|
.{ "c.prepend(document.createTextNode('foo'))", "undefined" },
|
||||||
.{ .src = "let d = document.getElementById('para')", .ex = "undefined" },
|
.{ "c.append(document.createTextNode('bar'))", "undefined" },
|
||||||
.{ .src = "d.previousElementSibling.nodeName", .ex = "P" },
|
}, .{});
|
||||||
.{ .src = "d.nextElementSibling", .ex = "null" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &elementSibling);
|
|
||||||
|
|
||||||
var querySelector = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let e = document.getElementById('content')", .ex = "undefined" },
|
.{ "let d = document.getElementById('para')", "undefined" },
|
||||||
.{ .src = "e.querySelector('foo')", .ex = "null" },
|
.{ "d.previousElementSibling.nodeName", "P" },
|
||||||
.{ .src = "e.querySelector('#foo')", .ex = "null" },
|
.{ "d.nextElementSibling", "null" },
|
||||||
.{ .src = "e.querySelector('#link').id", .ex = "link" },
|
}, .{});
|
||||||
.{ .src = "e.querySelector('#para').id", .ex = "para" },
|
|
||||||
.{ .src = "e.querySelector('*').id", .ex = "link" },
|
|
||||||
.{ .src = "e.querySelector('')", .ex = "null" },
|
|
||||||
.{ .src = "e.querySelector('*').id", .ex = "link" },
|
|
||||||
.{ .src = "e.querySelector('#content')", .ex = "null" },
|
|
||||||
.{ .src = "e.querySelector('#para').id", .ex = "para" },
|
|
||||||
.{ .src = "e.querySelector('.ok').id", .ex = "link" },
|
|
||||||
.{ .src = "e.querySelector('a ~ p').id", .ex = "para-empty" },
|
|
||||||
|
|
||||||
.{ .src = "e.querySelectorAll('foo').length", .ex = "0" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "e.querySelectorAll('#foo').length", .ex = "0" },
|
.{ "let e = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "e.querySelectorAll('#link').length", .ex = "1" },
|
.{ "e.querySelector('foo')", "null" },
|
||||||
.{ .src = "e.querySelectorAll('#link').item(0).id", .ex = "link" },
|
.{ "e.querySelector('#foo')", "null" },
|
||||||
.{ .src = "e.querySelectorAll('#para').length", .ex = "1" },
|
.{ "e.querySelector('#link').id", "link" },
|
||||||
.{ .src = "e.querySelectorAll('#para').item(0).id", .ex = "para" },
|
.{ "e.querySelector('#para').id", "para" },
|
||||||
.{ .src = "e.querySelectorAll('*').length", .ex = "4" },
|
.{ "e.querySelector('*').id", "link" },
|
||||||
.{ .src = "e.querySelectorAll('p').length", .ex = "2" },
|
.{ "e.querySelector('')", "null" },
|
||||||
.{ .src = "e.querySelectorAll('.ok').item(0).id", .ex = "link" },
|
.{ "e.querySelector('*').id", "link" },
|
||||||
};
|
.{ "e.querySelector('#content')", "null" },
|
||||||
try checkCases(js_env, &querySelector);
|
.{ "e.querySelector('#para').id", "para" },
|
||||||
|
.{ "e.querySelector('.ok').id", "link" },
|
||||||
|
.{ "e.querySelector('a ~ p').id", "para-empty" },
|
||||||
|
|
||||||
var attrNode = [_]Case{
|
.{ "e.querySelectorAll('foo').length", "0" },
|
||||||
.{ .src = "let f = document.getElementById('content')", .ex = "undefined" },
|
.{ "e.querySelectorAll('#foo').length", "0" },
|
||||||
.{ .src = "let ff = document.createAttribute('foo')", .ex = "undefined" },
|
.{ "e.querySelectorAll('#link').length", "1" },
|
||||||
.{ .src = "f.setAttributeNode(ff)", .ex = "null" },
|
.{ "e.querySelectorAll('#link').item(0).id", "link" },
|
||||||
.{ .src = "f.getAttributeNode('foo').name", .ex = "foo" },
|
.{ "e.querySelectorAll('#para').length", "1" },
|
||||||
.{ .src = "f.removeAttributeNode(ff).name", .ex = "foo" },
|
.{ "e.querySelectorAll('#para').item(0).id", "para" },
|
||||||
.{ .src = "f.getAttributeNode('bar')", .ex = "null" },
|
.{ "e.querySelectorAll('*').length", "4" },
|
||||||
};
|
.{ "e.querySelectorAll('p').length", "2" },
|
||||||
try checkCases(js_env, &attrNode);
|
.{ "e.querySelectorAll('.ok').item(0).id", "link" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
var innerHTML = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.getElementById('para').innerHTML", .ex = " And" },
|
.{ "let f = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "<span id=\"para-empty-child\"></span>" },
|
.{ "let ff = document.createAttribute('foo')", "undefined" },
|
||||||
|
.{ "f.setAttributeNode(ff)", "null" },
|
||||||
|
.{ "f.getAttributeNode('foo').name", "foo" },
|
||||||
|
.{ "f.removeAttributeNode(ff).name", "foo" },
|
||||||
|
.{ "f.getAttributeNode('bar')", "null" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
.{ .src = "let h = document.getElementById('para-empty')", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "const prev = h.innerHTML", .ex = "undefined" },
|
.{ "document.getElementById('para').innerHTML", " And" },
|
||||||
.{ .src = "h.innerHTML = '<p id=\"hello\">hello world</p>'", .ex = "<p id=\"hello\">hello world</p>" },
|
.{ "document.getElementById('para-empty').innerHTML.trim()", "<span id=\"para-empty-child\"></span>" },
|
||||||
.{ .src = "h.innerHTML", .ex = "<p id=\"hello\">hello world</p>" },
|
|
||||||
.{ .src = "h.firstChild.nodeName", .ex = "P" },
|
|
||||||
.{ .src = "h.firstChild.id", .ex = "hello" },
|
|
||||||
.{ .src = "h.firstChild.textContent", .ex = "hello world" },
|
|
||||||
.{ .src = "h.innerHTML = prev; true", .ex = "true" },
|
|
||||||
.{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "<span id=\"para-empty-child\"></span>" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &innerHTML);
|
|
||||||
|
|
||||||
var outerHTML = [_]Case{
|
.{ "let h = document.getElementById('para-empty')", "undefined" },
|
||||||
.{ .src = "document.getElementById('para').outerHTML", .ex = "<p id=\"para\"> And</p>" },
|
.{ "const prev = h.innerHTML", "undefined" },
|
||||||
};
|
.{ "h.innerHTML = '<p id=\"hello\">hello world</p>'", "<p id=\"hello\">hello world</p>" },
|
||||||
|
.{ "h.innerHTML", "<p id=\"hello\">hello world</p>" },
|
||||||
|
.{ "h.firstChild.nodeName", "P" },
|
||||||
|
.{ "h.firstChild.id", "hello" },
|
||||||
|
.{ "h.firstChild.textContent", "hello world" },
|
||||||
|
.{ "h.innerHTML = prev; true", "true" },
|
||||||
|
.{ "document.getElementById('para-empty').innerHTML.trim()", "<span id=\"para-empty-child\"></span>" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
var getBoundingClientRect = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.getElementById('para').clientWidth", .ex = "0" },
|
.{ "document.getElementById('para').outerHTML", "<p id=\"para\"> And</p>" },
|
||||||
.{ .src = "document.getElementById('para').clientHeight", .ex = "1" },
|
}, .{});
|
||||||
|
|
||||||
.{ .src = "let r1 = document.getElementById('para').getBoundingClientRect()", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "r1.x", .ex = "1" },
|
.{ "document.getElementById('para').clientWidth", "0" },
|
||||||
.{ .src = "r1.y", .ex = "0" },
|
.{ "document.getElementById('para').clientHeight", "1" },
|
||||||
.{ .src = "r1.width", .ex = "1" },
|
|
||||||
.{ .src = "r1.height", .ex = "1" },
|
|
||||||
|
|
||||||
.{ .src = "let r2 = document.getElementById('content').getBoundingClientRect()", .ex = "undefined" },
|
.{ "let r1 = document.getElementById('para').getBoundingClientRect()", "undefined" },
|
||||||
.{ .src = "r2.x", .ex = "2" },
|
.{ "r1.x", "1" },
|
||||||
.{ .src = "r2.y", .ex = "0" },
|
.{ "r1.y", "0" },
|
||||||
.{ .src = "r2.width", .ex = "1" },
|
.{ "r1.width", "1" },
|
||||||
.{ .src = "r2.height", .ex = "1" },
|
.{ "r1.height", "1" },
|
||||||
|
|
||||||
.{ .src = "let r3 = document.getElementById('para').getBoundingClientRect()", .ex = "undefined" },
|
.{ "let r2 = document.getElementById('content').getBoundingClientRect()", "undefined" },
|
||||||
.{ .src = "r3.x", .ex = "1" },
|
.{ "r2.x", "2" },
|
||||||
.{ .src = "r3.y", .ex = "0" },
|
.{ "r2.y", "0" },
|
||||||
.{ .src = "r3.width", .ex = "1" },
|
.{ "r2.width", "1" },
|
||||||
.{ .src = "r3.height", .ex = "1" },
|
.{ "r2.height", "1" },
|
||||||
|
|
||||||
.{ .src = "document.getElementById('para').clientWidth", .ex = "2" },
|
.{ "let r3 = document.getElementById('para').getBoundingClientRect()", "undefined" },
|
||||||
.{ .src = "document.getElementById('para').clientHeight", .ex = "1" },
|
.{ "r3.x", "1" },
|
||||||
};
|
.{ "r3.y", "0" },
|
||||||
try checkCases(js_env, &getBoundingClientRect);
|
.{ "r3.width", "1" },
|
||||||
|
.{ "r3.height", "1" },
|
||||||
|
|
||||||
try checkCases(js_env, &outerHTML);
|
.{ "document.getElementById('para').clientWidth", "2" },
|
||||||
|
.{ "document.getElementById('para').clientHeight", "1" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
226
src/browser/dom/event_target.zig
Normal file
226
src/browser/dom/event_target.zig
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
// 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 Env = @import("../env.zig").Env;
|
||||||
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
|
const EventHandler = @import("../events/event.zig").EventHandler;
|
||||||
|
|
||||||
|
const DOMException = @import("exceptions.zig").DOMException;
|
||||||
|
const Nod = @import("node.zig");
|
||||||
|
|
||||||
|
// EventTarget interfaces
|
||||||
|
pub const Union = Nod.Union;
|
||||||
|
|
||||||
|
// EventTarget implementation
|
||||||
|
pub const EventTarget = struct {
|
||||||
|
pub const Self = parser.EventTarget;
|
||||||
|
pub const Exception = DOMException;
|
||||||
|
|
||||||
|
pub fn toInterface(et: *parser.EventTarget) !Union {
|
||||||
|
// NOTE: for now we state that all EventTarget are Nodes
|
||||||
|
// TODO: handle other types (eg. Window)
|
||||||
|
return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// JS funcs
|
||||||
|
// --------
|
||||||
|
|
||||||
|
pub fn _addEventListener(
|
||||||
|
self: *parser.EventTarget,
|
||||||
|
eventType: []const u8,
|
||||||
|
cbk: Env.Callback,
|
||||||
|
capture: ?bool,
|
||||||
|
state: *SessionState,
|
||||||
|
// TODO: hanle EventListenerOptions
|
||||||
|
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
|
) !void {
|
||||||
|
// check if event target has already this listener
|
||||||
|
const lst = try parser.eventTargetHasListener(
|
||||||
|
self,
|
||||||
|
eventType,
|
||||||
|
capture orelse false,
|
||||||
|
cbk.id,
|
||||||
|
);
|
||||||
|
if (lst != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try parser.eventTargetAddEventListener(
|
||||||
|
self,
|
||||||
|
state.arena,
|
||||||
|
eventType,
|
||||||
|
EventHandler,
|
||||||
|
.{ .cbk = cbk },
|
||||||
|
capture orelse false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _removeEventListener(
|
||||||
|
self: *parser.EventTarget,
|
||||||
|
eventType: []const u8,
|
||||||
|
cbk: Env.Callback,
|
||||||
|
capture: ?bool,
|
||||||
|
state: *SessionState,
|
||||||
|
// TODO: hanle EventListenerOptions
|
||||||
|
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
|
) !void {
|
||||||
|
// check if event target has already this listener
|
||||||
|
const lst = try parser.eventTargetHasListener(
|
||||||
|
self,
|
||||||
|
eventType,
|
||||||
|
capture orelse false,
|
||||||
|
cbk.id,
|
||||||
|
);
|
||||||
|
if (lst == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove listener
|
||||||
|
try parser.eventTargetRemoveEventListener(
|
||||||
|
self,
|
||||||
|
state.arena,
|
||||||
|
eventType,
|
||||||
|
lst.?,
|
||||||
|
capture orelse false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
|
||||||
|
return try parser.eventTargetDispatchEvent(self, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *parser.EventTarget, state: *SessionState) void {
|
||||||
|
parser.eventTargetRemoveAllEventListeners(self, state.arena) catch unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser.DOM.EventTarget" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let content = document.getElementById('content')", "undefined" },
|
||||||
|
.{ "let para = document.getElementById('para')", "undefined" },
|
||||||
|
// NOTE: as some event properties will change during the event dispatching phases
|
||||||
|
// we need to copy thoses values in order to check them afterwards
|
||||||
|
.{
|
||||||
|
\\ var nb = 0; var evt; var phase; var cur;
|
||||||
|
\\ function cbk(event) {
|
||||||
|
\\ evt = event;
|
||||||
|
\\ phase = event.eventPhase;
|
||||||
|
\\ cur = event.currentTarget;
|
||||||
|
\\ nb ++;
|
||||||
|
\\ }
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "content.addEventListener('basic', cbk)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('basic'))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt instanceof Event", "true" },
|
||||||
|
.{ "evt.type", "basic" },
|
||||||
|
.{ "phase", "2" },
|
||||||
|
.{ "cur.getAttribute('id')", "content" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0; evt = undefined; phase = undefined; cur = undefined", "undefined" },
|
||||||
|
.{ "para.dispatchEvent(new Event('basic'))", "true" },
|
||||||
|
.{ "nb", "0" }, // handler is not called, no capture, not the target, no bubbling
|
||||||
|
.{ "evt === undefined", "true" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{ "content.addEventListener('basic', cbk)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('basic'))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{ "content.addEventListener('basic', cbk, true)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('basic'))", "true" },
|
||||||
|
.{ "nb", "2" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{ "content.removeEventListener('basic', cbk)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('basic'))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{ "content.removeEventListener('basic', cbk, true)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('basic'))", "true" },
|
||||||
|
.{ "nb", "0" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0; evt = undefined; phase = undefined; cur = undefined", "undefined" },
|
||||||
|
.{ "content.addEventListener('capture', cbk, true)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('capture'))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt instanceof Event", "true" },
|
||||||
|
.{ "evt.type", "capture" },
|
||||||
|
.{ "phase", "2" },
|
||||||
|
.{ "cur.getAttribute('id')", "content" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0; evt = undefined; phase = undefined; cur = undefined", "undefined" },
|
||||||
|
.{ "para.dispatchEvent(new Event('capture'))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt instanceof Event", "true" },
|
||||||
|
.{ "evt.type", "capture" },
|
||||||
|
.{ "phase", "1" },
|
||||||
|
.{ "cur.getAttribute('id')", "content" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0; evt = undefined; phase = undefined; cur = undefined", "undefined" },
|
||||||
|
.{ "content.addEventListener('bubbles', cbk)", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(new Event('bubbles', {bubbles: true}))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt instanceof Event", "true" },
|
||||||
|
.{ "evt.type", "bubbles" },
|
||||||
|
.{ "evt.bubbles", "true" },
|
||||||
|
.{ "phase", "2" },
|
||||||
|
.{ "cur.getAttribute('id')", "content" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0; evt = undefined; phase = undefined; cur = undefined", "undefined" },
|
||||||
|
.{ "para.dispatchEvent(new Event('bubbles', {bubbles: true}))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt instanceof Event", "true" },
|
||||||
|
.{ "evt.type", "bubbles" },
|
||||||
|
.{ "phase", "3" },
|
||||||
|
.{ "cur.getAttribute('id')", "content" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
@@ -19,19 +19,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const allocPrint = std.fmt.allocPrint;
|
const allocPrint = std.fmt.allocPrint;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
// https://webidl.spec.whatwg.org/#idl-DOMException
|
// https://webidl.spec.whatwg.org/#idl-DOMException
|
||||||
pub const DOMException = struct {
|
pub const DOMException = struct {
|
||||||
err: parser.DOMError,
|
err: parser.DOMError,
|
||||||
str: []const u8,
|
str: []const u8,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const ErrorSet = parser.DOMError;
|
pub const ErrorSet = parser.DOMError;
|
||||||
|
|
||||||
// static attributes
|
// static attributes
|
||||||
@@ -62,7 +56,7 @@ pub const DOMException = struct {
|
|||||||
pub const _DATA_CLONE_ERR = 25;
|
pub const _DATA_CLONE_ERR = 25;
|
||||||
|
|
||||||
// TODO: deinit
|
// TODO: deinit
|
||||||
pub fn init(alloc: std.mem.Allocator, err: anyerror, callerName: []const u8) anyerror!DOMException {
|
pub fn init(alloc: std.mem.Allocator, err: anyerror, callerName: []const u8) !DOMException {
|
||||||
const errCast = @as(parser.DOMError, @errorCast(err));
|
const errCast = @as(parser.DOMError, @errorCast(err));
|
||||||
const errName = DOMException.name(errCast);
|
const errName = DOMException.name(errCast);
|
||||||
const str = switch (errCast) {
|
const str = switch (errCast) {
|
||||||
@@ -120,7 +114,7 @@ pub const DOMException = struct {
|
|||||||
|
|
||||||
// JS properties and methods
|
// JS properties and methods
|
||||||
|
|
||||||
pub fn get_code(self: DOMException) u8 {
|
pub fn get_code(self: *const DOMException) u8 {
|
||||||
return switch (self.err) {
|
return switch (self.err) {
|
||||||
error.IndexSize => 1,
|
error.IndexSize => 1,
|
||||||
error.StringSize => 2,
|
error.StringSize => 2,
|
||||||
@@ -157,38 +151,41 @@ pub const DOMException = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_name(self: DOMException) []const u8 {
|
pub fn get_name(self: *const DOMException) []const u8 {
|
||||||
return DOMException.name(self.err);
|
return DOMException.name(self.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_message(self: DOMException) []const u8 {
|
pub fn get_message(self: *const DOMException) []const u8 {
|
||||||
const errName = DOMException.name(self.err);
|
const errName = DOMException.name(self.err);
|
||||||
return self.str[errName.len + 2 ..];
|
return self.str[errName.len + 2 ..];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _toString(self: DOMException) []const u8 {
|
pub fn _toString(self: *const DOMException) []const u8 {
|
||||||
return self.str;
|
return self.str;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.DOM.Exception" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
|
||||||
_: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
const err = "Failed to execute 'appendChild' on 'Node': The new child element contains the parent.";
|
const err = "Failed to execute 'appendChild' on 'Node': The new child element contains the parent.";
|
||||||
var cases = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
|
.{ "let content = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "let link = document.getElementById('link')", .ex = "undefined" },
|
.{ "let link = document.getElementById('link')", "undefined" },
|
||||||
// HierarchyRequestError
|
// HierarchyRequestError
|
||||||
.{ .src = "var HierarchyRequestError; try {link.appendChild(content)} catch (error) {HierarchyRequestError = error} HierarchyRequestError.name", .ex = "HierarchyRequestError" },
|
.{
|
||||||
.{ .src = "HierarchyRequestError.code", .ex = "3" },
|
\\ var he;
|
||||||
.{ .src = "HierarchyRequestError.message", .ex = err },
|
\\ try { link.appendChild(content) } catch (error) { he = error}
|
||||||
.{ .src = "HierarchyRequestError.toString()", .ex = "HierarchyRequestError: " ++ err },
|
\\ he.name
|
||||||
.{ .src = "HierarchyRequestError instanceof DOMException", .ex = "true" },
|
,
|
||||||
.{ .src = "HierarchyRequestError instanceof Error", .ex = "true" },
|
"HierarchyRequestError",
|
||||||
};
|
},
|
||||||
try checkCases(js_env, &cases);
|
.{ "he.code", "3" },
|
||||||
|
.{ "he.message", err },
|
||||||
|
.{ "he.toString()", "HierarchyRequestError: " ++ err },
|
||||||
|
.{ "he instanceof DOMException", "true" },
|
||||||
|
.{ "he instanceof Error", "true" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,17 +18,14 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
const generate = @import("../generate.zig");
|
|
||||||
|
|
||||||
const utils = @import("utils.z");
|
const utils = @import("utils.z");
|
||||||
const Element = @import("element.zig").Element;
|
const Element = @import("element.zig").Element;
|
||||||
const Union = @import("element.zig").Union;
|
const Union = @import("element.zig").Union;
|
||||||
|
|
||||||
|
const JsObject = @import("../env.zig").JsObject;
|
||||||
|
|
||||||
const Walker = @import("walker.zig").Walker;
|
const Walker = @import("walker.zig").Walker;
|
||||||
const WalkerDepthFirst = @import("walker.zig").WalkerDepthFirst;
|
const WalkerDepthFirst = @import("walker.zig").WalkerDepthFirst;
|
||||||
const WalkerChildren = @import("walker.zig").WalkerChildren;
|
const WalkerChildren = @import("walker.zig").WalkerChildren;
|
||||||
@@ -279,8 +276,6 @@ pub fn HTMLCollectionByAnchors(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const HTMLCollectionIterator = struct {
|
pub const HTMLCollectionIterator = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
coll: *HTMLCollection,
|
coll: *HTMLCollection,
|
||||||
index: u32 = 0,
|
index: u32 = 0,
|
||||||
|
|
||||||
@@ -311,8 +306,6 @@ pub const HTMLCollectionIterator = struct {
|
|||||||
// dom_html_collection expects a comparison function callback as arguement.
|
// dom_html_collection expects a comparison function callback as arguement.
|
||||||
// But we wanted a dynamically comparison here, according to the match tagname.
|
// But we wanted a dynamically comparison here, according to the match tagname.
|
||||||
pub const HTMLCollection = struct {
|
pub const HTMLCollection = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
walker: Walker,
|
walker: Walker,
|
||||||
|
|
||||||
@@ -327,10 +320,6 @@ pub const HTMLCollection = struct {
|
|||||||
cur_idx: ?u32 = undefined,
|
cur_idx: ?u32 = undefined,
|
||||||
cur_node: ?*parser.Node = undefined,
|
cur_node: ?*parser.Node = undefined,
|
||||||
|
|
||||||
// array_like_keys is used to keep reference to array like interface implementation.
|
|
||||||
// the collection generates keys string which must be free on deinit.
|
|
||||||
array_like_keys: std.ArrayListUnmanaged([]u8) = .{},
|
|
||||||
|
|
||||||
// start returns the first node to walk on.
|
// start returns the first node to walk on.
|
||||||
fn start(self: HTMLCollection) !?*parser.Node {
|
fn start(self: HTMLCollection) !?*parser.Node {
|
||||||
if (self.root == null) return null;
|
if (self.root == null) return null;
|
||||||
@@ -412,7 +401,7 @@ pub const HTMLCollection = struct {
|
|||||||
return try Element.toInterface(e);
|
return try Element.toInterface(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _namedItem(self: *HTMLCollection, name: []const u8) !?Union {
|
pub fn _namedItem(self: *const HTMLCollection, name: []const u8) !?Union {
|
||||||
if (self.root == null) return null;
|
if (self.root == null) return null;
|
||||||
if (name.len == 0) return null;
|
if (name.len == 0) return null;
|
||||||
|
|
||||||
@@ -454,81 +443,67 @@ pub const HTMLCollection = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn postAttach(self: *HTMLCollection, alloc: std.mem.Allocator, js_obj: jsruntime.JSObject) !void {
|
pub fn postAttach(self: *HTMLCollection, js_obj: JsObject) !void {
|
||||||
const ln = try self.get_length();
|
const len = try self.get_length();
|
||||||
var i: u32 = 0;
|
for (0..len) |i| {
|
||||||
while (i < ln) {
|
const node = try self.item(@intCast(i)) orelse unreachable;
|
||||||
defer i += 1;
|
|
||||||
const k = try std.fmt.allocPrint(alloc, "{d}", .{i});
|
|
||||||
try self.array_like_keys.append(alloc, k);
|
|
||||||
|
|
||||||
const node = try self.item(i) orelse unreachable;
|
|
||||||
const e = @as(*parser.Element, @ptrCast(node));
|
const e = @as(*parser.Element, @ptrCast(node));
|
||||||
try js_obj.set(k, e);
|
try js_obj.setIndex(@intCast(i), e);
|
||||||
|
|
||||||
if (try item_name(e)) |name| {
|
if (try item_name(e)) |name| {
|
||||||
try js_obj.set(name, e);
|
try js_obj.set(name, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *HTMLCollection, alloc: std.mem.Allocator) void {
|
|
||||||
for (self.array_like_keys_) |k| alloc.free(k);
|
|
||||||
self.array_like_keys.deinit(alloc);
|
|
||||||
self.matcher.deinit(alloc);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.DOM.HTMLCollection" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
try runner.testCases(&.{
|
||||||
_: std.mem.Allocator,
|
.{ "let getElementsByTagName = document.getElementsByTagName('p')", "undefined" },
|
||||||
js_env: *jsruntime.Env,
|
.{ "getElementsByTagName.length", "2" },
|
||||||
) anyerror!void {
|
.{ "let getElementsByTagNameCI = document.getElementsByTagName('P')", "undefined" },
|
||||||
var getElementsByTagName = [_]Case{
|
.{ "getElementsByTagNameCI.length", "2" },
|
||||||
.{ .src = "let getElementsByTagName = document.getElementsByTagName('p')", .ex = "undefined" },
|
.{ "getElementsByTagName.item(0).localName", "p" },
|
||||||
.{ .src = "getElementsByTagName.length", .ex = "2" },
|
.{ "getElementsByTagName.item(1).localName", "p" },
|
||||||
.{ .src = "let getElementsByTagNameCI = document.getElementsByTagName('P')", .ex = "undefined" },
|
.{ "let getElementsByTagNameAll = document.getElementsByTagName('*')", "undefined" },
|
||||||
.{ .src = "getElementsByTagNameCI.length", .ex = "2" },
|
.{ "getElementsByTagNameAll.length", "8" },
|
||||||
.{ .src = "getElementsByTagName.item(0).localName", .ex = "p" },
|
.{ "getElementsByTagNameAll.item(0).localName", "html" },
|
||||||
.{ .src = "getElementsByTagName.item(1).localName", .ex = "p" },
|
.{ "getElementsByTagNameAll.item(0).localName", "html" },
|
||||||
.{ .src = "let getElementsByTagNameAll = document.getElementsByTagName('*')", .ex = "undefined" },
|
.{ "getElementsByTagNameAll.item(1).localName", "head" },
|
||||||
.{ .src = "getElementsByTagNameAll.length", .ex = "8" },
|
.{ "getElementsByTagNameAll.item(0).localName", "html" },
|
||||||
.{ .src = "getElementsByTagNameAll.item(0).localName", .ex = "html" },
|
.{ "getElementsByTagNameAll.item(2).localName", "body" },
|
||||||
.{ .src = "getElementsByTagNameAll.item(0).localName", .ex = "html" },
|
.{ "getElementsByTagNameAll.item(3).localName", "div" },
|
||||||
.{ .src = "getElementsByTagNameAll.item(1).localName", .ex = "head" },
|
.{ "getElementsByTagNameAll.item(7).localName", "p" },
|
||||||
.{ .src = "getElementsByTagNameAll.item(0).localName", .ex = "html" },
|
.{ "getElementsByTagNameAll.namedItem('para-empty-child').localName", "span" },
|
||||||
.{ .src = "getElementsByTagNameAll.item(2).localName", .ex = "body" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.item(3).localName", .ex = "div" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.item(7).localName", .ex = "p" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.namedItem('para-empty-child').localName", .ex = "span" },
|
|
||||||
|
|
||||||
// array like
|
// array like
|
||||||
.{ .src = "getElementsByTagNameAll[0].localName", .ex = "html" },
|
.{ "getElementsByTagNameAll[0].localName", "html" },
|
||||||
.{ .src = "getElementsByTagNameAll[7].localName", .ex = "p" },
|
.{ "getElementsByTagNameAll[7].localName", "p" },
|
||||||
.{ .src = "getElementsByTagNameAll[8]", .ex = "undefined" },
|
.{ "getElementsByTagNameAll[8]", "undefined" },
|
||||||
.{ .src = "getElementsByTagNameAll['para-empty-child'].localName", .ex = "span" },
|
.{ "getElementsByTagNameAll['para-empty-child'].localName", "span" },
|
||||||
.{ .src = "getElementsByTagNameAll['foo']", .ex = "undefined" },
|
.{ "getElementsByTagNameAll['foo']", "undefined" },
|
||||||
|
|
||||||
.{ .src = "document.getElementById('content').getElementsByTagName('*').length", .ex = "4" },
|
.{ "document.getElementById('content').getElementsByTagName('*').length", "4" },
|
||||||
.{ .src = "document.getElementById('content').getElementsByTagName('p').length", .ex = "2" },
|
.{ "document.getElementById('content').getElementsByTagName('p').length", "2" },
|
||||||
.{ .src = "document.getElementById('content').getElementsByTagName('div').length", .ex = "0" },
|
.{ "document.getElementById('content').getElementsByTagName('div').length", "0" },
|
||||||
|
|
||||||
.{ .src = "document.children.length", .ex = "1" },
|
.{ "document.children.length", "1" },
|
||||||
.{ .src = "document.getElementById('content').children.length", .ex = "3" },
|
.{ "document.getElementById('content').children.length", "3" },
|
||||||
|
|
||||||
// check liveness
|
// check liveness
|
||||||
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
|
.{ "let content = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "let pe = document.getElementById('para-empty')", .ex = "undefined" },
|
.{ "let pe = document.getElementById('para-empty')", "undefined" },
|
||||||
.{ .src = "let p = document.createElement('p')", .ex = "undefined" },
|
.{ "let p = document.createElement('p')", "undefined" },
|
||||||
.{ .src = "p.textContent = 'OK live'", .ex = "OK live" },
|
.{ "p.textContent = 'OK live'", "OK live" },
|
||||||
.{ .src = "getElementsByTagName.item(1).textContent", .ex = " And" },
|
.{ "getElementsByTagName.item(1).textContent", " And" },
|
||||||
.{ .src = "content.appendChild(p) != undefined", .ex = "true" },
|
.{ "content.appendChild(p) != undefined", "true" },
|
||||||
.{ .src = "getElementsByTagName.length", .ex = "3" },
|
.{ "getElementsByTagName.length", "3" },
|
||||||
.{ .src = "getElementsByTagName.item(2).textContent", .ex = "OK live" },
|
.{ "getElementsByTagName.item(2).textContent", "OK live" },
|
||||||
.{ .src = "content.insertBefore(p, pe) != undefined", .ex = "true" },
|
.{ "content.insertBefore(p, pe) != undefined", "true" },
|
||||||
.{ .src = "getElementsByTagName.item(0).textContent", .ex = "OK live" },
|
.{ "getElementsByTagName.item(0).textContent", "OK live" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &getElementsByTagName);
|
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,8 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const Document = @import("document.zig").Document;
|
const Document = @import("document.zig").Document;
|
||||||
const DocumentType = @import("document_type.zig").DocumentType;
|
const DocumentType = @import("document_type.zig").DocumentType;
|
||||||
@@ -30,47 +27,47 @@ const DOMException = @import("exceptions.zig").DOMException;
|
|||||||
|
|
||||||
// WEB IDL https://dom.spec.whatwg.org/#domimplementation
|
// WEB IDL https://dom.spec.whatwg.org/#domimplementation
|
||||||
pub const DOMImplementation = struct {
|
pub const DOMImplementation = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const Exception = DOMException;
|
pub const Exception = DOMException;
|
||||||
|
|
||||||
pub fn _createDocumentType(
|
pub fn _createDocumentType(
|
||||||
_: *DOMImplementation,
|
_: *DOMImplementation,
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
qname: []const u8,
|
qname: []const u8,
|
||||||
publicId: []const u8,
|
publicId: []const u8,
|
||||||
systemId: []const u8,
|
systemId: []const u8,
|
||||||
|
state: *SessionState,
|
||||||
) !*parser.DocumentType {
|
) !*parser.DocumentType {
|
||||||
const cqname = try alloc.dupeZ(u8, qname);
|
const allocator = state.arena;
|
||||||
defer alloc.free(cqname);
|
const cqname = try allocator.dupeZ(u8, qname);
|
||||||
|
defer allocator.free(cqname);
|
||||||
|
|
||||||
const cpublicId = try alloc.dupeZ(u8, publicId);
|
const cpublicId = try allocator.dupeZ(u8, publicId);
|
||||||
defer alloc.free(cpublicId);
|
defer allocator.free(cpublicId);
|
||||||
|
|
||||||
const csystemId = try alloc.dupeZ(u8, systemId);
|
const csystemId = try allocator.dupeZ(u8, systemId);
|
||||||
defer alloc.free(csystemId);
|
defer allocator.free(csystemId);
|
||||||
|
|
||||||
return try parser.domImplementationCreateDocumentType(cqname, cpublicId, csystemId);
|
return try parser.domImplementationCreateDocumentType(cqname, cpublicId, csystemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _createDocument(
|
pub fn _createDocument(
|
||||||
_: *DOMImplementation,
|
_: *DOMImplementation,
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
namespace: ?[]const u8,
|
namespace: ?[]const u8,
|
||||||
qname: ?[]const u8,
|
qname: ?[]const u8,
|
||||||
doctype: ?*parser.DocumentType,
|
doctype: ?*parser.DocumentType,
|
||||||
|
state: *SessionState,
|
||||||
) !*parser.Document {
|
) !*parser.Document {
|
||||||
|
const allocator = state.arena;
|
||||||
var cnamespace: ?[:0]const u8 = null;
|
var cnamespace: ?[:0]const u8 = null;
|
||||||
if (namespace) |ns| {
|
if (namespace) |ns| {
|
||||||
cnamespace = try alloc.dupeZ(u8, ns);
|
cnamespace = try allocator.dupeZ(u8, ns);
|
||||||
}
|
}
|
||||||
defer if (cnamespace) |v| alloc.free(v);
|
defer if (cnamespace) |v| allocator.free(v);
|
||||||
|
|
||||||
var cqname: ?[:0]const u8 = null;
|
var cqname: ?[:0]const u8 = null;
|
||||||
if (qname) |qn| {
|
if (qname) |qn| {
|
||||||
cqname = try alloc.dupeZ(u8, qn);
|
cqname = try allocator.dupeZ(u8, qn);
|
||||||
}
|
}
|
||||||
defer if (cqname) |v| alloc.free(v);
|
defer if (cqname) |v| allocator.free(v);
|
||||||
|
|
||||||
return try parser.domImplementationCreateDocument(cnamespace, cqname, doctype);
|
return try parser.domImplementationCreateDocument(cnamespace, cqname, doctype);
|
||||||
}
|
}
|
||||||
@@ -89,17 +86,17 @@ pub const DOMImplementation = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.Implementation" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var getImplementation = [_]Case{
|
|
||||||
.{ .src = "let impl = document.implementation", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "impl.createHTMLDocument();", .ex = "[object HTMLDocument]" },
|
.{ "let impl = document.implementation", "undefined" },
|
||||||
.{ .src = "impl.createHTMLDocument('foo');", .ex = "[object HTMLDocument]" },
|
.{ "impl.createHTMLDocument();", "[object HTMLDocument]" },
|
||||||
.{ .src = "impl.createDocument(null, 'foo');", .ex = "[object Document]" },
|
.{ "impl.createHTMLDocument('foo');", "[object HTMLDocument]" },
|
||||||
.{ .src = "impl.createDocumentType('foo', 'bar', 'baz')", .ex = "[object DocumentType]" },
|
.{ "impl.createDocument(null, 'foo');", "[object Document]" },
|
||||||
.{ .src = "impl.hasFeature()", .ex = "true" },
|
.{ "impl.createDocumentType('foo', 'bar', 'baz')", "[object DocumentType]" },
|
||||||
};
|
.{ "impl.hasFeature()", "true" },
|
||||||
try checkCases(js_env, &getImplementation);
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,14 +18,11 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Callback = jsruntime.Callback;
|
|
||||||
const CallbackResult = jsruntime.CallbackResult;
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
|
const Env = @import("../env.zig").Env;
|
||||||
|
const JsObject = @import("../env.zig").JsObject;
|
||||||
const NodeList = @import("nodelist.zig").NodeList;
|
const NodeList = @import("nodelist.zig").NodeList;
|
||||||
|
|
||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
@@ -40,20 +37,18 @@ const log = std.log.scoped(.events);
|
|||||||
|
|
||||||
// WEB IDL https://dom.spec.whatwg.org/#interface-mutationobserver
|
// WEB IDL https://dom.spec.whatwg.org/#interface-mutationobserver
|
||||||
pub const MutationObserver = struct {
|
pub const MutationObserver = struct {
|
||||||
cbk: Callback,
|
cbk: Env.Callback,
|
||||||
observers: Observers,
|
observers: Observers,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
const Observer = struct {
|
const Observer = struct {
|
||||||
node: *parser.Node,
|
node: *parser.Node,
|
||||||
options: MutationObserverInit,
|
options: MutationObserverInit,
|
||||||
};
|
};
|
||||||
|
|
||||||
const deinitFunc = struct {
|
const deinitFunc = struct {
|
||||||
fn deinit(ctx: ?*anyopaque, alloc: std.mem.Allocator) void {
|
fn deinit(ctx: ?*anyopaque, allocator: std.mem.Allocator) void {
|
||||||
const o: *Observer = @ptrCast(@alignCast(ctx));
|
const o: *Observer = @ptrCast(@alignCast(ctx));
|
||||||
alloc.destroy(o);
|
allocator.destroy(o);
|
||||||
}
|
}
|
||||||
}.deinit;
|
}.deinit;
|
||||||
|
|
||||||
@@ -78,7 +73,7 @@ pub const MutationObserver = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn constructor(cbk: Callback) !MutationObserver {
|
pub fn constructor(cbk: Env.Callback) !MutationObserver {
|
||||||
return MutationObserver{
|
return MutationObserver{
|
||||||
.cbk = cbk,
|
.cbk = cbk,
|
||||||
.observers = .{},
|
.observers = .{},
|
||||||
@@ -90,22 +85,23 @@ pub const MutationObserver = struct {
|
|||||||
return opt orelse .{};
|
return opt orelse .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _observe(self: *MutationObserver, alloc: std.mem.Allocator, node: *parser.Node, options: ?MutationObserverInit) !void {
|
pub fn _observe(self: *MutationObserver, node: *parser.Node, options: ?MutationObserverInit, state: *SessionState) !void {
|
||||||
const o = try alloc.create(Observer);
|
const arena = state.arena;
|
||||||
|
const o = try arena.create(Observer);
|
||||||
o.* = .{
|
o.* = .{
|
||||||
.node = node,
|
.node = node,
|
||||||
.options = resolveOptions(options),
|
.options = resolveOptions(options),
|
||||||
};
|
};
|
||||||
errdefer alloc.destroy(o);
|
errdefer arena.destroy(o);
|
||||||
|
|
||||||
// register the new observer.
|
// register the new observer.
|
||||||
try self.observers.append(alloc, o);
|
try self.observers.append(arena, o);
|
||||||
|
|
||||||
// register node's events.
|
// register node's events.
|
||||||
if (o.options.childList or o.options.subtree) {
|
if (o.options.childList or o.options.subtree) {
|
||||||
try parser.eventTargetAddEventListener(
|
try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
alloc,
|
arena,
|
||||||
"DOMNodeInserted",
|
"DOMNodeInserted",
|
||||||
EventHandler,
|
EventHandler,
|
||||||
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
||||||
@@ -113,7 +109,7 @@ pub const MutationObserver = struct {
|
|||||||
);
|
);
|
||||||
try parser.eventTargetAddEventListener(
|
try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
alloc,
|
arena,
|
||||||
"DOMNodeRemoved",
|
"DOMNodeRemoved",
|
||||||
EventHandler,
|
EventHandler,
|
||||||
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
||||||
@@ -123,7 +119,7 @@ pub const MutationObserver = struct {
|
|||||||
if (o.options.attr()) {
|
if (o.options.attr()) {
|
||||||
try parser.eventTargetAddEventListener(
|
try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
alloc,
|
arena,
|
||||||
"DOMAttrModified",
|
"DOMAttrModified",
|
||||||
EventHandler,
|
EventHandler,
|
||||||
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
||||||
@@ -133,7 +129,7 @@ pub const MutationObserver = struct {
|
|||||||
if (o.options.cdata()) {
|
if (o.options.cdata()) {
|
||||||
try parser.eventTargetAddEventListener(
|
try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
alloc,
|
arena,
|
||||||
"DOMCharacterDataModified",
|
"DOMCharacterDataModified",
|
||||||
EventHandler,
|
EventHandler,
|
||||||
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
||||||
@@ -143,7 +139,7 @@ pub const MutationObserver = struct {
|
|||||||
if (o.options.subtree) {
|
if (o.options.subtree) {
|
||||||
try parser.eventTargetAddEventListener(
|
try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
alloc,
|
arena,
|
||||||
"DOMSubtreeModified",
|
"DOMSubtreeModified",
|
||||||
EventHandler,
|
EventHandler,
|
||||||
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
|
||||||
@@ -157,14 +153,17 @@ pub const MutationObserver = struct {
|
|||||||
// TODO unregister listeners.
|
// TODO unregister listeners.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *MutationObserver, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *MutationObserver, state: *SessionState) void {
|
||||||
|
const arena = state.arena;
|
||||||
// TODO unregister listeners.
|
// TODO unregister listeners.
|
||||||
for (self.observers.items) |o| alloc.destroy(o);
|
for (self.observers.items) |o| {
|
||||||
self.observers.deinit(alloc);
|
arena.destroy(o);
|
||||||
|
}
|
||||||
|
self.observers.deinit(arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
pub fn _takeRecords(_: MutationObserver) ?[]const u8 {
|
pub fn _takeRecords(_: *const MutationObserver) ?[]const u8 {
|
||||||
return &[_]u8{};
|
return &[_]u8{};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -173,15 +172,19 @@ pub const MutationObserver = struct {
|
|||||||
pub const MutationRecords = struct {
|
pub const MutationRecords = struct {
|
||||||
first: ?MutationRecord = null,
|
first: ?MutationRecord = null,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub fn get_length(self: *const MutationRecords) u32 {
|
||||||
|
|
||||||
pub fn get_length(self: *MutationRecords) u32 {
|
|
||||||
if (self.first == null) return 0;
|
if (self.first == null) return 0;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
pub fn indexed_get(self: *const MutationRecords, i: u32, has_value: *bool) ?MutationRecord {
|
||||||
pub fn postAttach(self: *MutationRecords, js_obj: jsruntime.JSObject) !void {
|
_ = i;
|
||||||
|
return self.first orelse {
|
||||||
|
has_value.* = false;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn postAttach(self: *const MutationRecords, js_obj: JsObject) !void {
|
||||||
if (self.first) |mr| {
|
if (self.first) |mr| {
|
||||||
try js_obj.set("0", mr);
|
try js_obj.set("0", mr);
|
||||||
}
|
}
|
||||||
@@ -199,41 +202,39 @@ pub const MutationRecord = struct {
|
|||||||
attributeNamespace: ?[]const u8 = null,
|
attributeNamespace: ?[]const u8 = null,
|
||||||
oldValue: ?[]const u8 = null,
|
oldValue: ?[]const u8 = null,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub fn get_type(self: *const MutationRecord) []const u8 {
|
||||||
|
|
||||||
pub fn get_type(self: MutationRecord) []const u8 {
|
|
||||||
return self.type;
|
return self.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_addedNodes(self: MutationRecord) NodeList {
|
pub fn get_addedNodes(self: *const MutationRecord) NodeList {
|
||||||
return self.addedNodes;
|
return self.addedNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_removedNodes(self: MutationRecord) NodeList {
|
pub fn get_removedNodes(self: *const MutationRecord) NodeList {
|
||||||
return self.addedNodes;
|
return self.addedNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_target(self: MutationRecord) *parser.Node {
|
pub fn get_target(self: *const MutationRecord) *parser.Node {
|
||||||
return self.target;
|
return self.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_attributeName(self: MutationRecord) ?[]const u8 {
|
pub fn get_attributeName(self: *const MutationRecord) ?[]const u8 {
|
||||||
return self.attributeName;
|
return self.attributeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_attributeNamespace(self: MutationRecord) ?[]const u8 {
|
pub fn get_attributeNamespace(self: *const MutationRecord) ?[]const u8 {
|
||||||
return self.attributeNamespace;
|
return self.attributeNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_previousSibling(self: MutationRecord) ?*parser.Node {
|
pub fn get_previousSibling(self: *const MutationRecord) ?*parser.Node {
|
||||||
return self.previousSibling;
|
return self.previousSibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_nextSibling(self: MutationRecord) ?*parser.Node {
|
pub fn get_nextSibling(self: *const MutationRecord) ?*parser.Node {
|
||||||
return self.nextSibling;
|
return self.nextSibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_oldValue(self: MutationRecord) ?[]const u8 {
|
pub fn get_oldValue(self: *const MutationRecord) ?[]const u8 {
|
||||||
return self.oldValue;
|
return self.oldValue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -283,7 +284,7 @@ const EventHandler = struct {
|
|||||||
const muevt = parser.eventToMutationEvent(evt.?);
|
const muevt = parser.eventToMutationEvent(evt.?);
|
||||||
|
|
||||||
// TODO get the allocator by another way?
|
// TODO get the allocator by another way?
|
||||||
const alloc = data.cbk.nat_ctx.alloc;
|
const alloc = data.cbk.executor.call_arena.allocator();
|
||||||
|
|
||||||
if (std.mem.eql(u8, t, "DOMAttrModified")) {
|
if (std.mem.eql(u8, t, "DOMAttrModified")) {
|
||||||
mrs.first = .{
|
mrs.first = .{
|
||||||
@@ -340,66 +341,63 @@ const EventHandler = struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = CallbackResult.init(alloc);
|
|
||||||
defer res.deinit();
|
|
||||||
|
|
||||||
// TODO pass MutationRecords and MutationObserver
|
// TODO pass MutationRecords and MutationObserver
|
||||||
data.cbk.trycall(.{mrs}, &res) catch |e| log.err("mutation event handler error: {any}", .{e});
|
var result: Env.Callback.Result = undefined;
|
||||||
|
data.cbk.tryCall(.{mrs}, &result) catch {
|
||||||
// in case of function error, we log the result and the trace.
|
log.err("mutation observer callback error: {s}", .{result.exception});
|
||||||
if (!res.success) {
|
log.debug("stack:\n{s}", .{result.stack orelse "???"});
|
||||||
log.info("mutation observer event handler error: {s}", .{res.result orelse "unknown"});
|
};
|
||||||
log.debug("{s}", .{res.stack orelse "no stack trace"});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.handle;
|
}.handle;
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.MutationObserver" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var constructor = [_]Case{
|
|
||||||
.{ .src = "new MutationObserver(() => {}).observe(document, { childList: true });", .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &constructor);
|
|
||||||
|
|
||||||
var attr = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src =
|
.{ "new MutationObserver(() => {}).observe(document, { childList: true });", "undefined" },
|
||||||
\\var nb = 0;
|
}, .{});
|
||||||
\\var mrs;
|
|
||||||
\\new MutationObserver((mu) => {
|
|
||||||
\\ mrs = mu;
|
|
||||||
\\ nb++;
|
|
||||||
\\}).observe(document.firstElementChild, { attributes: true, attributeOldValue: true });
|
|
||||||
\\document.firstElementChild.setAttribute("foo", "bar");
|
|
||||||
\\// ignored b/c it's about another target.
|
|
||||||
\\document.firstElementChild.firstChild.setAttribute("foo", "bar");
|
|
||||||
\\nb;
|
|
||||||
, .ex = "1" },
|
|
||||||
.{ .src = "mrs[0].type", .ex = "attributes" },
|
|
||||||
.{ .src = "mrs[0].target == document.firstElementChild", .ex = "true" },
|
|
||||||
.{ .src = "mrs[0].target.getAttribute('foo')", .ex = "bar" },
|
|
||||||
.{ .src = "mrs[0].attributeName", .ex = "foo" },
|
|
||||||
.{ .src = "mrs[0].oldValue", .ex = "null" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &attr);
|
|
||||||
|
|
||||||
var cdata = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src =
|
.{
|
||||||
\\var node = document.getElementById("para").firstChild;
|
\\ var nb = 0;
|
||||||
\\var nb2 = 0;
|
\\ var mrs;
|
||||||
\\var mrs2;
|
\\ new MutationObserver((mu) => {
|
||||||
\\new MutationObserver((mu) => {
|
\\ mrs = mu;
|
||||||
\\ mrs2 = mu;
|
\\ nb++;
|
||||||
\\ nb2++;
|
\\ }).observe(document.firstElementChild, { attributes: true, attributeOldValue: true });
|
||||||
\\}).observe(node, { characterData: true, characterDataOldValue: true });
|
\\ document.firstElementChild.setAttribute("foo", "bar");
|
||||||
\\node.data = "foo";
|
\\ // ignored b/c it's about another target.
|
||||||
\\nb2;
|
\\ document.firstElementChild.firstChild.setAttribute("foo", "bar");
|
||||||
, .ex = "1" },
|
\\ nb;
|
||||||
.{ .src = "mrs2[0].type", .ex = "characterData" },
|
,
|
||||||
.{ .src = "mrs2[0].target == node", .ex = "true" },
|
"1",
|
||||||
.{ .src = "mrs2[0].target.data", .ex = "foo" },
|
},
|
||||||
.{ .src = "mrs2[0].oldValue", .ex = " And" },
|
.{ "mrs[0].type", "attributes" },
|
||||||
};
|
.{ "mrs[0].target == document.firstElementChild", "true" },
|
||||||
try checkCases(js_env, &cdata);
|
.{ "mrs[0].target.getAttribute('foo')", "bar" },
|
||||||
|
.{ "mrs[0].attributeName", "foo" },
|
||||||
|
.{ "mrs[0].oldValue", "null" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{
|
||||||
|
\\ var node = document.getElementById("para").firstChild;
|
||||||
|
\\ var nb2 = 0;
|
||||||
|
\\ var mrs2;
|
||||||
|
\\ new MutationObserver((mu) => {
|
||||||
|
\\ mrs2 = mu;
|
||||||
|
\\ nb2++;
|
||||||
|
\\ }).observe(node, { characterData: true, characterDataOldValue: true });
|
||||||
|
\\ node.data = "foo";
|
||||||
|
\\ nb2;
|
||||||
|
,
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
.{ "mrs2[0].type", "characterData" },
|
||||||
|
.{ "mrs2[0].target == node", "true" },
|
||||||
|
.{ "mrs2[0].target.data", "foo" },
|
||||||
|
.{ "mrs2[0].oldValue", " And" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,18 +18,13 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const DOMException = @import("exceptions.zig").DOMException;
|
const DOMException = @import("exceptions.zig").DOMException;
|
||||||
|
|
||||||
// WEB IDL https://dom.spec.whatwg.org/#namednodemap
|
// WEB IDL https://dom.spec.whatwg.org/#namednodemap
|
||||||
pub const NamedNodeMap = struct {
|
pub const NamedNodeMap = struct {
|
||||||
pub const Self = parser.NamedNodeMap;
|
pub const Self = parser.NamedNodeMap;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const Exception = DOMException;
|
pub const Exception = DOMException;
|
||||||
|
|
||||||
@@ -80,18 +75,18 @@ pub const NamedNodeMap = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.NamedNodeMap" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var setItem = [_]Case{
|
|
||||||
.{ .src = "let a = document.getElementById('content').attributes", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "a.length", .ex = "1" },
|
.{ "let a = document.getElementById('content').attributes", "undefined" },
|
||||||
.{ .src = "a.item(0)", .ex = "[object Attr]" },
|
.{ "a.length", "1" },
|
||||||
.{ .src = "a.item(1)", .ex = "null" },
|
.{ "a.item(0)", "[object Attr]" },
|
||||||
.{ .src = "a.getNamedItem('id')", .ex = "[object Attr]" },
|
.{ "a.item(1)", "null" },
|
||||||
.{ .src = "a.getNamedItem('foo')", .ex = "null" },
|
.{ "a.getNamedItem('id')", "[object Attr]" },
|
||||||
.{ .src = "a.setNamedItem(a.getNamedItem('id'))", .ex = "[object Attr]" },
|
.{ "a.getNamedItem('foo')", "null" },
|
||||||
};
|
.{ "a.setNamedItem(a.getNamedItem('id'))", "[object Attr]" },
|
||||||
try checkCases(js_env, &setItem);
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,16 +18,10 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
const generate = @import("../../runtime/generate.zig");
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
const runScript = jsruntime.test_utils.runScript;
|
|
||||||
const Variadic = jsruntime.Variadic;
|
|
||||||
|
|
||||||
const generate = @import("../generate.zig");
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const EventTarget = @import("event_target.zig").EventTarget;
|
const EventTarget = @import("event_target.zig").EventTarget;
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
@@ -66,7 +60,6 @@ pub const Union = generate.Union(Interfaces);
|
|||||||
pub const Node = struct {
|
pub const Node = struct {
|
||||||
pub const Self = parser.Node;
|
pub const Self = parser.Node;
|
||||||
pub const prototype = *EventTarget;
|
pub const prototype = *EventTarget;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn toInterface(node: *parser.Node) !Union {
|
pub fn toInterface(node: *parser.Node) !Union {
|
||||||
return switch (try parser.nodeType(node)) {
|
return switch (try parser.nodeType(node)) {
|
||||||
@@ -262,13 +255,14 @@ pub const Node = struct {
|
|||||||
return try parser.nodeHasChildNodes(self);
|
return try parser.nodeHasChildNodes(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_childNodes(self: *parser.Node, alloc: std.mem.Allocator) !NodeList {
|
pub fn get_childNodes(self: *parser.Node, state: *SessionState) !NodeList {
|
||||||
|
const allocator = state.arena;
|
||||||
var list = NodeList.init();
|
var list = NodeList.init();
|
||||||
errdefer list.deinit(alloc);
|
errdefer list.deinit(allocator);
|
||||||
|
|
||||||
var n = try parser.nodeFirstChild(self) orelse return list;
|
var n = try parser.nodeFirstChild(self) orelse return list;
|
||||||
while (true) {
|
while (true) {
|
||||||
try list.append(alloc, n);
|
try list.append(allocator, n);
|
||||||
n = try parser.nodeNextSibling(n) orelse return list;
|
n = try parser.nodeNextSibling(n) orelse return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,11 +321,10 @@ pub const Node = struct {
|
|||||||
// For now, it checks only if new nodes are not self.
|
// For now, it checks only if new nodes are not self.
|
||||||
// TODO implements the others contraints.
|
// TODO implements the others contraints.
|
||||||
// see https://dom.spec.whatwg.org/#concept-node-tree
|
// see https://dom.spec.whatwg.org/#concept-node-tree
|
||||||
pub fn hierarchy(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !bool {
|
pub fn hierarchy(self: *parser.Node, nodes: []const *parser.Node) !bool {
|
||||||
if (nodes == null) return true;
|
if (nodes.len == 0) return true;
|
||||||
if (nodes.?.slice.len == 0) return true;
|
|
||||||
|
|
||||||
for (nodes.?.slice) |node| if (self == node) return false;
|
for (nodes) |node| if (self == node) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -339,22 +332,21 @@ pub const Node = struct {
|
|||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn prepend(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn prepend(self: *parser.Node, nodes: []const *parser.Node) !void {
|
||||||
if (nodes == null) return;
|
if (nodes.len == 0) return;
|
||||||
if (nodes.?.slice.len == 0) return;
|
|
||||||
|
|
||||||
// check hierarchy
|
// check hierarchy
|
||||||
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
|
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
|
||||||
|
|
||||||
const first = try parser.nodeFirstChild(self);
|
const first = try parser.nodeFirstChild(self);
|
||||||
if (first == null) {
|
if (first == null) {
|
||||||
for (nodes.?.slice) |node| {
|
for (nodes) |node| {
|
||||||
_ = try parser.nodeAppendChild(self, node);
|
_ = try parser.nodeAppendChild(self, node);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (nodes.?.slice) |node| {
|
for (nodes) |node| {
|
||||||
_ = try parser.nodeInsertBefore(self, node, first.?);
|
_ = try parser.nodeInsertBefore(self, node, first.?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,14 +354,13 @@ pub const Node = struct {
|
|||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn append(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn append(self: *parser.Node, nodes: []const *parser.Node) !void {
|
||||||
if (nodes == null) return;
|
if (nodes.len == 0) return;
|
||||||
if (nodes.?.slice.len == 0) return;
|
|
||||||
|
|
||||||
// check hierarchy
|
// check hierarchy
|
||||||
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
|
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
|
||||||
|
|
||||||
for (nodes.?.slice) |node| {
|
for (nodes) |node| {
|
||||||
_ = try parser.nodeAppendChild(self, node);
|
_ = try parser.nodeAppendChild(self, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,9 +368,8 @@ pub const Node = struct {
|
|||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
||||||
// function must accept either node or string.
|
// function must accept either node or string.
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
pub fn replaceChildren(self: *parser.Node, nodes: ?Variadic(*parser.Node)) !void {
|
pub fn replaceChildren(self: *parser.Node, nodes: []const *parser.Node) !void {
|
||||||
if (nodes == null) return;
|
if (nodes.len == 0) return;
|
||||||
if (nodes.?.slice.len == 0) return;
|
|
||||||
|
|
||||||
// check hierarchy
|
// check hierarchy
|
||||||
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
|
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
|
||||||
@@ -388,7 +378,7 @@ pub const Node = struct {
|
|||||||
try removeChildren(self);
|
try removeChildren(self);
|
||||||
|
|
||||||
// add new children
|
// add new children
|
||||||
for (nodes.?.slice) |node| {
|
for (nodes) |node| {
|
||||||
_ = try parser.nodeAppendChild(self, node);
|
_ = try parser.nodeAppendChild(self, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,219 +401,192 @@ pub const Node = struct {
|
|||||||
pub fn deinit(_: *parser.Node, _: std.mem.Allocator) void {}
|
pub fn deinit(_: *parser.Node, _: std.mem.Allocator) void {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.DOM.node" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
{
|
||||||
alloc: std.mem.Allocator,
|
var err_out: ?[]const u8 = null;
|
||||||
js_env: *jsruntime.Env,
|
try runner.exec(
|
||||||
) anyerror!void {
|
\\ function trimAndReplace(str) {
|
||||||
|
\\ str = str.replace(/(\r\n|\n|\r)/gm,'');
|
||||||
|
\\ str = str.replace(/\s+/g, ' ');
|
||||||
|
\\ str = str.trim();
|
||||||
|
\\ return str;
|
||||||
|
\\ }
|
||||||
|
, "trimAndReplace", &err_out);
|
||||||
|
}
|
||||||
|
|
||||||
// helper functions
|
try runner.testCases(&.{
|
||||||
const trim_and_replace =
|
.{ "document.body.compareDocumentPosition(document.firstChild); ", "10" },
|
||||||
\\function trimAndReplace(str) {
|
.{ "document.getElementById(\"para-empty\").compareDocumentPosition(document.getElementById(\"content\"));", "10" },
|
||||||
\\str = str.replace(/(\r\n|\n|\r)/gm,'');
|
.{ "document.getElementById(\"content\").compareDocumentPosition(document.getElementById(\"para-empty\"));", "20" },
|
||||||
\\str = str.replace(/\s+/g, ' ');
|
.{ "document.getElementById(\"link\").compareDocumentPosition(document.getElementById(\"link\"));", "0" },
|
||||||
\\str = str.trim();
|
.{ "document.getElementById(\"para-empty\").compareDocumentPosition(document.getElementById(\"link\"));", "2" },
|
||||||
\\return str;
|
.{ "document.getElementById(\"link\").compareDocumentPosition(document.getElementById(\"para-empty\"));", "4" },
|
||||||
\\}
|
}, .{});
|
||||||
;
|
|
||||||
try runScript(js_env, alloc, trim_and_replace, "proto_test");
|
|
||||||
|
|
||||||
var node_compare_document_position = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.body.compareDocumentPosition(document.firstChild); ", .ex = "10" },
|
.{ "document.getElementById('content').getRootNode().__proto__.constructor.name", "HTMLDocument" },
|
||||||
.{ .src = "document.getElementById(\"para-empty\").compareDocumentPosition(document.getElementById(\"content\"));", .ex = "10" },
|
}, .{});
|
||||||
.{ .src = "document.getElementById(\"content\").compareDocumentPosition(document.getElementById(\"para-empty\"));", .ex = "20" },
|
|
||||||
.{ .src = "document.getElementById(\"link\").compareDocumentPosition(document.getElementById(\"link\"));", .ex = "0" },
|
|
||||||
.{ .src = "document.getElementById(\"para-empty\").compareDocumentPosition(document.getElementById(\"link\"));", .ex = "2" },
|
|
||||||
.{ .src = "document.getElementById(\"link\").compareDocumentPosition(document.getElementById(\"para-empty\"));", .ex = "4" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &node_compare_document_position);
|
|
||||||
|
|
||||||
var get_root_node = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.getElementById('content').getRootNode().__proto__.constructor.name", .ex = "HTMLDocument" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &get_root_node);
|
|
||||||
|
|
||||||
var first_child = [_]Case{
|
|
||||||
// for next test cases
|
// for next test cases
|
||||||
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
|
.{ "let content = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "let link = document.getElementById('link')", .ex = "undefined" },
|
.{ "let link = document.getElementById('link')", "undefined" },
|
||||||
.{ .src = "let first_child = content.firstChild.nextSibling", .ex = "undefined" }, // nextSibling because of line return \n
|
.{ "let first_child = content.firstChild.nextSibling", "undefined" }, // nextSibling because of line return \n
|
||||||
|
|
||||||
.{ .src = "let body_first_child = document.body.firstChild", .ex = "undefined" },
|
.{ "let body_first_child = document.body.firstChild", "undefined" },
|
||||||
.{ .src = "body_first_child.localName", .ex = "div" },
|
.{ "body_first_child.localName", "div" },
|
||||||
.{ .src = "body_first_child.__proto__.constructor.name", .ex = "HTMLDivElement" },
|
.{ "body_first_child.__proto__.constructor.name", "HTMLDivElement" },
|
||||||
.{ .src = "document.getElementById('para-empty').firstChild.firstChild", .ex = "null" },
|
.{ "document.getElementById('para-empty').firstChild.firstChild", "null" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &first_child);
|
|
||||||
|
|
||||||
var last_child = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let last_child = content.lastChild.previousSibling", .ex = "undefined" }, // previousSibling because of line return \n
|
.{ "let last_child = content.lastChild.previousSibling", "undefined" }, // previousSibling because of line return \n
|
||||||
.{ .src = "last_child.__proto__.constructor.name", .ex = "Comment" },
|
.{ "last_child.__proto__.constructor.name", "Comment" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &last_child);
|
|
||||||
|
|
||||||
var next_sibling = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let next_sibling = link.nextSibling.nextSibling", .ex = "undefined" },
|
.{ "let next_sibling = link.nextSibling.nextSibling", "undefined" },
|
||||||
.{ .src = "next_sibling.localName", .ex = "p" },
|
.{ "next_sibling.localName", "p" },
|
||||||
.{ .src = "next_sibling.__proto__.constructor.name", .ex = "HTMLParagraphElement" },
|
.{ "next_sibling.__proto__.constructor.name", "HTMLParagraphElement" },
|
||||||
.{ .src = "content.nextSibling.nextSibling", .ex = "null" },
|
.{ "content.nextSibling.nextSibling", "null" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &next_sibling);
|
|
||||||
|
|
||||||
var prev_sibling = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let prev_sibling = document.getElementById('para-empty').previousSibling.previousSibling", .ex = "undefined" },
|
.{ "let prev_sibling = document.getElementById('para-empty').previousSibling.previousSibling", "undefined" },
|
||||||
.{ .src = "prev_sibling.localName", .ex = "a" },
|
.{ "prev_sibling.localName", "a" },
|
||||||
.{ .src = "prev_sibling.__proto__.constructor.name", .ex = "HTMLAnchorElement" },
|
.{ "prev_sibling.__proto__.constructor.name", "HTMLAnchorElement" },
|
||||||
.{ .src = "content.previousSibling", .ex = "null" },
|
.{ "content.previousSibling", "null" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &prev_sibling);
|
|
||||||
|
|
||||||
var parent = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let parent = document.getElementById('para').parentElement", .ex = "undefined" },
|
.{ "let parent = document.getElementById('para').parentElement", "undefined" },
|
||||||
.{ .src = "parent.localName", .ex = "div" },
|
.{ "parent.localName", "div" },
|
||||||
.{ .src = "parent.__proto__.constructor.name", .ex = "HTMLDivElement" },
|
.{ "parent.__proto__.constructor.name", "HTMLDivElement" },
|
||||||
.{ .src = "let h = content.parentElement.parentElement", .ex = "undefined" },
|
.{ "let h = content.parentElement.parentElement", "undefined" },
|
||||||
.{ .src = "h.parentElement", .ex = "null" },
|
.{ "h.parentElement", "null" },
|
||||||
.{ .src = "h.parentNode.__proto__.constructor.name", .ex = "HTMLDocument" },
|
.{ "h.parentNode.__proto__.constructor.name", "HTMLDocument" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &parent);
|
|
||||||
|
|
||||||
var node_name = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "first_child.nodeName === 'A'", .ex = "true" },
|
.{ "first_child.nodeName === 'A'", "true" },
|
||||||
.{ .src = "link.firstChild.nodeName === '#text'", .ex = "true" },
|
.{ "link.firstChild.nodeName === '#text'", "true" },
|
||||||
.{ .src = "last_child.nodeName === '#comment'", .ex = "true" },
|
.{ "last_child.nodeName === '#comment'", "true" },
|
||||||
.{ .src = "document.nodeName === '#document'", .ex = "true" },
|
.{ "document.nodeName === '#document'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_name);
|
|
||||||
|
|
||||||
var node_type = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "first_child.nodeType === 1", .ex = "true" },
|
.{ "first_child.nodeType === 1", "true" },
|
||||||
.{ .src = "link.firstChild.nodeType === 3", .ex = "true" },
|
.{ "link.firstChild.nodeType === 3", "true" },
|
||||||
.{ .src = "last_child.nodeType === 8", .ex = "true" },
|
.{ "last_child.nodeType === 8", "true" },
|
||||||
.{ .src = "document.nodeType === 9", .ex = "true" },
|
.{ "document.nodeType === 9", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_type);
|
|
||||||
|
|
||||||
var owner = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let owner = content.ownerDocument", .ex = "undefined" },
|
.{ "let owner = content.ownerDocument", "undefined" },
|
||||||
.{ .src = "owner.__proto__.constructor.name", .ex = "HTMLDocument" },
|
.{ "owner.__proto__.constructor.name", "HTMLDocument" },
|
||||||
.{ .src = "document.ownerDocument", .ex = "null" },
|
.{ "document.ownerDocument", "null" },
|
||||||
.{ .src = "let owner2 = document.createElement('div').ownerDocument", .ex = "undefined" },
|
.{ "let owner2 = document.createElement('div').ownerDocument", "undefined" },
|
||||||
.{ .src = "owner2.__proto__.constructor.name", .ex = "HTMLDocument" },
|
.{ "owner2.__proto__.constructor.name", "HTMLDocument" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &owner);
|
|
||||||
|
|
||||||
var connected = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "content.isConnected", .ex = "true" },
|
.{ "content.isConnected", "true" },
|
||||||
.{ .src = "document.isConnected", .ex = "true" },
|
.{ "document.isConnected", "true" },
|
||||||
.{ .src = "document.createElement('div').isConnected", .ex = "false" },
|
.{ "document.createElement('div').isConnected", "false" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &connected);
|
|
||||||
|
|
||||||
var node_value = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "last_child.nodeValue === 'comment'", .ex = "true" },
|
.{ "last_child.nodeValue === 'comment'", "true" },
|
||||||
.{ .src = "link.nodeValue === null", .ex = "true" },
|
.{ "link.nodeValue === null", "true" },
|
||||||
.{ .src = "let text = link.firstChild", .ex = "undefined" },
|
.{ "let text = link.firstChild", "undefined" },
|
||||||
.{ .src = "text.nodeValue === 'OK'", .ex = "true" },
|
.{ "text.nodeValue === 'OK'", "true" },
|
||||||
.{ .src = "text.nodeValue = 'OK modified'", .ex = "OK modified" },
|
.{ "text.nodeValue = 'OK modified'", "OK modified" },
|
||||||
.{ .src = "text.nodeValue === 'OK modified'", .ex = "true" },
|
.{ "text.nodeValue === 'OK modified'", "true" },
|
||||||
.{ .src = "link.nodeValue = 'nothing'", .ex = "nothing" },
|
.{ "link.nodeValue = 'nothing'", "nothing" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_value);
|
|
||||||
|
|
||||||
var node_text_content = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "text.textContent === 'OK modified'", .ex = "true" },
|
.{ "text.textContent === 'OK modified'", "true" },
|
||||||
.{ .src = "trimAndReplace(content.textContent) === 'OK modified And'", .ex = "true" },
|
.{ "trimAndReplace(content.textContent) === 'OK modified And'", "true" },
|
||||||
.{ .src = "text.textContent = 'OK'", .ex = "OK" },
|
.{ "text.textContent = 'OK'", "OK" },
|
||||||
.{ .src = "text.textContent", .ex = "OK" },
|
.{ "text.textContent", "OK" },
|
||||||
.{ .src = "trimAndReplace(document.getElementById('para-empty').textContent)", .ex = "" },
|
.{ "trimAndReplace(document.getElementById('para-empty').textContent)", "" },
|
||||||
.{ .src = "document.getElementById('para-empty').textContent = 'OK'", .ex = "OK" },
|
.{ "document.getElementById('para-empty').textContent = 'OK'", "OK" },
|
||||||
.{ .src = "document.getElementById('para-empty').firstChild.nodeName === '#text'", .ex = "true" },
|
.{ "document.getElementById('para-empty').firstChild.nodeName === '#text'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_text_content);
|
|
||||||
|
|
||||||
var node_append_child = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let append = document.createElement('h1')", .ex = "undefined" },
|
.{ "let append = document.createElement('h1')", "undefined" },
|
||||||
.{ .src = "content.appendChild(append).toString()", .ex = "[object HTMLHeadingElement]" },
|
.{ "content.appendChild(append).toString()", "[object HTMLHeadingElement]" },
|
||||||
.{ .src = "content.lastChild.__proto__.constructor.name", .ex = "HTMLHeadingElement" },
|
.{ "content.lastChild.__proto__.constructor.name", "HTMLHeadingElement" },
|
||||||
.{ .src = "content.appendChild(link).toString()", .ex = "[object HTMLAnchorElement]" },
|
.{ "content.appendChild(link).toString()", "[object HTMLAnchorElement]" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_append_child);
|
|
||||||
|
|
||||||
var node_clone = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let clone = link.cloneNode()", .ex = "undefined" },
|
.{ "let clone = link.cloneNode()", "undefined" },
|
||||||
.{ .src = "clone.toString()", .ex = "[object HTMLAnchorElement]" },
|
.{ "clone.toString()", "[object HTMLAnchorElement]" },
|
||||||
.{ .src = "clone.parentNode === null", .ex = "true" },
|
.{ "clone.parentNode === null", "true" },
|
||||||
.{ .src = "clone.firstChild === null", .ex = "true" },
|
.{ "clone.firstChild === null", "true" },
|
||||||
.{ .src = "let clone_deep = link.cloneNode(true)", .ex = "undefined" },
|
.{ "let clone_deep = link.cloneNode(true)", "undefined" },
|
||||||
.{ .src = "clone_deep.firstChild.nodeName === '#text'", .ex = "true" },
|
.{ "clone_deep.firstChild.nodeName === '#text'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_clone);
|
|
||||||
|
|
||||||
var node_contains = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "link.contains(text)", .ex = "true" },
|
.{ "link.contains(text)", "true" },
|
||||||
.{ .src = "text.contains(link)", .ex = "false" },
|
.{ "text.contains(link)", "false" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_contains);
|
|
||||||
|
|
||||||
var node_has_child_nodes = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "link.hasChildNodes()", .ex = "true" },
|
.{ "link.hasChildNodes()", "true" },
|
||||||
.{ .src = "text.hasChildNodes()", .ex = "false" },
|
.{ "text.hasChildNodes()", "false" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_has_child_nodes);
|
|
||||||
|
|
||||||
var node_child_nodes = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "link.childNodes.length", .ex = "1" },
|
.{ "link.childNodes.length", "1" },
|
||||||
.{ .src = "text.childNodes.length", .ex = "0" },
|
.{ "text.childNodes.length", "0" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_child_nodes);
|
|
||||||
|
|
||||||
var node_insert_before = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let insertBefore = document.createElement('a')", .ex = "undefined" },
|
.{ "let insertBefore = document.createElement('a')", "undefined" },
|
||||||
.{ .src = "link.insertBefore(insertBefore, text) !== undefined", .ex = "true" },
|
.{ "link.insertBefore(insertBefore, text) !== undefined", "true" },
|
||||||
.{ .src = "link.firstChild.localName === 'a'", .ex = "true" },
|
.{ "link.firstChild.localName === 'a'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_insert_before);
|
|
||||||
|
|
||||||
var node_is_default_namespace = [_]Case{
|
try runner.testCases(&.{
|
||||||
// TODO: does not seems to work
|
// TODO: does not seems to work
|
||||||
// .{ .src = "link.isDefaultNamespace('')", .ex = "true" },
|
// .{ "link.isDefaultNamespace('')", "true" },
|
||||||
.{ .src = "link.isDefaultNamespace('false')", .ex = "false" },
|
.{ "link.isDefaultNamespace('false')", "false" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_is_default_namespace);
|
|
||||||
|
|
||||||
var node_is_equal_node = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let equal1 = document.createElement('a')", .ex = "undefined" },
|
.{ "let equal1 = document.createElement('a')", "undefined" },
|
||||||
.{ .src = "let equal2 = document.createElement('a')", .ex = "undefined" },
|
.{ "let equal2 = document.createElement('a')", "undefined" },
|
||||||
.{ .src = "equal1.textContent = 'is equal'", .ex = "is equal" },
|
.{ "equal1.textContent = 'is equal'", "is equal" },
|
||||||
.{ .src = "equal2.textContent = 'is equal'", .ex = "is equal" },
|
.{ "equal2.textContent = 'is equal'", "is equal" },
|
||||||
// TODO: does not seems to work
|
// TODO: does not seems to work
|
||||||
// .{ .src = "equal1.isEqualNode(equal2)", .ex = "true" },
|
// .{ "equal1.isEqualNode(equal2)", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_is_equal_node);
|
|
||||||
|
|
||||||
var node_is_same_node = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.body.isSameNode(document.body)", .ex = "true" },
|
.{ "document.body.isSameNode(document.body)", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_is_same_node);
|
|
||||||
|
|
||||||
var node_normalize = [_]Case{
|
try runner.testCases(&.{
|
||||||
// TODO: no test
|
// TODO: no test
|
||||||
.{ .src = "link.normalize()", .ex = "undefined" },
|
.{ "link.normalize()", "undefined" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_normalize);
|
|
||||||
|
|
||||||
var node_remove_child = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "content.removeChild(append) !== undefined", .ex = "true" },
|
.{ "content.removeChild(append) !== undefined", "true" },
|
||||||
.{ .src = "last_child.__proto__.constructor.name !== 'HTMLHeadingElement'", .ex = "true" },
|
.{ "last_child.__proto__.constructor.name !== 'HTMLHeadingElement'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_remove_child);
|
|
||||||
|
|
||||||
var node_replace_child = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let replace = document.createElement('div')", .ex = "undefined" },
|
.{ "let replace = document.createElement('div')", "undefined" },
|
||||||
.{ .src = "link.replaceChild(replace, insertBefore) !== undefined", .ex = "true" },
|
.{ "link.replaceChild(replace, insertBefore) !== undefined", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &node_replace_child);
|
|
||||||
}
|
}
|
||||||
@@ -18,13 +18,11 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const JsObject = @import("../env.zig").JsObject;
|
||||||
const Callback = jsruntime.Callback;
|
const Callback = @import("../env.zig").Callback;
|
||||||
const CallbackResult = jsruntime.CallbackResult;
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const NodeUnion = @import("node.zig").Union;
|
const NodeUnion = @import("node.zig").Union;
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
@@ -41,8 +39,6 @@ pub const Interfaces = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const NodeListIterator = struct {
|
pub const NodeListIterator = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
coll: *NodeList,
|
coll: *NodeList,
|
||||||
index: u32 = 0,
|
index: u32 = 0,
|
||||||
|
|
||||||
@@ -69,8 +65,6 @@ pub const NodeListIterator = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const NodeListEntriesIterator = struct {
|
pub const NodeListEntriesIterator = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
coll: *NodeList,
|
coll: *NodeList,
|
||||||
index: u32 = 0,
|
index: u32 = 0,
|
||||||
|
|
||||||
@@ -104,7 +98,6 @@ pub const NodeListEntriesIterator = struct {
|
|||||||
// implementation allows only static nodelist.
|
// implementation allows only static nodelist.
|
||||||
// see https://dom.spec.whatwg.org/#old-style-collections
|
// see https://dom.spec.whatwg.org/#old-style-collections
|
||||||
pub const NodeList = struct {
|
pub const NodeList = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
pub const Exception = DOMException;
|
pub const Exception = DOMException;
|
||||||
|
|
||||||
const NodesArrayList = std.ArrayListUnmanaged(*parser.Node);
|
const NodesArrayList = std.ArrayListUnmanaged(*parser.Node);
|
||||||
@@ -130,7 +123,7 @@ pub const NodeList = struct {
|
|||||||
return @intCast(self.nodes.items.len);
|
return @intCast(self.nodes.items.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _item(self: *NodeList, index: u32) !?NodeUnion {
|
pub fn _item(self: *const NodeList, index: u32) !?NodeUnion {
|
||||||
if (index >= self.nodes.items.len) {
|
if (index >= self.nodes.items.len) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -139,17 +132,30 @@ pub const NodeList = struct {
|
|||||||
return try Node.toInterface(n);
|
return try Node.toInterface(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _forEach(self: *NodeList, alloc: std.mem.Allocator, cbk: Callback) !void { // TODO handle thisArg
|
// This code works, but it's _MUCH_ slower than using postAttach. The benefit
|
||||||
var res = CallbackResult.init(alloc);
|
// of this version, is that it's "live"..but we're talking many orders of
|
||||||
defer res.deinit();
|
// magnitude slower.
|
||||||
|
//
|
||||||
|
// You can test it by commenting out `postAttach`, uncommenting this and
|
||||||
|
// running:
|
||||||
|
// zig build wpt -- tests/wpt/dom/nodes/NodeList-static-length-getter-tampered-indexOf-1.html
|
||||||
|
//
|
||||||
|
// I think this _is_ the right way to do it, but I must be doing something
|
||||||
|
// wrong to make it so slow.
|
||||||
|
// pub fn indexed_get(self: *const NodeList, index: u32, has_value: *bool) !?NodeUnion {
|
||||||
|
// return (try self._item(index)) orelse {
|
||||||
|
// has_value.* = false;
|
||||||
|
// return null;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn _forEach(self: *NodeList, cbk: Callback) !void { // TODO handle thisArg
|
||||||
for (self.nodes.items, 0..) |n, i| {
|
for (self.nodes.items, 0..) |n, i| {
|
||||||
const ii: u32 = @intCast(i);
|
const ii: u32 = @intCast(i);
|
||||||
cbk.trycall(.{ n, ii, self }, &res) catch |e| {
|
var result: Callback.Result = undefined;
|
||||||
log.err("callback error: {s}", .{res.result orelse "unknown"});
|
cbk.tryCall(.{ n, ii, self }, &result) catch {
|
||||||
log.debug("{s}", .{res.stack orelse "no stack trace"});
|
log.err("callback error: {s}", .{result.exception});
|
||||||
|
log.debug("stack:\n{s}", .{result.stack orelse "???"});
|
||||||
return e;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,38 +177,32 @@ pub const NodeList = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
|
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
|
||||||
|
pub fn postAttach(self: *NodeList, js_obj: JsObject) !void {
|
||||||
pub fn postAttach(self: *NodeList, alloc: std.mem.Allocator, js_obj: jsruntime.JSObject) !void {
|
const len = self.get_length();
|
||||||
const ln = self.get_length();
|
for (0..len) |i| {
|
||||||
var i: u32 = 0;
|
const node = try self._item(@intCast(i)) orelse unreachable;
|
||||||
while (i < ln) {
|
try js_obj.setIndex(i, node);
|
||||||
defer i += 1;
|
|
||||||
const k = try std.fmt.allocPrint(alloc, "{d}", .{i});
|
|
||||||
|
|
||||||
const node = try self._item(i) orelse unreachable;
|
|
||||||
try js_obj.set(k, node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.DOM.NodeList" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
try runner.testCases(&.{
|
||||||
_: std.mem.Allocator,
|
.{ "let list = document.getElementById('content').childNodes", "undefined" },
|
||||||
js_env: *jsruntime.Env,
|
.{ "list.length", "9" },
|
||||||
) anyerror!void {
|
.{ "list[0].__proto__.constructor.name", "Text" },
|
||||||
var childnodes = [_]Case{
|
.{
|
||||||
.{ .src = "let list = document.getElementById('content').childNodes", .ex = "undefined" },
|
\\ let i = 0;
|
||||||
.{ .src = "list.length", .ex = "9" },
|
\\ list.forEach(function (n, idx) {
|
||||||
.{ .src = "list[0].__proto__.constructor.name", .ex = "Text" },
|
\\ i += idx;
|
||||||
.{ .src =
|
\\ });
|
||||||
\\let i = 0;
|
\\ i;
|
||||||
\\list.forEach(function (n, idx) {
|
,
|
||||||
\\ i += idx;
|
"36",
|
||||||
\\});
|
},
|
||||||
\\i;
|
}, .{});
|
||||||
, .ex = "36" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &childnodes);
|
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#processinginstruction
|
// https://dom.spec.whatwg.org/#processinginstruction
|
||||||
@@ -32,7 +28,6 @@ pub const ProcessingInstruction = struct {
|
|||||||
// TODO for libdom processing instruction inherit from node.
|
// TODO for libdom processing instruction inherit from node.
|
||||||
// But the spec says it must inherit from CDATA.
|
// But the spec says it must inherit from CDATA.
|
||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_target(self: *parser.ProcessingInstruction) ![]const u8 {
|
pub fn get_target(self: *parser.ProcessingInstruction) ![]const u8 {
|
||||||
// libdom stores the ProcessingInstruction target in the node's name.
|
// libdom stores the ProcessingInstruction target in the node's name.
|
||||||
@@ -52,18 +47,18 @@ pub const ProcessingInstruction = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.ProcessingInstruction" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var createProcessingInstruction = [_]Case{
|
|
||||||
.{ .src = "let pi = document.createProcessingInstruction('foo', 'bar')", .ex = "undefined" },
|
|
||||||
.{ .src = "pi.target", .ex = "foo" },
|
|
||||||
.{ .src = "pi.data", .ex = "bar" },
|
|
||||||
.{ .src = "pi.data = 'foo'", .ex = "foo" },
|
|
||||||
.{ .src = "pi.data", .ex = "foo" },
|
|
||||||
|
|
||||||
.{ .src = "let pi2 = pi.cloneNode()", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
};
|
.{ "let pi = document.createProcessingInstruction('foo', 'bar')", "undefined" },
|
||||||
try checkCases(js_env, &createProcessingInstruction);
|
.{ "pi.target", "foo" },
|
||||||
|
.{ "pi.data", "bar" },
|
||||||
|
.{ "pi.data = 'foo'", "foo" },
|
||||||
|
.{ "pi.data", "foo" },
|
||||||
|
|
||||||
|
.{ "let pi2 = pi.cloneNode()", "undefined" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,17 +18,12 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
const CharacterData = @import("character_data.zig").CharacterData;
|
const CharacterData = @import("character_data.zig").CharacterData;
|
||||||
const CDATASection = @import("cdata_section.zig").CDATASection;
|
const CDATASection = @import("cdata_section.zig").CDATASection;
|
||||||
|
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
|
||||||
|
|
||||||
// Text interfaces
|
// Text interfaces
|
||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
CDATASection,
|
CDATASection,
|
||||||
@@ -37,11 +32,10 @@ pub const Interfaces = .{
|
|||||||
pub const Text = struct {
|
pub const Text = struct {
|
||||||
pub const Self = parser.Text;
|
pub const Self = parser.Text;
|
||||||
pub const prototype = *CharacterData;
|
pub const prototype = *CharacterData;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn constructor(userctx: UserContext, data: ?[]const u8) !*parser.Text {
|
pub fn constructor(data: ?[]const u8, state: *const SessionState) !*parser.Text {
|
||||||
return parser.documentCreateTextNode(
|
return parser.documentCreateTextNode(
|
||||||
parser.documentHTMLToDocument(userctx.document),
|
parser.documentHTMLToDocument(state.document.?),
|
||||||
data orelse "",
|
data orelse "",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -66,30 +60,28 @@ pub const Text = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.Text" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var constructor = [_]Case{
|
|
||||||
.{ .src = "let t = new Text('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "t.data", .ex = "foo" },
|
|
||||||
|
|
||||||
.{ .src = "let emptyt = new Text()", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "emptyt.data", .ex = "" },
|
.{ "let t = new Text('foo')", "undefined" },
|
||||||
};
|
.{ "t.data", "foo" },
|
||||||
try checkCases(js_env, &constructor);
|
|
||||||
|
|
||||||
var get_whole_text = [_]Case{
|
.{ "let emptyt = new Text()", "undefined" },
|
||||||
.{ .src = "let text = document.getElementById('link').firstChild", .ex = "undefined" },
|
.{ "emptyt.data", "" },
|
||||||
.{ .src = "text.wholeText === 'OK'", .ex = "true" },
|
}, .{});
|
||||||
};
|
|
||||||
try checkCases(js_env, &get_whole_text);
|
|
||||||
|
|
||||||
var split_text = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "text.data = 'OK modified'", .ex = "OK modified" },
|
.{ "let text = document.getElementById('link').firstChild", "undefined" },
|
||||||
.{ .src = "let split = text.splitText('OK'.length)", .ex = "undefined" },
|
.{ "text.wholeText === 'OK'", "true" },
|
||||||
.{ .src = "split.data === ' modified'", .ex = "true" },
|
}, .{});
|
||||||
.{ .src = "text.data === 'OK'", .ex = "true" },
|
|
||||||
};
|
try runner.testCases(&.{
|
||||||
try checkCases(js_env, &split_text);
|
.{ "text.data = 'OK modified'", "OK modified" },
|
||||||
|
.{ "let split = text.splitText('OK'.length)", "undefined" },
|
||||||
|
.{ "split.data === ' modified'", "true" },
|
||||||
|
.{ "text.data === 'OK'", "true" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,12 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
const Variadic = jsruntime.Variadic;
|
|
||||||
|
|
||||||
const DOMException = @import("exceptions.zig").DOMException;
|
const DOMException = @import("exceptions.zig").DOMException;
|
||||||
|
|
||||||
@@ -31,7 +26,6 @@ const DOMException = @import("exceptions.zig").DOMException;
|
|||||||
pub const DOMTokenList = struct {
|
pub const DOMTokenList = struct {
|
||||||
pub const Self = parser.TokenList;
|
pub const Self = parser.TokenList;
|
||||||
pub const Exception = DOMException;
|
pub const Exception = DOMException;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_length(self: *parser.TokenList) !u32 {
|
pub fn get_length(self: *parser.TokenList) !u32 {
|
||||||
return parser.tokenListGetLength(self);
|
return parser.tokenListGetLength(self);
|
||||||
@@ -45,16 +39,14 @@ pub const DOMTokenList = struct {
|
|||||||
return parser.tokenListContains(self, token);
|
return parser.tokenListContains(self, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _add(self: *parser.TokenList, tokens: ?Variadic([]const u8)) !void {
|
pub fn _add(self: *parser.TokenList, tokens: []const []const u8) !void {
|
||||||
if (tokens == null) return;
|
for (tokens) |token| {
|
||||||
for (tokens.?.slice) |token| {
|
|
||||||
try parser.tokenListAdd(self, token);
|
try parser.tokenListAdd(self, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _remove(self: *parser.TokenList, tokens: ?Variadic([]const u8)) !void {
|
pub fn _remove(self: *parser.TokenList, tokens: []const []const u8) !void {
|
||||||
if (tokens == null) return;
|
for (tokens) |token| {
|
||||||
for (tokens.?.slice) |token| {
|
|
||||||
try parser.tokenListRemove(self, token);
|
try parser.tokenListRemove(self, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,52 +105,49 @@ pub const DOMTokenList = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.DOM.TokenList" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var dynamiclist = [_]Case{
|
|
||||||
.{ .src = "let gs = document.getElementById('para-empty')", .ex = "undefined" },
|
|
||||||
.{ .src = "let cl = gs.classList", .ex = "undefined" },
|
|
||||||
.{ .src = "gs.className", .ex = "ok empty" },
|
|
||||||
.{ .src = "cl.value", .ex = "ok empty" },
|
|
||||||
.{ .src = "cl.length", .ex = "2" },
|
|
||||||
.{ .src = "gs.className = 'foo bar baz'", .ex = "foo bar baz" },
|
|
||||||
.{ .src = "gs.className", .ex = "foo bar baz" },
|
|
||||||
.{ .src = "cl.length", .ex = "3" },
|
|
||||||
.{ .src = "gs.className = 'ok empty'", .ex = "ok empty" },
|
|
||||||
.{ .src = "cl.length", .ex = "2" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &dynamiclist);
|
|
||||||
|
|
||||||
var testcases = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let cl2 = gs.classList", .ex = "undefined" },
|
.{ "let gs = document.getElementById('para-empty')", "undefined" },
|
||||||
.{ .src = "cl2.length", .ex = "2" },
|
.{ "let cl = gs.classList", "undefined" },
|
||||||
.{ .src = "cl2.item(0)", .ex = "ok" },
|
.{ "gs.className", "ok empty" },
|
||||||
.{ .src = "cl2.item(1)", .ex = "empty" },
|
.{ "cl.value", "ok empty" },
|
||||||
.{ .src = "cl2.contains('ok')", .ex = "true" },
|
.{ "cl.length", "2" },
|
||||||
.{ .src = "cl2.contains('nok')", .ex = "false" },
|
.{ "gs.className = 'foo bar baz'", "foo bar baz" },
|
||||||
.{ .src = "cl2.add('foo', 'bar', 'baz')", .ex = "undefined" },
|
.{ "gs.className", "foo bar baz" },
|
||||||
.{ .src = "cl2.length", .ex = "5" },
|
.{ "cl.length", "3" },
|
||||||
.{ .src = "cl2.remove('foo', 'bar', 'baz')", .ex = "undefined" },
|
.{ "gs.className = 'ok empty'", "ok empty" },
|
||||||
.{ .src = "cl2.length", .ex = "2" },
|
.{ "cl.length", "2" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &testcases);
|
|
||||||
|
|
||||||
var toogle = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let cl3 = gs.classList", .ex = "undefined" },
|
.{ "let cl2 = gs.classList", "undefined" },
|
||||||
.{ .src = "cl3.toggle('ok')", .ex = "false" },
|
.{ "cl2.length", "2" },
|
||||||
.{ .src = "cl3.toggle('ok')", .ex = "true" },
|
.{ "cl2.item(0)", "ok" },
|
||||||
.{ .src = "cl3.length", .ex = "2" },
|
.{ "cl2.item(1)", "empty" },
|
||||||
};
|
.{ "cl2.contains('ok')", "true" },
|
||||||
try checkCases(js_env, &toogle);
|
.{ "cl2.contains('nok')", "false" },
|
||||||
|
.{ "cl2.add('foo', 'bar', 'baz')", "undefined" },
|
||||||
|
.{ "cl2.length", "5" },
|
||||||
|
.{ "cl2.remove('foo', 'bar', 'baz')", "undefined" },
|
||||||
|
.{ "cl2.length", "2" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
var replace = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let cl4 = gs.classList", .ex = "undefined" },
|
.{ "let cl3 = gs.classList", "undefined" },
|
||||||
.{ .src = "cl4.replace('ok', 'nok')", .ex = "true" },
|
.{ "cl3.toggle('ok')", "false" },
|
||||||
.{ .src = "cl4.value", .ex = "empty nok" },
|
.{ "cl3.toggle('ok')", "true" },
|
||||||
.{ .src = "cl4.replace('nok', 'ok')", .ex = "true" },
|
.{ "cl3.length", "2" },
|
||||||
.{ .src = "cl4.value", .ex = "empty ok" },
|
}, .{});
|
||||||
};
|
|
||||||
try checkCases(js_env, &replace);
|
try runner.testCases(&.{
|
||||||
|
.{ "let cl4 = gs.classList", "undefined" },
|
||||||
|
.{ "cl4.replace('ok', 'nok')", "true" },
|
||||||
|
.{ "cl4.value", "empty nok" },
|
||||||
|
.{ "cl4.replace('nok', 'ok')", "true" },
|
||||||
|
.{ "cl4.value", "empty ok" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
pub const Walker = union(enum) {
|
pub const Walker = union(enum) {
|
||||||
walkerDepthFirst: WalkerDepthFirst,
|
walkerDepthFirst: WalkerDepthFirst,
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const File = std.fs.File;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf.zig");
|
||||||
const Walker = @import("../dom/walker.zig").WalkerChildren;
|
const Walker = @import("dom/walker.zig").WalkerChildren;
|
||||||
|
|
||||||
// writer must be a std.io.Writer
|
// writer must be a std.io.Writer
|
||||||
pub fn writeHTML(doc: *parser.Document, writer: anytype) !void {
|
pub fn writeHTML(doc: *parser.Document, writer: anytype) !void {
|
||||||
|
|||||||
36
src/browser/env.zig
Normal file
36
src/browser/env.zig
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const parser = @import("netsurf.zig");
|
||||||
|
const URL = @import("../url.zig").URL;
|
||||||
|
const js = @import("../runtime/js.zig");
|
||||||
|
const storage = @import("storage/storage.zig");
|
||||||
|
const generate = @import("../runtime/generate.zig");
|
||||||
|
const Loop = @import("../runtime/loop.zig").Loop;
|
||||||
|
const HttpClient = @import("../http/client.zig").Client;
|
||||||
|
const Renderer = @import("browser.zig").Renderer;
|
||||||
|
|
||||||
|
const Interfaces = generate.Tuple(.{
|
||||||
|
@import("console/console.zig").Console,
|
||||||
|
@import("dom/dom.zig").Interfaces,
|
||||||
|
@import("events/event.zig").Interfaces,
|
||||||
|
@import("html/html.zig").Interfaces,
|
||||||
|
@import("iterator/iterator.zig").Interfaces,
|
||||||
|
@import("storage/storage.zig").Interfaces,
|
||||||
|
@import("url/url.zig").Interfaces,
|
||||||
|
@import("xhr/xhr.zig").Interfaces,
|
||||||
|
@import("xmlserializer/xmlserializer.zig").Interfaces,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const JsObject = Env.JsObject;
|
||||||
|
pub const Callback = Env.Callback;
|
||||||
|
pub const Env = js.Env(*SessionState, Interfaces{});
|
||||||
|
|
||||||
|
pub const SessionState = struct {
|
||||||
|
loop: *Loop,
|
||||||
|
url: *const URL,
|
||||||
|
renderer: *Renderer,
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
http_client: *HttpClient,
|
||||||
|
cookie_jar: *storage.CookieJar,
|
||||||
|
document: ?*parser.DocumentHTML,
|
||||||
|
};
|
||||||
245
src/browser/events/event.zig
Normal file
245
src/browser/events/event.zig
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
// 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 parser = @import("../netsurf.zig");
|
||||||
|
const Callback = @import("../env.zig").Callback;
|
||||||
|
const generate = @import("../../runtime/generate.zig");
|
||||||
|
|
||||||
|
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
||||||
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
|
const EventTargetUnion = @import("../dom/event_target.zig").Union;
|
||||||
|
|
||||||
|
const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.events);
|
||||||
|
|
||||||
|
// Event interfaces
|
||||||
|
pub const Interfaces = .{
|
||||||
|
Event,
|
||||||
|
ProgressEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Union = generate.Union(Interfaces);
|
||||||
|
|
||||||
|
// https://dom.spec.whatwg.org/#event
|
||||||
|
pub const Event = struct {
|
||||||
|
pub const Self = parser.Event;
|
||||||
|
pub const Exception = DOMException;
|
||||||
|
|
||||||
|
pub const EventInit = parser.EventInit;
|
||||||
|
|
||||||
|
// JS
|
||||||
|
// --
|
||||||
|
|
||||||
|
pub const _CAPTURING_PHASE = 1;
|
||||||
|
pub const _AT_TARGET = 2;
|
||||||
|
pub const _BUBBLING_PHASE = 3;
|
||||||
|
|
||||||
|
pub fn toInterface(evt: *parser.Event) !Union {
|
||||||
|
return switch (try parser.eventGetInternalType(evt)) {
|
||||||
|
.event => .{ .Event = evt },
|
||||||
|
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn constructor(eventType: []const u8, opts: ?EventInit) !*parser.Event {
|
||||||
|
const event = try parser.eventCreate();
|
||||||
|
try parser.eventInit(event, eventType, opts orelse EventInit{});
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
|
||||||
|
pub fn get_type(self: *parser.Event) ![]const u8 {
|
||||||
|
return try parser.eventType(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_target(self: *parser.Event) !?EventTargetUnion {
|
||||||
|
const et = try parser.eventTarget(self);
|
||||||
|
if (et == null) return null;
|
||||||
|
return try EventTarget.toInterface(et.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_currentTarget(self: *parser.Event) !?EventTargetUnion {
|
||||||
|
const et = try parser.eventCurrentTarget(self);
|
||||||
|
if (et == null) return null;
|
||||||
|
return try EventTarget.toInterface(et.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_eventPhase(self: *parser.Event) !u8 {
|
||||||
|
return try parser.eventPhase(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bubbles(self: *parser.Event) !bool {
|
||||||
|
return try parser.eventBubbles(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cancelable(self: *parser.Event) !bool {
|
||||||
|
return try parser.eventCancelable(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_defaultPrevented(self: *parser.Event) !bool {
|
||||||
|
return try parser.eventDefaultPrevented(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_isTrusted(self: *parser.Event) !bool {
|
||||||
|
return try parser.eventIsTrusted(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_timestamp(self: *parser.Event) !u32 {
|
||||||
|
return try parser.eventTimestamp(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
pub fn _initEvent(
|
||||||
|
self: *parser.Event,
|
||||||
|
eventType: []const u8,
|
||||||
|
bubbles: ?bool,
|
||||||
|
cancelable: ?bool,
|
||||||
|
) !void {
|
||||||
|
const opts = EventInit{
|
||||||
|
.bubbles = bubbles orelse false,
|
||||||
|
.cancelable = cancelable orelse false,
|
||||||
|
};
|
||||||
|
return try parser.eventInit(self, eventType, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _stopPropagation(self: *parser.Event) !void {
|
||||||
|
return try parser.eventStopPropagation(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _stopImmediatePropagation(self: *parser.Event) !void {
|
||||||
|
return try parser.eventStopImmediatePropagation(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _preventDefault(self: *parser.Event) !void {
|
||||||
|
return try parser.eventPreventDefault(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const EventHandler = struct {
|
||||||
|
fn handle(event: ?*parser.Event, data: parser.EventHandlerData) void {
|
||||||
|
var result: Callback.Result = undefined;
|
||||||
|
data.cbk.tryCall(.{if (event) |evt| Event.toInterface(evt) catch unreachable else null}, &result) catch {
|
||||||
|
log.err("event handler error: {s}", .{result.exception});
|
||||||
|
log.debug("stack:\n{s}", .{result.stack orelse "???"});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.handle;
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser.Event" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let content = document.getElementById('content')", "undefined" },
|
||||||
|
.{ "let para = document.getElementById('para')", "undefined" },
|
||||||
|
.{ "var nb = 0; var evt", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{
|
||||||
|
\\ content.addEventListener('target', function(e) {
|
||||||
|
\\ evt = e; nb = nb + 1;
|
||||||
|
\\ e.preventDefault();
|
||||||
|
\\ })
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
.{ "content.dispatchEvent(new Event('target', {bubbles: true, cancelable: true}))", "false" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt.target === content", "true" },
|
||||||
|
.{ "evt.bubbles", "true" },
|
||||||
|
.{ "evt.cancelable", "true" },
|
||||||
|
.{ "evt.defaultPrevented", "true" },
|
||||||
|
.{ "evt.isTrusted", "true" },
|
||||||
|
.{ "evt.timestamp > 1704063600", "true" }, // 2024/01/01 00:00
|
||||||
|
// event.type, event.currentTarget, event.phase checked in EventTarget
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{
|
||||||
|
\\ content.addEventListener('stop',function(e) {
|
||||||
|
\\ e.stopPropagation();
|
||||||
|
\\ nb = nb + 1;
|
||||||
|
\\ }, true)
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
// the following event listener will not be invoked
|
||||||
|
.{
|
||||||
|
\\ para.addEventListener('stop',function(e) {
|
||||||
|
\\ nb = nb + 1;
|
||||||
|
\\ })
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
.{ "para.dispatchEvent(new Event('stop'))", "true" },
|
||||||
|
.{ "nb", "1" }, // will be 2 if event was not stopped at content event listener
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{
|
||||||
|
\\ content.addEventListener('immediate', function(e) {
|
||||||
|
\\ e.stopImmediatePropagation();
|
||||||
|
\\ nb = nb + 1;
|
||||||
|
\\ })
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
// the following event listener will not be invoked
|
||||||
|
.{
|
||||||
|
\\ content.addEventListener('immediate', function(e) {
|
||||||
|
\\ nb = nb + 1;
|
||||||
|
\\ })
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
.{ "content.dispatchEvent(new Event('immediate'))", "true" },
|
||||||
|
.{ "nb", "1" }, // will be 2 if event was not stopped at first content event listener
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0", "0" },
|
||||||
|
.{
|
||||||
|
\\ content.addEventListener('legacy', function(e) {
|
||||||
|
\\ evt = e; nb = nb + 1;
|
||||||
|
\\ })
|
||||||
|
,
|
||||||
|
"undefined",
|
||||||
|
},
|
||||||
|
.{ "let evtLegacy = document.createEvent('Event')", "undefined" },
|
||||||
|
.{ "evtLegacy.initEvent('legacy')", "undefined" },
|
||||||
|
.{ "content.dispatchEvent(evtLegacy)", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var nb = 0; var evt = null; function cbk(event) { nb ++; evt=event; }", "undefined" },
|
||||||
|
.{ "document.addEventListener('count', cbk)", "undefined" },
|
||||||
|
.{ "document.removeEventListener('count', cbk)", "undefined" },
|
||||||
|
.{ "document.dispatchEvent(new Event('count'))", "true" },
|
||||||
|
.{ "nb", "0" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
@@ -18,11 +18,8 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const Node = @import("../dom/node.zig").Node;
|
const Node = @import("../dom/node.zig").Node;
|
||||||
const Document = @import("../dom/document.zig").Document;
|
const Document = @import("../dom/document.zig").Document;
|
||||||
@@ -32,15 +29,12 @@ const Location = @import("location.zig").Location;
|
|||||||
|
|
||||||
const collection = @import("../dom/html_collection.zig");
|
const collection = @import("../dom/html_collection.zig");
|
||||||
const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
||||||
|
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
|
||||||
const Cookie = @import("../storage/cookie.zig").Cookie;
|
const Cookie = @import("../storage/cookie.zig").Cookie;
|
||||||
|
|
||||||
// WEB IDL https://html.spec.whatwg.org/#the-document-object
|
// WEB IDL https://html.spec.whatwg.org/#the-document-object
|
||||||
pub const HTMLDocument = struct {
|
pub const HTMLDocument = struct {
|
||||||
pub const Self = parser.DocumentHTML;
|
pub const Self = parser.DocumentHTML;
|
||||||
pub const prototype = *Document;
|
pub const prototype = *Document;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const sub_type = "node";
|
pub const sub_type = "node";
|
||||||
|
|
||||||
@@ -84,20 +78,18 @@ pub const HTMLDocument = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cookie(_: *parser.DocumentHTML, arena: std.mem.Allocator, userctx: UserContext) ![]const u8 {
|
pub fn get_cookie(_: *parser.DocumentHTML, state: *SessionState) ![]const u8 {
|
||||||
var buf: std.ArrayListUnmanaged(u8) = .{};
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
try userctx.cookie_jar.forRequest(&userctx.url.uri, buf.writer(arena), .{ .navigation = true });
|
try state.cookie_jar.forRequest(&state.url.uri, buf.writer(state.arena), .{ .navigation = true });
|
||||||
return buf.items;
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cookie(_: *parser.DocumentHTML, userctx: UserContext, cookie_str: []const u8) ![]const u8 {
|
pub fn set_cookie(_: *parser.DocumentHTML, cookie_str: []const u8, state: *SessionState) ![]const u8 {
|
||||||
// we use the cookie jar's allocator to parse the cookie because it
|
// we use the cookie jar's allocator to parse the cookie because it
|
||||||
// outlives the page's arena.
|
// outlives the page's arena.
|
||||||
const c = try Cookie.parse(userctx.cookie_jar.allocator, &userctx.url.uri, cookie_str);
|
const c = try Cookie.parse(state.cookie_jar.allocator, &state.url.uri, cookie_str);
|
||||||
errdefer c.deinit();
|
errdefer c.deinit();
|
||||||
|
try state.cookie_jar.add(c, std.time.timestamp());
|
||||||
try userctx.cookie_jar.add(c, std.time.timestamp());
|
|
||||||
|
|
||||||
return cookie_str;
|
return cookie_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,44 +102,45 @@ pub const HTMLDocument = struct {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _getElementsByName(self: *parser.DocumentHTML, alloc: std.mem.Allocator, name: []const u8) !NodeList {
|
pub fn _getElementsByName(self: *parser.DocumentHTML, name: []const u8, state: *SessionState) !NodeList {
|
||||||
|
const arena = state.arena;
|
||||||
var list = NodeList.init();
|
var list = NodeList.init();
|
||||||
errdefer list.deinit(alloc);
|
errdefer list.deinit(arena);
|
||||||
|
|
||||||
if (name.len == 0) return list;
|
if (name.len == 0) return list;
|
||||||
|
|
||||||
const root = parser.documentHTMLToNode(self);
|
const root = parser.documentHTMLToNode(self);
|
||||||
var c = try collection.HTMLCollectionByName(alloc, root, name, false);
|
var c = try collection.HTMLCollectionByName(arena, root, name, false);
|
||||||
|
|
||||||
const ln = try c.get_length();
|
const ln = try c.get_length();
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < ln) {
|
while (i < ln) {
|
||||||
const n = try c.item(i) orelse break;
|
const n = try c.item(i) orelse break;
|
||||||
try list.append(alloc, n);
|
try list.append(arena, n);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_images(self: *parser.DocumentHTML, alloc: std.mem.Allocator) !collection.HTMLCollection {
|
pub fn get_images(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
|
||||||
return try collection.HTMLCollectionByTagName(alloc, parser.documentHTMLToNode(self), "img", false);
|
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "img", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_embeds(self: *parser.DocumentHTML, alloc: std.mem.Allocator) !collection.HTMLCollection {
|
pub fn get_embeds(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
|
||||||
return try collection.HTMLCollectionByTagName(alloc, parser.documentHTMLToNode(self), "embed", false);
|
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "embed", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_plugins(self: *parser.DocumentHTML, alloc: std.mem.Allocator) !collection.HTMLCollection {
|
pub fn get_plugins(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
|
||||||
return get_embeds(self, alloc);
|
return get_embeds(self, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_forms(self: *parser.DocumentHTML, alloc: std.mem.Allocator) !collection.HTMLCollection {
|
pub fn get_forms(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
|
||||||
return try collection.HTMLCollectionByTagName(alloc, parser.documentHTMLToNode(self), "form", false);
|
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "form", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_scripts(self: *parser.DocumentHTML, alloc: std.mem.Allocator) !collection.HTMLCollection {
|
pub fn get_scripts(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
|
||||||
return try collection.HTMLCollectionByTagName(alloc, parser.documentHTMLToNode(self), "script", false);
|
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "script", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_applets(_: *parser.DocumentHTML) !collection.HTMLCollection {
|
pub fn get_applets(_: *parser.DocumentHTML) !collection.HTMLCollection {
|
||||||
@@ -218,62 +211,57 @@ pub const HTMLDocument = struct {
|
|||||||
pub fn set_bgColor(_: *parser.DocumentHTML, _: []const u8) []const u8 {
|
pub fn set_bgColor(_: *parser.DocumentHTML, _: []const u8) []const u8 {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *parser.DocumentHTML, _: std.mem.Allocator) void {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
var constructor = [_]Case{
|
|
||||||
.{ .src = "document.__proto__.constructor.name", .ex = "HTMLDocument" },
|
|
||||||
.{ .src = "document.__proto__.__proto__.constructor.name", .ex = "Document" },
|
|
||||||
.{ .src = "document.body.localName == 'body'", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &constructor);
|
|
||||||
|
|
||||||
var getters = [_]Case{
|
test "Browser.HTML.Document" {
|
||||||
.{ .src = "document.domain", .ex = "" },
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
.{ .src = "document.referrer", .ex = "" },
|
defer runner.deinit();
|
||||||
.{ .src = "document.title", .ex = "" },
|
|
||||||
.{ .src = "document.body.localName", .ex = "body" },
|
|
||||||
.{ .src = "document.head.localName", .ex = "head" },
|
|
||||||
.{ .src = "document.images.length", .ex = "0" },
|
|
||||||
.{ .src = "document.embeds.length", .ex = "0" },
|
|
||||||
.{ .src = "document.plugins.length", .ex = "0" },
|
|
||||||
.{ .src = "document.scripts.length", .ex = "0" },
|
|
||||||
.{ .src = "document.forms.length", .ex = "0" },
|
|
||||||
.{ .src = "document.links.length", .ex = "1" },
|
|
||||||
.{ .src = "document.applets.length", .ex = "0" },
|
|
||||||
.{ .src = "document.anchors.length", .ex = "0" },
|
|
||||||
.{ .src = "document.all.length", .ex = "8" },
|
|
||||||
.{ .src = "document.currentScript", .ex = "null" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getters);
|
|
||||||
|
|
||||||
var titles = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.title = 'foo'", .ex = "foo" },
|
.{ "document.__proto__.constructor.name", "HTMLDocument" },
|
||||||
.{ .src = "document.title", .ex = "foo" },
|
.{ "document.__proto__.__proto__.constructor.name", "Document" },
|
||||||
.{ .src = "document.title = ''", .ex = "" },
|
.{ "document.body.localName == 'body'", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &titles);
|
|
||||||
|
|
||||||
var getElementsByName = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.getElementById('link').setAttribute('name', 'foo')", .ex = "undefined" },
|
.{ "document.domain", "" },
|
||||||
.{ .src = "let list = document.getElementsByName('foo')", .ex = "undefined" },
|
.{ "document.referrer", "" },
|
||||||
.{ .src = "list.length", .ex = "1" },
|
.{ "document.title", "" },
|
||||||
};
|
.{ "document.body.localName", "body" },
|
||||||
try checkCases(js_env, &getElementsByName);
|
.{ "document.head.localName", "head" },
|
||||||
|
.{ "document.images.length", "0" },
|
||||||
|
.{ "document.embeds.length", "0" },
|
||||||
|
.{ "document.plugins.length", "0" },
|
||||||
|
.{ "document.scripts.length", "0" },
|
||||||
|
.{ "document.forms.length", "0" },
|
||||||
|
.{ "document.links.length", "1" },
|
||||||
|
.{ "document.applets.length", "0" },
|
||||||
|
.{ "document.anchors.length", "0" },
|
||||||
|
.{ "document.all.length", "8" },
|
||||||
|
.{ "document.currentScript", "null" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
var cookie = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "document.cookie", .ex = "" },
|
.{ "document.title = 'foo'", "foo" },
|
||||||
.{ .src = "document.cookie = 'name=Oeschger; SameSite=None; Secure'", .ex = "name=Oeschger; SameSite=None; Secure" },
|
.{ "document.title", "foo" },
|
||||||
.{ .src = "document.cookie = 'favorite_food=tripe; SameSite=None; Secure'", .ex = "favorite_food=tripe; SameSite=None; Secure" },
|
.{ "document.title = ''", "" },
|
||||||
.{ .src = "document.cookie", .ex = "name=Oeschger; favorite_food=tripe" },
|
}, .{});
|
||||||
};
|
|
||||||
try checkCases(js_env, &cookie);
|
try runner.testCases(&.{
|
||||||
|
.{ "document.getElementById('link').setAttribute('name', 'foo')", "undefined" },
|
||||||
|
.{ "let list = document.getElementsByName('foo')", "undefined" },
|
||||||
|
.{ "list.length", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "document.cookie", "" },
|
||||||
|
.{ "document.cookie = 'name=Oeschger; SameSite=None; Secure'", "name=Oeschger; SameSite=None; Secure" },
|
||||||
|
.{ "document.cookie = 'favorite_food=tripe; SameSite=None; Secure'", "favorite_food=tripe; SameSite=None; Secure" },
|
||||||
|
.{ "document.cookie", "name=Oeschger; favorite_food=tripe" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -17,16 +17,13 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
const generate = @import("../generate.zig");
|
const generate = @import("../../runtime/generate.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const Element = @import("../dom/element.zig").Element;
|
|
||||||
const URL = @import("../url/url.zig").URL;
|
const URL = @import("../url/url.zig").URL;
|
||||||
const Node = @import("../dom/node.zig").Node;
|
const Node = @import("../dom/node.zig").Node;
|
||||||
|
const Element = @import("../dom/element.zig").Element;
|
||||||
|
|
||||||
// HTMLElement interfaces
|
// HTMLElement interfaces
|
||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
@@ -105,14 +102,11 @@ pub const Union = generate.Union(Interfaces);
|
|||||||
// Abstract class
|
// Abstract class
|
||||||
// --------------
|
// --------------
|
||||||
|
|
||||||
const CSSProperties = struct {
|
const CSSProperties = struct {};
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const HTMLElement = struct {
|
pub const HTMLElement = struct {
|
||||||
pub const Self = parser.ElementHTML;
|
pub const Self = parser.ElementHTML;
|
||||||
pub const prototype = *Element;
|
pub const prototype = *Element;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_style(_: *parser.ElementHTML) CSSProperties {
|
pub fn get_style(_: *parser.ElementHTML) CSSProperties {
|
||||||
return .{};
|
return .{};
|
||||||
@@ -148,7 +142,6 @@ pub const HTMLElement = struct {
|
|||||||
pub const HTMLMediaElement = struct {
|
pub const HTMLMediaElement = struct {
|
||||||
pub const Self = parser.MediaElement;
|
pub const Self = parser.MediaElement;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// HTML elements
|
// HTML elements
|
||||||
@@ -157,14 +150,12 @@ pub const HTMLMediaElement = struct {
|
|||||||
pub const HTMLUnknownElement = struct {
|
pub const HTMLUnknownElement = struct {
|
||||||
pub const Self = parser.Unknown;
|
pub const Self = parser.Unknown;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#the-a-element
|
// https://html.spec.whatwg.org/#the-a-element
|
||||||
pub const HTMLAnchorElement = struct {
|
pub const HTMLAnchorElement = struct {
|
||||||
pub const Self = parser.Anchor;
|
pub const Self = parser.Anchor;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_target(self: *parser.Anchor) ![]const u8 {
|
pub fn get_target(self: *parser.Anchor) ![]const u8 {
|
||||||
return try parser.anchorGetTarget(self);
|
return try parser.anchorGetTarget(self);
|
||||||
@@ -174,7 +165,7 @@ pub const HTMLAnchorElement = struct {
|
|||||||
return try parser.anchorSetTarget(self, href);
|
return try parser.anchorSetTarget(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_download(_: *parser.Anchor) ![]const u8 {
|
pub fn get_download(_: *const parser.Anchor) ![]const u8 {
|
||||||
return ""; // TODO
|
return ""; // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,47 +209,39 @@ pub const HTMLAnchorElement = struct {
|
|||||||
return try parser.nodeSetTextContent(parser.anchorToNode(self), v);
|
return try parser.nodeSetTextContent(parser.anchorToNode(self), v);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn url(self: *parser.Anchor, alloc: std.mem.Allocator) !URL {
|
inline fn url(self: *parser.Anchor, state: *SessionState) !URL {
|
||||||
const href = try parser.anchorGetHref(self);
|
const href = try parser.anchorGetHref(self);
|
||||||
return URL.constructor(alloc, href, null); // TODO inject base url
|
return URL.constructor(href, null, state); // TODO inject base url
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_origin(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_origin(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try u.get_origin(state);
|
||||||
|
|
||||||
return try u.get_origin(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_protocol(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_protocol(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return u.get_protocol(state);
|
||||||
|
|
||||||
return u.get_protocol(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_protocol(self: *parser.Anchor, alloc: std.mem.Allocator, v: []const u8) !void {
|
pub fn set_protocol(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
u.uri.scheme = v;
|
u.uri.scheme = v;
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_host(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_host(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try u.get_host(state);
|
||||||
|
|
||||||
return try u.get_host(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_host(self: *parser.Anchor, alloc: std.mem.Allocator, v: []const u8) !void {
|
pub fn set_host(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
|
||||||
// search : separator
|
// search : separator
|
||||||
var p: ?u16 = null;
|
var p: ?u16 = null;
|
||||||
var h: []const u8 = undefined;
|
var h: []const u8 = undefined;
|
||||||
@@ -270,8 +253,8 @@ pub const HTMLAnchorElement = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
if (p) |pp| {
|
if (p) |pp| {
|
||||||
u.uri.host = .{ .raw = h };
|
u.uri.host = .{ .raw = h };
|
||||||
@@ -281,40 +264,33 @@ pub const HTMLAnchorElement = struct {
|
|||||||
u.uri.port = null;
|
u.uri.port = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_hostname(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_hostname(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try state.arena.dupe(u8, u.get_hostname());
|
||||||
|
|
||||||
return try alloc.dupe(u8, u.get_hostname());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_hostname(self: *parser.Anchor, alloc: std.mem.Allocator, v: []const u8) !void {
|
pub fn set_hostname(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
u.uri.host = .{ .raw = v };
|
u.uri.host = .{ .raw = v };
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_port(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_port(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try u.get_port(state);
|
||||||
|
|
||||||
return try u.get_port(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_port(self: *parser.Anchor, alloc: std.mem.Allocator, v: ?[]const u8) !void {
|
pub fn set_port(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
if (v != null and v.?.len > 0) {
|
if (v != null and v.?.len > 0) {
|
||||||
u.uri.port = try std.fmt.parseInt(u16, v.?, 10);
|
u.uri.port = try std.fmt.parseInt(u16, v.?, 10);
|
||||||
@@ -322,407 +298,340 @@ pub const HTMLAnchorElement = struct {
|
|||||||
u.uri.port = null;
|
u.uri.port = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_username(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_username(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try state.arena.dupe(u8, u.get_username());
|
||||||
|
|
||||||
return try alloc.dupe(u8, u.get_username());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_username(self: *parser.Anchor, alloc: std.mem.Allocator, v: ?[]const u8) !void {
|
pub fn set_username(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
if (v) |vv| {
|
if (v) |vv| {
|
||||||
u.uri.user = .{ .raw = vv };
|
u.uri.user = .{ .raw = vv };
|
||||||
} else {
|
} else {
|
||||||
u.uri.user = null;
|
u.uri.user = null;
|
||||||
}
|
}
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_password(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_password(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try state.arena.dupe(u8, u.get_password());
|
||||||
|
|
||||||
return try alloc.dupe(u8, u.get_password());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_password(self: *parser.Anchor, alloc: std.mem.Allocator, v: ?[]const u8) !void {
|
pub fn set_password(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
if (v) |vv| {
|
if (v) |vv| {
|
||||||
u.uri.password = .{ .raw = vv };
|
u.uri.password = .{ .raw = vv };
|
||||||
} else {
|
} else {
|
||||||
u.uri.password = null;
|
u.uri.password = null;
|
||||||
}
|
}
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_pathname(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_pathname(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try state.arena.dupe(u8, u.get_pathname());
|
||||||
|
|
||||||
return try alloc.dupe(u8, u.get_pathname());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pathname(self: *parser.Anchor, alloc: std.mem.Allocator, v: []const u8) !void {
|
pub fn set_pathname(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
u.uri.path = .{ .raw = v };
|
u.uri.path = .{ .raw = v };
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_search(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_search(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try u.get_search(state);
|
||||||
|
|
||||||
return try u.get_search(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_search(self: *parser.Anchor, alloc: std.mem.Allocator, v: ?[]const u8) !void {
|
pub fn set_search(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
if (v) |vv| {
|
if (v) |vv| {
|
||||||
u.uri.query = .{ .raw = vv };
|
u.uri.query = .{ .raw = vv };
|
||||||
} else {
|
} else {
|
||||||
u.uri.query = null;
|
u.uri.query = null;
|
||||||
}
|
}
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return a disposable string
|
// TODO return a disposable string
|
||||||
pub fn get_hash(self: *parser.Anchor, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_hash(self: *parser.Anchor, state: *SessionState) ![]const u8 {
|
||||||
var u = try url(self, alloc);
|
var u = try url(self, state);
|
||||||
defer u.deinit(alloc);
|
return try u.get_hash(state);
|
||||||
|
|
||||||
return try u.get_hash(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_hash(self: *parser.Anchor, alloc: std.mem.Allocator, v: ?[]const u8) !void {
|
pub fn set_hash(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
|
||||||
var u = try url(self, alloc);
|
const arena = state.arena;
|
||||||
defer u.deinit(alloc);
|
var u = try url(self, state);
|
||||||
|
|
||||||
if (v) |vv| {
|
if (v) |vv| {
|
||||||
u.uri.fragment = .{ .raw = vv };
|
u.uri.fragment = .{ .raw = vv };
|
||||||
} else {
|
} else {
|
||||||
u.uri.fragment = null;
|
u.uri.fragment = null;
|
||||||
}
|
}
|
||||||
const href = try u.format(alloc);
|
const href = try u.toString(arena);
|
||||||
defer alloc.free(href);
|
|
||||||
|
|
||||||
try parser.anchorSetHref(self, href);
|
try parser.anchorSetHref(self, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *parser.Anchor, _: std.mem.Allocator) void {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLAppletElement = struct {
|
pub const HTMLAppletElement = struct {
|
||||||
pub const Self = parser.Applet;
|
pub const Self = parser.Applet;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLAreaElement = struct {
|
pub const HTMLAreaElement = struct {
|
||||||
pub const Self = parser.Area;
|
pub const Self = parser.Area;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLAudioElement = struct {
|
pub const HTMLAudioElement = struct {
|
||||||
pub const Self = parser.Audio;
|
pub const Self = parser.Audio;
|
||||||
pub const prototype = *HTMLMediaElement;
|
pub const prototype = *HTMLMediaElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLBRElement = struct {
|
pub const HTMLBRElement = struct {
|
||||||
pub const Self = parser.BR;
|
pub const Self = parser.BR;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLBaseElement = struct {
|
pub const HTMLBaseElement = struct {
|
||||||
pub const Self = parser.Base;
|
pub const Self = parser.Base;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLBodyElement = struct {
|
pub const HTMLBodyElement = struct {
|
||||||
pub const Self = parser.Body;
|
pub const Self = parser.Body;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLButtonElement = struct {
|
pub const HTMLButtonElement = struct {
|
||||||
pub const Self = parser.Button;
|
pub const Self = parser.Button;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLCanvasElement = struct {
|
pub const HTMLCanvasElement = struct {
|
||||||
pub const Self = parser.Canvas;
|
pub const Self = parser.Canvas;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLDListElement = struct {
|
pub const HTMLDListElement = struct {
|
||||||
pub const Self = parser.DList;
|
pub const Self = parser.DList;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLDataElement = struct {
|
pub const HTMLDataElement = struct {
|
||||||
pub const Self = parser.Data;
|
pub const Self = parser.Data;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLDataListElement = struct {
|
pub const HTMLDataListElement = struct {
|
||||||
pub const Self = parser.DataList;
|
pub const Self = parser.DataList;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLDialogElement = struct {
|
pub const HTMLDialogElement = struct {
|
||||||
pub const Self = parser.Dialog;
|
pub const Self = parser.Dialog;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLDirectoryElement = struct {
|
pub const HTMLDirectoryElement = struct {
|
||||||
pub const Self = parser.Directory;
|
pub const Self = parser.Directory;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLDivElement = struct {
|
pub const HTMLDivElement = struct {
|
||||||
pub const Self = parser.Div;
|
pub const Self = parser.Div;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLEmbedElement = struct {
|
pub const HTMLEmbedElement = struct {
|
||||||
pub const Self = parser.Embed;
|
pub const Self = parser.Embed;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLFieldSetElement = struct {
|
pub const HTMLFieldSetElement = struct {
|
||||||
pub const Self = parser.FieldSet;
|
pub const Self = parser.FieldSet;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLFontElement = struct {
|
pub const HTMLFontElement = struct {
|
||||||
pub const Self = parser.Font;
|
pub const Self = parser.Font;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLFormElement = struct {
|
pub const HTMLFormElement = struct {
|
||||||
pub const Self = parser.Form;
|
pub const Self = parser.Form;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLFrameElement = struct {
|
pub const HTMLFrameElement = struct {
|
||||||
pub const Self = parser.Frame;
|
pub const Self = parser.Frame;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLFrameSetElement = struct {
|
pub const HTMLFrameSetElement = struct {
|
||||||
pub const Self = parser.FrameSet;
|
pub const Self = parser.FrameSet;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLHRElement = struct {
|
pub const HTMLHRElement = struct {
|
||||||
pub const Self = parser.HR;
|
pub const Self = parser.HR;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLHeadElement = struct {
|
pub const HTMLHeadElement = struct {
|
||||||
pub const Self = parser.Head;
|
pub const Self = parser.Head;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLHeadingElement = struct {
|
pub const HTMLHeadingElement = struct {
|
||||||
pub const Self = parser.Heading;
|
pub const Self = parser.Heading;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLHtmlElement = struct {
|
pub const HTMLHtmlElement = struct {
|
||||||
pub const Self = parser.Html;
|
pub const Self = parser.Html;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLIFrameElement = struct {
|
pub const HTMLIFrameElement = struct {
|
||||||
pub const Self = parser.IFrame;
|
pub const Self = parser.IFrame;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLImageElement = struct {
|
pub const HTMLImageElement = struct {
|
||||||
pub const Self = parser.Image;
|
pub const Self = parser.Image;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLInputElement = struct {
|
pub const HTMLInputElement = struct {
|
||||||
pub const Self = parser.Input;
|
pub const Self = parser.Input;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLLIElement = struct {
|
pub const HTMLLIElement = struct {
|
||||||
pub const Self = parser.LI;
|
pub const Self = parser.LI;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLLabelElement = struct {
|
pub const HTMLLabelElement = struct {
|
||||||
pub const Self = parser.Label;
|
pub const Self = parser.Label;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLLegendElement = struct {
|
pub const HTMLLegendElement = struct {
|
||||||
pub const Self = parser.Legend;
|
pub const Self = parser.Legend;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLLinkElement = struct {
|
pub const HTMLLinkElement = struct {
|
||||||
pub const Self = parser.Link;
|
pub const Self = parser.Link;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLMapElement = struct {
|
pub const HTMLMapElement = struct {
|
||||||
pub const Self = parser.Map;
|
pub const Self = parser.Map;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLMetaElement = struct {
|
pub const HTMLMetaElement = struct {
|
||||||
pub const Self = parser.Meta;
|
pub const Self = parser.Meta;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLMeterElement = struct {
|
pub const HTMLMeterElement = struct {
|
||||||
pub const Self = parser.Meter;
|
pub const Self = parser.Meter;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLModElement = struct {
|
pub const HTMLModElement = struct {
|
||||||
pub const Self = parser.Mod;
|
pub const Self = parser.Mod;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLOListElement = struct {
|
pub const HTMLOListElement = struct {
|
||||||
pub const Self = parser.OList;
|
pub const Self = parser.OList;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLObjectElement = struct {
|
pub const HTMLObjectElement = struct {
|
||||||
pub const Self = parser.Object;
|
pub const Self = parser.Object;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLOptGroupElement = struct {
|
pub const HTMLOptGroupElement = struct {
|
||||||
pub const Self = parser.OptGroup;
|
pub const Self = parser.OptGroup;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLOptionElement = struct {
|
pub const HTMLOptionElement = struct {
|
||||||
pub const Self = parser.Option;
|
pub const Self = parser.Option;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLOutputElement = struct {
|
pub const HTMLOutputElement = struct {
|
||||||
pub const Self = parser.Output;
|
pub const Self = parser.Output;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLParagraphElement = struct {
|
pub const HTMLParagraphElement = struct {
|
||||||
pub const Self = parser.Paragraph;
|
pub const Self = parser.Paragraph;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLParamElement = struct {
|
pub const HTMLParamElement = struct {
|
||||||
pub const Self = parser.Param;
|
pub const Self = parser.Param;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLPictureElement = struct {
|
pub const HTMLPictureElement = struct {
|
||||||
pub const Self = parser.Picture;
|
pub const Self = parser.Picture;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLPreElement = struct {
|
pub const HTMLPreElement = struct {
|
||||||
pub const Self = parser.Pre;
|
pub const Self = parser.Pre;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLProgressElement = struct {
|
pub const HTMLProgressElement = struct {
|
||||||
pub const Self = parser.Progress;
|
pub const Self = parser.Progress;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLQuoteElement = struct {
|
pub const HTMLQuoteElement = struct {
|
||||||
pub const Self = parser.Quote;
|
pub const Self = parser.Quote;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#the-script-element
|
// https://html.spec.whatwg.org/#the-script-element
|
||||||
pub const HTMLScriptElement = struct {
|
pub const HTMLScriptElement = struct {
|
||||||
pub const Self = parser.Script;
|
pub const Self = parser.Script;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn get_src(self: *parser.Script) !?[]const u8 {
|
pub fn get_src(self: *parser.Script) !?[]const u8 {
|
||||||
return try parser.elementGetAttribute(
|
return try parser.elementGetAttribute(
|
||||||
@@ -837,103 +746,86 @@ pub const HTMLScriptElement = struct {
|
|||||||
pub const HTMLSelectElement = struct {
|
pub const HTMLSelectElement = struct {
|
||||||
pub const Self = parser.Select;
|
pub const Self = parser.Select;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLSourceElement = struct {
|
pub const HTMLSourceElement = struct {
|
||||||
pub const Self = parser.Source;
|
pub const Self = parser.Source;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLSpanElement = struct {
|
pub const HTMLSpanElement = struct {
|
||||||
pub const Self = parser.Span;
|
pub const Self = parser.Span;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLStyleElement = struct {
|
pub const HTMLStyleElement = struct {
|
||||||
pub const Self = parser.Style;
|
pub const Self = parser.Style;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTableElement = struct {
|
pub const HTMLTableElement = struct {
|
||||||
pub const Self = parser.Table;
|
pub const Self = parser.Table;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTableCaptionElement = struct {
|
pub const HTMLTableCaptionElement = struct {
|
||||||
pub const Self = parser.TableCaption;
|
pub const Self = parser.TableCaption;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTableCellElement = struct {
|
pub const HTMLTableCellElement = struct {
|
||||||
pub const Self = parser.TableCell;
|
pub const Self = parser.TableCell;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTableColElement = struct {
|
pub const HTMLTableColElement = struct {
|
||||||
pub const Self = parser.TableCol;
|
pub const Self = parser.TableCol;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTableRowElement = struct {
|
pub const HTMLTableRowElement = struct {
|
||||||
pub const Self = parser.TableRow;
|
pub const Self = parser.TableRow;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTableSectionElement = struct {
|
pub const HTMLTableSectionElement = struct {
|
||||||
pub const Self = parser.TableSection;
|
pub const Self = parser.TableSection;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTemplateElement = struct {
|
pub const HTMLTemplateElement = struct {
|
||||||
pub const Self = parser.Template;
|
pub const Self = parser.Template;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTextAreaElement = struct {
|
pub const HTMLTextAreaElement = struct {
|
||||||
pub const Self = parser.TextArea;
|
pub const Self = parser.TextArea;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTimeElement = struct {
|
pub const HTMLTimeElement = struct {
|
||||||
pub const Self = parser.Time;
|
pub const Self = parser.Time;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTitleElement = struct {
|
pub const HTMLTitleElement = struct {
|
||||||
pub const Self = parser.Title;
|
pub const Self = parser.Title;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLTrackElement = struct {
|
pub const HTMLTrackElement = struct {
|
||||||
pub const Self = parser.Track;
|
pub const Self = parser.Track;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLUListElement = struct {
|
pub const HTMLUListElement = struct {
|
||||||
pub const Self = parser.UList;
|
pub const Self = parser.UList;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const HTMLVideoElement = struct {
|
pub const HTMLVideoElement = struct {
|
||||||
pub const Self = parser.Video;
|
pub const Self = parser.Video;
|
||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn toInterface(comptime T: type, e: *parser.Element) !T {
|
pub fn toInterface(comptime T: type, e: *parser.Element) !T {
|
||||||
@@ -1010,89 +902,84 @@ pub fn toInterface(comptime T: type, e: *parser.Element) !T {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.HTML.Element" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
try runner.testCases(&.{
|
||||||
_: std.mem.Allocator,
|
.{ "let a = document.getElementById('link')", "undefined" },
|
||||||
js_env: *jsruntime.Env,
|
.{ "a.target", "" },
|
||||||
) anyerror!void {
|
.{ "a.target = '_blank'", "_blank" },
|
||||||
var anchor = [_]Case{
|
.{ "a.target", "_blank" },
|
||||||
.{ .src = "let a = document.getElementById('link')", .ex = "undefined" },
|
.{ "a.target = ''", "" },
|
||||||
.{ .src = "a.target", .ex = "" },
|
|
||||||
.{ .src = "a.target = '_blank'", .ex = "_blank" },
|
|
||||||
.{ .src = "a.target", .ex = "_blank" },
|
|
||||||
.{ .src = "a.target = ''", .ex = "" },
|
|
||||||
|
|
||||||
.{ .src = "a.href", .ex = "foo" },
|
.{ "a.href", "foo" },
|
||||||
.{ .src = "a.href = 'https://lightpanda.io/'", .ex = "https://lightpanda.io/" },
|
.{ "a.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
|
||||||
.{ .src = "a.href", .ex = "https://lightpanda.io/" },
|
.{ "a.href", "https://lightpanda.io/" },
|
||||||
|
|
||||||
.{ .src = "a.origin", .ex = "https://lightpanda.io" },
|
.{ "a.origin", "https://lightpanda.io" },
|
||||||
|
|
||||||
.{ .src = "a.host = 'lightpanda.io:443'", .ex = "lightpanda.io:443" },
|
.{ "a.host = 'lightpanda.io:443'", "lightpanda.io:443" },
|
||||||
.{ .src = "a.host", .ex = "lightpanda.io:443" },
|
.{ "a.host", "lightpanda.io:443" },
|
||||||
.{ .src = "a.port", .ex = "443" },
|
.{ "a.port", "443" },
|
||||||
.{ .src = "a.hostname", .ex = "lightpanda.io" },
|
.{ "a.hostname", "lightpanda.io" },
|
||||||
|
|
||||||
.{ .src = "a.host = 'lightpanda.io'", .ex = "lightpanda.io" },
|
.{ "a.host = 'lightpanda.io'", "lightpanda.io" },
|
||||||
.{ .src = "a.host", .ex = "lightpanda.io" },
|
.{ "a.host", "lightpanda.io" },
|
||||||
.{ .src = "a.port", .ex = "" },
|
.{ "a.port", "" },
|
||||||
.{ .src = "a.hostname", .ex = "lightpanda.io" },
|
.{ "a.hostname", "lightpanda.io" },
|
||||||
|
|
||||||
.{ .src = "a.host", .ex = "lightpanda.io" },
|
.{ "a.host", "lightpanda.io" },
|
||||||
.{ .src = "a.hostname", .ex = "lightpanda.io" },
|
.{ "a.hostname", "lightpanda.io" },
|
||||||
.{ .src = "a.hostname = 'foo.bar'", .ex = "foo.bar" },
|
.{ "a.hostname = 'foo.bar'", "foo.bar" },
|
||||||
.{ .src = "a.href", .ex = "https://foo.bar/" },
|
.{ "a.href", "https://foo.bar/" },
|
||||||
|
|
||||||
.{ .src = "a.search", .ex = "" },
|
.{ "a.search", "" },
|
||||||
.{ .src = "a.search = 'q=bar'", .ex = "q=bar" },
|
.{ "a.search = 'q=bar'", "q=bar" },
|
||||||
.{ .src = "a.search", .ex = "?q=bar" },
|
.{ "a.search", "?q=bar" },
|
||||||
.{ .src = "a.href", .ex = "https://foo.bar/?q=bar" },
|
.{ "a.href", "https://foo.bar/?q=bar" },
|
||||||
|
|
||||||
.{ .src = "a.hash", .ex = "" },
|
.{ "a.hash", "" },
|
||||||
.{ .src = "a.hash = 'frag'", .ex = "frag" },
|
.{ "a.hash = 'frag'", "frag" },
|
||||||
.{ .src = "a.hash", .ex = "#frag" },
|
.{ "a.hash", "#frag" },
|
||||||
.{ .src = "a.href", .ex = "https://foo.bar/?q=bar#frag" },
|
.{ "a.href", "https://foo.bar/?q=bar#frag" },
|
||||||
|
|
||||||
.{ .src = "a.port", .ex = "" },
|
.{ "a.port", "" },
|
||||||
.{ .src = "a.port = '443'", .ex = "443" },
|
.{ "a.port = '443'", "443" },
|
||||||
.{ .src = "a.host", .ex = "foo.bar:443" },
|
.{ "a.host", "foo.bar:443" },
|
||||||
.{ .src = "a.hostname", .ex = "foo.bar" },
|
.{ "a.hostname", "foo.bar" },
|
||||||
.{ .src = "a.href", .ex = "https://foo.bar:443/?q=bar#frag" },
|
.{ "a.href", "https://foo.bar:443/?q=bar#frag" },
|
||||||
.{ .src = "a.port = null", .ex = "null" },
|
.{ "a.port = null", "null" },
|
||||||
.{ .src = "a.href", .ex = "https://foo.bar/?q=bar#frag" },
|
.{ "a.href", "https://foo.bar/?q=bar#frag" },
|
||||||
|
|
||||||
.{ .src = "a.href = 'foo'", .ex = "foo" },
|
.{ "a.href = 'foo'", "foo" },
|
||||||
|
|
||||||
.{ .src = "a.type", .ex = "" },
|
.{ "a.type", "" },
|
||||||
.{ .src = "a.type = 'text/html'", .ex = "text/html" },
|
.{ "a.type = 'text/html'", "text/html" },
|
||||||
.{ .src = "a.type", .ex = "text/html" },
|
.{ "a.type", "text/html" },
|
||||||
.{ .src = "a.type = ''", .ex = "" },
|
.{ "a.type = ''", "" },
|
||||||
|
|
||||||
.{ .src = "a.text", .ex = "OK" },
|
.{ "a.text", "OK" },
|
||||||
.{ .src = "a.text = 'foo'", .ex = "foo" },
|
.{ "a.text = 'foo'", "foo" },
|
||||||
.{ .src = "a.text", .ex = "foo" },
|
.{ "a.text", "foo" },
|
||||||
.{ .src = "a.text = 'OK'", .ex = "OK" },
|
.{ "a.text = 'OK'", "OK" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &anchor);
|
|
||||||
|
|
||||||
var script = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "let script = document.createElement('script')", .ex = "undefined" },
|
.{ "let script = document.createElement('script')", "undefined" },
|
||||||
.{ .src = "script.src = 'foo.bar'", .ex = "foo.bar" },
|
.{ "script.src = 'foo.bar'", "foo.bar" },
|
||||||
|
|
||||||
.{ .src = "script.async = true", .ex = "true" },
|
.{ "script.async = true", "true" },
|
||||||
.{ .src = "script.async", .ex = "true" },
|
.{ "script.async", "true" },
|
||||||
.{ .src = "script.async = false", .ex = "false" },
|
.{ "script.async = false", "false" },
|
||||||
.{ .src = "script.async", .ex = "false" },
|
.{ "script.async", "false" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &script);
|
|
||||||
|
|
||||||
var innertext = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "const backup = document.getElementById('content')", .ex = "undefined" },
|
.{ "const backup = document.getElementById('content')", "undefined" },
|
||||||
.{ .src = "document.getElementById('content').innerText = 'foo';", .ex = "foo" },
|
.{ "document.getElementById('content').innerText = 'foo';", "foo" },
|
||||||
.{ .src = "document.getElementById('content').innerText", .ex = "foo" },
|
.{ "document.getElementById('content').innerText", "foo" },
|
||||||
.{ .src = "document.getElementById('content').innerHTML = backup; true;", .ex = "true" },
|
.{ "document.getElementById('content').innerHTML = backup; true;", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &innertext);
|
|
||||||
}
|
}
|
||||||
@@ -19,15 +19,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
|
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
|
||||||
pub const History = struct {
|
pub const History = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
const ScrollRestorationMode = enum {
|
const ScrollRestorationMode = enum {
|
||||||
auto,
|
auto,
|
||||||
manual,
|
manual,
|
||||||
@@ -98,31 +92,31 @@ pub const History = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.HTML.History" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var history = [_]Case{
|
|
||||||
.{ .src = "history.scrollRestoration", .ex = "auto" },
|
|
||||||
.{ .src = "history.scrollRestoration = 'manual'", .ex = "manual" },
|
|
||||||
.{ .src = "history.scrollRestoration = 'foo'", .ex = "foo" },
|
|
||||||
.{ .src = "history.scrollRestoration", .ex = "manual" },
|
|
||||||
.{ .src = "history.scrollRestoration = 'auto'", .ex = "auto" },
|
|
||||||
.{ .src = "history.scrollRestoration", .ex = "auto" },
|
|
||||||
|
|
||||||
.{ .src = "history.state", .ex = "null" },
|
try runner.testCases(&.{
|
||||||
|
.{ "history.scrollRestoration", "auto" },
|
||||||
|
.{ "history.scrollRestoration = 'manual'", "manual" },
|
||||||
|
.{ "history.scrollRestoration = 'foo'", "foo" },
|
||||||
|
.{ "history.scrollRestoration", "manual" },
|
||||||
|
.{ "history.scrollRestoration = 'auto'", "auto" },
|
||||||
|
.{ "history.scrollRestoration", "auto" },
|
||||||
|
|
||||||
.{ .src = "history.pushState({}, null, '')", .ex = "undefined" },
|
.{ "history.state", "null" },
|
||||||
|
|
||||||
.{ .src = "history.replaceState({}, null, '')", .ex = "undefined" },
|
.{ "history.pushState({}, null, '')", "undefined" },
|
||||||
|
|
||||||
.{ .src = "history.go()", .ex = "undefined" },
|
.{ "history.replaceState({}, null, '')", "undefined" },
|
||||||
.{ .src = "history.go(1)", .ex = "undefined" },
|
|
||||||
.{ .src = "history.go(-1)", .ex = "undefined" },
|
|
||||||
|
|
||||||
.{ .src = "history.forward()", .ex = "undefined" },
|
.{ "history.go()", "undefined" },
|
||||||
|
.{ "history.go(1)", "undefined" },
|
||||||
|
.{ "history.go(-1)", "undefined" },
|
||||||
|
|
||||||
.{ .src = "history.back()", .ex = "undefined" },
|
.{ "history.forward()", "undefined" },
|
||||||
};
|
|
||||||
try checkCases(js_env, &history);
|
.{ "history.back()", "undefined" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const generate = @import("../generate.zig");
|
|
||||||
|
|
||||||
const HTMLDocument = @import("document.zig").HTMLDocument;
|
const HTMLDocument = @import("document.zig").HTMLDocument;
|
||||||
const HTMLElem = @import("elements.zig");
|
const HTMLElem = @import("elements.zig");
|
||||||
const Window = @import("window.zig").Window;
|
const Window = @import("window.zig").Window;
|
||||||
111
src/browser/html/location.zig
Normal file
111
src/browser/html/location.zig
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// 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 SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const URL = @import("../url/url.zig").URL;
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-location-interface
|
||||||
|
pub const Location = struct {
|
||||||
|
url: ?URL = null,
|
||||||
|
|
||||||
|
pub fn get_href(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_href(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_protocol(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_protocol(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_host(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_host(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hostname(self: *Location) []const u8 {
|
||||||
|
if (self.url) |*u| return u.get_hostname();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_port(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_port(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pathname(self: *Location) []const u8 {
|
||||||
|
if (self.url) |*u| return u.get_pathname();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_search(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_search(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hash(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_hash(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_origin(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
if (self.url) |*u| return u.get_origin(state);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn _assign(_: *Location, url: []const u8) !void {
|
||||||
|
_ = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn _replace(_: *Location, url: []const u8) !void {
|
||||||
|
_ = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn _reload(_: *Location) !void {}
|
||||||
|
|
||||||
|
pub fn _toString(self: *Location, state: *SessionState) ![]const u8 {
|
||||||
|
return try self.get_href(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser.HTML.Location" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "location.href", "https://lightpanda.io/opensource-browser/" },
|
||||||
|
.{ "document.location.href", "https://lightpanda.io/opensource-browser/" },
|
||||||
|
|
||||||
|
.{ "location.host", "lightpanda.io" },
|
||||||
|
.{ "location.hostname", "lightpanda.io" },
|
||||||
|
.{ "location.origin", "https://lightpanda.io" },
|
||||||
|
.{ "location.pathname", "/opensource-browser/" },
|
||||||
|
.{ "location.hash", "" },
|
||||||
|
.{ "location.port", "" },
|
||||||
|
.{ "location.search", "" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
@@ -19,15 +19,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/system-state.html#navigator
|
// https://html.spec.whatwg.org/multipage/system-state.html#navigator
|
||||||
pub const Navigator = struct {
|
pub const Navigator = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
agent: []const u8 = "Lightpanda/1.0",
|
agent: []const u8 = "Lightpanda/1.0",
|
||||||
version: []const u8 = "1.0",
|
version: []const u8 = "1.0",
|
||||||
vendor: []const u8 = "",
|
vendor: []const u8 = "",
|
||||||
@@ -89,14 +83,14 @@ pub const Navigator = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.HTML.Navigator" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var navigator = [_]Case{
|
|
||||||
.{ .src = "navigator.userAgent", .ex = "Lightpanda/1.0" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "navigator.appVersion", .ex = "1.0" },
|
.{ "navigator.userAgent", "Lightpanda/1.0" },
|
||||||
.{ .src = "navigator.language", .ex = "en-US" },
|
.{ "navigator.appVersion", "1.0" },
|
||||||
};
|
.{ "navigator.language", "en-US" },
|
||||||
try checkCases(js_env, &navigator);
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -18,17 +18,14 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
const jsruntime = @import("jsruntime");
|
const Callback = @import("../env.zig").Callback;
|
||||||
const Callback = jsruntime.Callback;
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const CallbackArg = jsruntime.CallbackArg;
|
|
||||||
const Loop = jsruntime.Loop;
|
|
||||||
|
|
||||||
const URL = @import("../../../url.zig").URL;
|
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
|
||||||
const Navigator = @import("navigator.zig").Navigator;
|
const Navigator = @import("navigator.zig").Navigator;
|
||||||
const History = @import("history.zig").History;
|
const History = @import("history.zig").History;
|
||||||
const Location = @import("location.zig").Location;
|
const Location = @import("location.zig").Location;
|
||||||
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
|
|
||||||
const storage = @import("../storage/storage.zig");
|
const storage = @import("../storage/storage.zig");
|
||||||
|
|
||||||
@@ -36,14 +33,12 @@ const storage = @import("../storage/storage.zig");
|
|||||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
||||||
pub const Window = struct {
|
pub const Window = struct {
|
||||||
pub const prototype = *EventTarget;
|
pub const prototype = *EventTarget;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
pub const global_type = true;
|
|
||||||
|
|
||||||
// Extend libdom event target for pure zig struct.
|
// Extend libdom event target for pure zig struct.
|
||||||
base: parser.EventTargetTBase = parser.EventTargetTBase{},
|
base: parser.EventTargetTBase = parser.EventTargetTBase{},
|
||||||
|
|
||||||
document: ?*parser.DocumentHTML = null,
|
document: ?*parser.DocumentHTML = null,
|
||||||
target: []const u8,
|
target: []const u8 = "",
|
||||||
history: History = .{},
|
history: History = .{},
|
||||||
location: Location = .{},
|
location: Location = .{},
|
||||||
storage_shelf: ?*storage.Shelf = null,
|
storage_shelf: ?*storage.Shelf = null,
|
||||||
@@ -53,7 +48,7 @@ pub const Window = struct {
|
|||||||
timeoutid: u32 = 0,
|
timeoutid: u32 = 0,
|
||||||
timeoutids: [512]u64 = undefined,
|
timeoutids: [512]u64 = undefined,
|
||||||
|
|
||||||
navigator: Navigator,
|
navigator: Navigator = .{},
|
||||||
|
|
||||||
pub fn create(target: ?[]const u8, navigator: ?Navigator) Window {
|
pub fn create(target: ?[]const u8, navigator: ?Navigator) Window {
|
||||||
return .{
|
return .{
|
||||||
@@ -121,11 +116,11 @@ pub const Window = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle callback arguments.
|
// TODO handle callback arguments.
|
||||||
pub fn _setTimeout(self: *Window, loop: *Loop, cbk: Callback, delay: ?u32) !u32 {
|
pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
|
||||||
if (self.timeoutid >= self.timeoutids.len) return error.TooMuchTimeout;
|
if (self.timeoutid >= self.timeoutids.len) return error.TooMuchTimeout;
|
||||||
|
|
||||||
const ddelay: u63 = delay orelse 0;
|
const ddelay: u63 = delay orelse 0;
|
||||||
const id = try loop.timeout(ddelay * std.time.ns_per_ms, cbk);
|
const id = try state.loop.timeout(ddelay * std.time.ns_per_ms, cbk);
|
||||||
|
|
||||||
self.timeoutids[self.timeoutid] = id;
|
self.timeoutids[self.timeoutid] = id;
|
||||||
defer self.timeoutid += 1;
|
defer self.timeoutid += 1;
|
||||||
@@ -133,12 +128,12 @@ pub const Window = struct {
|
|||||||
return self.timeoutid;
|
return self.timeoutid;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _clearTimeout(self: *Window, loop: *Loop, id: u32) !void {
|
pub fn _clearTimeout(self: *Window, id: u32, state: *SessionState) !void {
|
||||||
// I do would prefer return an error in this case, but it seems some JS
|
// I do would prefer return an error in this case, but it seems some JS
|
||||||
// uses invalid id, in particular id 0.
|
// uses invalid id, in particular id 0.
|
||||||
// So we silently ignore invalid id for now.
|
// So we silently ignore invalid id for now.
|
||||||
if (id >= self.timeoutid) return;
|
if (id >= self.timeoutid) return;
|
||||||
|
|
||||||
try loop.cancel(self.timeoutids[id], null);
|
try state.loop.cancel(self.timeoutids[id], null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -5,8 +5,6 @@ pub const Interfaces = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const U32Iterator = struct {
|
pub const U32Iterator = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
length: u32,
|
length: u32,
|
||||||
index: u32 = 0,
|
index: u32 = 0,
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ pub const Mime = struct {
|
|||||||
content_type: ContentType,
|
content_type: ContentType,
|
||||||
params: []const u8 = "",
|
params: []const u8 = "",
|
||||||
charset: ?[]const u8 = null,
|
charset: ?[]const u8 = null,
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
|
|
||||||
pub const ContentTypeEnum = enum {
|
pub const ContentTypeEnum = enum {
|
||||||
text_xml,
|
text_xml,
|
||||||
@@ -39,19 +38,15 @@ pub const Mime = struct {
|
|||||||
other: struct { type: []const u8, sub_type: []const u8 },
|
other: struct { type: []const u8, sub_type: []const u8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse(allocator: Allocator, input: []const u8) !Mime {
|
pub fn parse(arena: Allocator, input: []const u8) !Mime {
|
||||||
if (input.len > 255) {
|
if (input.len > 255) {
|
||||||
return error.TooBig;
|
return error.TooBig;
|
||||||
}
|
}
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
||||||
errdefer arena.deinit();
|
|
||||||
|
|
||||||
var trimmed = trim(input);
|
var trimmed = trim(input);
|
||||||
|
|
||||||
const content_type, const type_len = try parseContentType(trimmed);
|
const content_type, const type_len = try parseContentType(trimmed);
|
||||||
if (type_len >= trimmed.len) {
|
if (type_len >= trimmed.len) {
|
||||||
return .{ .arena = arena, .content_type = content_type };
|
return .{ .content_type = content_type };
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = trimLeft(trimmed[type_len..]);
|
const params = trimLeft(trimmed[type_len..]);
|
||||||
@@ -70,24 +65,19 @@ pub const Mime = struct {
|
|||||||
|
|
||||||
switch (name.len) {
|
switch (name.len) {
|
||||||
7 => if (isCaseEqual("charset", name)) {
|
7 => if (isCaseEqual("charset", name)) {
|
||||||
charset = try parseValue(arena.allocator(), value);
|
charset = try parseValue(arena, value);
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.arena = arena,
|
|
||||||
.params = params,
|
.params = params,
|
||||||
.charset = charset,
|
.charset = charset,
|
||||||
.content_type = content_type,
|
.content_type = content_type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Mime) void {
|
|
||||||
self.arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isHTML(self: *const Mime) bool {
|
pub fn isHTML(self: *const Mime) bool {
|
||||||
return self.content_type == .text_html;
|
return self.content_type == .text_html;
|
||||||
}
|
}
|
||||||
@@ -158,7 +148,7 @@ pub const Mime = struct {
|
|||||||
break :blk v;
|
break :blk v;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parseValue(allocator: Allocator, value: []const u8) ![]const u8 {
|
fn parseValue(arena: Allocator, value: []const u8) ![]const u8 {
|
||||||
if (value[0] != '"') {
|
if (value[0] != '"') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -191,7 +181,7 @@ pub const Mime = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
value_pos = 1;
|
value_pos = 1;
|
||||||
const owned = try allocator.alloc(u8, unescaped_len);
|
const owned = try arena.alloc(u8, unescaped_len);
|
||||||
for (0..unescaped_len) |i| {
|
for (0..unescaped_len) |i| {
|
||||||
switch (value[value_pos]) {
|
switch (value[value_pos]) {
|
||||||
'"' => break,
|
'"' => break,
|
||||||
@@ -344,8 +334,9 @@ test "Mime: parse charset" {
|
|||||||
test "Mime: isHTML" {
|
test "Mime: isHTML" {
|
||||||
const isHTML = struct {
|
const isHTML = struct {
|
||||||
fn isHTML(expected: bool, input: []const u8) !void {
|
fn isHTML(expected: bool, input: []const u8) !void {
|
||||||
var mime = try Mime.parse(testing.allocator, input);
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
defer mime.deinit();
|
defer arena.deinit();
|
||||||
|
var mime = try Mime.parse(arena.allocator(), input);
|
||||||
try testing.expectEqual(expected, mime.isHTML());
|
try testing.expectEqual(expected, mime.isHTML());
|
||||||
}
|
}
|
||||||
}.isHTML;
|
}.isHTML;
|
||||||
@@ -364,8 +355,10 @@ const Expectation = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn expect(expected: Expectation, input: []const u8) !void {
|
fn expect(expected: Expectation, input: []const u8) !void {
|
||||||
var actual = try Mime.parse(testing.allocator, input);
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
defer actual.deinit();
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const actual = try Mime.parse(arena.allocator(), input);
|
||||||
|
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
std.meta.activeTag(expected.content_type),
|
std.meta.activeTag(expected.content_type),
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ const c = @cImport({
|
|||||||
@cInclude("events/mouse_event.h");
|
@cInclude("events/mouse_event.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
const mimalloc = @import("mimalloc");
|
const mimalloc = @import("mimalloc.zig");
|
||||||
|
|
||||||
const Callback = @import("jsruntime").Callback;
|
const Callback = @import("env.zig").Callback;
|
||||||
|
const SessionState = @import("env.zig").SessionState;
|
||||||
|
|
||||||
// init initializes netsurf lib.
|
// init initializes netsurf lib.
|
||||||
// init starts a mimalloc heap arena for the netsurf session. The caller must
|
// init starts a mimalloc heap arena for the netsurf session. The caller must
|
||||||
@@ -617,7 +618,7 @@ pub fn eventTargetHasListener(
|
|||||||
defer c.dom_event_listener_unref(listener);
|
defer c.dom_event_listener_unref(listener);
|
||||||
const ehd = EventHandlerDataInternal.fromListener(listener);
|
const ehd = EventHandlerDataInternal.fromListener(listener);
|
||||||
if (ehd) |d| {
|
if (ehd) |d| {
|
||||||
if (cbk_id == d.data.cbk.id()) {
|
if (cbk_id == d.data.cbk.id) {
|
||||||
return lst;
|
return lst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -669,7 +670,7 @@ pub const EventHandlerData = struct {
|
|||||||
// deinitFunc implements the data deinitialization.
|
// deinitFunc implements the data deinitialization.
|
||||||
deinitFunc: ?DeinitFunc = null,
|
deinitFunc: ?DeinitFunc = null,
|
||||||
|
|
||||||
pub const DeinitFunc = *const fn (data: ?*anyopaque, alloc: std.mem.Allocator) void;
|
pub const DeinitFunc = *const fn (data: ?*anyopaque, allocator: std.mem.Allocator) void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// EventHandlerDataInternal groups the EventHandlerFunc and the EventHandlerData.
|
// EventHandlerDataInternal groups the EventHandlerFunc and the EventHandlerData.
|
||||||
@@ -687,8 +688,9 @@ const EventHandlerDataInternal = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *EventHandlerDataInternal, alloc: std.mem.Allocator) void {
|
fn deinit(self: *EventHandlerDataInternal, alloc: std.mem.Allocator) void {
|
||||||
if (self.data.deinitFunc) |d| d(self.data.data, alloc);
|
if (self.data.deinitFunc) |d| {
|
||||||
self.data.cbk.deinit(alloc);
|
d(self.data.data, alloc);
|
||||||
|
}
|
||||||
alloc.destroy(self);
|
alloc.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,7 +725,7 @@ pub fn eventTargetAddEventListener(
|
|||||||
// When a function is used as an event handler, its this parameter is bound
|
// When a function is used as an event handler, its this parameter is bound
|
||||||
// to the DOM element on which the listener is placed.
|
// to the DOM element on which the listener is placed.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#this_in_dom_event_handlers
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#this_in_dom_event_handlers
|
||||||
try ehd.data.cbk.setThisArg(et);
|
try ehd.data.cbk.setThis(et);
|
||||||
|
|
||||||
const ctx = @as(*anyopaque, @ptrCast(ehd));
|
const ctx = @as(*anyopaque, @ptrCast(ehd));
|
||||||
var listener: ?*EventListener = undefined;
|
var listener: ?*EventListener = undefined;
|
||||||
47
src/browser/polyfill/fetch.zig
Normal file
47
src/browser/polyfill/fetch.zig
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
// fetch.js code comes from
|
||||||
|
// https://github.com/JakeChampion/fetch/blob/main/fetch.js
|
||||||
|
//
|
||||||
|
// The original code source is available in MIT license.
|
||||||
|
//
|
||||||
|
// The script comes from the built version from npm.
|
||||||
|
// You can get the package with the command:
|
||||||
|
//
|
||||||
|
// wget $(npm view whatwg-fetch dist.tarball)
|
||||||
|
//
|
||||||
|
// The source is the content of `package/dist/fetch.umd.js` file.
|
||||||
|
pub const source = @embedFile("fetch.js");
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser.fetch" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try @import("polyfill.zig").load(testing.allocator, runner.executor);
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{
|
||||||
|
\\ var ok = false;
|
||||||
|
\\ const request = new Request("http://127.0.0.1:9582/loader");
|
||||||
|
\\ fetch(request).then((response) => { ok = response.ok; });
|
||||||
|
\\ false;
|
||||||
|
,
|
||||||
|
"false",
|
||||||
|
},
|
||||||
|
// all events have been resolved.
|
||||||
|
.{ "ok", "true" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{
|
||||||
|
\\ var ok2 = false;
|
||||||
|
\\ const request2 = new Request("http://127.0.0.1:9582/loader");
|
||||||
|
\\ (async function () { resp = await fetch(request2); ok2 = resp.ok; }());
|
||||||
|
\\ false;
|
||||||
|
,
|
||||||
|
"false",
|
||||||
|
},
|
||||||
|
// all events have been resolved.
|
||||||
|
.{ "ok2", "true" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
@@ -19,10 +19,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const Allocator = std.mem.Allocator;
|
||||||
const Env = jsruntime.Env;
|
const Env = @import("../env.zig").Env;
|
||||||
|
|
||||||
const fetch = @import("fetch.zig").fetch_polyfill;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.polyfill);
|
const log = std.log.scoped(.polyfill);
|
||||||
|
|
||||||
@@ -33,23 +31,23 @@ const modules = [_]struct {
|
|||||||
.{ .name = "polyfill-fetch", .source = @import("fetch.zig").source },
|
.{ .name = "polyfill-fetch", .source = @import("fetch.zig").source },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn load(alloc: std.mem.Allocator, env: *const Env) !void {
|
pub fn load(allocator: Allocator, executor: *Env.Executor) !void {
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(env);
|
try_catch.init(executor);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
for (modules) |m| {
|
for (modules) |m| {
|
||||||
const res = env.exec(m.source, m.name) catch {
|
const res = executor.exec(m.source, m.name) catch |err| {
|
||||||
if (try try_catch.err(alloc, env)) |msg| {
|
if (try try_catch.err(allocator)) |msg| {
|
||||||
defer alloc.free(msg);
|
defer allocator.free(msg);
|
||||||
log.err("load {s}: {s}", .{ m.name, msg });
|
log.err("load {s}: {s}", .{ m.name, msg });
|
||||||
}
|
}
|
||||||
return;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
if (builtin.mode == .Debug) {
|
||||||
const msg = try res.toString(alloc, env);
|
const msg = try res.toString(allocator);
|
||||||
defer alloc.free(msg);
|
defer allocator.free(msg);
|
||||||
log.debug("load {s}: {s}", .{ m.name, msg });
|
log.debug("load {s}: {s}", .{ m.name, msg });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,9 @@ const Uri = std.Uri;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const http = @import("../http/client.zig");
|
const http = @import("../../http/client.zig");
|
||||||
const DateTime = @import("../datetime.zig").DateTime;
|
const DateTime = @import("../../datetime.zig").DateTime;
|
||||||
const public_suffix_list = @import("../data/public_suffix_list.zig").lookup;
|
const public_suffix_list = @import("../../data/public_suffix_list.zig").lookup;
|
||||||
|
|
||||||
const log = std.log.scoped(.cookie);
|
const log = std.log.scoped(.cookie);
|
||||||
|
|
||||||
@@ -464,7 +464,7 @@ fn trimRight(str: []const u8) []const u8 {
|
|||||||
return std.mem.trimLeft(u8, str, &std.ascii.whitespace);
|
return std.mem.trimLeft(u8, str, &std.ascii.whitespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "cookie: findSecondLevelDomain" {
|
test "cookie: findSecondLevelDomain" {
|
||||||
const cases = [_]struct { []const u8, []const u8 }{
|
const cases = [_]struct { []const u8, []const u8 }{
|
||||||
.{ "", "" },
|
.{ "", "" },
|
||||||
@@ -18,10 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const DOMError = @import("../netsurf.zig").DOMError;
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
const DOMError = @import("netsurf").DOMError;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.storage);
|
const log = std.log.scoped(.storage);
|
||||||
|
|
||||||
@@ -103,7 +100,6 @@ pub const Bucket = struct {
|
|||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/webstorage.html#the-storage-interface
|
// https://html.spec.whatwg.org/multipage/webstorage.html#the-storage-interface
|
||||||
pub const Bottle = struct {
|
pub const Bottle = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
const Map = std.StringHashMapUnmanaged([]const u8);
|
const Map = std.StringHashMapUnmanaged([]const u8);
|
||||||
|
|
||||||
// allocator is stored. we don't use the JS env allocator b/c the storage
|
// allocator is stored. we don't use the JS env allocator b/c the storage
|
||||||
@@ -216,27 +212,27 @@ pub const Bottle = struct {
|
|||||||
// Tests
|
// Tests
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.Storage.LocalStorage" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var storage = [_]Case{
|
|
||||||
.{ .src = "localStorage.length", .ex = "0" },
|
|
||||||
|
|
||||||
.{ .src = "localStorage.setItem('foo', 'bar')", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "localStorage.length", .ex = "1" },
|
.{ "localStorage.length", "0" },
|
||||||
.{ .src = "localStorage.getItem('foo')", .ex = "bar" },
|
|
||||||
.{ .src = "localStorage.removeItem('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "localStorage.length", .ex = "0" },
|
|
||||||
|
|
||||||
// .{ .src = "localStorage['foo'] = 'bar'", .ex = "undefined" },
|
.{ "localStorage.setItem('foo', 'bar')", "undefined" },
|
||||||
// .{ .src = "localStorage['foo']", .ex = "bar" },
|
.{ "localStorage.length", "1" },
|
||||||
// .{ .src = "localStorage.length", .ex = "1" },
|
.{ "localStorage.getItem('foo')", "bar" },
|
||||||
|
.{ "localStorage.removeItem('foo')", "undefined" },
|
||||||
|
.{ "localStorage.length", "0" },
|
||||||
|
|
||||||
.{ .src = "localStorage.clear()", .ex = "undefined" },
|
// .{ "localStorage['foo'] = 'bar'", "undefined" },
|
||||||
.{ .src = "localStorage.length", .ex = "0" },
|
// .{ "localStorage['foo']", "bar" },
|
||||||
};
|
// .{ "localStorage.length", "1" },
|
||||||
try checkCases(js_env, &storage);
|
|
||||||
|
.{ "localStorage.clear()", "undefined" },
|
||||||
|
.{ "localStorage.length", "0" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "storage bottle" {
|
test "storage bottle" {
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const Reader = @import("../str/parser.zig").Reader;
|
const Reader = @import("../../str/parser.zig").Reader;
|
||||||
const asUint = @import("../str/parser.zig").asUint;
|
const asUint = @import("../../str/parser.zig").asUint;
|
||||||
|
|
||||||
// Values is a map with string key of string values.
|
// Values is a map with string key of string values.
|
||||||
pub const Values = struct {
|
pub const Values = struct {
|
||||||
@@ -17,10 +17,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const query = @import("query.zig");
|
const query = @import("query.zig");
|
||||||
|
|
||||||
@@ -31,14 +28,14 @@ pub const Interfaces = .{
|
|||||||
|
|
||||||
// https://url.spec.whatwg.org/#url
|
// https://url.spec.whatwg.org/#url
|
||||||
//
|
//
|
||||||
// TODO we could avoid many of these getter string allocation in two differents
|
// TODO we could avoid many of these getter string allocatoration in two differents
|
||||||
// way:
|
// way:
|
||||||
//
|
//
|
||||||
// 1. We can eventually get the slice of scheme *with* the following char in
|
// 1. We can eventually get the slice of scheme *with* the following char in
|
||||||
// the underlying string. But I don't know if it's possible and how to do that.
|
// the underlying string. But I don't know if it's possible and how to do that.
|
||||||
// I mean, if the rawuri contains `https://foo.bar`, uri.scheme is a slice
|
// I mean, if the rawuri contains `https://foo.bar`, uri.scheme is a slice
|
||||||
// containing only `https`. I want `https:` so, in theory, I don't need to
|
// containing only `https`. I want `https:` so, in theory, I don't need to
|
||||||
// allocate data, I should be able to retrieve the scheme + the following `:`
|
// allocatorate data, I should be able to retrieve the scheme + the following `:`
|
||||||
// from rawuri.
|
// from rawuri.
|
||||||
//
|
//
|
||||||
// 2. The other way would bu to copy the `std.Uri` code to ahve a dedicated
|
// 2. The other way would bu to copy the `std.Uri` code to ahve a dedicated
|
||||||
@@ -47,9 +44,12 @@ pub const URL = struct {
|
|||||||
uri: std.Uri,
|
uri: std.Uri,
|
||||||
search_params: URLSearchParams,
|
search_params: URLSearchParams,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub fn constructor(
|
||||||
|
url: []const u8,
|
||||||
pub fn constructor(arena: std.mem.Allocator, url: []const u8, base: ?[]const u8) !URL {
|
base: ?[]const u8,
|
||||||
|
state: *SessionState,
|
||||||
|
) !URL {
|
||||||
|
const arena = state.arena;
|
||||||
const raw = try std.mem.concat(arena, u8, &[_][]const u8{ url, base orelse "" });
|
const raw = try std.mem.concat(arena, u8, &[_][]const u8{ url, base orelse "" });
|
||||||
errdefer arena.free(raw);
|
errdefer arena.free(raw);
|
||||||
|
|
||||||
@@ -60,24 +60,15 @@ pub const URL = struct {
|
|||||||
pub fn init(arena: std.mem.Allocator, uri: std.Uri) !URL {
|
pub fn init(arena: std.mem.Allocator, uri: std.Uri) !URL {
|
||||||
return .{
|
return .{
|
||||||
.uri = uri,
|
.uri = uri,
|
||||||
.search_params = try URLSearchParams.constructor(
|
.search_params = try URLSearchParams.init(
|
||||||
arena,
|
arena,
|
||||||
uriComponentNullStr(uri.query),
|
uriComponentNullStr(uri.query),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *URL, alloc: std.mem.Allocator) void {
|
pub fn get_origin(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
self.search_params.deinit(alloc);
|
var buf = std.ArrayList(u8).init(state.arena);
|
||||||
}
|
|
||||||
|
|
||||||
// the caller must free the returned string.
|
|
||||||
// TODO return a disposable string
|
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
|
||||||
pub fn get_origin(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
try self.uri.writeToStream(.{
|
try self.uri.writeToStream(.{
|
||||||
.scheme = true,
|
.scheme = true,
|
||||||
.authentication = false,
|
.authentication = false,
|
||||||
@@ -86,31 +77,27 @@ pub const URL = struct {
|
|||||||
.query = false,
|
.query = false,
|
||||||
.fragment = false,
|
.fragment = false,
|
||||||
}, buf.writer());
|
}, buf.writer());
|
||||||
return try buf.toOwnedSlice();
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get_href returns the URL by writing all its components.
|
// get_href returns the URL by writing all its components.
|
||||||
// The query is replaced by a dump of search params.
|
// The query is replaced by a dump of search params.
|
||||||
//
|
//
|
||||||
// the caller must free the returned string.
|
pub fn get_href(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
// TODO return a disposable string
|
const arena = state.arena;
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
|
||||||
pub fn get_href(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
// retrieve the query search from search_params.
|
// retrieve the query search from search_params.
|
||||||
const cur = self.uri.query;
|
const cur = self.uri.query;
|
||||||
defer self.uri.query = cur;
|
defer self.uri.query = cur;
|
||||||
var q = std.ArrayList(u8).init(alloc);
|
var q = std.ArrayList(u8).init(arena);
|
||||||
defer q.deinit();
|
|
||||||
try self.search_params.values.encode(q.writer());
|
try self.search_params.values.encode(q.writer());
|
||||||
self.uri.query = .{ .percent_encoded = q.items };
|
self.uri.query = .{ .percent_encoded = q.items };
|
||||||
|
|
||||||
return try self.format(alloc);
|
return try self.toString(arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
// format the url with all its components.
|
// format the url with all its components.
|
||||||
pub fn format(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn toString(self: *URL, arena: std.mem.Allocator) ![]const u8 {
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
var buf = std.ArrayList(u8).init(arena);
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
try self.uri.writeToStream(.{
|
try self.uri.writeToStream(.{
|
||||||
.scheme = true,
|
.scheme = true,
|
||||||
@@ -120,14 +107,11 @@ pub const URL = struct {
|
|||||||
.query = uriComponentNullStr(self.uri.query).len > 0,
|
.query = uriComponentNullStr(self.uri.query).len > 0,
|
||||||
.fragment = uriComponentNullStr(self.uri.fragment).len > 0,
|
.fragment = uriComponentNullStr(self.uri.fragment).len > 0,
|
||||||
}, buf.writer());
|
}, buf.writer());
|
||||||
return try buf.toOwnedSlice();
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the caller must free the returned string.
|
pub fn get_protocol(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
// TODO return a disposable string
|
return try std.mem.concat(state.arena, u8, &[_][]const u8{ self.uri.scheme, ":" });
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
|
||||||
pub fn get_protocol(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
return try std.mem.concat(alloc, u8, &[_][]const u8{ self.uri.scheme, ":" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_username(self: *URL) []const u8 {
|
pub fn get_username(self: *URL) []const u8 {
|
||||||
@@ -138,12 +122,8 @@ pub const URL = struct {
|
|||||||
return uriComponentNullStr(self.uri.password);
|
return uriComponentNullStr(self.uri.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the caller must free the returned string.
|
pub fn get_host(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
// TODO return a disposable string
|
var buf = std.ArrayList(u8).init(state.arena);
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
|
||||||
pub fn get_host(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
try self.uri.writeToStream(.{
|
try self.uri.writeToStream(.{
|
||||||
.scheme = false,
|
.scheme = false,
|
||||||
@@ -153,24 +133,20 @@ pub const URL = struct {
|
|||||||
.query = false,
|
.query = false,
|
||||||
.fragment = false,
|
.fragment = false,
|
||||||
}, buf.writer());
|
}, buf.writer());
|
||||||
return try buf.toOwnedSlice();
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hostname(self: *URL) []const u8 {
|
pub fn get_hostname(self: *URL) []const u8 {
|
||||||
return uriComponentNullStr(self.uri.host);
|
return uriComponentNullStr(self.uri.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the caller must free the returned string.
|
pub fn get_port(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
// TODO return a disposable string
|
const arena = state.arena;
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
if (self.uri.port == null) return try arena.dupe(u8, "");
|
||||||
pub fn get_port(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.uri.port == null) return try alloc.dupe(u8, "");
|
|
||||||
|
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8).init(arena);
|
||||||
try std.fmt.formatInt(self.uri.port.?, 10, .lower, .{}, buf.writer());
|
try std.fmt.formatInt(self.uri.port.?, 10, .lower, .{}, buf.writer());
|
||||||
return try buf.toOwnedSlice();
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pathname(self: *URL) []const u8 {
|
pub fn get_pathname(self: *URL) []const u8 {
|
||||||
@@ -178,35 +154,30 @@ pub const URL = struct {
|
|||||||
return uriComponentStr(self.uri.path);
|
return uriComponentStr(self.uri.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the caller must free the returned string.
|
pub fn get_search(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
// TODO return a disposable string
|
const arena = state.arena;
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
if (self.search_params.get_size() == 0) return try arena.dupe(u8, "");
|
||||||
pub fn get_search(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.search_params.get_size() == 0) return try alloc.dupe(u8, "");
|
|
||||||
|
|
||||||
var buf: std.ArrayListUnmanaged(u8) = .{};
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
defer buf.deinit(alloc);
|
|
||||||
|
|
||||||
try buf.append(alloc, '?');
|
try buf.append(arena, '?');
|
||||||
try self.search_params.values.encode(buf.writer(alloc));
|
try self.search_params.values.encode(buf.writer(arena));
|
||||||
return buf.toOwnedSlice(alloc);
|
return buf.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the caller must free the returned string.
|
pub fn get_hash(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
// TODO return a disposable string
|
const arena = state.arena;
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
if (self.uri.fragment == null) return try arena.dupe(u8, "");
|
||||||
pub fn get_hash(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.uri.fragment == null) return try alloc.dupe(u8, "");
|
|
||||||
|
|
||||||
return try std.mem.concat(alloc, u8, &[_][]const u8{ "#", uriComponentNullStr(self.uri.fragment) });
|
return try std.mem.concat(arena, u8, &[_][]const u8{ "#", uriComponentNullStr(self.uri.fragment) });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_searchParams(self: *URL) *URLSearchParams {
|
pub fn get_searchParams(self: *URL) *URLSearchParams {
|
||||||
return &self.search_params;
|
return &self.search_params;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _toJSON(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn _toJSON(self: *URL, state: *SessionState) ![]const u8 {
|
||||||
return try self.get_href(alloc);
|
return try self.get_href(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -230,16 +201,14 @@ fn uriComponentStr(c: std.Uri.Component) []const u8 {
|
|||||||
pub const URLSearchParams = struct {
|
pub const URLSearchParams = struct {
|
||||||
values: query.Values,
|
values: query.Values,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub fn constructor(qs: ?[]const u8, state: *SessionState) !URLSearchParams {
|
||||||
|
return init(state.arena, qs);
|
||||||
pub fn constructor(alloc: std.mem.Allocator, init: ?[]const u8) !URLSearchParams {
|
|
||||||
return .{
|
|
||||||
.values = try query.parseQuery(alloc, init orelse ""),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *URLSearchParams, _: std.mem.Allocator) void {
|
pub fn init(arena: std.mem.Allocator, qs: ?[]const u8) !URLSearchParams {
|
||||||
self.values.deinit();
|
return .{
|
||||||
|
.values = try query.parseQuery(arena, qs orelse ""),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_size(self: *URLSearchParams) u32 {
|
pub fn get_size(self: *URLSearchParams) u32 {
|
||||||
@@ -269,47 +238,43 @@ pub const URLSearchParams = struct {
|
|||||||
pub fn _sort(_: *URLSearchParams) void {}
|
pub fn _sort(_: *URLSearchParams) void {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.URL" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
try runner.testCases(&.{
|
||||||
_: std.mem.Allocator,
|
.{ "var url = new URL('https://foo.bar/path?query#fragment')", "undefined" },
|
||||||
js_env: *jsruntime.Env,
|
.{ "url.origin", "https://foo.bar" },
|
||||||
) anyerror!void {
|
.{ "url.href", "https://foo.bar/path?query#fragment" },
|
||||||
var url = [_]Case{
|
.{ "url.protocol", "https:" },
|
||||||
.{ .src = "var url = new URL('https://foo.bar/path?query#fragment')", .ex = "undefined" },
|
.{ "url.username", "" },
|
||||||
.{ .src = "url.origin", .ex = "https://foo.bar" },
|
.{ "url.password", "" },
|
||||||
.{ .src = "url.href", .ex = "https://foo.bar/path?query#fragment" },
|
.{ "url.host", "foo.bar" },
|
||||||
.{ .src = "url.protocol", .ex = "https:" },
|
.{ "url.hostname", "foo.bar" },
|
||||||
.{ .src = "url.username", .ex = "" },
|
.{ "url.port", "" },
|
||||||
.{ .src = "url.password", .ex = "" },
|
.{ "url.pathname", "/path" },
|
||||||
.{ .src = "url.host", .ex = "foo.bar" },
|
.{ "url.search", "?query" },
|
||||||
.{ .src = "url.hostname", .ex = "foo.bar" },
|
.{ "url.hash", "#fragment" },
|
||||||
.{ .src = "url.port", .ex = "" },
|
.{ "url.searchParams.get('query')", "" },
|
||||||
.{ .src = "url.pathname", .ex = "/path" },
|
}, .{});
|
||||||
.{ .src = "url.search", .ex = "?query" },
|
|
||||||
.{ .src = "url.hash", .ex = "#fragment" },
|
|
||||||
.{ .src = "url.searchParams.get('query')", .ex = "" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &url);
|
|
||||||
|
|
||||||
var qs = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "var url = new URL('https://foo.bar/path?a=~&b=%7E#fragment')", .ex = "undefined" },
|
.{ "var url = new URL('https://foo.bar/path?a=~&b=%7E#fragment')", "undefined" },
|
||||||
.{ .src = "url.searchParams.get('a')", .ex = "~" },
|
.{ "url.searchParams.get('a')", "~" },
|
||||||
.{ .src = "url.searchParams.get('b')", .ex = "~" },
|
.{ "url.searchParams.get('b')", "~" },
|
||||||
.{ .src = "url.searchParams.append('c', 'foo')", .ex = "undefined" },
|
.{ "url.searchParams.append('c', 'foo')", "undefined" },
|
||||||
.{ .src = "url.searchParams.get('c')", .ex = "foo" },
|
.{ "url.searchParams.get('c')", "foo" },
|
||||||
.{ .src = "url.searchParams.size", .ex = "3" },
|
.{ "url.searchParams.size", "3" },
|
||||||
|
|
||||||
// search is dynamic
|
// search is dynamic
|
||||||
.{ .src = "url.search", .ex = "?a=%7E&b=%7E&c=foo" },
|
.{ "url.search", "?a=%7E&b=%7E&c=foo" },
|
||||||
// href is dynamic
|
// href is dynamic
|
||||||
.{ .src = "url.href", .ex = "https://foo.bar/path?a=%7E&b=%7E&c=foo#fragment" },
|
.{ "url.href", "https://foo.bar/path?a=%7E&b=%7E&c=foo#fragment" },
|
||||||
|
|
||||||
.{ .src = "url.searchParams.delete('c', 'foo')", .ex = "undefined" },
|
.{ "url.searchParams.delete('c', 'foo')", "undefined" },
|
||||||
.{ .src = "url.searchParams.get('c')", .ex = "" },
|
.{ "url.searchParams.get('c')", "" },
|
||||||
.{ .src = "url.searchParams.delete('a')", .ex = "undefined" },
|
.{ "url.searchParams.delete('a')", "undefined" },
|
||||||
.{ .src = "url.searchParams.get('a')", .ex = "" },
|
.{ "url.searchParams.get('a')", "" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &qs);
|
|
||||||
}
|
}
|
||||||
@@ -18,19 +18,19 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const Env = @import("../env.zig").Env;
|
||||||
const Callback = jsruntime.Callback;
|
const Callback = Env.Callback;
|
||||||
|
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
const EventHandler = @import("../events/event.zig").EventHandler;
|
const EventHandler = @import("../events/event.zig").EventHandler;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
const log = std.log.scoped(.xhr);
|
const log = std.log.scoped(.xhr);
|
||||||
|
|
||||||
pub const XMLHttpRequestEventTarget = struct {
|
pub const XMLHttpRequestEventTarget = struct {
|
||||||
pub const prototype = *EventTarget;
|
pub const prototype = *EventTarget;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
// Extend libdom event target for pure zig struct.
|
// Extend libdom event target for pure zig struct.
|
||||||
base: parser.EventTargetTBase = parser.EventTargetTBase{},
|
base: parser.EventTargetTBase = parser.EventTargetTBase{},
|
||||||
@@ -60,7 +60,7 @@ pub const XMLHttpRequestEventTarget = struct {
|
|||||||
fn unregister(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void {
|
fn unregister(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void {
|
||||||
const et = @as(*parser.EventTarget, @ptrCast(self));
|
const et = @as(*parser.EventTarget, @ptrCast(self));
|
||||||
// check if event target has already this listener
|
// check if event target has already this listener
|
||||||
const lst = try parser.eventTargetHasListener(et, typ, false, cbk.id());
|
const lst = try parser.eventTargetHasListener(et, typ, false, cbk.id);
|
||||||
if (lst == null) {
|
if (lst == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -88,39 +88,46 @@ pub const XMLHttpRequestEventTarget = struct {
|
|||||||
return self.onloadend_cbk;
|
return self.onloadend_cbk;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, handler: Callback) !void {
|
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
|
||||||
if (self.onloadstart_cbk) |cbk| try self.unregister(alloc, "loadstart", cbk);
|
const arena = state.arena;
|
||||||
try self.register(alloc, "loadstart", handler);
|
if (self.onloadstart_cbk) |cbk| try self.unregister(arena, "loadstart", cbk);
|
||||||
|
try self.register(arena, "loadstart", handler);
|
||||||
self.onloadstart_cbk = handler;
|
self.onloadstart_cbk = handler;
|
||||||
}
|
}
|
||||||
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, handler: Callback) !void {
|
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
|
||||||
if (self.onprogress_cbk) |cbk| try self.unregister(alloc, "progress", cbk);
|
const arena = state.arena;
|
||||||
try self.register(alloc, "progress", handler);
|
if (self.onprogress_cbk) |cbk| try self.unregister(arena, "progress", cbk);
|
||||||
|
try self.register(arena, "progress", handler);
|
||||||
self.onprogress_cbk = handler;
|
self.onprogress_cbk = handler;
|
||||||
}
|
}
|
||||||
pub fn set_onabort(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, handler: Callback) !void {
|
pub fn set_onabort(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
|
||||||
if (self.onabort_cbk) |cbk| try self.unregister(alloc, "abort", cbk);
|
const arena = state.arena;
|
||||||
try self.register(alloc, "abort", handler);
|
if (self.onabort_cbk) |cbk| try self.unregister(arena, "abort", cbk);
|
||||||
|
try self.register(arena, "abort", handler);
|
||||||
self.onabort_cbk = handler;
|
self.onabort_cbk = handler;
|
||||||
}
|
}
|
||||||
pub fn set_onload(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, handler: Callback) !void {
|
pub fn set_onload(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
|
||||||
if (self.onload_cbk) |cbk| try self.unregister(alloc, "load", cbk);
|
const arena = state.arena;
|
||||||
try self.register(alloc, "load", handler);
|
if (self.onload_cbk) |cbk| try self.unregister(arena, "load", cbk);
|
||||||
|
try self.register(arena, "load", handler);
|
||||||
self.onload_cbk = handler;
|
self.onload_cbk = handler;
|
||||||
}
|
}
|
||||||
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, handler: Callback) !void {
|
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
|
||||||
if (self.ontimeout_cbk) |cbk| try self.unregister(alloc, "timeout", cbk);
|
const arena = state.arena;
|
||||||
try self.register(alloc, "timeout", handler);
|
if (self.ontimeout_cbk) |cbk| try self.unregister(arena, "timeout", cbk);
|
||||||
|
try self.register(arena, "timeout", handler);
|
||||||
self.ontimeout_cbk = handler;
|
self.ontimeout_cbk = handler;
|
||||||
}
|
}
|
||||||
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, handler: Callback) !void {
|
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
|
||||||
if (self.onloadend_cbk) |cbk| try self.unregister(alloc, "loadend", cbk);
|
const arena = state.arena;
|
||||||
try self.register(alloc, "loadend", handler);
|
if (self.onloadend_cbk) |cbk| try self.unregister(arena, "loadend", cbk);
|
||||||
|
try self.register(arena, "loadend", handler);
|
||||||
self.onloadend_cbk = handler;
|
self.onloadend_cbk = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *XMLHttpRequestEventTarget, state: *SessionState) void {
|
||||||
parser.eventTargetRemoveAllEventListeners(@as(*parser.EventTarget, @ptrCast(self)), alloc) catch |e| {
|
const arena = state.arena;
|
||||||
|
parser.eventTargetRemoveAllEventListeners(@as(*parser.EventTarget, @ptrCast(self)), arena) catch |e| {
|
||||||
log.err("remove all listeners: {any}", .{e});
|
log.err("remove all listeners: {any}", .{e});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const parser = @import("../netsurf.zig");
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const Event = @import("../events/event.zig").Event;
|
const Event = @import("../events/event.zig").Event;
|
||||||
|
|
||||||
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
||||||
@@ -30,7 +26,6 @@ const DOMException = @import("../dom/exceptions.zig").DOMException;
|
|||||||
pub const ProgressEvent = struct {
|
pub const ProgressEvent = struct {
|
||||||
pub const prototype = *Event;
|
pub const prototype = *Event;
|
||||||
pub const Exception = DOMException;
|
pub const Exception = DOMException;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const EventInit = struct {
|
pub const EventInit = struct {
|
||||||
lengthComputable: bool = false,
|
lengthComputable: bool = false,
|
||||||
@@ -59,32 +54,32 @@ pub const ProgressEvent = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_lengthComputable(self: ProgressEvent) bool {
|
pub fn get_lengthComputable(self: *const ProgressEvent) bool {
|
||||||
return self.lengthComputable;
|
return self.lengthComputable;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_loaded(self: ProgressEvent) u64 {
|
pub fn get_loaded(self: *const ProgressEvent) u64 {
|
||||||
return self.loaded;
|
return self.loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_total(self: ProgressEvent) u64 {
|
pub fn get_total(self: *const ProgressEvent) u64 {
|
||||||
return self.total;
|
return self.total;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.XHR.ProgressEvent" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var progress_event = [_]Case{
|
|
||||||
.{ .src = "let pevt = new ProgressEvent('foo');", .ex = "undefined" },
|
try runner.testCases(&.{
|
||||||
.{ .src = "pevt.loaded", .ex = "0" },
|
.{ "let pevt = new ProgressEvent('foo');", "undefined" },
|
||||||
.{ .src = "pevt instanceof ProgressEvent", .ex = "true" },
|
.{ "pevt.loaded", "0" },
|
||||||
.{ .src = "var nnb = 0; var eevt = null; function ccbk(event) { nnb ++; eevt = event; }", .ex = "undefined" },
|
.{ "pevt instanceof ProgressEvent", "true" },
|
||||||
.{ .src = "document.addEventListener('foo', ccbk)", .ex = "undefined" },
|
.{ "var nnb = 0; var eevt = null; function ccbk(event) { nnb ++; eevt = event; }", "undefined" },
|
||||||
.{ .src = "document.dispatchEvent(pevt)", .ex = "true" },
|
.{ "document.addEventListener('foo', ccbk)", "undefined" },
|
||||||
.{ .src = "eevt.type", .ex = "foo" },
|
.{ "document.dispatchEvent(pevt)", "true" },
|
||||||
.{ .src = "eevt instanceof ProgressEvent", .ex = "true" },
|
.{ "eevt.type", "foo" },
|
||||||
};
|
.{ "eevt instanceof ProgressEvent", "true" },
|
||||||
try checkCases(js_env, &progress_event);
|
}, .{});
|
||||||
}
|
}
|
||||||
@@ -17,27 +17,20 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const DOMError = @import("../netsurf.zig").DOMError;
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const DOMError = @import("netsurf").DOMError;
|
|
||||||
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
||||||
|
|
||||||
const ProgressEvent = @import("progress_event.zig").ProgressEvent;
|
const ProgressEvent = @import("progress_event.zig").ProgressEvent;
|
||||||
const XMLHttpRequestEventTarget = @import("event_target.zig").XMLHttpRequestEventTarget;
|
const XMLHttpRequestEventTarget = @import("event_target.zig").XMLHttpRequestEventTarget;
|
||||||
|
|
||||||
const Mime = @import("../browser/mime.zig").Mime;
|
const URL = @import("../../url.zig").URL;
|
||||||
|
const Mime = @import("../mime.zig").Mime;
|
||||||
const Loop = jsruntime.Loop;
|
const parser = @import("../netsurf.zig");
|
||||||
const URL = @import("../url.zig").URL;
|
const http = @import("../../http/client.zig");
|
||||||
const http = @import("../http/client.zig");
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
const CookieJar = @import("../storage/storage.zig").CookieJar;
|
const CookieJar = @import("../storage/storage.zig").CookieJar;
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.xhr);
|
const log = std.log.scoped(.xhr);
|
||||||
|
|
||||||
@@ -51,7 +44,6 @@ pub const Interfaces = .{
|
|||||||
|
|
||||||
pub const XMLHttpRequestUpload = struct {
|
pub const XMLHttpRequestUpload = struct {
|
||||||
pub const prototype = *XMLHttpRequestEventTarget;
|
pub const prototype = *XMLHttpRequestEventTarget;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||||
};
|
};
|
||||||
@@ -83,7 +75,7 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
|
|||||||
|
|
||||||
// Duplicate the body content.
|
// Duplicate the body content.
|
||||||
// The caller owns the allocated string.
|
// The caller owns the allocated string.
|
||||||
fn dupe(self: XMLHttpRequestBodyInit, alloc: std.mem.Allocator) ![]const u8 {
|
fn dupe(self: XMLHttpRequestBodyInit, alloc: Allocator) ![]const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.Blob => error.NotImplemented,
|
.Blob => error.NotImplemented,
|
||||||
.BufferSource => error.NotImplemented,
|
.BufferSource => error.NotImplemented,
|
||||||
@@ -96,7 +88,7 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
|
|||||||
|
|
||||||
pub const XMLHttpRequest = struct {
|
pub const XMLHttpRequest = struct {
|
||||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||||
alloc: std.mem.Allocator,
|
arena: Allocator,
|
||||||
client: *http.Client,
|
client: *http.Client,
|
||||||
request: ?http.Request = null,
|
request: ?http.Request = null,
|
||||||
|
|
||||||
@@ -134,10 +126,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
response_type: ResponseType = .Empty,
|
response_type: ResponseType = .Empty,
|
||||||
response_headers: Headers,
|
response_headers: Headers,
|
||||||
|
|
||||||
// used by zig client to parse response headers.
|
|
||||||
// use 16KB for headers buffer size.
|
|
||||||
response_header_buffer: [1024 * 16]u8 = undefined,
|
|
||||||
|
|
||||||
response_status: u16 = 0,
|
response_status: u16 = 0,
|
||||||
|
|
||||||
// TODO uncomment this field causes casting issue with
|
// TODO uncomment this field causes casting issue with
|
||||||
@@ -151,7 +139,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
send_flag: bool = false,
|
send_flag: bool = false,
|
||||||
|
|
||||||
pub const prototype = *XMLHttpRequestEventTarget;
|
pub const prototype = *XMLHttpRequestEventTarget;
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
const State = enum(u16) {
|
const State = enum(u16) {
|
||||||
unsent = 0,
|
unsent = 0,
|
||||||
@@ -174,41 +161,41 @@ pub const XMLHttpRequest = struct {
|
|||||||
const JSONValue = std.json.Value;
|
const JSONValue = std.json.Value;
|
||||||
|
|
||||||
const Headers = struct {
|
const Headers = struct {
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
list: List,
|
list: List,
|
||||||
|
arena: Allocator,
|
||||||
|
|
||||||
const List = std.ArrayListUnmanaged(std.http.Header);
|
const List = std.ArrayListUnmanaged(std.http.Header);
|
||||||
|
|
||||||
fn init(alloc: std.mem.Allocator) Headers {
|
fn init(arena: Allocator) Headers {
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.arena = arena,
|
||||||
.list = List{},
|
.list = .{},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Headers) void {
|
fn deinit(self: *Headers) void {
|
||||||
self.free();
|
self.free();
|
||||||
self.list.deinit(self.alloc);
|
self.list.deinit(self.arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append(self: *Headers, k: []const u8, v: []const u8) !void {
|
fn append(self: *Headers, k: []const u8, v: []const u8) !void {
|
||||||
// duplicate strings
|
// duplicate strings
|
||||||
const kk = try self.alloc.dupe(u8, k);
|
const kk = try self.arena.dupe(u8, k);
|
||||||
const vv = try self.alloc.dupe(u8, v);
|
const vv = try self.arena.dupe(u8, v);
|
||||||
try self.list.append(self.alloc, .{ .name = kk, .value = vv });
|
try self.list.append(self.arena, .{ .name = kk, .value = vv });
|
||||||
}
|
}
|
||||||
|
|
||||||
// free all strings allocated.
|
// free all strings allocated.
|
||||||
fn free(self: *Headers) void {
|
fn free(self: *Headers) void {
|
||||||
for (self.list.items) |h| {
|
for (self.list.items) |h| {
|
||||||
self.alloc.free(h.name);
|
self.arena.free(h.name);
|
||||||
self.alloc.free(h.value);
|
self.arena.free(h.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clearAndFree(self: *Headers) void {
|
fn clearAndFree(self: *Headers) void {
|
||||||
self.free();
|
self.free();
|
||||||
self.list.clearAndFree(self.alloc);
|
self.list.clearAndFree(self.arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has(self: Headers, k: []const u8) bool {
|
fn has(self: Headers, k: []const u8) bool {
|
||||||
@@ -236,8 +223,8 @@ pub const XMLHttpRequest = struct {
|
|||||||
for (self.list.items, 0..) |h, i| {
|
for (self.list.items, 0..) |h, i| {
|
||||||
if (std.ascii.eqlIgnoreCase(k, h.name)) {
|
if (std.ascii.eqlIgnoreCase(k, h.name)) {
|
||||||
const hh = self.list.swapRemove(i);
|
const hh = self.list.swapRemove(i);
|
||||||
self.alloc.free(hh.name);
|
self.arena.free(hh.name);
|
||||||
self.alloc.free(hh.value);
|
self.arena.free(hh.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.append(k, v);
|
self.append(k, v);
|
||||||
@@ -286,17 +273,18 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
const min_delay: u64 = 50000000; // 50ms
|
const min_delay: u64 = 50000000; // 50ms
|
||||||
|
|
||||||
pub fn constructor(alloc: std.mem.Allocator, userctx: UserContext) !XMLHttpRequest {
|
pub fn constructor(session_state: *SessionState) !XMLHttpRequest {
|
||||||
|
const arena = session_state.arena;
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.arena = arena,
|
||||||
.headers = Headers.init(alloc),
|
.headers = Headers.init(arena),
|
||||||
.response_headers = Headers.init(alloc),
|
.response_headers = Headers.init(arena),
|
||||||
.method = undefined,
|
.method = undefined,
|
||||||
.state = .unsent,
|
.state = .unsent,
|
||||||
.url = null,
|
.url = null,
|
||||||
.origin_url = userctx.url,
|
.origin_url = session_state.url,
|
||||||
.client = userctx.http_client,
|
.client = session_state.http_client,
|
||||||
.cookie_jar = userctx.cookie_jar,
|
.cookie_jar = session_state.cookie_jar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,10 +295,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
self.response_obj = null;
|
self.response_obj = null;
|
||||||
self.response_type = .Empty;
|
self.response_type = .Empty;
|
||||||
if (self.response_mime) |*mime| {
|
self.response_mime = null;
|
||||||
mime.deinit();
|
|
||||||
self.response_mime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO should we clearRetainingCapacity instead?
|
// TODO should we clearRetainingCapacity instead?
|
||||||
self.headers.clearAndFree();
|
self.headers.clearAndFree();
|
||||||
@@ -322,7 +307,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.priv_state = .new;
|
self.priv_state = .new;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *XMLHttpRequest, alloc: Allocator) void {
|
||||||
self.reset();
|
self.reset();
|
||||||
self.headers.deinit();
|
self.headers.deinit();
|
||||||
self.response_headers.deinit();
|
self.response_headers.deinit();
|
||||||
@@ -362,7 +347,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
pub fn _open(
|
pub fn _open(
|
||||||
self: *XMLHttpRequest,
|
self: *XMLHttpRequest,
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
method: []const u8,
|
method: []const u8,
|
||||||
url: []const u8,
|
url: []const u8,
|
||||||
asyn: ?bool,
|
asyn: ?bool,
|
||||||
@@ -375,12 +359,13 @@ pub const XMLHttpRequest = struct {
|
|||||||
// TODO If this’s relevant global object is a Window object and its
|
// TODO If this’s relevant global object is a Window object and its
|
||||||
// associated Document is not fully active, then throw an
|
// associated Document is not fully active, then throw an
|
||||||
// "InvalidStateError" DOMException.
|
// "InvalidStateError" DOMException.
|
||||||
|
|
||||||
self.method = try validMethod(method);
|
|
||||||
|
|
||||||
self.reset();
|
self.reset();
|
||||||
|
|
||||||
self.url = try self.origin_url.resolve(alloc, url);
|
self.method = try validMethod(method);
|
||||||
|
const arena = self.arena;
|
||||||
|
|
||||||
|
self.url = try self.origin_url.resolve(arena, url);
|
||||||
|
|
||||||
log.debug("open url ({s})", .{self.url.?});
|
log.debug("open url ({s})", .{self.url.?});
|
||||||
self.sync = if (asyn) |b| !b else false;
|
self.sync = if (asyn) |b| !b else false;
|
||||||
|
|
||||||
@@ -466,7 +451,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO body can be either a XMLHttpRequestBodyInit or a document
|
// TODO body can be either a XMLHttpRequestBodyInit or a document
|
||||||
pub fn _send(self: *XMLHttpRequest, loop: *Loop, alloc: std.mem.Allocator, body: ?[]const u8) !void {
|
pub fn _send(self: *XMLHttpRequest, body: ?[]const u8, session_state: *SessionState) !void {
|
||||||
if (self.state != .opened) return DOMError.InvalidState;
|
if (self.state != .opened) return DOMError.InvalidState;
|
||||||
if (self.send_flag) return DOMError.InvalidState;
|
if (self.send_flag) return DOMError.InvalidState;
|
||||||
|
|
||||||
@@ -485,7 +470,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(alloc), .{
|
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(session_state.arena), .{
|
||||||
.navigation = false,
|
.navigation = false,
|
||||||
.origin_uri = &self.origin_url.uri,
|
.origin_uri = &self.origin_url.uri,
|
||||||
});
|
});
|
||||||
@@ -501,12 +486,12 @@ pub const XMLHttpRequest = struct {
|
|||||||
// var used_body: ?XMLHttpRequestBodyInit = null;
|
// var used_body: ?XMLHttpRequestBodyInit = null;
|
||||||
if (body) |b| {
|
if (body) |b| {
|
||||||
if (self.method != .GET and self.method != .HEAD) {
|
if (self.method != .GET and self.method != .HEAD) {
|
||||||
request.body = try alloc.dupe(u8, b);
|
request.body = try session_state.arena.dupe(u8, b);
|
||||||
try request.addHeader("Content-Type", "text/plain; charset=UTF-8", .{});
|
try request.addHeader("Content-Type", "text/plain; charset=UTF-8", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try request.sendAsync(loop, self, .{});
|
try request.sendAsync(session_state.loop, self, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: anyerror!http.Progress) !void {
|
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: anyerror!http.Progress) !void {
|
||||||
@@ -526,8 +511,15 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// extract a mime type from headers.
|
// extract a mime type from headers.
|
||||||
const ct = header.get("content-type") orelse "text/xml";
|
{
|
||||||
self.response_mime = Mime.parse(self.alloc, ct) catch |e| return self.onErr(e);
|
var raw: []const u8 = "text/xml";
|
||||||
|
if (header.get("content-type")) |ct| {
|
||||||
|
raw = try self.arena.dupe(u8, ct);
|
||||||
|
}
|
||||||
|
self.response_mime = Mime.parse(self.arena, raw) catch |e| {
|
||||||
|
return self.onErr(e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO handle override mime type
|
// TODO handle override mime type
|
||||||
self.state = .headers_received;
|
self.state = .headers_received;
|
||||||
@@ -545,7 +537,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (progress.data) |data| {
|
if (progress.data) |data| {
|
||||||
try self.response_bytes.appendSlice(self.alloc, data);
|
try self.response_bytes.appendSlice(self.arena, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaded = self.response_bytes.items.len;
|
const loaded = self.response_bytes.items.len;
|
||||||
@@ -636,7 +628,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
return url.raw;
|
return url.raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_responseXML(self: *XMLHttpRequest, alloc: std.mem.Allocator) !?Response {
|
pub fn get_responseXML(self: *XMLHttpRequest) !?Response {
|
||||||
if (self.response_type != .Empty and self.response_type != .Document) {
|
if (self.response_type != .Empty and self.response_type != .Document) {
|
||||||
return DOMError.InvalidState;
|
return DOMError.InvalidState;
|
||||||
}
|
}
|
||||||
@@ -652,7 +644,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setResponseObjDocument(alloc);
|
self.setResponseObjDocument();
|
||||||
|
|
||||||
if (self.response_obj) |obj| {
|
if (self.response_obj) |obj| {
|
||||||
return switch (obj) {
|
return switch (obj) {
|
||||||
@@ -665,7 +657,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://xhr.spec.whatwg.org/#the-response-attribute
|
// https://xhr.spec.whatwg.org/#the-response-attribute
|
||||||
pub fn get_response(self: *XMLHttpRequest, alloc: std.mem.Allocator) !?Response {
|
pub fn get_response(self: *XMLHttpRequest) !?Response {
|
||||||
if (self.response_type == .Empty or self.response_type == .Text) {
|
if (self.response_type == .Empty or self.response_type == .Text) {
|
||||||
if (self.state == .loading or self.state == .done) {
|
if (self.state == .loading or self.state == .done) {
|
||||||
return .{ .Text = try self.get_responseText() };
|
return .{ .Text = try self.get_responseText() };
|
||||||
@@ -703,7 +695,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
// Otherwise, if this’s response type is "document", set a
|
// Otherwise, if this’s response type is "document", set a
|
||||||
// document response for this.
|
// document response for this.
|
||||||
if (self.response_type == .Document) {
|
if (self.response_type == .Document) {
|
||||||
self.setResponseObjDocument(alloc);
|
self.setResponseObjDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.response_type == .JSON) {
|
if (self.response_type == .JSON) {
|
||||||
@@ -712,7 +704,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
// TODO Let jsonObject be the result of running parse JSON from bytes
|
// TODO Let jsonObject be the result of running parse JSON from bytes
|
||||||
// on this’s received bytes. If that threw an exception, then return
|
// on this’s received bytes. If that threw an exception, then return
|
||||||
// null.
|
// null.
|
||||||
self.setResponseObjJSON(alloc);
|
self.setResponseObjJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.response_obj) |obj| {
|
if (self.response_obj) |obj| {
|
||||||
@@ -731,19 +723,23 @@ pub const XMLHttpRequest = struct {
|
|||||||
// If the par sing fails, a Failure is stored in response_obj.
|
// If the par sing fails, a Failure is stored in response_obj.
|
||||||
// TODO parse XML.
|
// TODO parse XML.
|
||||||
// https://xhr.spec.whatwg.org/#response-object
|
// https://xhr.spec.whatwg.org/#response-object
|
||||||
fn setResponseObjDocument(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
fn setResponseObjDocument(self: *XMLHttpRequest) void {
|
||||||
const response_mime = &self.response_mime.?;
|
const response_mime = &self.response_mime.?;
|
||||||
const isHTML = response_mime.isHTML();
|
const isHTML = response_mime.isHTML();
|
||||||
|
|
||||||
// TODO If finalMIME is not an HTML MIME type or an XML MIME type, then
|
// TODO If finalMIME is not an HTML MIME type or an XML MIME type, then
|
||||||
// return.
|
// return.
|
||||||
if (!isHTML) return;
|
if (!isHTML) {
|
||||||
|
|
||||||
const ccharset = alloc.dupeZ(u8, response_mime.charset orelse "utf-8") catch {
|
|
||||||
self.response_obj = .{ .Failure = true };
|
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
defer alloc.free(ccharset);
|
|
||||||
|
var ccharset: [:0]const u8 = "utf-8";
|
||||||
|
if (response_mime.charset) |rc| {
|
||||||
|
ccharset = self.arena.dupeZ(u8, rc) catch {
|
||||||
|
self.response_obj = .{ .Failure = true };
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var fbs = std.io.fixedBufferStream(self.response_bytes.items);
|
var fbs = std.io.fixedBufferStream(self.response_bytes.items);
|
||||||
const doc = parser.documentHTMLParse(fbs.reader(), ccharset) catch {
|
const doc = parser.documentHTMLParse(fbs.reader(), ccharset) catch {
|
||||||
@@ -760,12 +756,12 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setResponseObjJSON parses the received bytes as a std.json.Value.
|
// setResponseObjJSON parses the received bytes as a std.json.Value.
|
||||||
fn setResponseObjJSON(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
fn setResponseObjJSON(self: *XMLHttpRequest) void {
|
||||||
// TODO should we use parseFromSliceLeaky if we expect the allocator is
|
// TODO should we use parseFromSliceLeaky if we expect the allocator is
|
||||||
// already an arena?
|
// already an arena?
|
||||||
const p = std.json.parseFromSlice(
|
const p = std.json.parseFromSlice(
|
||||||
JSONValue,
|
JSONValue,
|
||||||
alloc,
|
self.arena,
|
||||||
self.response_bytes.items,
|
self.response_bytes.items,
|
||||||
.{},
|
.{},
|
||||||
) catch |e| {
|
) catch |e| {
|
||||||
@@ -790,14 +786,12 @@ pub const XMLHttpRequest = struct {
|
|||||||
// TODO change the return type to express the string ownership and let
|
// TODO change the return type to express the string ownership and let
|
||||||
// jsruntime free the string once copied to v8.
|
// jsruntime free the string once copied to v8.
|
||||||
// see https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
// see https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
||||||
pub fn _getAllResponseHeaders(self: *XMLHttpRequest, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn _getAllResponseHeaders(self: *XMLHttpRequest) ![]const u8 {
|
||||||
if (self.response_headers.list.items.len == 0) return "";
|
if (self.response_headers.list.items.len == 0) return "";
|
||||||
self.response_headers.sort();
|
self.response_headers.sort();
|
||||||
|
|
||||||
var buf: std.ArrayListUnmanaged(u8) = .{};
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
errdefer buf.deinit(alloc);
|
const w = buf.writer(self.arena);
|
||||||
|
|
||||||
const w = buf.writer(alloc);
|
|
||||||
|
|
||||||
for (self.response_headers.list.items) |entry| {
|
for (self.response_headers.list.items) |entry| {
|
||||||
if (entry.value.len == 0) continue;
|
if (entry.value.len == 0) continue;
|
||||||
@@ -822,101 +816,96 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn testExecFn(
|
const testing = @import("../../testing.zig");
|
||||||
_: std.mem.Allocator,
|
test "Browser.XHR.XMLHttpRequest" {
|
||||||
js_env: *jsruntime.Env,
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
) anyerror!void {
|
defer runner.deinit();
|
||||||
var send = [_]Case{
|
|
||||||
.{ .src = "var nb = 0; var evt = null; function cbk(event) { nb ++; evt = event; }", .ex = "undefined" },
|
|
||||||
.{ .src = "const req = new XMLHttpRequest()", .ex = "undefined" },
|
|
||||||
|
|
||||||
.{ .src = "req.onload = cbk", .ex = "function cbk(event) { nb ++; evt = event; }" },
|
try runner.testCases(&.{
|
||||||
// Getter returning a callback crashes.
|
.{ "var nb = 0; var evt = null; function cbk(event) { nb ++; evt = event; }", "undefined" },
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/200
|
.{ "const req = new XMLHttpRequest()", "undefined" },
|
||||||
// .{ .src = "req.onload", .ex = "function cbk(event) { nb ++; evt = event; }" },
|
|
||||||
//.{ .src = "req.onload = cbk", .ex = "function cbk(event) { nb ++; evt = event; }" },
|
|
||||||
|
|
||||||
.{ .src = "req.open('GET', 'https://httpbin.io/html')", .ex = "undefined" },
|
.{ "req.onload = cbk", "function cbk(event) { nb ++; evt = event; }" },
|
||||||
.{ .src = "req.setRequestHeader('User-Agent', 'lightpanda/1.0')", .ex = "undefined" },
|
|
||||||
|
.{ "req.onload", "function cbk(event) { nb ++; evt = event; }" },
|
||||||
|
.{ "req.onload = cbk", "function cbk(event) { nb ++; evt = event; }" },
|
||||||
|
|
||||||
|
.{ "req.open('GET', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
||||||
|
.{ "req.setRequestHeader('User-Agent', 'lightpanda/1.0')", "undefined" },
|
||||||
|
|
||||||
// ensure open resets values
|
// ensure open resets values
|
||||||
.{ .src = "req.status", .ex = "0" },
|
.{ "req.status ", "0" },
|
||||||
.{ .src = "req.statusText", .ex = "" },
|
.{ "req.statusText", "" },
|
||||||
.{ .src = "req.getAllResponseHeaders()", .ex = "" },
|
.{ "req.getAllResponseHeaders()", "" },
|
||||||
.{ .src = "req.getResponseHeader('Content-Type')", .ex = "null" },
|
.{ "req.getResponseHeader('Content-Type')", "null" },
|
||||||
.{ .src = "req.responseText", .ex = "" },
|
.{ "req.responseText", "" },
|
||||||
|
|
||||||
.{ .src = "req.send(); nb", .ex = "0" },
|
.{ "req.send(); nb", "0" },
|
||||||
|
|
||||||
|
// Each case executed waits for all loop callback calls.
|
||||||
|
// So the url has been retrieved.
|
||||||
|
.{ "nb", "1" },
|
||||||
|
.{ "evt.type", "load" },
|
||||||
|
.{ "evt.loaded > 0", "true" },
|
||||||
|
.{ "evt instanceof ProgressEvent", "true" },
|
||||||
|
.{ "req.status", "200" },
|
||||||
|
.{ "req.statusText", "OK" },
|
||||||
|
.{ "req.getResponseHeader('Content-Type')", "text/html; charset=utf-8" },
|
||||||
|
.{ "req.getAllResponseHeaders().length", "61" },
|
||||||
|
.{ "req.responseText.length", "100" },
|
||||||
|
.{ "req.response.length == req.responseText.length", "true" },
|
||||||
|
.{ "req.responseXML instanceof Document", "true" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "const req2 = new XMLHttpRequest()", "undefined" },
|
||||||
|
.{ "req2.open('GET', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
||||||
|
.{ "req2.responseType = 'document'", "document" },
|
||||||
|
|
||||||
|
.{ "req2.send()", "undefined" },
|
||||||
|
|
||||||
// Each case executed waits for all loop callaback calls.
|
// Each case executed waits for all loop callaback calls.
|
||||||
// So the url has been retrieved.
|
// So the url has been retrieved.
|
||||||
.{ .src = "nb", .ex = "1" },
|
.{ "req2.status", "200" },
|
||||||
.{ .src = "evt.type", .ex = "load" },
|
.{ "req2.statusText", "OK" },
|
||||||
.{ .src = "evt.loaded > 0", .ex = "true" },
|
.{ "req2.response instanceof Document", "true" },
|
||||||
.{ .src = "evt instanceof ProgressEvent", .ex = "true" },
|
.{ "req2.responseXML instanceof Document", "true" },
|
||||||
.{ .src = "req.status", .ex = "200" },
|
}, .{});
|
||||||
.{ .src = "req.statusText", .ex = "OK" },
|
|
||||||
.{ .src = "req.getResponseHeader('Content-Type')", .ex = "text/html; charset=utf-8" },
|
|
||||||
.{ .src = "req.getAllResponseHeaders().length > 64", .ex = "true" },
|
|
||||||
.{ .src = "req.responseText.length > 64", .ex = "true" },
|
|
||||||
.{ .src = "req.response.length == req.responseText.length", .ex = "true" },
|
|
||||||
.{ .src = "req.responseXML instanceof Document", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &send);
|
|
||||||
|
|
||||||
var document = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "const req2 = new XMLHttpRequest()", .ex = "undefined" },
|
.{ "const req3 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ .src = "req2.open('GET', 'https://httpbin.io/html')", .ex = "undefined" },
|
.{ "req3.open('GET', 'https://127.0.0.1:9581/xhr/json')", "undefined" },
|
||||||
.{ .src = "req2.responseType = 'document'", .ex = "document" },
|
.{ "req3.responseType = 'json'", "json" },
|
||||||
|
|
||||||
.{ .src = "req2.send()", .ex = "undefined" },
|
.{ "req3.send()", "undefined" },
|
||||||
|
|
||||||
// Each case executed waits for all loop callaback calls.
|
// Each case executed waits for all loop callaback calls.
|
||||||
// So the url has been retrieved.
|
// So the url has been retrieved.
|
||||||
.{ .src = "req2.status", .ex = "200" },
|
.{ "req3.status", "200" },
|
||||||
.{ .src = "req2.statusText", .ex = "OK" },
|
.{ "req3.statusText", "OK" },
|
||||||
.{ .src = "req2.response instanceof Document", .ex = "true" },
|
.{ "req3.response.over", "9000!!!" },
|
||||||
.{ .src = "req2.responseXML instanceof Document", .ex = "true" },
|
}, .{});
|
||||||
};
|
|
||||||
try checkCases(js_env, &document);
|
|
||||||
|
|
||||||
var json = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "const req3 = new XMLHttpRequest()", .ex = "undefined" },
|
.{ "const req4 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ .src = "req3.open('GET', 'https://httpbin.io/json')", .ex = "undefined" },
|
.{ "req4.open('POST', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
||||||
.{ .src = "req3.responseType = 'json'", .ex = "json" },
|
.{ "req4.send('foo')", "undefined" },
|
||||||
|
|
||||||
.{ .src = "req3.send()", .ex = "undefined" },
|
|
||||||
|
|
||||||
// Each case executed waits for all loop callaback calls.
|
// Each case executed waits for all loop callaback calls.
|
||||||
// So the url has been retrieved.
|
// So the url has been retrieved.
|
||||||
.{ .src = "req3.status", .ex = "200" },
|
.{ "req4.status", "200" },
|
||||||
.{ .src = "req3.statusText", .ex = "OK" },
|
.{ "req4.statusText", "OK" },
|
||||||
.{ .src = "req3.response.slideshow.author", .ex = "Yours Truly" },
|
.{ "req4.responseText.length > 64", "true" },
|
||||||
};
|
}, .{});
|
||||||
try checkCases(js_env, &json);
|
|
||||||
|
|
||||||
var post = [_]Case{
|
try runner.testCases(&.{
|
||||||
.{ .src = "const req4 = new XMLHttpRequest()", .ex = "undefined" },
|
.{ "const req5 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ .src = "req4.open('POST', 'https://httpbin.io/post')", .ex = "undefined" },
|
.{ "req5.open('GET', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
||||||
.{ .src = "req4.send('foo')", .ex = "undefined" },
|
.{ "var status = 0; req5.onload = function () { status = this.status };", "function () { status = this.status }" },
|
||||||
|
.{ "req5.send()", "undefined" },
|
||||||
|
|
||||||
// Each case executed waits for all loop callaback calls.
|
// Each case executed waits for all loop callaback calls.
|
||||||
// So the url has been retrieved.
|
// So the url has been retrieved.
|
||||||
.{ .src = "req4.status", .ex = "200" },
|
.{ "status", "200" },
|
||||||
.{ .src = "req4.statusText", .ex = "OK" },
|
}, .{});
|
||||||
.{ .src = "req4.responseText.length > 64", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &post);
|
|
||||||
|
|
||||||
var cbk = [_]Case{
|
|
||||||
.{ .src = "const req5 = new XMLHttpRequest()", .ex = "undefined" },
|
|
||||||
.{ .src = "req5.open('GET', 'https://httpbin.io/json')", .ex = "undefined" },
|
|
||||||
.{ .src = "var status = 0; req5.onload = function () { status = this.status };", .ex = "function () { status = this.status }" },
|
|
||||||
.{ .src = "req5.send()", .ex = "undefined" },
|
|
||||||
|
|
||||||
// Each case executed waits for all loop callaback calls.
|
|
||||||
// So the url has been retrieved.
|
|
||||||
.{ .src = "status", .ex = "200" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &cbk);
|
|
||||||
}
|
}
|
||||||
@@ -18,14 +18,11 @@
|
|||||||
//
|
//
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const SessionState = @import("../env.zig").SessionState;
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const DOMError = @import("netsurf").DOMError;
|
const dump = @import("../dump.zig");
|
||||||
|
const parser = @import("../netsurf.zig");
|
||||||
const parser = @import("netsurf");
|
const DOMError = parser.DOMError;
|
||||||
const dump = @import("../browser/dump.zig");
|
|
||||||
|
|
||||||
pub const Interfaces = .{
|
pub const Interfaces = .{
|
||||||
XMLSerializer,
|
XMLSerializer,
|
||||||
@@ -33,39 +30,28 @@ pub const Interfaces = .{
|
|||||||
|
|
||||||
// https://w3c.github.io/DOM-Parsing/#dom-xmlserializer-constructor
|
// https://w3c.github.io/DOM-Parsing/#dom-xmlserializer-constructor
|
||||||
pub const XMLSerializer = struct {
|
pub const XMLSerializer = struct {
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn constructor() !XMLSerializer {
|
pub fn constructor() !XMLSerializer {
|
||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *XMLSerializer, _: std.mem.Allocator) void {}
|
pub fn _serializeToString(_: *const XMLSerializer, root: *parser.Node, state: *SessionState) ![]const u8 {
|
||||||
|
var buf = std.ArrayList(u8).init(state.arena);
|
||||||
pub fn _serializeToString(_: XMLSerializer, alloc: std.mem.Allocator, root: *parser.Node) ![]const u8 {
|
|
||||||
var buf = std.ArrayList(u8).init(alloc);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
if (try parser.nodeType(root) == .document) {
|
if (try parser.nodeType(root) == .document) {
|
||||||
try dump.writeHTML(@as(*parser.Document, @ptrCast(root)), buf.writer());
|
try dump.writeHTML(@as(*parser.Document, @ptrCast(root)), buf.writer());
|
||||||
} else {
|
} else {
|
||||||
try dump.writeNode(root, buf.writer());
|
try dump.writeNode(root, buf.writer());
|
||||||
}
|
}
|
||||||
// TODO express the caller owned the slice.
|
return buf.items;
|
||||||
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
|
||||||
return try buf.toOwnedSlice();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
const testing = @import("../../testing.zig");
|
||||||
// -----
|
test "Browser.XMLSerializer" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
pub fn testExecFn(
|
try runner.testCases(&.{
|
||||||
_: std.mem.Allocator,
|
.{ "const s = new XMLSerializer()", "undefined" },
|
||||||
js_env: *jsruntime.Env,
|
.{ "s.serializeToString(document.getElementById('para'))", "<p id=\"para\"> And</p>" },
|
||||||
) anyerror!void {
|
}, .{});
|
||||||
var serializer = [_]Case{
|
|
||||||
.{ .src = "const s = new XMLSerializer()", .ex = "undefined" },
|
|
||||||
.{ .src = "s.serializeToString(document.getElementById('para'))", .ex = "<p id=\"para\"> And</p>" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &serializer);
|
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,10 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const parser = @import("netsurf");
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const parser = @import("../browser/netsurf.zig");
|
||||||
|
|
||||||
pub const Id = u32;
|
pub const Id = u32;
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp_node);
|
const log = std.log.scoped(.cdp_node);
|
||||||
|
|||||||
@@ -72,13 +72,16 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
pub const Browser = TypeProvider.Browser;
|
pub const Browser = TypeProvider.Browser;
|
||||||
pub const Session = TypeProvider.Session;
|
pub const Session = TypeProvider.Session;
|
||||||
|
|
||||||
pub fn init(app: *App, client: TypeProvider.Client) Self {
|
pub fn init(app: *App, client: TypeProvider.Client) !Self {
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
|
const browser = try Browser.init(app);
|
||||||
|
errdefer browser.deinit();
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.client = client,
|
.client = client,
|
||||||
|
.browser = browser,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.browser_context = null,
|
.browser_context = null,
|
||||||
.browser = Browser.init(app),
|
|
||||||
.message_arena = std.heap.ArenaAllocator.init(allocator),
|
.message_arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.browser_context_pool = std.heap.MemoryPool(BrowserContext(Self)).init(allocator),
|
.browser_context_pool = std.heap.MemoryPool(BrowserContext(Self)).init(allocator),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const parser = @import("netsurf");
|
|
||||||
const Node = @import("../Node.zig");
|
const Node = @import("../Node.zig");
|
||||||
const css = @import("../../dom/css.zig");
|
const css = @import("../../browser/dom/css.zig");
|
||||||
const dom_node = @import("../../dom/node.zig");
|
const parser = @import("../../browser/netsurf.zig");
|
||||||
|
const dom_node = @import("../../browser/dom/node.zig");
|
||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
@@ -134,17 +134,20 @@ fn resolveNode(cmd: anytype) !void {
|
|||||||
|
|
||||||
// node._node is a *parser.Node we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement
|
// node._node is a *parser.Node we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement
|
||||||
// So we use the Node.Union when retrieve the value from the environment
|
// So we use the Node.Union when retrieve the value from the environment
|
||||||
const jsValue = try bc.session.env.findOrAddValue(try dom_node.Node.toInterface(node._node));
|
const remote_object = try bc.session.inspector.getRemoteObject(
|
||||||
const remoteObject = try bc.session.inspector.getRemoteObject(&bc.session.env, jsValue, params.objectGroup orelse "");
|
bc.session.executor,
|
||||||
defer remoteObject.deinit();
|
params.objectGroup orelse "",
|
||||||
|
try dom_node.Node.toInterface(node._node),
|
||||||
|
);
|
||||||
|
defer remote_object.deinit();
|
||||||
|
|
||||||
const arena = cmd.arena;
|
const arena = cmd.arena;
|
||||||
return cmd.sendResult(.{ .object = .{
|
return cmd.sendResult(.{ .object = .{
|
||||||
.type = try remoteObject.getType(arena),
|
.type = try remote_object.getType(arena),
|
||||||
.subtype = try remoteObject.getSubtype(arena),
|
.subtype = try remote_object.getSubtype(arena),
|
||||||
.className = try remoteObject.getClassName(arena),
|
.className = try remote_object.getClassName(arena),
|
||||||
.description = try remoteObject.getDescription(arena),
|
.description = try remote_object.getDescription(arena),
|
||||||
.objectId = try remoteObject.getObjectId(arena),
|
.objectId = try remote_object.getObjectId(arena),
|
||||||
} }, .{});
|
} }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Testing = @This();
|
const Testing = @This();
|
||||||
|
|
||||||
const main = @import("cdp.zig");
|
const main = @import("cdp.zig");
|
||||||
const parser = @import("netsurf");
|
|
||||||
const URL = @import("../url.zig").URL;
|
const URL = @import("../url.zig").URL;
|
||||||
const App = @import("../app.zig").App;
|
const App = @import("../app.zig").App;
|
||||||
|
const parser = @import("../browser/netsurf.zig");
|
||||||
|
|
||||||
const base = @import("../testing.zig");
|
const base = @import("../testing.zig");
|
||||||
pub const allocator = base.allocator;
|
pub const allocator = base.allocator;
|
||||||
@@ -40,7 +40,7 @@ const Browser = struct {
|
|||||||
session: ?*Session = null,
|
session: ?*Session = null,
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
pub fn init(app: *App) Browser {
|
pub fn init(app: *App) !Browser {
|
||||||
return .{
|
return .{
|
||||||
.arena = std.heap.ArenaAllocator.init(app.allocator),
|
.arena = std.heap.ArenaAllocator.init(app.allocator),
|
||||||
};
|
};
|
||||||
@@ -61,8 +61,8 @@ const Browser = struct {
|
|||||||
self.session.?.* = .{
|
self.session.?.* = .{
|
||||||
.page = null,
|
.page = null,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.env = Env{},
|
.executor = .{},
|
||||||
.inspector = Inspector{},
|
.inspector = .{},
|
||||||
};
|
};
|
||||||
return self.session.?;
|
return self.session.?;
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ const Browser = struct {
|
|||||||
const Session = struct {
|
const Session = struct {
|
||||||
page: ?Page = null,
|
page: ?Page = null,
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
env: Env,
|
executor: Executor,
|
||||||
inspector: Inspector,
|
inspector: Inspector,
|
||||||
|
|
||||||
pub fn currentPage(self: *Session) ?*Page {
|
pub fn currentPage(self: *Session) ?*Page {
|
||||||
@@ -107,19 +107,19 @@ const Session = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Env = struct {
|
const Executor = struct {};
|
||||||
pub fn findOrAddValue(self: *Env, value: anytype) !@TypeOf(value) { // ?
|
|
||||||
_ = self;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Inspector = struct {
|
const Inspector = struct {
|
||||||
pub fn getRemoteObject(self: Inspector, env: *Env, jsValue: anytype, groupName: []const u8) !RemoteObject {
|
pub fn getRemoteObject(
|
||||||
|
self: *const Inspector,
|
||||||
|
executor: Executor,
|
||||||
|
group: []const u8,
|
||||||
|
value: anytype,
|
||||||
|
) !RemoteObject {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = env;
|
_ = executor;
|
||||||
_ = jsValue;
|
_ = group;
|
||||||
_ = groupName;
|
_ = value;
|
||||||
return RemoteObject{};
|
return RemoteObject{};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -217,7 +217,7 @@ const TestContext = struct {
|
|||||||
self.client = Client.init(self.arena.allocator());
|
self.client = Client.init(self.arena.allocator());
|
||||||
// Don't use the arena here. We want to detect leaks in CDP.
|
// Don't use the arena here. We want to detect leaks in CDP.
|
||||||
// The arena is only for test-specific stuff
|
// The arena is only for test-specific stuff
|
||||||
self.cdp_ = TestCDP.init(self.app, &self.client.?);
|
self.cdp_ = try TestCDP.init(self.app, &self.client.?);
|
||||||
}
|
}
|
||||||
return &self.cdp_.?;
|
return &self.cdp_.?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,468 +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 parser = @import("netsurf");
|
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
const Variadic = jsruntime.Variadic;
|
|
||||||
|
|
||||||
const Node = @import("node.zig").Node;
|
|
||||||
const NodeList = @import("nodelist.zig").NodeList;
|
|
||||||
const NodeUnion = @import("node.zig").Union;
|
|
||||||
|
|
||||||
const collection = @import("html_collection.zig");
|
|
||||||
const css = @import("css.zig");
|
|
||||||
|
|
||||||
const Element = @import("element.zig").Element;
|
|
||||||
const ElementUnion = @import("element.zig").Union;
|
|
||||||
|
|
||||||
const DocumentType = @import("document_type.zig").DocumentType;
|
|
||||||
const DocumentFragment = @import("document_fragment.zig").DocumentFragment;
|
|
||||||
const DOMImplementation = @import("implementation.zig").DOMImplementation;
|
|
||||||
|
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
|
||||||
|
|
||||||
// WEB IDL https://dom.spec.whatwg.org/#document
|
|
||||||
pub const Document = struct {
|
|
||||||
pub const Self = parser.Document;
|
|
||||||
pub const prototype = *Node;
|
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn constructor(userctx: UserContext) !*parser.DocumentHTML {
|
|
||||||
const doc = try parser.documentCreateDocument(
|
|
||||||
try parser.documentHTMLGetTitle(userctx.document),
|
|
||||||
);
|
|
||||||
|
|
||||||
// we have to work w/ document instead of html document.
|
|
||||||
const ddoc = parser.documentHTMLToDocument(doc);
|
|
||||||
const ccur = parser.documentHTMLToDocument(userctx.document);
|
|
||||||
try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur));
|
|
||||||
try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur));
|
|
||||||
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JS funcs
|
|
||||||
// --------
|
|
||||||
pub fn get_implementation(_: *parser.Document) DOMImplementation {
|
|
||||||
return DOMImplementation{};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_documentElement(self: *parser.Document) !?ElementUnion {
|
|
||||||
const e = try parser.documentGetDocumentElement(self);
|
|
||||||
if (e == null) return null;
|
|
||||||
return try Element.toInterface(e.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_documentURI(self: *parser.Document) ![]const u8 {
|
|
||||||
return try parser.documentGetDocumentURI(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_URL(self: *parser.Document) ![]const u8 {
|
|
||||||
return try get_documentURI(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO implement contentType
|
|
||||||
pub fn get_contentType(self: *parser.Document) []const u8 {
|
|
||||||
_ = self;
|
|
||||||
return "text/html";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO implement compactMode
|
|
||||||
pub fn get_compatMode(self: *parser.Document) []const u8 {
|
|
||||||
_ = self;
|
|
||||||
return "CSS1Compat";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_characterSet(self: *parser.Document) ![]const u8 {
|
|
||||||
return try parser.documentGetInputEncoding(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// alias of get_characterSet
|
|
||||||
pub fn get_charset(self: *parser.Document) ![]const u8 {
|
|
||||||
return try get_characterSet(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// alias of get_characterSet
|
|
||||||
pub fn get_inputEncoding(self: *parser.Document) ![]const u8 {
|
|
||||||
return try get_characterSet(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_doctype(self: *parser.Document) !?*parser.DocumentType {
|
|
||||||
return try parser.documentGetDoctype(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createEvent(_: *parser.Document, eventCstr: []const u8) !*parser.Event {
|
|
||||||
// TODO: for now only "Event" constructor is supported
|
|
||||||
// see table on https://dom.spec.whatwg.org/#dom-document-createevent $2
|
|
||||||
if (std.ascii.eqlIgnoreCase(eventCstr, "Event") or std.ascii.eqlIgnoreCase(eventCstr, "Events")) {
|
|
||||||
return try parser.eventCreate();
|
|
||||||
}
|
|
||||||
return parser.DOMError.NotSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _getElementById(self: *parser.Document, id: []const u8) !?ElementUnion {
|
|
||||||
const e = try parser.documentGetElementById(self, id) orelse return null;
|
|
||||||
return try Element.toInterface(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createElement(self: *parser.Document, tag_name: []const u8) !ElementUnion {
|
|
||||||
const e = try parser.documentCreateElement(self, tag_name);
|
|
||||||
return try Element.toInterface(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createElementNS(self: *parser.Document, ns: []const u8, tag_name: []const u8) !ElementUnion {
|
|
||||||
const e = try parser.documentCreateElementNS(self, ns, tag_name);
|
|
||||||
return try Element.toInterface(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't simply use libdom dom_document_get_elements_by_tag_name here.
|
|
||||||
// Indeed, netsurf implemented a previous dom spec when
|
|
||||||
// getElementsByTagName returned a NodeList.
|
|
||||||
// But since
|
|
||||||
// https://github.com/whatwg/dom/commit/190700b7c12ecfd3b5ebdb359ab1d6ea9cbf7749
|
|
||||||
// the spec changed to return an HTMLCollection instead.
|
|
||||||
// That's why we reimplemented getElementsByTagName by using an
|
|
||||||
// HTMLCollection in zig here.
|
|
||||||
pub fn _getElementsByTagName(
|
|
||||||
self: *parser.Document,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
tag_name: []const u8,
|
|
||||||
) !collection.HTMLCollection {
|
|
||||||
return try collection.HTMLCollectionByTagName(alloc, parser.documentToNode(self), tag_name, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _getElementsByClassName(
|
|
||||||
self: *parser.Document,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
classNames: []const u8,
|
|
||||||
) !collection.HTMLCollection {
|
|
||||||
return try collection.HTMLCollectionByClassName(alloc, parser.documentToNode(self), classNames, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createDocumentFragment(self: *parser.Document) !*parser.DocumentFragment {
|
|
||||||
return try parser.documentCreateDocumentFragment(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createTextNode(self: *parser.Document, data: []const u8) !*parser.Text {
|
|
||||||
return try parser.documentCreateTextNode(self, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createCDATASection(self: *parser.Document, data: []const u8) !*parser.CDATASection {
|
|
||||||
return try parser.documentCreateCDATASection(self, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createComment(self: *parser.Document, data: []const u8) !*parser.Comment {
|
|
||||||
return try parser.documentCreateComment(self, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createProcessingInstruction(self: *parser.Document, target: []const u8, data: []const u8) !*parser.ProcessingInstruction {
|
|
||||||
return try parser.documentCreateProcessingInstruction(self, target, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _importNode(self: *parser.Document, node: *parser.Node, deep: ?bool) !NodeUnion {
|
|
||||||
const n = try parser.documentImportNode(self, node, deep orelse false);
|
|
||||||
return try Node.toInterface(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _adoptNode(self: *parser.Document, node: *parser.Node) !NodeUnion {
|
|
||||||
const n = try parser.documentAdoptNode(self, node);
|
|
||||||
return try Node.toInterface(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createAttribute(self: *parser.Document, name: []const u8) !*parser.Attribute {
|
|
||||||
return try parser.documentCreateAttribute(self, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _createAttributeNS(self: *parser.Document, ns: []const u8, qname: []const u8) !*parser.Attribute {
|
|
||||||
return try parser.documentCreateAttributeNS(self, ns, qname);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParentNode
|
|
||||||
// https://dom.spec.whatwg.org/#parentnode
|
|
||||||
pub fn get_children(self: *parser.Document) !collection.HTMLCollection {
|
|
||||||
return try collection.HTMLCollectionChildren(parser.documentToNode(self), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_firstElementChild(self: *parser.Document) !?ElementUnion {
|
|
||||||
const elt = try parser.documentGetDocumentElement(self) orelse return null;
|
|
||||||
return try Element.toInterface(elt);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_lastElementChild(self: *parser.Document) !?ElementUnion {
|
|
||||||
const elt = try parser.documentGetDocumentElement(self) orelse return null;
|
|
||||||
return try Element.toInterface(elt);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_childElementCount(self: *parser.Document) !u32 {
|
|
||||||
_ = try parser.documentGetDocumentElement(self) orelse return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _querySelector(self: *parser.Document, alloc: std.mem.Allocator, selector: []const u8) !?ElementUnion {
|
|
||||||
if (selector.len == 0) return null;
|
|
||||||
|
|
||||||
const n = try css.querySelector(alloc, parser.documentToNode(self), selector);
|
|
||||||
|
|
||||||
if (n == null) return null;
|
|
||||||
|
|
||||||
return try Element.toInterface(parser.nodeToElement(n.?));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _querySelectorAll(self: *parser.Document, alloc: std.mem.Allocator, selector: []const u8) !NodeList {
|
|
||||||
return css.querySelectorAll(alloc, parser.documentToNode(self), selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
|
||||||
// function must accept either node or string.
|
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
|
||||||
pub fn _prepend(self: *parser.Document, nodes: ?Variadic(*parser.Node)) !void {
|
|
||||||
return Node.prepend(parser.documentToNode(self), nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
|
||||||
// function must accept either node or string.
|
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
|
||||||
pub fn _append(self: *parser.Document, nodes: ?Variadic(*parser.Node)) !void {
|
|
||||||
return Node.append(parser.documentToNode(self), nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
|
|
||||||
// function must accept either node or string.
|
|
||||||
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
|
||||||
pub fn _replaceChildren(self: *parser.Document, nodes: ?Variadic(*parser.Node)) !void {
|
|
||||||
return Node.replaceChildren(parser.documentToNode(self), nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(_: *parser.Document, _: std.mem.Allocator) void {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
// -----
|
|
||||||
|
|
||||||
pub fn testExecFn(
|
|
||||||
_: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
var constructor = [_]Case{
|
|
||||||
.{ .src = "document.__proto__.__proto__.constructor.name", .ex = "Document" },
|
|
||||||
.{ .src = "document.__proto__.__proto__.__proto__.constructor.name", .ex = "Node" },
|
|
||||||
.{ .src = "document.__proto__.__proto__.__proto__.__proto__.constructor.name", .ex = "EventTarget" },
|
|
||||||
|
|
||||||
.{ .src = "let newdoc = new Document()", .ex = "undefined" },
|
|
||||||
.{ .src = "newdoc.documentElement", .ex = "null" },
|
|
||||||
.{ .src = "newdoc.children.length", .ex = "0" },
|
|
||||||
.{ .src = "newdoc.getElementsByTagName('*').length", .ex = "0" },
|
|
||||||
.{ .src = "newdoc.getElementsByTagName('*').item(0)", .ex = "null" },
|
|
||||||
.{ .src = "newdoc.inputEncoding === document.inputEncoding", .ex = "true" },
|
|
||||||
.{ .src = "newdoc.documentURI === document.documentURI", .ex = "true" },
|
|
||||||
.{ .src = "newdoc.URL === document.URL", .ex = "true" },
|
|
||||||
.{ .src = "newdoc.compatMode === document.compatMode", .ex = "true" },
|
|
||||||
.{ .src = "newdoc.characterSet === document.characterSet", .ex = "true" },
|
|
||||||
.{ .src = "newdoc.charset === document.charset", .ex = "true" },
|
|
||||||
.{ .src = "newdoc.contentType === document.contentType", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &constructor);
|
|
||||||
|
|
||||||
var getElementById = [_]Case{
|
|
||||||
.{ .src = "let getElementById = document.getElementById('content')", .ex = "undefined" },
|
|
||||||
.{ .src = "getElementById.constructor.name", .ex = "HTMLDivElement" },
|
|
||||||
.{ .src = "getElementById.localName", .ex = "div" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getElementById);
|
|
||||||
|
|
||||||
var getElementsByTagName = [_]Case{
|
|
||||||
.{ .src = "let getElementsByTagName = document.getElementsByTagName('p')", .ex = "undefined" },
|
|
||||||
.{ .src = "getElementsByTagName.length", .ex = "2" },
|
|
||||||
.{ .src = "getElementsByTagName.item(0).localName", .ex = "p" },
|
|
||||||
.{ .src = "getElementsByTagName.item(1).localName", .ex = "p" },
|
|
||||||
.{ .src = "let getElementsByTagNameAll = document.getElementsByTagName('*')", .ex = "undefined" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.length", .ex = "8" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.item(0).localName", .ex = "html" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.item(7).localName", .ex = "p" },
|
|
||||||
.{ .src = "getElementsByTagNameAll.namedItem('para-empty-child').localName", .ex = "span" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getElementsByTagName);
|
|
||||||
|
|
||||||
var getElementsByClassName = [_]Case{
|
|
||||||
.{ .src = "let ok = document.getElementsByClassName('ok')", .ex = "undefined" },
|
|
||||||
.{ .src = "ok.length", .ex = "2" },
|
|
||||||
.{ .src = "let empty = document.getElementsByClassName('empty')", .ex = "undefined" },
|
|
||||||
.{ .src = "empty.length", .ex = "1" },
|
|
||||||
.{ .src = "let emptyok = document.getElementsByClassName('empty ok')", .ex = "undefined" },
|
|
||||||
.{ .src = "emptyok.length", .ex = "1" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getElementsByClassName);
|
|
||||||
|
|
||||||
var getDocumentElement = [_]Case{
|
|
||||||
.{ .src = "let e = document.documentElement", .ex = "undefined" },
|
|
||||||
.{ .src = "e.localName", .ex = "html" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getDocumentElement);
|
|
||||||
|
|
||||||
var getCharacterSet = [_]Case{
|
|
||||||
.{ .src = "document.characterSet", .ex = "UTF-8" },
|
|
||||||
.{ .src = "document.charset", .ex = "UTF-8" },
|
|
||||||
.{ .src = "document.inputEncoding", .ex = "UTF-8" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getCharacterSet);
|
|
||||||
|
|
||||||
var getCompatMode = [_]Case{
|
|
||||||
.{ .src = "document.compatMode", .ex = "CSS1Compat" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getCompatMode);
|
|
||||||
|
|
||||||
var getContentType = [_]Case{
|
|
||||||
.{ .src = "document.contentType", .ex = "text/html" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getContentType);
|
|
||||||
|
|
||||||
var getDocumentURI = [_]Case{
|
|
||||||
.{ .src = "document.documentURI", .ex = "about:blank" },
|
|
||||||
.{ .src = "document.URL", .ex = "about:blank" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getDocumentURI);
|
|
||||||
|
|
||||||
var getImplementation = [_]Case{
|
|
||||||
.{ .src = "let impl = document.implementation", .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &getImplementation);
|
|
||||||
|
|
||||||
var new = [_]Case{
|
|
||||||
.{ .src = "let d = new Document()", .ex = "undefined" },
|
|
||||||
.{ .src = "d.characterSet", .ex = "UTF-8" },
|
|
||||||
.{ .src = "d.URL", .ex = "about:blank" },
|
|
||||||
.{ .src = "d.documentURI", .ex = "about:blank" },
|
|
||||||
.{ .src = "d.compatMode", .ex = "CSS1Compat" },
|
|
||||||
.{ .src = "d.contentType", .ex = "text/html" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &new);
|
|
||||||
|
|
||||||
var createDocumentFragment = [_]Case{
|
|
||||||
.{ .src = "var v = document.createDocumentFragment()", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "#document-fragment" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &createDocumentFragment);
|
|
||||||
|
|
||||||
var createTextNode = [_]Case{
|
|
||||||
.{ .src = "var v = document.createTextNode('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "#text" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &createTextNode);
|
|
||||||
|
|
||||||
var createCDATASection = [_]Case{
|
|
||||||
.{ .src = "var v = document.createCDATASection('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "#cdata-section" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &createCDATASection);
|
|
||||||
|
|
||||||
var createComment = [_]Case{
|
|
||||||
.{ .src = "var v = document.createComment('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "#comment" },
|
|
||||||
.{ .src = "let v2 = v.cloneNode()", .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &createComment);
|
|
||||||
|
|
||||||
var createProcessingInstruction = [_]Case{
|
|
||||||
.{ .src = "let pi = document.createProcessingInstruction('foo', 'bar')", .ex = "undefined" },
|
|
||||||
.{ .src = "pi.target", .ex = "foo" },
|
|
||||||
.{ .src = "let pi2 = pi.cloneNode()", .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &createProcessingInstruction);
|
|
||||||
|
|
||||||
var importNode = [_]Case{
|
|
||||||
.{ .src = "let nimp = document.getElementById('content')", .ex = "undefined" },
|
|
||||||
.{ .src = "var v = document.importNode(nimp)", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "DIV" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &importNode);
|
|
||||||
|
|
||||||
var createAttr = [_]Case{
|
|
||||||
.{ .src = "var v = document.createAttribute('foo')", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "foo" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &createAttr);
|
|
||||||
|
|
||||||
var parentNode = [_]Case{
|
|
||||||
.{ .src = "document.children.length", .ex = "1" },
|
|
||||||
.{ .src = "document.children.item(0).nodeName", .ex = "HTML" },
|
|
||||||
.{ .src = "document.firstElementChild.nodeName", .ex = "HTML" },
|
|
||||||
.{ .src = "document.lastElementChild.nodeName", .ex = "HTML" },
|
|
||||||
.{ .src = "document.childElementCount", .ex = "1" },
|
|
||||||
|
|
||||||
.{ .src = "let nd = new Document()", .ex = "undefined" },
|
|
||||||
.{ .src = "nd.children.length", .ex = "0" },
|
|
||||||
.{ .src = "nd.children.item(0)", .ex = "null" },
|
|
||||||
.{ .src = "nd.firstElementChild", .ex = "null" },
|
|
||||||
.{ .src = "nd.lastElementChild", .ex = "null" },
|
|
||||||
.{ .src = "nd.childElementCount", .ex = "0" },
|
|
||||||
|
|
||||||
.{ .src = "let emptydoc = document.createElement('html')", .ex = "undefined" },
|
|
||||||
.{ .src = "emptydoc.prepend(document.createElement('html'))", .ex = "undefined" },
|
|
||||||
|
|
||||||
.{ .src = "let emptydoc2 = document.createElement('html')", .ex = "undefined" },
|
|
||||||
.{ .src = "emptydoc2.append(document.createElement('html'))", .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &parentNode);
|
|
||||||
|
|
||||||
var querySelector = [_]Case{
|
|
||||||
.{ .src = "document.querySelector('')", .ex = "null" },
|
|
||||||
.{ .src = "document.querySelector('*').nodeName", .ex = "HTML" },
|
|
||||||
.{ .src = "document.querySelector('#content').id", .ex = "content" },
|
|
||||||
.{ .src = "document.querySelector('#para').id", .ex = "para" },
|
|
||||||
.{ .src = "document.querySelector('.ok').id", .ex = "link" },
|
|
||||||
.{ .src = "document.querySelector('a ~ p').id", .ex = "para-empty" },
|
|
||||||
.{ .src = "document.querySelector(':root').nodeName", .ex = "HTML" },
|
|
||||||
|
|
||||||
.{ .src = "document.querySelectorAll('p').length", .ex = "2" },
|
|
||||||
.{ .src =
|
|
||||||
\\Array.from(document.querySelectorAll('#content > p#para-empty'))
|
|
||||||
\\.map(row => row.querySelector('span').textContent)
|
|
||||||
\\.length;
|
|
||||||
, .ex = "1" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &querySelector);
|
|
||||||
|
|
||||||
// this test breaks the doc structure, keep it at the end of the test
|
|
||||||
// suite.
|
|
||||||
var adoptNode = [_]Case{
|
|
||||||
.{ .src = "let nadop = document.getElementById('content')", .ex = "undefined" },
|
|
||||||
.{ .src = "var v = document.adoptNode(nadop)", .ex = "undefined" },
|
|
||||||
.{ .src = "v.nodeName", .ex = "DIV" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &adoptNode);
|
|
||||||
|
|
||||||
const tags = comptime parser.Tag.all();
|
|
||||||
var createElements: [(tags.len) * 2]Case = undefined;
|
|
||||||
inline for (tags, 0..) |tag, i| {
|
|
||||||
const tag_name = @tagName(tag);
|
|
||||||
createElements[i * 2] = Case{
|
|
||||||
.src = "var " ++ tag_name ++ "Elem = document.createElement('" ++ tag_name ++ "')",
|
|
||||||
.ex = "undefined",
|
|
||||||
};
|
|
||||||
createElements[(i * 2) + 1] = Case{
|
|
||||||
.src = tag_name ++ "Elem.localName",
|
|
||||||
.ex = tag_name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
try checkCases(js_env, &createElements);
|
|
||||||
}
|
|
||||||
@@ -1,243 +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 jsruntime = @import("jsruntime");
|
|
||||||
const Callback = jsruntime.Callback;
|
|
||||||
const JSObjectID = jsruntime.JSObjectID;
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const EventHandler = @import("../events/event.zig").EventHandler;
|
|
||||||
|
|
||||||
const DOMException = @import("exceptions.zig").DOMException;
|
|
||||||
const Nod = @import("node.zig");
|
|
||||||
|
|
||||||
// EventTarget interfaces
|
|
||||||
pub const Union = Nod.Union;
|
|
||||||
|
|
||||||
// EventTarget implementation
|
|
||||||
pub const EventTarget = struct {
|
|
||||||
pub const Self = parser.EventTarget;
|
|
||||||
pub const Exception = DOMException;
|
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub fn toInterface(et: *parser.EventTarget) !Union {
|
|
||||||
// NOTE: for now we state that all EventTarget are Nodes
|
|
||||||
// TODO: handle other types (eg. Window)
|
|
||||||
return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// JS funcs
|
|
||||||
// --------
|
|
||||||
|
|
||||||
pub fn _addEventListener(
|
|
||||||
self: *parser.EventTarget,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
eventType: []const u8,
|
|
||||||
cbk: Callback,
|
|
||||||
capture: ?bool,
|
|
||||||
// TODO: hanle EventListenerOptions
|
|
||||||
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
|
||||||
) !void {
|
|
||||||
|
|
||||||
// check if event target has already this listener
|
|
||||||
const lst = try parser.eventTargetHasListener(
|
|
||||||
self,
|
|
||||||
eventType,
|
|
||||||
capture orelse false,
|
|
||||||
cbk.id(),
|
|
||||||
);
|
|
||||||
if (lst != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try parser.eventTargetAddEventListener(
|
|
||||||
self,
|
|
||||||
alloc,
|
|
||||||
eventType,
|
|
||||||
EventHandler,
|
|
||||||
.{ .cbk = cbk },
|
|
||||||
capture orelse false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _removeEventListener(
|
|
||||||
self: *parser.EventTarget,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
eventType: []const u8,
|
|
||||||
cbk_id: JSObjectID,
|
|
||||||
capture: ?bool,
|
|
||||||
// TODO: hanle EventListenerOptions
|
|
||||||
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
|
||||||
) !void {
|
|
||||||
|
|
||||||
// check if event target has already this listener
|
|
||||||
const lst = try parser.eventTargetHasListener(
|
|
||||||
self,
|
|
||||||
eventType,
|
|
||||||
capture orelse false,
|
|
||||||
cbk_id.get(),
|
|
||||||
);
|
|
||||||
if (lst == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove listener
|
|
||||||
try parser.eventTargetRemoveEventListener(
|
|
||||||
self,
|
|
||||||
alloc,
|
|
||||||
eventType,
|
|
||||||
lst.?,
|
|
||||||
capture orelse false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
|
|
||||||
return try parser.eventTargetDispatchEvent(self, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *parser.EventTarget, alloc: std.mem.Allocator) void {
|
|
||||||
parser.eventTargetRemoveAllEventListeners(self, alloc) catch unreachable;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
// -----
|
|
||||||
|
|
||||||
pub fn testExecFn(
|
|
||||||
_: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
var common = [_]Case{
|
|
||||||
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
|
|
||||||
.{ .src = "let para = document.getElementById('para')", .ex = "undefined" },
|
|
||||||
// NOTE: as some event properties will change during the event dispatching phases
|
|
||||||
// we need to copy thoses values in order to check them afterwards
|
|
||||||
.{ .src =
|
|
||||||
\\var nb = 0; var evt; var phase; var cur;
|
|
||||||
\\function cbk(event) {
|
|
||||||
\\evt = event;
|
|
||||||
\\phase = event.eventPhase;
|
|
||||||
\\cur = event.currentTarget;
|
|
||||||
\\nb ++;
|
|
||||||
\\}
|
|
||||||
, .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &common);
|
|
||||||
|
|
||||||
var basic = [_]Case{
|
|
||||||
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
.{ .src = "evt instanceof Event", .ex = "true" },
|
|
||||||
.{ .src = "evt.type", .ex = "basic" },
|
|
||||||
.{ .src = "phase", .ex = "2" },
|
|
||||||
.{ .src = "cur.getAttribute('id')", .ex = "content" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic);
|
|
||||||
|
|
||||||
var basic_child = [_]Case{
|
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
|
||||||
.{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling
|
|
||||||
.{ .src = "evt === undefined", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic_child);
|
|
||||||
|
|
||||||
var basic_twice = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic_twice);
|
|
||||||
|
|
||||||
var basic_twice_capture = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src = "content.addEventListener('basic', cbk, true)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "2" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic_twice_capture);
|
|
||||||
|
|
||||||
var basic_remove = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src = "content.removeEventListener('basic', cbk)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic_remove);
|
|
||||||
|
|
||||||
var basic_capture_remove = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src = "content.removeEventListener('basic', cbk, true)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "0" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic_capture_remove);
|
|
||||||
|
|
||||||
var capture = [_]Case{
|
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
|
||||||
.{ .src = "content.addEventListener('capture', cbk, true)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('capture'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
.{ .src = "evt instanceof Event", .ex = "true" },
|
|
||||||
.{ .src = "evt.type", .ex = "capture" },
|
|
||||||
.{ .src = "phase", .ex = "2" },
|
|
||||||
.{ .src = "cur.getAttribute('id')", .ex = "content" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &capture);
|
|
||||||
|
|
||||||
var capture_child = [_]Case{
|
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
|
||||||
.{ .src = "para.dispatchEvent(new Event('capture'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
.{ .src = "evt instanceof Event", .ex = "true" },
|
|
||||||
.{ .src = "evt.type", .ex = "capture" },
|
|
||||||
.{ .src = "phase", .ex = "1" },
|
|
||||||
.{ .src = "cur.getAttribute('id')", .ex = "content" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &capture_child);
|
|
||||||
|
|
||||||
var bubbles = [_]Case{
|
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
|
||||||
.{ .src = "content.addEventListener('bubbles', cbk)", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('bubbles', {bubbles: true}))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
.{ .src = "evt instanceof Event", .ex = "true" },
|
|
||||||
.{ .src = "evt.type", .ex = "bubbles" },
|
|
||||||
.{ .src = "evt.bubbles", .ex = "true" },
|
|
||||||
.{ .src = "phase", .ex = "2" },
|
|
||||||
.{ .src = "cur.getAttribute('id')", .ex = "content" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &bubbles);
|
|
||||||
|
|
||||||
var bubbles_child = [_]Case{
|
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
|
||||||
.{ .src = "para.dispatchEvent(new Event('bubbles', {bubbles: true}))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
.{ .src = "evt instanceof Event", .ex = "true" },
|
|
||||||
.{ .src = "evt.type", .ex = "bubbles" },
|
|
||||||
.{ .src = "phase", .ex = "3" },
|
|
||||||
.{ .src = "cur.getAttribute('id')", .ex = "content" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &bubbles_child);
|
|
||||||
}
|
|
||||||
@@ -1,263 +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 generate = @import("../generate.zig");
|
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Callback = jsruntime.Callback;
|
|
||||||
const CallbackResult = jsruntime.CallbackResult;
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
|
|
||||||
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
|
||||||
const EventTargetUnion = @import("../dom/event_target.zig").Union;
|
|
||||||
|
|
||||||
const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.events);
|
|
||||||
|
|
||||||
// Event interfaces
|
|
||||||
pub const Interfaces = .{
|
|
||||||
Event,
|
|
||||||
ProgressEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Union = generate.Union(Interfaces);
|
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#event
|
|
||||||
pub const Event = struct {
|
|
||||||
pub const Self = parser.Event;
|
|
||||||
pub const Exception = DOMException;
|
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
pub const EventInit = parser.EventInit;
|
|
||||||
|
|
||||||
// JS
|
|
||||||
// --
|
|
||||||
|
|
||||||
pub const _CAPTURING_PHASE = 1;
|
|
||||||
pub const _AT_TARGET = 2;
|
|
||||||
pub const _BUBBLING_PHASE = 3;
|
|
||||||
|
|
||||||
pub fn toInterface(evt: *parser.Event) !Union {
|
|
||||||
return switch (try parser.eventGetInternalType(evt)) {
|
|
||||||
.event => .{ .Event = evt },
|
|
||||||
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn constructor(eventType: []const u8, opts: ?EventInit) !*parser.Event {
|
|
||||||
const event = try parser.eventCreate();
|
|
||||||
try parser.eventInit(event, eventType, opts orelse EventInit{});
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
|
|
||||||
pub fn get_type(self: *parser.Event) ![]const u8 {
|
|
||||||
return try parser.eventType(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_target(self: *parser.Event) !?EventTargetUnion {
|
|
||||||
const et = try parser.eventTarget(self);
|
|
||||||
if (et == null) return null;
|
|
||||||
return try EventTarget.toInterface(et.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_currentTarget(self: *parser.Event) !?EventTargetUnion {
|
|
||||||
const et = try parser.eventCurrentTarget(self);
|
|
||||||
if (et == null) return null;
|
|
||||||
return try EventTarget.toInterface(et.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_eventPhase(self: *parser.Event) !u8 {
|
|
||||||
return try parser.eventPhase(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_bubbles(self: *parser.Event) !bool {
|
|
||||||
return try parser.eventBubbles(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_cancelable(self: *parser.Event) !bool {
|
|
||||||
return try parser.eventCancelable(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_defaultPrevented(self: *parser.Event) !bool {
|
|
||||||
return try parser.eventDefaultPrevented(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_isTrusted(self: *parser.Event) !bool {
|
|
||||||
return try parser.eventIsTrusted(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_timestamp(self: *parser.Event) !u32 {
|
|
||||||
return try parser.eventTimestamp(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
|
|
||||||
pub fn _initEvent(
|
|
||||||
self: *parser.Event,
|
|
||||||
eventType: []const u8,
|
|
||||||
bubbles: ?bool,
|
|
||||||
cancelable: ?bool,
|
|
||||||
) !void {
|
|
||||||
const opts = EventInit{
|
|
||||||
.bubbles = bubbles orelse false,
|
|
||||||
.cancelable = cancelable orelse false,
|
|
||||||
};
|
|
||||||
return try parser.eventInit(self, eventType, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _stopPropagation(self: *parser.Event) !void {
|
|
||||||
return try parser.eventStopPropagation(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _stopImmediatePropagation(self: *parser.Event) !void {
|
|
||||||
return try parser.eventStopImmediatePropagation(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _preventDefault(self: *parser.Event) !void {
|
|
||||||
return try parser.eventPreventDefault(self);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn testExecFn(
|
|
||||||
_: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
var common = [_]Case{
|
|
||||||
.{ .src = "let content = document.getElementById('content')", .ex = "undefined" },
|
|
||||||
.{ .src = "let para = document.getElementById('para')", .ex = "undefined" },
|
|
||||||
.{ .src = "var nb = 0; var evt", .ex = "undefined" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &common);
|
|
||||||
|
|
||||||
var basic = [_]Case{
|
|
||||||
.{ .src =
|
|
||||||
\\content.addEventListener('target',
|
|
||||||
\\function(e) {
|
|
||||||
\\evt = e; nb = nb + 1;
|
|
||||||
\\e.preventDefault();
|
|
||||||
\\})
|
|
||||||
, .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('target', {bubbles: true, cancelable: true}))", .ex = "false" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
.{ .src = "evt.target === content", .ex = "true" },
|
|
||||||
.{ .src = "evt.bubbles", .ex = "true" },
|
|
||||||
.{ .src = "evt.cancelable", .ex = "true" },
|
|
||||||
.{ .src = "evt.defaultPrevented", .ex = "true" },
|
|
||||||
.{ .src = "evt.isTrusted", .ex = "true" },
|
|
||||||
.{ .src = "evt.timestamp > 1704063600", .ex = "true" }, // 2024/01/01 00:00
|
|
||||||
// event.type, event.currentTarget, event.phase checked in EventTarget
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &basic);
|
|
||||||
|
|
||||||
var stop = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src =
|
|
||||||
\\content.addEventListener('stop',
|
|
||||||
\\function(e) {
|
|
||||||
\\e.stopPropagation();
|
|
||||||
\\nb = nb + 1;
|
|
||||||
\\}, true)
|
|
||||||
, .ex = "undefined" },
|
|
||||||
// the following event listener will not be invoked
|
|
||||||
.{ .src =
|
|
||||||
\\para.addEventListener('stop',
|
|
||||||
\\function(e) {
|
|
||||||
\\nb = nb + 1;
|
|
||||||
\\})
|
|
||||||
, .ex = "undefined" },
|
|
||||||
.{ .src = "para.dispatchEvent(new Event('stop'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" }, // will be 2 if event was not stopped at content event listener
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &stop);
|
|
||||||
|
|
||||||
var stop_immediate = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src =
|
|
||||||
\\content.addEventListener('immediate',
|
|
||||||
\\function(e) {
|
|
||||||
\\e.stopImmediatePropagation();
|
|
||||||
\\nb = nb + 1;
|
|
||||||
\\})
|
|
||||||
, .ex = "undefined" },
|
|
||||||
// the following event listener will not be invoked
|
|
||||||
.{ .src =
|
|
||||||
\\content.addEventListener('immediate',
|
|
||||||
\\function(e) {
|
|
||||||
\\nb = nb + 1;
|
|
||||||
\\})
|
|
||||||
, .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(new Event('immediate'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" }, // will be 2 if event was not stopped at first content event listener
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &stop_immediate);
|
|
||||||
|
|
||||||
var legacy = [_]Case{
|
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
|
||||||
.{ .src =
|
|
||||||
\\content.addEventListener('legacy',
|
|
||||||
\\function(e) {
|
|
||||||
\\evt = e; nb = nb + 1;
|
|
||||||
\\})
|
|
||||||
, .ex = "undefined" },
|
|
||||||
.{ .src = "let evtLegacy = document.createEvent('Event')", .ex = "undefined" },
|
|
||||||
.{ .src = "evtLegacy.initEvent('legacy')", .ex = "undefined" },
|
|
||||||
.{ .src = "content.dispatchEvent(evtLegacy)", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "1" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &legacy);
|
|
||||||
|
|
||||||
var remove = [_]Case{
|
|
||||||
.{ .src = "var nb = 0; var evt = null; function cbk(event) { nb ++; evt=event; }", .ex = "undefined" },
|
|
||||||
.{ .src = "document.addEventListener('count', cbk)", .ex = "undefined" },
|
|
||||||
.{ .src = "document.removeEventListener('count', cbk)", .ex = "undefined" },
|
|
||||||
.{ .src = "document.dispatchEvent(new Event('count'))", .ex = "true" },
|
|
||||||
.{ .src = "nb", .ex = "0" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EventHandler = struct {
|
|
||||||
fn handle(event: ?*parser.Event, data: parser.EventHandlerData) void {
|
|
||||||
// TODO get the allocator by another way?
|
|
||||||
var res = CallbackResult.init(data.cbk.nat_ctx.alloc);
|
|
||||||
defer res.deinit();
|
|
||||||
|
|
||||||
if (event) |evt| {
|
|
||||||
data.cbk.trycall(.{
|
|
||||||
Event.toInterface(evt) catch unreachable,
|
|
||||||
}, &res) catch |e| log.err("event handler error: {any}", .{e});
|
|
||||||
} else {
|
|
||||||
data.cbk.trycall(.{event}, &res) catch |e| log.err("event handler error (null event): {any}", .{e});
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case of function error, we log the result and the trace.
|
|
||||||
if (!res.success) {
|
|
||||||
log.info("event handler error try catch: {s}", .{res.result orelse "unknown"});
|
|
||||||
log.debug("{s}", .{res.stack orelse "no stack trace"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.handle;
|
|
||||||
@@ -1,129 +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 jsruntime = @import("jsruntime");
|
|
||||||
|
|
||||||
const URL = @import("../url/url.zig").URL;
|
|
||||||
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-location-interface
|
|
||||||
pub const Location = struct {
|
|
||||||
pub const mem_guarantied = true;
|
|
||||||
|
|
||||||
url: ?URL = null,
|
|
||||||
|
|
||||||
pub fn deinit(_: *Location, _: std.mem.Allocator) void {}
|
|
||||||
|
|
||||||
pub fn get_href(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_href(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_protocol(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_protocol(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_host(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_hostname(self: *Location) []const u8 {
|
|
||||||
if (self.url) |*u| return u.get_hostname();
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_port(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_port(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_pathname(self: *Location) []const u8 {
|
|
||||||
if (self.url) |*u| return u.get_pathname();
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_search(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_search(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_hash(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_hash(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_origin(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
if (self.url) |*u| return u.get_origin(alloc);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
pub fn _assign(_: *Location, url: []const u8) !void {
|
|
||||||
_ = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
pub fn _replace(_: *Location, url: []const u8) !void {
|
|
||||||
_ = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
pub fn _reload(_: *Location) !void {}
|
|
||||||
|
|
||||||
pub fn _toString(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
|
||||||
return try self.get_href(alloc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
// -----
|
|
||||||
|
|
||||||
pub fn testExecFn(
|
|
||||||
_: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
var location = [_]Case{
|
|
||||||
.{ .src = "location.href", .ex = "https://lightpanda.io/opensource-browser/" },
|
|
||||||
.{ .src = "document.location.href", .ex = "https://lightpanda.io/opensource-browser/" },
|
|
||||||
|
|
||||||
.{ .src = "location.host", .ex = "lightpanda.io" },
|
|
||||||
.{ .src = "location.hostname", .ex = "lightpanda.io" },
|
|
||||||
.{ .src = "location.origin", .ex = "https://lightpanda.io" },
|
|
||||||
.{ .src = "location.pathname", .ex = "/opensource-browser/" },
|
|
||||||
.{ .src = "location.hash", .ex = "" },
|
|
||||||
.{ .src = "location.port", .ex = "" },
|
|
||||||
.{ .src = "location.search", .ex = "" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &location);
|
|
||||||
}
|
|
||||||
@@ -27,9 +27,8 @@ const MemoryPool = std.heap.MemoryPool;
|
|||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const tls = @import("tls");
|
const tls = @import("tls");
|
||||||
const jsruntime = @import("jsruntime");
|
const IO = @import("../runtime/loop.zig").IO;
|
||||||
const IO = jsruntime.IO;
|
const Loop = @import("../runtime/loop.zig").Loop;
|
||||||
const Loop = jsruntime.Loop;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.http_client);
|
const log = std.log.scoped(.http_client);
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ pub const Client = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, max_concurrent: usize, opts: Opts) !Client {
|
pub fn init(allocator: Allocator, max_concurrent: usize, opts: Opts) !Client {
|
||||||
var root_ca = try tls.config.CertBundle.fromSystem(allocator);
|
var root_ca: tls.config.CertBundle = if (builtin.is_test) .{} else try tls.config.CertBundle.fromSystem(allocator);
|
||||||
errdefer root_ca.deinit(allocator);
|
errdefer root_ca.deinit(allocator);
|
||||||
|
|
||||||
const state_pool = try StatePool.init(allocator, max_concurrent);
|
const state_pool = try StatePool.init(allocator, max_concurrent);
|
||||||
@@ -69,7 +68,9 @@ pub const Client = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Client) void {
|
pub fn deinit(self: *Client) void {
|
||||||
const allocator = self.allocator;
|
const allocator = self.allocator;
|
||||||
self.root_ca.deinit(allocator);
|
if (builtin.is_test == false) {
|
||||||
|
self.root_ca.deinit(allocator);
|
||||||
|
}
|
||||||
self.state_pool.deinit(allocator);
|
self.state_pool.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1907,7 +1908,7 @@ test "HttpClient: sync GET redirect" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "HttpClient: async connect error" {
|
test "HttpClient: async connect error" {
|
||||||
var loop = try jsruntime.Loop.init(testing.allocator);
|
var loop = try Loop.init(testing.allocator);
|
||||||
defer loop.deinit();
|
defer loop.deinit();
|
||||||
|
|
||||||
const Handler = struct {
|
const Handler = struct {
|
||||||
@@ -2193,7 +2194,7 @@ const TestResponse = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CaptureHandler = struct {
|
const CaptureHandler = struct {
|
||||||
loop: jsruntime.Loop,
|
loop: Loop,
|
||||||
reset: Thread.ResetEvent,
|
reset: Thread.ResetEvent,
|
||||||
response: TestResponse,
|
response: TestResponse,
|
||||||
|
|
||||||
@@ -2201,7 +2202,7 @@ const CaptureHandler = struct {
|
|||||||
return .{
|
return .{
|
||||||
.reset = .{},
|
.reset = .{},
|
||||||
.response = TestResponse.init(),
|
.response = TestResponse.init(),
|
||||||
.loop = try jsruntime.Loop.init(testing.allocator),
|
.loop = try Loop.init(testing.allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
281
src/main.zig
281
src/main.zig
@@ -20,25 +20,19 @@ const std = @import("std");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
|
|
||||||
const App = @import("app.zig").App;
|
|
||||||
const Browser = @import("browser/browser.zig").Browser;
|
|
||||||
const server = @import("server.zig");
|
const server = @import("server.zig");
|
||||||
|
const App = @import("app.zig").App;
|
||||||
|
const Platform = @import("runtime/js.zig").Platform;
|
||||||
|
const Browser = @import("browser/browser.zig").Browser;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("browser/netsurf.zig");
|
||||||
const apiweb = @import("apiweb.zig");
|
|
||||||
|
|
||||||
pub const Types = jsruntime.reflect(apiweb.Interfaces);
|
|
||||||
pub const UserContext = apiweb.UserContext;
|
|
||||||
pub const IO = @import("asyncio").Wrapper(jsruntime.Loop);
|
|
||||||
const version = @import("build_info").git_commit;
|
const version = @import("build_info").git_commit;
|
||||||
|
|
||||||
const log = std.log.scoped(.cli);
|
const log = std.log.scoped(.cli);
|
||||||
|
|
||||||
pub const std_options = std.Options{
|
pub const std_options = std.Options{
|
||||||
// Set the log level to info
|
// Set the log level to info
|
||||||
.log_level = .debug,
|
.log_level = .info,
|
||||||
|
|
||||||
// Define logFn to override the std implementation
|
// Define logFn to override the std implementation
|
||||||
.logFn = logFn,
|
.logFn = logFn,
|
||||||
@@ -60,23 +54,34 @@ pub fn main() !void {
|
|||||||
const args = try parseArgs(args_arena.allocator());
|
const args = try parseArgs(args_arena.allocator());
|
||||||
|
|
||||||
switch (args.mode) {
|
switch (args.mode) {
|
||||||
.help => args.printUsageAndExit(args.mode.help),
|
.help => {
|
||||||
|
args.printUsageAndExit(args.mode.help);
|
||||||
|
return std.process.cleanExit();
|
||||||
|
},
|
||||||
.version => {
|
.version => {
|
||||||
std.debug.print("{s}\n", .{version});
|
std.debug.print("{s}\n", .{version});
|
||||||
return std.process.cleanExit();
|
return std.process.cleanExit();
|
||||||
},
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = Platform.init();
|
||||||
|
defer platform.deinit();
|
||||||
|
|
||||||
|
var app = try App.init(alloc, .{
|
||||||
|
.run_mode = args.mode,
|
||||||
|
.gc_hints = args.gcHints(),
|
||||||
|
.tls_verify_host = args.tlsVerifyHost(),
|
||||||
|
});
|
||||||
|
defer app.deinit();
|
||||||
|
app.telemetry.record(.{ .run = {} });
|
||||||
|
|
||||||
|
switch (args.mode) {
|
||||||
.serve => |opts| {
|
.serve => |opts| {
|
||||||
const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
|
const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
|
||||||
log.err("address (host:port) {any}\n", .{err});
|
log.err("address (host:port) {any}\n", .{err});
|
||||||
return args.printUsageAndExit(false);
|
return args.printUsageAndExit(false);
|
||||||
};
|
};
|
||||||
var app = try App.init(alloc, .{
|
|
||||||
.run_mode = args.mode,
|
|
||||||
.tls_verify_host = opts.tls_verify_host,
|
|
||||||
});
|
|
||||||
defer app.deinit();
|
|
||||||
|
|
||||||
app.telemetry.record(.{ .run = {} });
|
|
||||||
|
|
||||||
const timeout = std.time.ns_per_s * @as(u64, opts.timeout);
|
const timeout = std.time.ns_per_s * @as(u64, opts.timeout);
|
||||||
server.run(app, address, timeout) catch |err| {
|
server.run(app, address, timeout) catch |err| {
|
||||||
@@ -88,19 +93,8 @@ pub fn main() !void {
|
|||||||
log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump });
|
log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump });
|
||||||
const url = try @import("url.zig").URL.parse(opts.url, null);
|
const url = try @import("url.zig").URL.parse(opts.url, null);
|
||||||
|
|
||||||
var app = try App.init(alloc, .{
|
|
||||||
.run_mode = args.mode,
|
|
||||||
.tls_verify_host = opts.tls_verify_host,
|
|
||||||
});
|
|
||||||
defer app.deinit();
|
|
||||||
app.telemetry.record(.{ .run = {} });
|
|
||||||
|
|
||||||
// vm
|
|
||||||
const vm = jsruntime.VM.init();
|
|
||||||
defer vm.deinit();
|
|
||||||
|
|
||||||
// browser
|
// browser
|
||||||
var browser = Browser.init(app);
|
var browser = try Browser.init(app);
|
||||||
defer browser.deinit();
|
defer browser.deinit();
|
||||||
|
|
||||||
var session = try browser.newSession({});
|
var session = try browser.newSession({});
|
||||||
@@ -126,6 +120,7 @@ pub fn main() !void {
|
|||||||
try page.dump(std.io.getStdOut());
|
try page.dump(std.io.getStdOut());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
else => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +128,21 @@ const Command = struct {
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
exec_name: []const u8,
|
exec_name: []const u8,
|
||||||
|
|
||||||
|
fn gcHints(self: *const Command) bool {
|
||||||
|
return switch (self.mode) {
|
||||||
|
.serve => |opts| opts.gc_hints,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tlsVerifyHost(self: *const Command) bool {
|
||||||
|
return switch (self.mode) {
|
||||||
|
.serve => |opts| opts.tls_verify_host,
|
||||||
|
.fetch => |opts| opts.tls_verify_host,
|
||||||
|
else => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const Mode = union(App.RunMode) {
|
const Mode = union(App.RunMode) {
|
||||||
help: bool, // false when being printed because of an error
|
help: bool, // false when being printed because of an error
|
||||||
fetch: Fetch,
|
fetch: Fetch,
|
||||||
@@ -144,6 +154,7 @@ const Command = struct {
|
|||||||
host: []const u8,
|
host: []const u8,
|
||||||
port: u16,
|
port: u16,
|
||||||
timeout: u16,
|
timeout: u16,
|
||||||
|
gc_hints: bool,
|
||||||
tls_verify_host: bool,
|
tls_verify_host: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,6 +198,9 @@ const Command = struct {
|
|||||||
\\--timeout Inactivity timeout in seconds before disconnecting clients
|
\\--timeout Inactivity timeout in seconds before disconnecting clients
|
||||||
\\ Defaults to 3 (seconds)
|
\\ Defaults to 3 (seconds)
|
||||||
\\
|
\\
|
||||||
|
\\--gc_hints Encourage V8 to cleanup garbage for each new browser context.
|
||||||
|
\\ Defaults to false
|
||||||
|
\\
|
||||||
\\--insecure_disable_tls_host_verification
|
\\--insecure_disable_tls_host_verification
|
||||||
\\ Disables host verification on all HTTP requests.
|
\\ Disables host verification on all HTTP requests.
|
||||||
\\ This is an advanced option which should only be
|
\\ This is an advanced option which should only be
|
||||||
@@ -266,6 +280,11 @@ fn inferMode(opt: []const u8) ?App.RunMode {
|
|||||||
if (std.mem.eql(u8, opt, "--timeout")) {
|
if (std.mem.eql(u8, opt, "--timeout")) {
|
||||||
return .serve;
|
return .serve;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, opt, "--gc_hints")) {
|
||||||
|
return .serve;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +295,7 @@ fn parseServeArgs(
|
|||||||
var host: []const u8 = "127.0.0.1";
|
var host: []const u8 = "127.0.0.1";
|
||||||
var port: u16 = 9222;
|
var port: u16 = 9222;
|
||||||
var timeout: u16 = 3;
|
var timeout: u16 = 3;
|
||||||
|
var gc_hints = false;
|
||||||
var tls_verify_host = true;
|
var tls_verify_host = true;
|
||||||
|
|
||||||
while (args.next()) |opt| {
|
while (args.next()) |opt| {
|
||||||
@@ -319,6 +339,11 @@ fn parseServeArgs(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, "--gc_hints", opt)) {
|
||||||
|
gc_hints = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
log.err("Unknown option to serve command: '{s}'", .{opt});
|
log.err("Unknown option to serve command: '{s}'", .{opt});
|
||||||
return error.UnkownOption;
|
return error.UnkownOption;
|
||||||
}
|
}
|
||||||
@@ -327,6 +352,7 @@ fn parseServeArgs(
|
|||||||
.host = host,
|
.host = host,
|
||||||
.port = port,
|
.port = port,
|
||||||
.timeout = timeout,
|
.timeout = timeout,
|
||||||
|
.gc_hints = gc_hints,
|
||||||
.tls_verify_host = tls_verify_host,
|
.tls_verify_host = tls_verify_host,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -388,3 +414,196 @@ fn logFn(
|
|||||||
// default std log function.
|
// default std log function.
|
||||||
std.log.defaultLog(level, scope, format, args);
|
std.log.defaultLog(level, scope, format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|
||||||
|
var test_wg: std.Thread.WaitGroup = .{};
|
||||||
|
test "tests:beforeAll" {
|
||||||
|
try parser.init();
|
||||||
|
test_wg.startMany(3);
|
||||||
|
_ = Platform.init();
|
||||||
|
|
||||||
|
{
|
||||||
|
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
|
||||||
|
const thread = try std.Thread.spawn(.{}, serveHTTP, .{address});
|
||||||
|
thread.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const address = try std.net.Address.parseIp("127.0.0.1", 9581);
|
||||||
|
const thread = try std.Thread.spawn(.{}, serveHTTPS, .{address});
|
||||||
|
thread.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const address = try std.net.Address.parseIp("127.0.0.1", 9583);
|
||||||
|
const thread = try std.Thread.spawn(.{}, serveCDP, .{address});
|
||||||
|
thread.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to wait for the servers to be listening, else tests will fail because
|
||||||
|
// they aren't able to connect.
|
||||||
|
test_wg.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "tests:afterAll" {
|
||||||
|
parser.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serveHTTP(address: std.net.Address) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
var listener = try address.listen(.{ .reuse_address = true });
|
||||||
|
defer listener.deinit();
|
||||||
|
|
||||||
|
test_wg.finish();
|
||||||
|
|
||||||
|
var read_buffer: [1024]u8 = undefined;
|
||||||
|
ACCEPT: while (true) {
|
||||||
|
defer _ = arena.reset(.{ .free_all = {} });
|
||||||
|
const aa = arena.allocator();
|
||||||
|
|
||||||
|
var conn = try listener.accept();
|
||||||
|
defer conn.stream.close();
|
||||||
|
var http_server = std.http.Server.init(conn, &read_buffer);
|
||||||
|
|
||||||
|
while (http_server.state == .ready) {
|
||||||
|
var request = http_server.receiveHead() catch |err| switch (err) {
|
||||||
|
error.HttpConnectionClosing => continue :ACCEPT,
|
||||||
|
else => {
|
||||||
|
std.debug.print("Test HTTP Server error: {}\n", .{err});
|
||||||
|
return err;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = request.head.target;
|
||||||
|
if (std.mem.eql(u8, path, "/loader")) {
|
||||||
|
try request.respond("Hello!", .{});
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/simple")) {
|
||||||
|
try request.respond("", .{});
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/redirect")) {
|
||||||
|
try request.respond("", .{
|
||||||
|
.status = .moved_permanently,
|
||||||
|
.extra_headers = &.{.{ .name = "LOCATION", .value = "../http_client/echo" }},
|
||||||
|
});
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/redirect/secure")) {
|
||||||
|
try request.respond("", .{
|
||||||
|
.status = .moved_permanently,
|
||||||
|
.extra_headers = &.{.{ .name = "LOCATION", .value = "https://127.0.0.1:9581/http_client/body" }},
|
||||||
|
});
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/echo")) {
|
||||||
|
var headers: std.ArrayListUnmanaged(std.http.Header) = .{};
|
||||||
|
|
||||||
|
var it = request.iterateHeaders();
|
||||||
|
while (it.next()) |hdr| {
|
||||||
|
try headers.append(aa, .{
|
||||||
|
.name = try std.fmt.allocPrint(aa, "_{s}", .{hdr.name}),
|
||||||
|
.value = hdr.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try request.respond("over 9000!", .{
|
||||||
|
.status = .created,
|
||||||
|
.extra_headers = headers.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a lot of work for testing TLS, but the TLS (async) code is complicated
|
||||||
|
// This "server" is written specifically to test the client. It assumes the client
|
||||||
|
// isn't a jerk.
|
||||||
|
fn serveHTTPS(address: std.net.Address) !void {
|
||||||
|
const tls = @import("tls");
|
||||||
|
|
||||||
|
var listener = try address.listen(.{ .reuse_address = true });
|
||||||
|
defer listener.deinit();
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
test_wg.finish();
|
||||||
|
|
||||||
|
var seed: u64 = undefined;
|
||||||
|
std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
|
||||||
|
var r = std.Random.DefaultPrng.init(seed);
|
||||||
|
const rand = r.random();
|
||||||
|
|
||||||
|
var read_buffer: [1024]u8 = undefined;
|
||||||
|
while (true) {
|
||||||
|
// defer _ = arena.reset(.{ .retain_with_limit = 1024 });
|
||||||
|
// const aa = arena.allocator();
|
||||||
|
|
||||||
|
const stream = blk: {
|
||||||
|
const conn = try listener.accept();
|
||||||
|
break :blk conn.stream;
|
||||||
|
};
|
||||||
|
defer stream.close();
|
||||||
|
|
||||||
|
var conn = try tls.server(stream, .{ .auth = null });
|
||||||
|
defer conn.close() catch {};
|
||||||
|
|
||||||
|
var pos: usize = 0;
|
||||||
|
while (true) {
|
||||||
|
const n = try conn.read(read_buffer[pos..]);
|
||||||
|
if (n == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += n;
|
||||||
|
const header_end = std.mem.indexOf(u8, read_buffer[0..pos], "\r\n\r\n") orelse {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
var it = std.mem.splitScalar(u8, read_buffer[0..header_end], ' ');
|
||||||
|
_ = it.next() orelse unreachable; // method
|
||||||
|
const path = it.next() orelse unreachable;
|
||||||
|
|
||||||
|
var fragment = false;
|
||||||
|
var response: []const u8 = undefined;
|
||||||
|
if (std.mem.eql(u8, path, "/http_client/simple")) {
|
||||||
|
fragment = true;
|
||||||
|
response = "HTTP/1.1 200 \r\nContent-Length: 0\r\n\r\n";
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/body")) {
|
||||||
|
fragment = true;
|
||||||
|
response = "HTTP/1.1 201 CREATED\r\nContent-Length: 20\r\n Another : HEaDer \r\n\r\n1234567890abcdefhijk";
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/redirect/insecure")) {
|
||||||
|
fragment = true;
|
||||||
|
response = "HTTP/1.1 307 GOTO\r\nLocation: http://127.0.0.1:9582/http_client/redirect\r\n\r\n";
|
||||||
|
} else if (std.mem.eql(u8, path, "/xhr")) {
|
||||||
|
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 100\r\n\r\n" ++ ("1234567890" ** 10);
|
||||||
|
} else if (std.mem.eql(u8, path, "/xhr/json")) {
|
||||||
|
response = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 18\r\n\r\n{\"over\":\"9000!!!\"}";
|
||||||
|
} else {
|
||||||
|
// should not have an unknown path
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unsent = response;
|
||||||
|
while (unsent.len > 0) {
|
||||||
|
const to_send = if (fragment) rand.intRangeAtMost(usize, 1, unsent.len) else unsent.len;
|
||||||
|
const sent = try conn.write(unsent[0..to_send]);
|
||||||
|
unsent = unsent[sent..];
|
||||||
|
std.time.sleep(std.time.ns_per_us * 5);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serveCDP(address: std.net.Address) !void {
|
||||||
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||||
|
var app = try App.init(gpa.allocator(), .{
|
||||||
|
.run_mode = .serve,
|
||||||
|
.tls_verify_host = false,
|
||||||
|
});
|
||||||
|
defer app.deinit();
|
||||||
|
|
||||||
|
test_wg.finish();
|
||||||
|
server.run(app, address, std.time.ns_per_s * 2) catch |err| {
|
||||||
|
std.debug.print("CDP server error: {}", .{err});
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,420 +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 jsruntime = @import("jsruntime");
|
|
||||||
const generate = @import("generate.zig");
|
|
||||||
const pretty = @import("pretty");
|
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const apiweb = @import("apiweb.zig");
|
|
||||||
const browser = @import("browser/browser.zig");
|
|
||||||
const Window = @import("html/window.zig").Window;
|
|
||||||
const xhr = @import("xhr/xhr.zig");
|
|
||||||
const storage = @import("storage/storage.zig");
|
|
||||||
const URL = @import("url.zig").URL;
|
|
||||||
|
|
||||||
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
|
||||||
const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn;
|
|
||||||
const nodeTestExecFn = @import("dom/node.zig").testExecFn;
|
|
||||||
const characterDataTestExecFn = @import("dom/character_data.zig").testExecFn;
|
|
||||||
const textTestExecFn = @import("dom/text.zig").testExecFn;
|
|
||||||
const elementTestExecFn = @import("dom/element.zig").testExecFn;
|
|
||||||
const HTMLCollectionTestExecFn = @import("dom/html_collection.zig").testExecFn;
|
|
||||||
const DOMExceptionTestExecFn = @import("dom/exceptions.zig").testExecFn;
|
|
||||||
const DOMImplementationExecFn = @import("dom/implementation.zig").testExecFn;
|
|
||||||
const NamedNodeMapExecFn = @import("dom/namednodemap.zig").testExecFn;
|
|
||||||
const DOMTokenListExecFn = @import("dom/token_list.zig").testExecFn;
|
|
||||||
const NodeListTestExecFn = @import("dom/nodelist.zig").testExecFn;
|
|
||||||
const AttrTestExecFn = @import("dom/attribute.zig").testExecFn;
|
|
||||||
const EventTargetTestExecFn = @import("dom/event_target.zig").testExecFn;
|
|
||||||
const ProcessingInstructionTestExecFn = @import("dom/processing_instruction.zig").testExecFn;
|
|
||||||
const CommentTestExecFn = @import("dom/comment.zig").testExecFn;
|
|
||||||
const DocumentFragmentTestExecFn = @import("dom/document_fragment.zig").testExecFn;
|
|
||||||
const EventTestExecFn = @import("events/event.zig").testExecFn;
|
|
||||||
const XHRTestExecFn = xhr.testExecFn;
|
|
||||||
const ProgressEventTestExecFn = @import("xhr/progress_event.zig").testExecFn;
|
|
||||||
const StorageTestExecFn = storage.testExecFn;
|
|
||||||
const URLTestExecFn = @import("url/url.zig").testExecFn;
|
|
||||||
const HTMLElementTestExecFn = @import("html/elements.zig").testExecFn;
|
|
||||||
const MutationObserverTestExecFn = @import("dom/mutation_observer.zig").testExecFn;
|
|
||||||
|
|
||||||
pub const Types = jsruntime.reflect(apiweb.Interfaces);
|
|
||||||
pub const UserContext = @import("user_context.zig").UserContext;
|
|
||||||
pub const IO = @import("asyncio").Wrapper(jsruntime.Loop);
|
|
||||||
|
|
||||||
var doc: *parser.DocumentHTML = undefined;
|
|
||||||
|
|
||||||
fn testExecFn(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
comptime execFn: jsruntime.ContextExecFn,
|
|
||||||
) anyerror!void {
|
|
||||||
try parser.init();
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
// start JS env
|
|
||||||
try js_env.start();
|
|
||||||
defer js_env.stop();
|
|
||||||
|
|
||||||
var storageShelf = storage.Shelf.init(alloc);
|
|
||||||
defer storageShelf.deinit();
|
|
||||||
|
|
||||||
// document
|
|
||||||
const file = try std.fs.cwd().openFile("test.html", .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
doc = try parser.documentHTMLParse(file.reader(), "UTF-8");
|
|
||||||
defer parser.documentHTMLClose(doc) catch |err| {
|
|
||||||
std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)});
|
|
||||||
};
|
|
||||||
|
|
||||||
var http_client = try @import("http/client.zig").Client.init(alloc, 5, .{});
|
|
||||||
defer http_client.deinit();
|
|
||||||
|
|
||||||
// alias global as self and window
|
|
||||||
var window = Window.create(null, null);
|
|
||||||
|
|
||||||
const url = try URL.parse("https://lightpanda.io/opensource-browser/", null);
|
|
||||||
|
|
||||||
var cookie_jar = storage.CookieJar.init(alloc);
|
|
||||||
defer cookie_jar.deinit();
|
|
||||||
|
|
||||||
var renderer = browser.Renderer.init(alloc);
|
|
||||||
defer renderer.elements.deinit(alloc);
|
|
||||||
defer renderer.positions.deinit(alloc);
|
|
||||||
|
|
||||||
try js_env.setUserContext(.{
|
|
||||||
.url = &url,
|
|
||||||
.document = doc,
|
|
||||||
.renderer = &renderer,
|
|
||||||
.cookie_jar = &cookie_jar,
|
|
||||||
.http_client = &http_client,
|
|
||||||
});
|
|
||||||
|
|
||||||
try window.replaceLocation(.{ .url = try url.toWebApi(alloc) });
|
|
||||||
|
|
||||||
try window.replaceDocument(doc);
|
|
||||||
window.setStorageShelf(&storageShelf);
|
|
||||||
|
|
||||||
try js_env.bindGlobal(window);
|
|
||||||
|
|
||||||
// run test
|
|
||||||
try execFn(alloc, js_env);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testsAllExecFn(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
const testFns = [_]jsruntime.ContextExecFn{
|
|
||||||
documentTestExecFn,
|
|
||||||
HTMLDocumentTestExecFn,
|
|
||||||
nodeTestExecFn,
|
|
||||||
characterDataTestExecFn,
|
|
||||||
textTestExecFn,
|
|
||||||
elementTestExecFn,
|
|
||||||
HTMLCollectionTestExecFn,
|
|
||||||
DOMExceptionTestExecFn,
|
|
||||||
DOMImplementationExecFn,
|
|
||||||
NamedNodeMapExecFn,
|
|
||||||
DOMTokenListExecFn,
|
|
||||||
NodeListTestExecFn,
|
|
||||||
AttrTestExecFn,
|
|
||||||
CommentTestExecFn,
|
|
||||||
DocumentFragmentTestExecFn,
|
|
||||||
EventTargetTestExecFn,
|
|
||||||
EventTestExecFn,
|
|
||||||
XHRTestExecFn,
|
|
||||||
ProgressEventTestExecFn,
|
|
||||||
ProcessingInstructionTestExecFn,
|
|
||||||
StorageTestExecFn,
|
|
||||||
URLTestExecFn,
|
|
||||||
HTMLElementTestExecFn,
|
|
||||||
MutationObserverTestExecFn,
|
|
||||||
@import("polyfill/fetch.zig").testExecFn,
|
|
||||||
@import("html/navigator.zig").testExecFn,
|
|
||||||
@import("html/history.zig").testExecFn,
|
|
||||||
@import("html/location.zig").testExecFn,
|
|
||||||
@import("xmlserializer/xmlserializer.zig").testExecFn,
|
|
||||||
};
|
|
||||||
|
|
||||||
inline for (testFns) |testFn| {
|
|
||||||
try testExecFn(alloc, js_env, testFn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const usage =
|
|
||||||
\\usage: test [options]
|
|
||||||
\\ Run the tests. By default the command will run both js and unit tests.
|
|
||||||
\\
|
|
||||||
\\ -h, --help Print this help message and exit.
|
|
||||||
\\ --browser run only browser js tests
|
|
||||||
\\ --unit run only js unit tests
|
|
||||||
\\ --json bench result is formatted in JSON.
|
|
||||||
\\ only browser tests are benchmarked.
|
|
||||||
\\
|
|
||||||
;
|
|
||||||
|
|
||||||
// Out list all the ouputs handled by benchmark result and written on stdout.
|
|
||||||
const Out = enum {
|
|
||||||
text,
|
|
||||||
json,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Which tests must be run.
|
|
||||||
const Run = enum {
|
|
||||||
all,
|
|
||||||
browser,
|
|
||||||
unit,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
||||||
defer _ = gpa.deinit();
|
|
||||||
const gpa_alloc = gpa.allocator();
|
|
||||||
|
|
||||||
var args = try std.process.argsWithAllocator(gpa_alloc);
|
|
||||||
defer args.deinit();
|
|
||||||
|
|
||||||
// ignore the exec name.
|
|
||||||
_ = args.next().?;
|
|
||||||
|
|
||||||
var out: Out = .text;
|
|
||||||
var run: Run = .all;
|
|
||||||
|
|
||||||
while (args.next()) |arg| {
|
|
||||||
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
|
|
||||||
try std.io.getStdErr().writer().print(usage, .{});
|
|
||||||
std.posix.exit(0);
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--json", arg)) {
|
|
||||||
out = .json;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--browser", arg)) {
|
|
||||||
run = .browser;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--unit", arg)) {
|
|
||||||
run = .unit;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run js tests
|
|
||||||
if (run == .all or run == .browser) try run_js(out);
|
|
||||||
|
|
||||||
// run standard unit tests.
|
|
||||||
if (run == .all or run == .unit) {
|
|
||||||
std.debug.print("\n", .{});
|
|
||||||
for (builtin.test_functions) |test_fn| {
|
|
||||||
if (std.mem.startsWith(u8, test_fn.name, "http.client.test")) {
|
|
||||||
// covered by unit test, needs a dummy server started, which
|
|
||||||
// main_test doesn't do.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try parser.init();
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
std.testing.allocator_instance = .{};
|
|
||||||
try test_fn.func();
|
|
||||||
|
|
||||||
if (std.testing.allocator_instance.deinit() == .leak) {
|
|
||||||
std.debug.print("======Memory Leak: {s}======\n", .{test_fn.name});
|
|
||||||
} else {
|
|
||||||
std.debug.print("{s}\tOK\n", .{test_fn.name});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run js test and display the output depending of the output parameter.
|
|
||||||
fn run_js(out: Out) !void {
|
|
||||||
var bench_alloc = jsruntime.bench_allocator(std.testing.allocator);
|
|
||||||
|
|
||||||
const start = try std.time.Instant.now();
|
|
||||||
|
|
||||||
// run js exectuion tests
|
|
||||||
try testJSRuntime(bench_alloc.allocator());
|
|
||||||
|
|
||||||
const duration = std.time.Instant.since(try std.time.Instant.now(), start);
|
|
||||||
const stats = bench_alloc.stats();
|
|
||||||
|
|
||||||
// get and display the results
|
|
||||||
if (out == .json) {
|
|
||||||
const res = [_]struct {
|
|
||||||
name: []const u8,
|
|
||||||
bench: struct {
|
|
||||||
duration: u64,
|
|
||||||
|
|
||||||
alloc_nb: usize,
|
|
||||||
realloc_nb: usize,
|
|
||||||
alloc_size: usize,
|
|
||||||
},
|
|
||||||
}{
|
|
||||||
.{ .name = "browser", .bench = .{
|
|
||||||
.duration = duration,
|
|
||||||
.alloc_nb = stats.alloc_nb,
|
|
||||||
.realloc_nb = stats.realloc_nb,
|
|
||||||
.alloc_size = stats.alloc_size,
|
|
||||||
} },
|
|
||||||
// TODO get libdom bench info.
|
|
||||||
.{ .name = "libdom", .bench = .{
|
|
||||||
.duration = duration,
|
|
||||||
.alloc_nb = 0,
|
|
||||||
.realloc_nb = 0,
|
|
||||||
.alloc_size = 0,
|
|
||||||
} },
|
|
||||||
// TODO get v8 bench info.
|
|
||||||
.{ .name = "v8", .bench = .{
|
|
||||||
.duration = duration,
|
|
||||||
.alloc_nb = 0,
|
|
||||||
.realloc_nb = 0,
|
|
||||||
.alloc_size = 0,
|
|
||||||
} },
|
|
||||||
// TODO get main bench info.
|
|
||||||
.{ .name = "main", .bench = .{
|
|
||||||
.duration = duration,
|
|
||||||
.alloc_nb = 0,
|
|
||||||
.realloc_nb = 0,
|
|
||||||
.alloc_size = 0,
|
|
||||||
} },
|
|
||||||
};
|
|
||||||
|
|
||||||
try std.json.stringify(res, .{ .whitespace = .indent_2 }, std.io.getStdOut().writer());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// display console result by default
|
|
||||||
const dur = pretty.Measure{ .unit = "ms", .value = duration / ms };
|
|
||||||
const size = pretty.Measure{ .unit = "kb", .value = stats.alloc_size / kb };
|
|
||||||
|
|
||||||
const zerosize = pretty.Measure{ .unit = "kb", .value = 0 };
|
|
||||||
|
|
||||||
// benchmark table
|
|
||||||
const row_shape = .{ []const u8, pretty.Measure, u64, u64, pretty.Measure };
|
|
||||||
const table = try pretty.GenerateTable(4, row_shape, pretty.TableConf{ .margin_left = " " });
|
|
||||||
const header = .{ "FUNCTION", "DURATION", "ALLOCATIONS (nb)", "RE-ALLOCATIONS (nb)", "HEAP SIZE" };
|
|
||||||
var t = table.init("Benchmark lightpanda 🚀", header);
|
|
||||||
try t.addRow(.{ "browser", dur, stats.alloc_nb, stats.realloc_nb, size });
|
|
||||||
try t.addRow(.{ "libdom", dur, 0, 0, zerosize }); // TODO get libdom bench info.
|
|
||||||
try t.addRow(.{ "v8", dur, 0, 0, zerosize }); // TODO get v8 bench info.
|
|
||||||
try t.addRow(.{ "main", dur, 0, 0, zerosize }); // TODO get main bench info.
|
|
||||||
try t.render(std.io.getStdOut().writer());
|
|
||||||
}
|
|
||||||
|
|
||||||
const kb = 1024;
|
|
||||||
const ms = std.time.ns_per_ms;
|
|
||||||
|
|
||||||
test {
|
|
||||||
const dumpTest = @import("browser/dump.zig");
|
|
||||||
std.testing.refAllDecls(dumpTest);
|
|
||||||
|
|
||||||
const mimeTest = @import("browser/mime.zig");
|
|
||||||
std.testing.refAllDecls(mimeTest);
|
|
||||||
|
|
||||||
const cssTest = @import("css/css.zig");
|
|
||||||
std.testing.refAllDecls(cssTest);
|
|
||||||
|
|
||||||
const cssParserTest = @import("css/parser.zig");
|
|
||||||
std.testing.refAllDecls(cssParserTest);
|
|
||||||
|
|
||||||
const cssMatchTest = @import("css/match_test.zig");
|
|
||||||
std.testing.refAllDecls(cssMatchTest);
|
|
||||||
|
|
||||||
const cssLibdomTest = @import("css/libdom_test.zig");
|
|
||||||
std.testing.refAllDecls(cssLibdomTest);
|
|
||||||
|
|
||||||
const queryTest = @import("url/query.zig");
|
|
||||||
std.testing.refAllDecls(queryTest);
|
|
||||||
|
|
||||||
std.testing.refAllDecls(@import("generate.zig"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testJSRuntime(alloc: std.mem.Allocator) !void {
|
|
||||||
// create JS vm
|
|
||||||
const vm = jsruntime.VM.init();
|
|
||||||
defer vm.deinit();
|
|
||||||
|
|
||||||
var arena_alloc = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
defer arena_alloc.deinit();
|
|
||||||
|
|
||||||
try jsruntime.loadEnv(&arena_alloc, null, testsAllExecFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "DocumentHTMLParseFromStr" {
|
|
||||||
const file = try std.fs.cwd().openFile("test.html", .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
const str = try file.readToEndAlloc(std.testing.allocator, std.math.maxInt(u32));
|
|
||||||
defer std.testing.allocator.free(str);
|
|
||||||
|
|
||||||
doc = try parser.documentHTMLParseFromStr(str);
|
|
||||||
parser.documentHTMLClose(doc) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/lightpanda-io/libdom/issues/4
|
|
||||||
test "bug document html parsing #4" {
|
|
||||||
const file = try std.fs.cwd().openFile("tests/html/bug-html-parsing-4.html", .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
doc = try parser.documentHTMLParse(file.reader(), "UTF-8");
|
|
||||||
parser.documentHTMLClose(doc) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Window is a libdom event target" {
|
|
||||||
var window = Window.create(null, null);
|
|
||||||
|
|
||||||
const event = try parser.eventCreate();
|
|
||||||
try parser.eventInit(event, "foo", .{});
|
|
||||||
|
|
||||||
const et = parser.toEventTarget(Window, &window);
|
|
||||||
_ = try parser.eventTargetDispatchEvent(et, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "DocumentHTML is a libdom event target" {
|
|
||||||
doc = try parser.documentHTMLParseFromStr("<body></body>");
|
|
||||||
parser.documentHTMLClose(doc) catch {};
|
|
||||||
|
|
||||||
const event = try parser.eventCreate();
|
|
||||||
try parser.eventInit(event, "foo", .{});
|
|
||||||
|
|
||||||
const et = parser.toEventTarget(parser.DocumentHTML, doc);
|
|
||||||
_ = try parser.eventTargetDispatchEvent(et, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "XMLHttpRequest.validMethod" {
|
|
||||||
// valid methods
|
|
||||||
for ([_][]const u8{ "get", "GET", "head", "HEAD" }) |tc| {
|
|
||||||
_ = try xhr.XMLHttpRequest.validMethod(tc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// forbidden
|
|
||||||
for ([_][]const u8{ "connect", "CONNECT" }) |tc| {
|
|
||||||
try std.testing.expectError(parser.DOMError.Security, xhr.XMLHttpRequest.validMethod(tc));
|
|
||||||
}
|
|
||||||
|
|
||||||
// syntax
|
|
||||||
for ([_][]const u8{ "foo", "BAR" }) |tc| {
|
|
||||||
try std.testing.expectError(parser.DOMError.Syntax, xhr.XMLHttpRequest.validMethod(tc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +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 tls = @import("tls");
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
test {
|
|
||||||
std.testing.refAllDecls(@import("url/query.zig"));
|
|
||||||
std.testing.refAllDecls(@import("browser/dump.zig"));
|
|
||||||
std.testing.refAllDecls(@import("browser/mime.zig"));
|
|
||||||
std.testing.refAllDecls(@import("css/css.zig"));
|
|
||||||
std.testing.refAllDecls(@import("css/libdom_test.zig"));
|
|
||||||
std.testing.refAllDecls(@import("css/match_test.zig"));
|
|
||||||
std.testing.refAllDecls(@import("css/parser.zig"));
|
|
||||||
std.testing.refAllDecls(@import("generate.zig"));
|
|
||||||
std.testing.refAllDecls(@import("storage/storage.zig"));
|
|
||||||
std.testing.refAllDecls(@import("storage/cookie.zig"));
|
|
||||||
std.testing.refAllDecls(@import("iterator/iterator.zig"));
|
|
||||||
std.testing.refAllDecls(@import("server.zig"));
|
|
||||||
std.testing.refAllDecls(@import("cdp/cdp.zig"));
|
|
||||||
std.testing.refAllDecls(@import("log.zig"));
|
|
||||||
std.testing.refAllDecls(@import("datetime.zig"));
|
|
||||||
std.testing.refAllDecls(@import("telemetry/telemetry.zig"));
|
|
||||||
std.testing.refAllDecls(@import("http/client.zig"));
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg: std.Thread.WaitGroup = .{};
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
||||||
test "tests:beforeAll" {
|
|
||||||
try parser.init();
|
|
||||||
wg.startMany(3);
|
|
||||||
|
|
||||||
{
|
|
||||||
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
|
|
||||||
const thread = try std.Thread.spawn(.{}, serveHTTP, .{address});
|
|
||||||
thread.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const address = try std.net.Address.parseIp("127.0.0.1", 9581);
|
|
||||||
const thread = try std.Thread.spawn(.{}, serveHTTPS, .{address});
|
|
||||||
thread.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const address = try std.net.Address.parseIp("127.0.0.1", 9583);
|
|
||||||
const thread = try std.Thread.spawn(.{}, serveCDP, .{address});
|
|
||||||
thread.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to wait for the servers to be listening, else tests will fail because
|
|
||||||
// they aren't able to connect.
|
|
||||||
wg.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
test "tests:afterAll" {
|
|
||||||
parser.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serveHTTP(address: std.net.Address) !void {
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
var listener = try address.listen(.{ .reuse_address = true });
|
|
||||||
defer listener.deinit();
|
|
||||||
|
|
||||||
wg.finish();
|
|
||||||
|
|
||||||
var read_buffer: [1024]u8 = undefined;
|
|
||||||
ACCEPT: while (true) {
|
|
||||||
defer _ = arena.reset(.{ .retain_with_limit = 1024 });
|
|
||||||
const aa = arena.allocator();
|
|
||||||
|
|
||||||
var conn = try listener.accept();
|
|
||||||
defer conn.stream.close();
|
|
||||||
var server = std.http.Server.init(conn, &read_buffer);
|
|
||||||
|
|
||||||
while (server.state == .ready) {
|
|
||||||
var request = server.receiveHead() catch |err| switch (err) {
|
|
||||||
error.HttpConnectionClosing => continue :ACCEPT,
|
|
||||||
else => {
|
|
||||||
std.debug.print("Test HTTP Server error: {}\n", .{err});
|
|
||||||
return err;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const path = request.head.target;
|
|
||||||
if (std.mem.eql(u8, path, "/loader")) {
|
|
||||||
try request.respond("Hello!", .{});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/simple")) {
|
|
||||||
try request.respond("", .{});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/redirect")) {
|
|
||||||
try request.respond("", .{
|
|
||||||
.status = .moved_permanently,
|
|
||||||
.extra_headers = &.{.{ .name = "LOCATION", .value = "../http_client/echo" }},
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/redirect/secure")) {
|
|
||||||
try request.respond("", .{
|
|
||||||
.status = .moved_permanently,
|
|
||||||
.extra_headers = &.{.{ .name = "LOCATION", .value = "https://127.0.0.1:9581/http_client/body" }},
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/echo")) {
|
|
||||||
var headers: std.ArrayListUnmanaged(std.http.Header) = .{};
|
|
||||||
|
|
||||||
var it = request.iterateHeaders();
|
|
||||||
while (it.next()) |hdr| {
|
|
||||||
try headers.append(aa, .{
|
|
||||||
.name = try std.fmt.allocPrint(aa, "_{s}", .{hdr.name}),
|
|
||||||
.value = hdr.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try request.respond("over 9000!", .{
|
|
||||||
.status = .created,
|
|
||||||
.extra_headers = headers.items,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a lot of work for testing TLS, but the TLS (async) code is complicated
|
|
||||||
// This "server" is written specifically to test the client. It assumes the client
|
|
||||||
// isn't a jerk.
|
|
||||||
fn serveHTTPS(address: std.net.Address) !void {
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
var listener = try address.listen(.{ .reuse_address = true });
|
|
||||||
defer listener.deinit();
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
|
|
||||||
wg.finish();
|
|
||||||
|
|
||||||
var seed: u64 = undefined;
|
|
||||||
std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
|
|
||||||
var r = std.Random.DefaultPrng.init(seed);
|
|
||||||
const rand = r.random();
|
|
||||||
|
|
||||||
var read_buffer: [1024]u8 = undefined;
|
|
||||||
while (true) {
|
|
||||||
// defer _ = arena.reset(.{ .retain_with_limit = 1024 });
|
|
||||||
// const aa = arena.allocator();
|
|
||||||
|
|
||||||
const stream = blk: {
|
|
||||||
const conn = try listener.accept();
|
|
||||||
break :blk conn.stream;
|
|
||||||
};
|
|
||||||
defer stream.close();
|
|
||||||
|
|
||||||
var conn = try tls.server(stream, .{ .auth = null });
|
|
||||||
defer conn.close() catch {};
|
|
||||||
|
|
||||||
var pos: usize = 0;
|
|
||||||
while (true) {
|
|
||||||
const n = try conn.read(read_buffer[pos..]);
|
|
||||||
if (n == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos += n;
|
|
||||||
const header_end = std.mem.indexOf(u8, read_buffer[0..pos], "\r\n\r\n") orelse {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
var it = std.mem.splitScalar(u8, read_buffer[0..header_end], ' ');
|
|
||||||
_ = it.next() orelse unreachable; // method
|
|
||||||
const path = it.next() orelse unreachable;
|
|
||||||
|
|
||||||
var response: []const u8 = undefined;
|
|
||||||
if (std.mem.eql(u8, path, "/http_client/simple")) {
|
|
||||||
response = "HTTP/1.1 200 \r\nContent-Length: 0\r\n\r\n";
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/body")) {
|
|
||||||
response = "HTTP/1.1 201 CREATED\r\nContent-Length: 20\r\n Another : HEaDer \r\n\r\n1234567890abcdefhijk";
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/redirect/insecure")) {
|
|
||||||
response = "HTTP/1.1 307 GOTO\r\nLocation: http://127.0.0.1:9582/http_client/redirect\r\n\r\n";
|
|
||||||
} else {
|
|
||||||
// should not have an unknown path
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
var unsent = response;
|
|
||||||
while (unsent.len > 0) {
|
|
||||||
const to_send = rand.intRangeAtMost(usize, 1, unsent.len);
|
|
||||||
const sent = try conn.write(unsent[0..to_send]);
|
|
||||||
unsent = unsent[sent..];
|
|
||||||
std.time.sleep(std.time.ns_per_us * 5);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serveCDP(address: std.net.Address) !void {
|
|
||||||
const App = @import("app.zig").App;
|
|
||||||
var app = try App.init(gpa.allocator(), .{ .run_mode = .serve });
|
|
||||||
defer app.deinit();
|
|
||||||
|
|
||||||
const server = @import("server.zig");
|
|
||||||
wg.finish();
|
|
||||||
server.run(app, address, std.time.ns_per_s * 2) catch |err| {
|
|
||||||
std.debug.print("CDP server error: {}", .{err});
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -18,14 +18,10 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
|
|
||||||
const Suite = @import("wpt/testcase.zig").Suite;
|
|
||||||
const FileLoader = @import("wpt/fileloader.zig").FileLoader;
|
|
||||||
const wpt = @import("wpt/run.zig");
|
const wpt = @import("wpt/run.zig");
|
||||||
|
const Suite = @import("wpt/testcase.zig").Suite;
|
||||||
const apiweb = @import("apiweb.zig");
|
const Platform = @import("runtime/js.zig").Platform;
|
||||||
const HTMLElem = @import("html/elements.zig");
|
const FileLoader = @import("wpt/fileloader.zig").FileLoader;
|
||||||
|
|
||||||
const wpt_dir = "tests/wpt";
|
const wpt_dir = "tests/wpt";
|
||||||
|
|
||||||
@@ -47,10 +43,10 @@ const Out = enum {
|
|||||||
text,
|
text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Types = jsruntime.reflect(apiweb.Interfaces);
|
pub const std_options = std.Options{
|
||||||
pub const GlobalType = apiweb.GlobalType;
|
// Set the log level to info
|
||||||
pub const UserContext = apiweb.UserContext;
|
.log_level = .info,
|
||||||
pub const IO = @import("asyncio").Wrapper(jsruntime.Loop);
|
};
|
||||||
|
|
||||||
// TODO For now the WPT tests run is specific to WPT.
|
// TODO For now the WPT tests run is specific to WPT.
|
||||||
// It manually load js framwork libs, and run the first script w/ js content in
|
// It manually load js framwork libs, and run the first script w/ js content in
|
||||||
@@ -122,8 +118,8 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initialize VM JS lib.
|
// initialize VM JS lib.
|
||||||
const vm = jsruntime.VM.init();
|
const platform = Platform.init();
|
||||||
defer vm.deinit();
|
defer platform.deinit();
|
||||||
|
|
||||||
// prepare libraries to load on each test case.
|
// prepare libraries to load on each test case.
|
||||||
var loader = FileLoader.init(alloc, wpt_dir);
|
var loader = FileLoader.init(alloc, wpt_dir);
|
||||||
@@ -142,8 +138,9 @@ pub fn main() !void {
|
|||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
|
|
||||||
const res = wpt.run(&arena, wpt_dir, tc, &loader) catch |err| {
|
var msg_out: ?[]const u8 = null;
|
||||||
const suite = try Suite.init(alloc, tc, false, @errorName(err));
|
const res = wpt.run(arena.allocator(), wpt_dir, tc, &loader, &msg_out) catch |err| {
|
||||||
|
const suite = try Suite.init(alloc, tc, false, if (msg_out) |msg| msg else @errorName(err));
|
||||||
try results.append(suite);
|
try results.append(suite);
|
||||||
|
|
||||||
if (out == .text) {
|
if (out == .text) {
|
||||||
@@ -152,9 +149,8 @@ pub fn main() !void {
|
|||||||
failures += 1;
|
failures += 1;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
defer res.deinit(arena.allocator());
|
|
||||||
|
|
||||||
const suite = try Suite.init(alloc, tc, res.ok, res.msg orelse "");
|
const suite = try Suite.init(alloc, tc, true, res);
|
||||||
try results.append(suite);
|
try results.append(suite);
|
||||||
|
|
||||||
if (out == .json) {
|
if (out == .json) {
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Case = jsruntime.test_utils.Case;
|
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
|
||||||
|
|
||||||
// fetch.js code comes from
|
|
||||||
// https://github.com/JakeChampion/fetch/blob/main/fetch.js
|
|
||||||
//
|
|
||||||
// The original code source is available in MIT license.
|
|
||||||
//
|
|
||||||
// The script comes from the built version from npm.
|
|
||||||
// You can get the package with the command:
|
|
||||||
//
|
|
||||||
// wget $(npm view whatwg-fetch dist.tarball)
|
|
||||||
//
|
|
||||||
// The source is the content of `package/dist/fetch.umd.js` file.
|
|
||||||
pub const source = @embedFile("fetch.js");
|
|
||||||
|
|
||||||
pub fn testExecFn(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
js_env: *jsruntime.Env,
|
|
||||||
) anyerror!void {
|
|
||||||
try @import("polyfill.zig").load(alloc, js_env);
|
|
||||||
|
|
||||||
var fetch = [_]Case{
|
|
||||||
.{
|
|
||||||
.src =
|
|
||||||
\\var ok = false;
|
|
||||||
\\const request = new Request("https://httpbin.io/json");
|
|
||||||
\\fetch(request)
|
|
||||||
\\ .then((response) => { ok = response.ok; });
|
|
||||||
\\false;
|
|
||||||
,
|
|
||||||
.ex = "false",
|
|
||||||
},
|
|
||||||
// all events have been resolved.
|
|
||||||
.{ .src = "ok", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &fetch);
|
|
||||||
|
|
||||||
var fetch2 = [_]Case{
|
|
||||||
.{
|
|
||||||
.src =
|
|
||||||
\\var ok2 = false;
|
|
||||||
\\const request2 = new Request("https://httpbin.io/json");
|
|
||||||
\\(async function () { resp = await fetch(request2); ok2 = resp.ok; }());
|
|
||||||
\\false;
|
|
||||||
,
|
|
||||||
.ex = "false",
|
|
||||||
},
|
|
||||||
// all events have been resolved.
|
|
||||||
.{ .src = "ok2", .ex = "true" },
|
|
||||||
};
|
|
||||||
try checkCases(js_env, &fetch2);
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,7 @@ const Type = std.builtin.Type;
|
|||||||
// -----
|
// -----
|
||||||
|
|
||||||
// Generate a flatten tagged Union from a Tuple
|
// Generate a flatten tagged Union from a Tuple
|
||||||
pub fn Union(interfaces: anytype) type {
|
pub fn Union(comptime interfaces: anytype) type {
|
||||||
// @setEvalBranchQuota(10000);
|
// @setEvalBranchQuota(10000);
|
||||||
const tuple = Tuple(interfaces){};
|
const tuple = Tuple(interfaces){};
|
||||||
const fields = std.meta.fields(@TypeOf(tuple));
|
const fields = std.meta.fields(@TypeOf(tuple));
|
||||||
@@ -93,7 +93,7 @@ pub fn Union(interfaces: anytype) type {
|
|||||||
// Flattens and depuplicates a list of nested tuples. For example
|
// Flattens and depuplicates a list of nested tuples. For example
|
||||||
// input: {A, B, {C, B, D}, {A, E}}
|
// input: {A, B, {C, B, D}, {A, E}}
|
||||||
// output {A, B, C, D, E}
|
// output {A, B, C, D, E}
|
||||||
pub fn Tuple(args: anytype) type {
|
pub fn Tuple(comptime args: anytype) type {
|
||||||
@setEvalBranchQuota(100000);
|
@setEvalBranchQuota(100000);
|
||||||
|
|
||||||
const count = countInterfaces(args, 0);
|
const count = countInterfaces(args, 0);
|
||||||
@@ -188,7 +188,7 @@ test "generate.Union" {
|
|||||||
const value = Union(.{ Astruct, Bstruct, .{Cstruct} });
|
const value = Union(.{ Astruct, Bstruct, .{Cstruct} });
|
||||||
const ti = @typeInfo(value).@"union";
|
const ti = @typeInfo(value).@"union";
|
||||||
try std.testing.expectEqual(3, ti.fields.len);
|
try std.testing.expectEqual(3, ti.fields.len);
|
||||||
try std.testing.expectEqualStrings("*generate.test.generate.Union.Astruct.Other", @typeName(ti.fields[0].type));
|
try std.testing.expectEqualStrings("*runtime.generate.test.generate.Union.Astruct.Other", @typeName(ti.fields[0].type));
|
||||||
try std.testing.expectEqualStrings(ti.fields[0].name, "Astruct");
|
try std.testing.expectEqualStrings(ti.fields[0].name, "Astruct");
|
||||||
try std.testing.expectEqual(Bstruct, ti.fields[1].type);
|
try std.testing.expectEqual(Bstruct, ti.fields[1].type);
|
||||||
try std.testing.expectEqualStrings(ti.fields[1].name, "Bstruct");
|
try std.testing.expectEqualStrings(ti.fields[1].name, "Bstruct");
|
||||||
2303
src/runtime/js.zig
Normal file
2303
src/runtime/js.zig
Normal file
File diff suppressed because it is too large
Load Diff
469
src/runtime/loop.zig
Normal file
469
src/runtime/loop.zig
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
// 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 MemoryPool = std.heap.MemoryPool;
|
||||||
|
|
||||||
|
pub const IO = @import("tigerbeetle-io").IO;
|
||||||
|
|
||||||
|
const JSCallback = @import("../browser/env.zig").Env.Callback;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.loop);
|
||||||
|
|
||||||
|
// SingleThreaded I/O Loop based on Tigerbeetle io_uring loop.
|
||||||
|
// On Linux it's using io_uring.
|
||||||
|
// On MacOS and Windows it's using kqueue/IOCP with a ring design.
|
||||||
|
// This is a thread-unsafe version without any lock on shared resources,
|
||||||
|
// use it only on a single thread.
|
||||||
|
// The loop provides I/O APIs based on callbacks.
|
||||||
|
// I/O APIs based on async/await might be added in the future.
|
||||||
|
pub const Loop = struct {
|
||||||
|
alloc: std.mem.Allocator, // TODO: unmanaged version ?
|
||||||
|
io: IO,
|
||||||
|
|
||||||
|
// both events_nb are used to track how many callbacks are to be called.
|
||||||
|
// We use these counters to wait until all the events are finished.
|
||||||
|
js_events_nb: usize,
|
||||||
|
zig_events_nb: usize,
|
||||||
|
|
||||||
|
cbk_error: bool = false,
|
||||||
|
|
||||||
|
// js_ctx_id is incremented each time the loop is reset for JS.
|
||||||
|
// All JS callbacks store an initial js_ctx_id and compare before execution.
|
||||||
|
// If a ctx is outdated, the callback is ignored.
|
||||||
|
// This is a weak way to cancel all future JS callbacks.
|
||||||
|
js_ctx_id: u32 = 0,
|
||||||
|
|
||||||
|
// zig_ctx_id is incremented each time the loop is reset for Zig.
|
||||||
|
// All Zig callbacks store an initial zig_ctx_id and compare before execution.
|
||||||
|
// If a ctx is outdated, the callback is ignored.
|
||||||
|
// This is a weak way to cancel all future Zig callbacks.
|
||||||
|
zig_ctx_id: u32 = 0,
|
||||||
|
|
||||||
|
// The MacOS event loop doesn't support cancellation. We use this to track
|
||||||
|
// cancellation ids and, on the timeout callback, we can can check here
|
||||||
|
// to see if it's been cancelled.
|
||||||
|
cancelled: std.AutoHashMapUnmanaged(usize, void),
|
||||||
|
|
||||||
|
cancel_pool: MemoryPool(ContextCancel),
|
||||||
|
timeout_pool: MemoryPool(ContextTimeout),
|
||||||
|
event_callback_pool: MemoryPool(EventCallbackContext),
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub const Completion = IO.Completion;
|
||||||
|
|
||||||
|
pub const ConnectError = IO.ConnectError;
|
||||||
|
pub const RecvError = IO.RecvError;
|
||||||
|
pub const SendError = IO.SendError;
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !Self {
|
||||||
|
return Self{
|
||||||
|
.alloc = alloc,
|
||||||
|
.cancelled = .{},
|
||||||
|
.io = try IO.init(32, 0),
|
||||||
|
.js_events_nb = 0,
|
||||||
|
.zig_events_nb = 0,
|
||||||
|
.cancel_pool = MemoryPool(ContextCancel).init(alloc),
|
||||||
|
.timeout_pool = MemoryPool(ContextTimeout).init(alloc),
|
||||||
|
.event_callback_pool = MemoryPool(EventCallbackContext).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
// first disable callbacks for existing events.
|
||||||
|
// We don't want a callback re-create a setTimeout, it could create an
|
||||||
|
// infinite loop on wait for events.
|
||||||
|
self.resetJS();
|
||||||
|
self.resetZig();
|
||||||
|
|
||||||
|
// run tail events. We do run the tail events to ensure all the
|
||||||
|
// contexts are correcly free.
|
||||||
|
while (self.eventsNb(.js) > 0 or self.eventsNb(.zig) > 0) {
|
||||||
|
self.io.run_for_ns(10 * std.time.ns_per_ms) catch |err| {
|
||||||
|
log.err("deinit run tail events: {any}", .{err});
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (comptime CANCEL_SUPPORTED) {
|
||||||
|
self.io.cancel_all();
|
||||||
|
}
|
||||||
|
self.io.deinit();
|
||||||
|
self.cancel_pool.deinit();
|
||||||
|
self.timeout_pool.deinit();
|
||||||
|
self.event_callback_pool.deinit();
|
||||||
|
self.cancelled.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all registred I/O events completed by OS kernel,
|
||||||
|
// and execute sequentially their callbacks.
|
||||||
|
// Stops when there is no more I/O events registered on the loop.
|
||||||
|
// Note that I/O events callbacks might register more I/O events
|
||||||
|
// on the go when they are executed (ie. nested I/O events).
|
||||||
|
pub fn run(self: *Self) !void {
|
||||||
|
while (self.eventsNb(.js) > 0) {
|
||||||
|
try self.io.run_for_ns(10 * std.time.ns_per_ms);
|
||||||
|
// at each iteration we might have new events registred by previous callbacks
|
||||||
|
}
|
||||||
|
// TODO: return instead immediatly on the first JS callback error
|
||||||
|
// and let the caller decide what to do next
|
||||||
|
// (typically retrieve the exception through the TryCatch and
|
||||||
|
// continue the execution of callbacks with a new call to loop.run)
|
||||||
|
if (self.cbk_error) {
|
||||||
|
return error.JSExecCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Event = enum { js, zig };
|
||||||
|
|
||||||
|
fn eventsPtr(self: *Self, comptime event: Event) *usize {
|
||||||
|
return switch (event) {
|
||||||
|
.zig => &self.zig_events_nb,
|
||||||
|
.js => &self.js_events_nb,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register events atomically
|
||||||
|
// - add 1 event and return previous value
|
||||||
|
fn addEvent(self: *Self, comptime event: Event) void {
|
||||||
|
_ = @atomicRmw(usize, self.eventsPtr(event), .Add, 1, .acq_rel);
|
||||||
|
}
|
||||||
|
// - remove 1 event and return previous value
|
||||||
|
fn removeEvent(self: *Self, comptime event: Event) void {
|
||||||
|
_ = @atomicRmw(usize, self.eventsPtr(event), .Sub, 1, .acq_rel);
|
||||||
|
}
|
||||||
|
// - get the number of current events
|
||||||
|
fn eventsNb(self: *Self, comptime event: Event) usize {
|
||||||
|
return @atomicLoad(usize, self.eventsPtr(event), .seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// JS callbacks APIs
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
|
||||||
|
const ContextTimeout = struct {
|
||||||
|
loop: *Self,
|
||||||
|
js_cbk: ?JSCallback,
|
||||||
|
js_ctx_id: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn timeoutCallback(
|
||||||
|
ctx: *ContextTimeout,
|
||||||
|
completion: *IO.Completion,
|
||||||
|
result: IO.TimeoutError!void,
|
||||||
|
) void {
|
||||||
|
const loop = ctx.loop;
|
||||||
|
defer {
|
||||||
|
loop.removeEvent(.js);
|
||||||
|
loop.timeout_pool.destroy(ctx);
|
||||||
|
loop.alloc.destroy(completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime CANCEL_SUPPORTED == false) {
|
||||||
|
if (loop.cancelled.remove(@intFromPtr(completion))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the loop's context id has changed, don't call the js callback
|
||||||
|
// function. The callback's memory has already be cleaned and the
|
||||||
|
// events nb reset.
|
||||||
|
if (ctx.js_ctx_id != loop.js_ctx_id) return;
|
||||||
|
|
||||||
|
// TODO: return the error to the callback
|
||||||
|
result catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.Canceled => {},
|
||||||
|
else => log.err("timeout callback: {any}", .{err}),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// js callback
|
||||||
|
if (ctx.js_cbk) |*js_cbk| {
|
||||||
|
js_cbk.call(null) catch {
|
||||||
|
loop.cbk_error = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timeout(self: *Self, nanoseconds: u63, js_cbk: ?JSCallback) !usize {
|
||||||
|
const completion = try self.alloc.create(Completion);
|
||||||
|
errdefer self.alloc.destroy(completion);
|
||||||
|
completion.* = undefined;
|
||||||
|
|
||||||
|
const ctx = try self.timeout_pool.create();
|
||||||
|
errdefer self.timeout_pool.destroy(ctx);
|
||||||
|
ctx.* = ContextTimeout{
|
||||||
|
.loop = self,
|
||||||
|
.js_cbk = js_cbk,
|
||||||
|
.js_ctx_id = self.js_ctx_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.addEvent(.js);
|
||||||
|
self.io.timeout(*ContextTimeout, ctx, timeoutCallback, completion, nanoseconds);
|
||||||
|
return @intFromPtr(completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextCancel = struct {
|
||||||
|
loop: *Self,
|
||||||
|
js_cbk: ?JSCallback,
|
||||||
|
js_ctx_id: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn cancelCallback(
|
||||||
|
ctx: *ContextCancel,
|
||||||
|
completion: *IO.Completion,
|
||||||
|
result: IO.CancelOneError!void,
|
||||||
|
) void {
|
||||||
|
const loop = ctx.loop;
|
||||||
|
|
||||||
|
defer {
|
||||||
|
loop.removeEvent(.js);
|
||||||
|
loop.cancel_pool.destroy(ctx);
|
||||||
|
loop.alloc.destroy(completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the loop's context id has changed, don't call the js callback
|
||||||
|
// function. The callback's memory has already be cleaned and the
|
||||||
|
// events nb reset.
|
||||||
|
if (ctx.js_ctx_id != loop.js_ctx_id) return;
|
||||||
|
|
||||||
|
// TODO: return the error to the callback
|
||||||
|
result catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.NotFound => log.debug("cancel callback: {any}", .{err}),
|
||||||
|
else => log.err("cancel callback: {any}", .{err}),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// js callback
|
||||||
|
if (ctx.js_cbk) |*js_cbk| {
|
||||||
|
js_cbk.call(null) catch {
|
||||||
|
loop.cbk_error = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(self: *Self, id: usize, js_cbk: ?JSCallback) !void {
|
||||||
|
const alloc = self.alloc;
|
||||||
|
if (comptime CANCEL_SUPPORTED == false) {
|
||||||
|
try self.cancelled.put(alloc, id, {});
|
||||||
|
if (js_cbk) |cbk| {
|
||||||
|
cbk.call(null) catch {
|
||||||
|
self.cbk_error = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const comp_cancel: *IO.Completion = @ptrFromInt(id);
|
||||||
|
|
||||||
|
const completion = try alloc.create(Completion);
|
||||||
|
errdefer alloc.destroy(completion);
|
||||||
|
completion.* = undefined;
|
||||||
|
|
||||||
|
const ctx = self.alloc.create(ContextCancel) catch unreachable;
|
||||||
|
ctx.* = ContextCancel{
|
||||||
|
.loop = self,
|
||||||
|
.js_cbk = js_cbk,
|
||||||
|
.js_ctx_id = self.js_ctx_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.addEvent(.js);
|
||||||
|
self.io.cancel_one(*ContextCancel, ctx, cancelCallback, completion, comp_cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all existing JS callbacks.
|
||||||
|
// The existing events will happen and their memory will be cleanup but the
|
||||||
|
// corresponding callbacks will not be called.
|
||||||
|
pub fn resetJS(self: *Self) void {
|
||||||
|
self.js_ctx_id += 1;
|
||||||
|
self.cancelled.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all existing Zig callbacks.
|
||||||
|
// The existing events will happen and their memory will be cleanup but the
|
||||||
|
// corresponding callbacks will not be called.
|
||||||
|
pub fn resetZig(self: *Self) void {
|
||||||
|
self.zig_ctx_id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IO callbacks APIs
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
|
||||||
|
pub fn connect(
|
||||||
|
self: *Self,
|
||||||
|
comptime Ctx: type,
|
||||||
|
ctx: *Ctx,
|
||||||
|
completion: *Completion,
|
||||||
|
comptime cbk: fn (ctx: *Ctx, _: *Completion, res: ConnectError!void) void,
|
||||||
|
socket: std.posix.socket_t,
|
||||||
|
address: std.net.Address,
|
||||||
|
) !void {
|
||||||
|
const onConnect = struct {
|
||||||
|
fn onConnect(callback: *EventCallbackContext, completion_: *Completion, res: ConnectError!void) void {
|
||||||
|
defer callback.loop.event_callback_pool.destroy(callback);
|
||||||
|
callback.loop.removeEvent(.js);
|
||||||
|
cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res);
|
||||||
|
}
|
||||||
|
}.onConnect;
|
||||||
|
|
||||||
|
const callback = try self.event_callback_pool.create();
|
||||||
|
errdefer self.event_callback_pool.destroy(callback);
|
||||||
|
callback.* = .{ .loop = self, .ctx = ctx };
|
||||||
|
|
||||||
|
self.addEvent(.js);
|
||||||
|
self.io.connect(*EventCallbackContext, callback, onConnect, completion, socket, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send
|
||||||
|
|
||||||
|
pub fn send(
|
||||||
|
self: *Self,
|
||||||
|
comptime Ctx: type,
|
||||||
|
ctx: *Ctx,
|
||||||
|
completion: *Completion,
|
||||||
|
comptime cbk: fn (ctx: *Ctx, completion: *Completion, res: SendError!usize) void,
|
||||||
|
socket: std.posix.socket_t,
|
||||||
|
buf: []const u8,
|
||||||
|
) !void {
|
||||||
|
const onSend = struct {
|
||||||
|
fn onSend(callback: *EventCallbackContext, completion_: *Completion, res: SendError!usize) void {
|
||||||
|
defer callback.loop.event_callback_pool.destroy(callback);
|
||||||
|
callback.loop.removeEvent(.js);
|
||||||
|
cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res);
|
||||||
|
}
|
||||||
|
}.onSend;
|
||||||
|
|
||||||
|
const callback = try self.event_callback_pool.create();
|
||||||
|
errdefer self.event_callback_pool.destroy(callback);
|
||||||
|
callback.* = .{ .loop = self, .ctx = ctx };
|
||||||
|
|
||||||
|
self.addEvent(.js);
|
||||||
|
self.io.send(*EventCallbackContext, callback, onSend, completion, socket, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recv
|
||||||
|
|
||||||
|
pub fn recv(
|
||||||
|
self: *Self,
|
||||||
|
comptime Ctx: type,
|
||||||
|
ctx: *Ctx,
|
||||||
|
completion: *Completion,
|
||||||
|
comptime cbk: fn (ctx: *Ctx, completion: *Completion, res: RecvError!usize) void,
|
||||||
|
socket: std.posix.socket_t,
|
||||||
|
buf: []u8,
|
||||||
|
) !void {
|
||||||
|
const onRecv = struct {
|
||||||
|
fn onRecv(callback: *EventCallbackContext, completion_: *Completion, res: RecvError!usize) void {
|
||||||
|
defer callback.loop.event_callback_pool.destroy(callback);
|
||||||
|
callback.loop.removeEvent(.js);
|
||||||
|
cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res);
|
||||||
|
}
|
||||||
|
}.onRecv;
|
||||||
|
|
||||||
|
const callback = try self.event_callback_pool.create();
|
||||||
|
errdefer self.event_callback_pool.destroy(callback);
|
||||||
|
callback.* = .{ .loop = self, .ctx = ctx };
|
||||||
|
|
||||||
|
self.addEvent(.js);
|
||||||
|
self.io.recv(*EventCallbackContext, callback, onRecv, completion, socket, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zig timeout
|
||||||
|
|
||||||
|
const ContextZigTimeout = struct {
|
||||||
|
loop: *Self,
|
||||||
|
zig_ctx_id: u32,
|
||||||
|
|
||||||
|
context: *anyopaque,
|
||||||
|
callback: *const fn (
|
||||||
|
context: ?*anyopaque,
|
||||||
|
) void,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn zigTimeoutCallback(
|
||||||
|
ctx: *ContextZigTimeout,
|
||||||
|
completion: *IO.Completion,
|
||||||
|
result: IO.TimeoutError!void,
|
||||||
|
) void {
|
||||||
|
const loop = ctx.loop;
|
||||||
|
defer {
|
||||||
|
loop.removeEvent(.zig);
|
||||||
|
loop.alloc.destroy(ctx);
|
||||||
|
loop.alloc.destroy(completion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the loop's context id has changed, don't call the js callback
|
||||||
|
// function. The callback's memory has already be cleaned and the
|
||||||
|
// events nb reset.
|
||||||
|
if (ctx.zig_ctx_id != loop.zig_ctx_id) return;
|
||||||
|
|
||||||
|
result catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.Canceled => {},
|
||||||
|
else => log.err("zig timeout callback: {any}", .{err}),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// callback
|
||||||
|
ctx.callback(ctx.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// zigTimeout performs a timeout but the callback is a zig function.
|
||||||
|
pub fn zigTimeout(
|
||||||
|
self: *Self,
|
||||||
|
nanoseconds: u63,
|
||||||
|
comptime Context: type,
|
||||||
|
context: Context,
|
||||||
|
comptime callback: fn (context: Context) void,
|
||||||
|
) void {
|
||||||
|
const completion = self.alloc.create(IO.Completion) catch unreachable;
|
||||||
|
completion.* = undefined;
|
||||||
|
const ctxtimeout = self.alloc.create(ContextZigTimeout) catch unreachable;
|
||||||
|
ctxtimeout.* = ContextZigTimeout{
|
||||||
|
.loop = self,
|
||||||
|
.zig_ctx_id = self.zig_ctx_id,
|
||||||
|
.context = context,
|
||||||
|
.callback = struct {
|
||||||
|
fn wrapper(ctx: ?*anyopaque) void {
|
||||||
|
callback(@ptrCast(@alignCast(ctx)));
|
||||||
|
}
|
||||||
|
}.wrapper,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.addEvent(.zig);
|
||||||
|
self.io.timeout(*ContextZigTimeout, ctxtimeout, zigTimeoutCallback, completion, nanoseconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const EventCallbackContext = struct {
|
||||||
|
ctx: *anyopaque,
|
||||||
|
loop: *Loop,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CANCEL_SUPPORTED = switch (builtin.target.os.tag) {
|
||||||
|
.linux => true,
|
||||||
|
.macos, .tvos, .watchos, .ios => false,
|
||||||
|
else => @compileError("IO is not supported for platform"),
|
||||||
|
};
|
||||||
260
src/runtime/test_complex_types.zig
Normal file
260
src/runtime/test_complex_types.zig
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// 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 Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const MyList = struct {
|
||||||
|
items: []u8,
|
||||||
|
|
||||||
|
pub fn constructor(elem1: u8, elem2: u8, elem3: u8, state: State) MyList {
|
||||||
|
var items = state.arena.alloc(u8, 3) catch unreachable;
|
||||||
|
items[0] = elem1;
|
||||||
|
items[1] = elem2;
|
||||||
|
items[2] = elem3;
|
||||||
|
return .{ .items = items };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _first(self: *const MyList) u8 {
|
||||||
|
return self.items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _symbol_iterator(self: *const MyList) IterableU8 {
|
||||||
|
return IterableU8.init(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MyVariadic = struct {
|
||||||
|
member: u8,
|
||||||
|
|
||||||
|
pub fn constructor() MyVariadic {
|
||||||
|
return .{ .member = 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _len(_: *const MyVariadic, variadic: []bool) u64 {
|
||||||
|
return @as(u64, variadic.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _first(_: *const MyVariadic, _: []const u8, variadic: []bool) bool {
|
||||||
|
return variadic[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _last(_: *const MyVariadic, variadic: []bool) bool {
|
||||||
|
return variadic[variadic.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _empty(_: *const MyVariadic, _: []bool) bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _myListLen(_: *const MyVariadic, variadic: []*const MyList) u8 {
|
||||||
|
return @as(u8, @intCast(variadic.len));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _myListFirst(_: *const MyVariadic, variadic: []*const MyList) ?u8 {
|
||||||
|
if (variadic.len == 0) return null;
|
||||||
|
return variadic[0]._first();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MyErrorUnion = struct {
|
||||||
|
pub fn constructor(is_err: bool) !MyErrorUnion {
|
||||||
|
if (is_err) return error.MyError;
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_withoutError(_: *const MyErrorUnion) !u8 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_withError(_: *const MyErrorUnion) !u8 {
|
||||||
|
return error.MyError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_withoutError(_: *const MyErrorUnion, _: bool) !void {}
|
||||||
|
|
||||||
|
pub fn set_withError(_: *const MyErrorUnion, _: bool) !void {
|
||||||
|
return error.MyError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _funcWithoutError(_: *const MyErrorUnion) !void {}
|
||||||
|
|
||||||
|
pub fn _funcWithError(_: *const MyErrorUnion) !void {
|
||||||
|
return error.MyError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MyException = struct {
|
||||||
|
err: ErrorSet,
|
||||||
|
|
||||||
|
const errorNames = [_][]const u8{
|
||||||
|
"MyCustomError",
|
||||||
|
};
|
||||||
|
const errorMsgs = [_][]const u8{
|
||||||
|
"Some custom message.",
|
||||||
|
};
|
||||||
|
fn errorStrings(comptime i: usize) []const u8 {
|
||||||
|
return errorNames[0] ++ ": " ++ errorMsgs[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface definition
|
||||||
|
|
||||||
|
pub const ErrorSet = error{
|
||||||
|
MyCustomError,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(_: Allocator, err: anyerror, _: []const u8) !MyException {
|
||||||
|
return .{ .err = @as(ErrorSet, @errorCast(err)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name(self: *const MyException) []const u8 {
|
||||||
|
return switch (self.err) {
|
||||||
|
ErrorSet.MyCustomError => errorNames[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_message(self: *const MyException) []const u8 {
|
||||||
|
return switch (self.err) {
|
||||||
|
ErrorSet.MyCustomError => errorMsgs[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _toString(self: *const MyException) []const u8 {
|
||||||
|
return switch (self.err) {
|
||||||
|
ErrorSet.MyCustomError => errorStrings(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MyTypeWithException = struct {
|
||||||
|
pub const Exception = MyException;
|
||||||
|
|
||||||
|
pub fn constructor() MyTypeWithException {
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _withoutError(_: *const MyTypeWithException) MyException.ErrorSet!void {}
|
||||||
|
|
||||||
|
pub fn _withError(_: *const MyTypeWithException) MyException.ErrorSet!void {
|
||||||
|
return MyException.ErrorSet.MyCustomError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _superSetError(_: *const MyTypeWithException) !void {
|
||||||
|
return MyException.ErrorSet.MyCustomError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _outOfMemory(_: *const MyTypeWithException) !void {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const IterableU8 = Iterable(u8);
|
||||||
|
|
||||||
|
pub fn Iterable(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
items: []T,
|
||||||
|
index: usize = 0,
|
||||||
|
|
||||||
|
pub fn init(items: []T) Self {
|
||||||
|
return .{ .items = items };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Return = struct {
|
||||||
|
value: ?T,
|
||||||
|
done: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn _next(self: *Self) Return {
|
||||||
|
if (self.items.len > self.index) {
|
||||||
|
const val = self.items[self.index];
|
||||||
|
self.index += 1;
|
||||||
|
return .{ .value = val, .done = false };
|
||||||
|
} else {
|
||||||
|
return .{ .value = null, .done = true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const State = struct {
|
||||||
|
arena: Allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("testing.zig");
|
||||||
|
test "JS: complex types" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
var runner = try testing.Runner(State, void, .{
|
||||||
|
MyList,
|
||||||
|
IterableU8,
|
||||||
|
MyVariadic,
|
||||||
|
MyErrorUnion,
|
||||||
|
MyException,
|
||||||
|
MyTypeWithException,
|
||||||
|
}).init(.{ .arena = arena.allocator() }, {});
|
||||||
|
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let myList = new MyList(1, 2, 3);", "undefined" },
|
||||||
|
.{ "myList.first();", "1" },
|
||||||
|
.{ "let iter = myList[Symbol.iterator]();", "undefined" },
|
||||||
|
.{ "iter.next().value;", "1" },
|
||||||
|
.{ "iter.next().value;", "2" },
|
||||||
|
.{ "iter.next().value;", "3" },
|
||||||
|
.{ "iter.next().done;", "true" },
|
||||||
|
.{ "let arr = Array.from(myList);", "undefined" },
|
||||||
|
.{ "arr.length;", "3" },
|
||||||
|
.{ "arr[0];", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let myVariadic = new MyVariadic();", "undefined" },
|
||||||
|
.{ "myVariadic.len(true, false, true)", "3" },
|
||||||
|
.{ "myVariadic.first('a_str', true, false, true, false)", "true" },
|
||||||
|
.{ "myVariadic.last(true, false)", "false" },
|
||||||
|
.{ "myVariadic.empty()", "true" },
|
||||||
|
.{ "myVariadic.myListLen(myList)", "1" },
|
||||||
|
.{ "myVariadic.myListFirst(myList)", "1" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "var myErrorCstr = ''; try {new MyErrorUnion(true)} catch (error) {myErrorCstr = error}; myErrorCstr", "Error: MyError" },
|
||||||
|
.{ "let myErrorUnion = new MyErrorUnion(false);", "undefined" },
|
||||||
|
.{ "myErrorUnion.withoutError", "0" },
|
||||||
|
.{ "var myErrorGetter = ''; try {myErrorUnion.withError} catch (error) {myErrorGetter = error}; myErrorGetter", "Error: MyError" },
|
||||||
|
.{ "myErrorUnion.withoutError = true", "true" },
|
||||||
|
.{ "var myErrorSetter = ''; try {myErrorUnion.withError = true} catch (error) {myErrorSetter = error}; myErrorSetter", "Error: MyError" },
|
||||||
|
.{ "myErrorUnion.funcWithoutError()", "undefined" },
|
||||||
|
.{ "var myErrorFunc = ''; try {myErrorUnion.funcWithError()} catch (error) {myErrorFunc = error}; myErrorFunc", "Error: MyError" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "MyException.prototype.__proto__ === Error.prototype", "true" },
|
||||||
|
.{ "let myTypeWithException = new MyTypeWithException();", "undefined" },
|
||||||
|
.{ "myTypeWithException.withoutError()", "undefined" },
|
||||||
|
.{ "var myCustomError = ''; try {myTypeWithException.withError()} catch (error) {myCustomError = error}", "MyCustomError: Some custom message." },
|
||||||
|
.{ "myCustomError instanceof MyException", "true" },
|
||||||
|
.{ "myCustomError instanceof Error", "true" },
|
||||||
|
.{ "var mySuperError = ''; try {myTypeWithException.superSetError()} catch (error) {mySuperError = error}", "MyCustomError: Some custom message." },
|
||||||
|
.{ "var oomError = ''; try {myTypeWithException.outOfMemory()} catch (error) {oomError = error}; oomError", "Error: out of memory" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
123
src/runtime/test_object_types.zig
Normal file
123
src/runtime/test_object_types.zig
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// 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 Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
pub const Other = struct {
|
||||||
|
val: u8,
|
||||||
|
|
||||||
|
fn init(val: u8) Other {
|
||||||
|
return .{ .val = val };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _val(self: *const Other) u8 {
|
||||||
|
return self.val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OtherUnion = union(enum) {
|
||||||
|
Other: Other,
|
||||||
|
Bool: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MyObject = struct {
|
||||||
|
val: bool,
|
||||||
|
|
||||||
|
pub fn constructor(do_set: bool) MyObject {
|
||||||
|
return .{
|
||||||
|
.val = do_set,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn named_get(_: *const MyObject, name: []const u8, has_value: *bool) ?OtherUnion {
|
||||||
|
if (std.mem.eql(u8, name, "a")) {
|
||||||
|
has_value.* = true;
|
||||||
|
return .{ .Other = .{ .val = 4 } };
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, name, "c")) {
|
||||||
|
has_value.* = true;
|
||||||
|
return .{ .Bool = true };
|
||||||
|
}
|
||||||
|
has_value.* = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_val(self: *const MyObject) bool {
|
||||||
|
return self.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_val(self: *MyObject, val: bool) void {
|
||||||
|
self.val = val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MyAPI = struct {
|
||||||
|
pub fn constructor() MyAPI {
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _obj(_: *const MyAPI) !MyObject {
|
||||||
|
return MyObject.constructor(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const State = struct {
|
||||||
|
arena: Allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("testing.zig");
|
||||||
|
test "JS: object types" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
var runner = try testing.Runner(State, void, .{
|
||||||
|
Other,
|
||||||
|
MyObject,
|
||||||
|
MyAPI,
|
||||||
|
}).init(.{ .arena = arena.allocator() }, {});
|
||||||
|
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
// v8 has 5 default "own" properties
|
||||||
|
const own_base = "5";
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "Object.getOwnPropertyNames(MyObject).length;", own_base },
|
||||||
|
.{ "let myObj = new MyObject(true);", "undefined" },
|
||||||
|
// check object property
|
||||||
|
.{ "myObj.a.val()", "4" },
|
||||||
|
.{ "myObj.b", "undefined" },
|
||||||
|
.{ "Object.getOwnPropertyNames(myObj).length;", "0" },
|
||||||
|
|
||||||
|
// check if setter (pointer) still works
|
||||||
|
.{ "myObj.val", "true" },
|
||||||
|
.{ "myObj.val = false", "false" },
|
||||||
|
.{ "myObj.val", "false" },
|
||||||
|
|
||||||
|
.{ "let myObj2 = new MyObject(false);", "undefined" },
|
||||||
|
.{ "myObj2.c", "true" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let myAPI = new MyAPI();", "undefined" },
|
||||||
|
.{ "let myObjIndirect = myAPI.obj();", "undefined" },
|
||||||
|
// check object property
|
||||||
|
.{ "myObjIndirect.a.val()", "4" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
176
src/runtime/test_primitive_types.zig
Normal file
176
src/runtime/test_primitive_types.zig
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
// 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");
|
||||||
|
|
||||||
|
// TODO: use functions instead of "fake" struct once we handle function API generation
|
||||||
|
const Primitives = struct {
|
||||||
|
pub fn constructor() Primitives {
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of bytes (string)
|
||||||
|
pub fn _checkString(_: *const Primitives, v: []u8) []u8 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integers signed
|
||||||
|
|
||||||
|
pub fn _checkI32(_: *const Primitives, v: i32) i32 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _checkI64(_: *const Primitives, v: i64) i64 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integers unsigned
|
||||||
|
|
||||||
|
pub fn _checkU32(_: *const Primitives, v: u32) u32 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _checkU64(_: *const Primitives, v: u64) u64 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floats
|
||||||
|
|
||||||
|
pub fn _checkF32(_: *const Primitives, v: f32) f32 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _checkF64(_: *const Primitives, v: f64) f64 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool
|
||||||
|
pub fn _checkBool(_: *const Primitives, v: bool) bool {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undefined
|
||||||
|
// TODO: there is a bug with this function
|
||||||
|
// void paramater does not work => avoid for now
|
||||||
|
// pub fn _checkUndefined(_: *const Primitives, v: void) void {
|
||||||
|
// return v;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Null
|
||||||
|
pub fn _checkNullEmpty(_: *const Primitives, v: ?u32) bool {
|
||||||
|
return (v == null);
|
||||||
|
}
|
||||||
|
pub fn _checkNullNotEmpty(_: *const Primitives, v: ?u32) bool {
|
||||||
|
return (v != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionals
|
||||||
|
pub fn _checkOptional(_: *const Primitives, _: ?u8, v: u8, _: ?u8, _: ?u8) u8 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
pub fn _checkNonOptional(_: *const Primitives, v: u8) u8 {
|
||||||
|
std.debug.print("x: {d}\n", .{v});
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
pub fn _checkOptionalReturn(_: *const Primitives) ?bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pub fn _checkOptionalReturnNull(_: *const Primitives) ?bool {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
pub fn _checkOptionalReturnString(_: *const Primitives) ?[]const u8 {
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("testing.zig");
|
||||||
|
test "JS: primitive types" {
|
||||||
|
var runner = try testing.Runner(void, void, .{Primitives}).init({}, {});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
// constructor
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "let p = new Primitives();", "undefined" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
// JS <> Native translation of primitive types
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "p.checkString('ok ascii') === 'ok ascii';", "true" },
|
||||||
|
.{ "p.checkString('ok emoji 🚀') === 'ok emoji 🚀';", "true" },
|
||||||
|
.{ "p.checkString('ok chinese 鿍') === 'ok chinese 鿍';", "true" },
|
||||||
|
|
||||||
|
// String (JS liberal cases)
|
||||||
|
.{ "p.checkString(1) === '1';", "true" },
|
||||||
|
.{ "p.checkString(null) === 'null';", "true" },
|
||||||
|
.{ "p.checkString(undefined) === 'undefined';", "true" },
|
||||||
|
|
||||||
|
// Integers
|
||||||
|
|
||||||
|
// signed
|
||||||
|
.{ "const min_i32 = -2147483648", "undefined" },
|
||||||
|
.{ "p.checkI32(min_i32) === min_i32;", "true" },
|
||||||
|
.{ "p.checkI32(min_i32-1) === min_i32-1;", "false" },
|
||||||
|
.{ "try { p.checkI32(9007199254740995n) } catch(e) { e instanceof TypeError; }", "true" },
|
||||||
|
|
||||||
|
// unsigned
|
||||||
|
.{ "const max_u32 = 4294967295", "undefined" },
|
||||||
|
.{ "p.checkU32(max_u32) === max_u32;", "true" },
|
||||||
|
.{ "p.checkU32(max_u32+1) === max_u32+1;", "false" },
|
||||||
|
|
||||||
|
// int64 (with BigInt)
|
||||||
|
.{ "const big_int = 9007199254740995n", "undefined" },
|
||||||
|
.{ "p.checkI64(big_int) === big_int", "true" },
|
||||||
|
.{ "p.checkU64(big_int) === big_int;", "true" },
|
||||||
|
.{ "p.checkI64(0) === 0;", "true" },
|
||||||
|
.{ "p.checkI64(-1) === -1;", "true" },
|
||||||
|
.{ "p.checkU64(0) === 0;", "true" },
|
||||||
|
|
||||||
|
// Floats
|
||||||
|
// use round 2 decimals for float to ensure equality
|
||||||
|
.{ "const r = function(x) {return Math.round(x * 100) / 100};", "undefined" },
|
||||||
|
.{ "const double = 10.02;", "undefined" },
|
||||||
|
.{ "r(p.checkF32(double)) === double;", "true" },
|
||||||
|
.{ "r(p.checkF64(double)) === double;", "true" },
|
||||||
|
|
||||||
|
// Bool
|
||||||
|
.{ "p.checkBool(true);", "true" },
|
||||||
|
.{ "p.checkBool(false);", "false" },
|
||||||
|
.{ "p.checkBool(0);", "false" },
|
||||||
|
.{ "p.checkBool(1);", "true" },
|
||||||
|
|
||||||
|
// Bool (JS liberal cases)
|
||||||
|
.{ "p.checkBool(null);", "false" },
|
||||||
|
.{ "p.checkBool(undefined);", "false" },
|
||||||
|
|
||||||
|
// Undefined
|
||||||
|
// see TODO on Primitives.checkUndefined
|
||||||
|
// .{ "p.checkUndefined(undefined) === undefined;", "true" },
|
||||||
|
|
||||||
|
// Null
|
||||||
|
.{ "p.checkNullEmpty(null);", "true" },
|
||||||
|
.{ "p.checkNullEmpty(undefined);", "true" },
|
||||||
|
.{ "p.checkNullNotEmpty(1);", "true" },
|
||||||
|
|
||||||
|
// Optional
|
||||||
|
.{ "p.checkOptional(null, 3);", "3" },
|
||||||
|
.{ "p.checkNonOptional();", "TypeError" },
|
||||||
|
.{ "p.checkOptionalReturn() === true;", "true" },
|
||||||
|
.{ "p.checkOptionalReturnNull() === null;", "true" },
|
||||||
|
.{ "p.checkOptionalReturnString() === 'ok';", "true" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
104
src/runtime/testing.zig
Normal file
104
src/runtime/testing.zig
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// 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 js = @import("js.zig");
|
||||||
|
const generate = @import("generate.zig");
|
||||||
|
|
||||||
|
pub const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
// Very similar to the JSRunner in src/testing.zig, but it isn't tied to the
|
||||||
|
// browser.Env or the browser.SessionState
|
||||||
|
pub fn Runner(comptime State: type, comptime Global: type, comptime types: anytype) type {
|
||||||
|
const AdjustedTypes = if (Global == void) generate.Tuple(.{ types, DefaultGlobal }) else types;
|
||||||
|
const Env = js.Env(State, AdjustedTypes{});
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
env: *Env,
|
||||||
|
executor: *Env.Executor,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(state: State, global: Global) !*Self {
|
||||||
|
const runner = try allocator.create(Self);
|
||||||
|
errdefer allocator.destroy(runner);
|
||||||
|
|
||||||
|
runner.env = try Env.init(allocator, .{});
|
||||||
|
errdefer runner.env.deinit();
|
||||||
|
|
||||||
|
const G = if (Global == void) DefaultGlobal else Global;
|
||||||
|
|
||||||
|
runner.executor = try runner.env.startExecutor(G, state, runner);
|
||||||
|
errdefer runner.env.stopExecutor(runner.executor);
|
||||||
|
|
||||||
|
try runner.executor.startScope(if (Global == void) &default_global else global);
|
||||||
|
return runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.executor.endScope();
|
||||||
|
self.env.stopExecutor(self.executor);
|
||||||
|
self.env.deinit();
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RunOpts = struct {};
|
||||||
|
pub const Case = std.meta.Tuple(&.{ []const u8, []const u8 });
|
||||||
|
pub fn testCases(self: *Self, cases: []const Case, _: RunOpts) !void {
|
||||||
|
for (cases, 0..) |case, i| {
|
||||||
|
var try_catch: Env.TryCatch = undefined;
|
||||||
|
try_catch.init(self.executor);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
const value = self.executor.exec(case.@"0", null) catch |err| {
|
||||||
|
if (try try_catch.err(allocator)) |msg| {
|
||||||
|
defer allocator.free(msg);
|
||||||
|
if (isExpectedTypeError(case.@"1", msg)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actual = try value.toString(allocator);
|
||||||
|
defer allocator.free(actual);
|
||||||
|
if (std.mem.eql(u8, case.@"1", actual) == false) {
|
||||||
|
std.debug.print("Expected:\n{s}\n\nGot:\n{s}\n\nCase: {d}\n{s}\n", .{ case.@"1", actual, i + 1, case.@"0" });
|
||||||
|
return error.UnexpectedResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) ![]const u8 {
|
||||||
|
_ = ctx;
|
||||||
|
_ = specifier;
|
||||||
|
return error.DummyModuleLoader;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isExpectedTypeError(expected: []const u8, msg: []const u8) bool {
|
||||||
|
if (!std.mem.eql(u8, expected, "TypeError")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std.mem.startsWith(u8, msg, "TypeError: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
var default_global = DefaultGlobal{};
|
||||||
|
const DefaultGlobal = struct {};
|
||||||
@@ -25,14 +25,15 @@ const posix = std.posix;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const IO = @import("runtime/loop.zig").IO;
|
||||||
const Completion = jsruntime.IO.Completion;
|
const Completion = IO.Completion;
|
||||||
const AcceptError = jsruntime.IO.AcceptError;
|
const AcceptError = IO.AcceptError;
|
||||||
const RecvError = jsruntime.IO.RecvError;
|
const RecvError = IO.RecvError;
|
||||||
const SendError = jsruntime.IO.SendError;
|
const SendError = IO.SendError;
|
||||||
const CloseError = jsruntime.IO.CloseError;
|
const CloseError = IO.CloseError;
|
||||||
const CancelError = jsruntime.IO.CancelOneError;
|
const CancelError = IO.CancelOneError;
|
||||||
const TimeoutError = jsruntime.IO.TimeoutError;
|
const TimeoutError = IO.TimeoutError;
|
||||||
|
const Loop = @import("runtime/loop.zig").Loop;
|
||||||
|
|
||||||
const App = @import("app.zig").App;
|
const App = @import("app.zig").App;
|
||||||
const CDP = @import("cdp/cdp.zig").CDP;
|
const CDP = @import("cdp/cdp.zig").CDP;
|
||||||
@@ -51,7 +52,7 @@ const MAX_MESSAGE_SIZE = 256 * 1024 + 14;
|
|||||||
const Server = struct {
|
const Server = struct {
|
||||||
app: *App,
|
app: *App,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
loop: *jsruntime.Loop,
|
loop: *Loop,
|
||||||
|
|
||||||
// internal fields
|
// internal fields
|
||||||
listener: posix.socket_t,
|
listener: posix.socket_t,
|
||||||
@@ -453,7 +454,7 @@ pub const Client = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.mode = .websocket;
|
self.mode = .websocket;
|
||||||
self.cdp = CDP.init(self.server.app, self);
|
self.cdp = try CDP.init(self.server.app, self);
|
||||||
return self.send(arena, response);
|
return self.send(arena, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1023,10 +1024,6 @@ pub fn run(
|
|||||||
try posix.bind(listener, &address.any, address.getOsSockLen());
|
try posix.bind(listener, &address.any, address.getOsSockLen());
|
||||||
try posix.listen(listener, 1);
|
try posix.listen(listener, 1);
|
||||||
|
|
||||||
// create v8 vm
|
|
||||||
const vm = jsruntime.VM.init();
|
|
||||||
defer vm.deinit();
|
|
||||||
|
|
||||||
var loop = app.loop;
|
var loop = app.loop;
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
const json_version_response = try buildJSONVersionResponse(allocator, address);
|
const json_version_response = try buildJSONVersionResponse(allocator, address);
|
||||||
@@ -1451,7 +1448,7 @@ const MockCDP = struct {
|
|||||||
|
|
||||||
allocator: Allocator = testing.allocator,
|
allocator: Allocator = testing.allocator,
|
||||||
|
|
||||||
fn init(_: Allocator, client: anytype, loop: *jsruntime.Loop) MockCDP {
|
fn init(_: Allocator, client: anytype, loop: *Loop) MockCDP {
|
||||||
_ = loop;
|
_ = loop;
|
||||||
_ = client;
|
_ = client;
|
||||||
return .{};
|
return .{};
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ const BORDER = "=" ** 80;
|
|||||||
// use in custom panic handler
|
// use in custom panic handler
|
||||||
var current_test: ?[]const u8 = null;
|
var current_test: ?[]const u8 = null;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
pub const Types = jsruntime.reflect(@import("generate.zig").Tuple(.{}){});
|
|
||||||
pub const UserContext = @import("user_context.zig").UserContext;
|
|
||||||
|
|
||||||
pub const std_options = std.Options{
|
pub const std_options = std.Options{
|
||||||
.log_level = .warn,
|
.log_level = .warn,
|
||||||
|
|
||||||
@@ -38,6 +34,9 @@ pub const std_options = std.Options{
|
|||||||
.side_channels_mitigations = .none,
|
.side_channels_mitigations = .none,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub var js_runner_duration: usize = 0;
|
||||||
|
pub var tracking_allocator = TrackingAllocator.init(std.testing.allocator);
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var mem: [8192]u8 = undefined;
|
var mem: [8192]u8 = undefined;
|
||||||
var fba = std.heap.FixedBufferAllocator.init(&mem);
|
var fba = std.heap.FixedBufferAllocator.init(&mem);
|
||||||
@@ -50,6 +49,19 @@ pub fn main() !void {
|
|||||||
var slowest = SlowTracker.init(allocator, 5);
|
var slowest = SlowTracker.init(allocator, 5);
|
||||||
defer slowest.deinit();
|
defer slowest.deinit();
|
||||||
|
|
||||||
|
var args = try std.process.argsWithAllocator(allocator);
|
||||||
|
defer args.deinit();
|
||||||
|
|
||||||
|
// ignore the exec name.
|
||||||
|
_ = args.next();
|
||||||
|
var json_stats = false;
|
||||||
|
while (args.next()) |arg| {
|
||||||
|
if (std.mem.eql(u8, "--json", arg)) {
|
||||||
|
json_stats = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var pass: usize = 0;
|
var pass: usize = 0;
|
||||||
var fail: usize = 0;
|
var fail: usize = 0;
|
||||||
var skip: usize = 0;
|
var skip: usize = 0;
|
||||||
@@ -159,6 +171,38 @@ pub fn main() !void {
|
|||||||
printer.fmt("\n", .{});
|
printer.fmt("\n", .{});
|
||||||
try slowest.display(printer);
|
try slowest.display(printer);
|
||||||
printer.fmt("\n", .{});
|
printer.fmt("\n", .{});
|
||||||
|
|
||||||
|
// TODO: at the very least, `browser` should return real stats
|
||||||
|
if (json_stats) {
|
||||||
|
const stats = tracking_allocator.stats();
|
||||||
|
try std.json.stringify(&.{
|
||||||
|
.{ .name = "browser", .bench = .{
|
||||||
|
.duration = js_runner_duration,
|
||||||
|
.alloc_nb = stats.allocation_count,
|
||||||
|
.realloc_nb = stats.reallocation_count,
|
||||||
|
.alloc_size = stats.allocated_bytes,
|
||||||
|
} },
|
||||||
|
.{ .name = "libdom", .bench = .{
|
||||||
|
.duration = js_runner_duration,
|
||||||
|
.alloc_nb = 0,
|
||||||
|
.realloc_nb = 0,
|
||||||
|
.alloc_size = 0,
|
||||||
|
} },
|
||||||
|
.{ .name = "v8", .bench = .{
|
||||||
|
.duration = js_runner_duration,
|
||||||
|
.alloc_nb = 0,
|
||||||
|
.realloc_nb = 0,
|
||||||
|
.alloc_size = 0,
|
||||||
|
} },
|
||||||
|
.{ .name = "main", .bench = .{
|
||||||
|
.duration = js_runner_duration,
|
||||||
|
.alloc_nb = 0,
|
||||||
|
.realloc_nb = 0,
|
||||||
|
.alloc_size = 0,
|
||||||
|
} },
|
||||||
|
}, .{ .whitespace = .indent_2 }, std.io.getStdOut().writer());
|
||||||
|
}
|
||||||
|
|
||||||
std.posix.exit(if (fail == 0) 0 else 1);
|
std.posix.exit(if (fail == 0) 0 else 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,3 +379,90 @@ fn isSetup(t: std.builtin.TestFn) bool {
|
|||||||
fn isTeardown(t: std.builtin.TestFn) bool {
|
fn isTeardown(t: std.builtin.TestFn) bool {
|
||||||
return std.mem.endsWith(u8, t.name, "tests:afterAll");
|
return std.mem.endsWith(u8, t.name, "tests:afterAll");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const TrackingAllocator = struct {
|
||||||
|
parent_allocator: Allocator,
|
||||||
|
free_count: usize = 0,
|
||||||
|
allocated_bytes: usize = 0,
|
||||||
|
allocation_count: usize = 0,
|
||||||
|
reallocation_count: usize = 0,
|
||||||
|
|
||||||
|
const Stats = struct {
|
||||||
|
allocated_bytes: usize,
|
||||||
|
allocation_count: usize,
|
||||||
|
reallocation_count: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn init(parent_allocator: Allocator) TrackingAllocator {
|
||||||
|
return .{
|
||||||
|
.parent_allocator = parent_allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stats(self: *const TrackingAllocator) Stats {
|
||||||
|
return .{
|
||||||
|
.allocated_bytes = self.allocated_bytes,
|
||||||
|
.allocation_count = self.allocation_count,
|
||||||
|
.reallocation_count = self.reallocation_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocator(self: *TrackingAllocator) Allocator {
|
||||||
|
return .{ .ptr = self, .vtable = &.{
|
||||||
|
.alloc = alloc,
|
||||||
|
.resize = resize,
|
||||||
|
.free = free,
|
||||||
|
.remap = remap,
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc(
|
||||||
|
ctx: *anyopaque,
|
||||||
|
len: usize,
|
||||||
|
alignment: std.mem.Alignment,
|
||||||
|
return_address: usize,
|
||||||
|
) ?[*]u8 {
|
||||||
|
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
|
||||||
|
const result = self.parent_allocator.rawAlloc(len, alignment, return_address);
|
||||||
|
self.allocation_count += 1;
|
||||||
|
self.allocated_bytes += len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(
|
||||||
|
ctx: *anyopaque,
|
||||||
|
old_mem: []u8,
|
||||||
|
alignment: std.mem.Alignment,
|
||||||
|
new_len: usize,
|
||||||
|
ra: usize,
|
||||||
|
) bool {
|
||||||
|
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
|
||||||
|
const result = self.parent_allocator.rawResize(old_mem, alignment, new_len, ra);
|
||||||
|
self.reallocation_count += 1; // TODO: only if result is not null?
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(
|
||||||
|
ctx: *anyopaque,
|
||||||
|
old_mem: []u8,
|
||||||
|
alignment: std.mem.Alignment,
|
||||||
|
ra: usize,
|
||||||
|
) void {
|
||||||
|
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
|
||||||
|
self.parent_allocator.rawFree(old_mem, alignment, ra);
|
||||||
|
self.free_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap(
|
||||||
|
ctx: *anyopaque,
|
||||||
|
memory: []u8,
|
||||||
|
alignment: std.mem.Alignment,
|
||||||
|
new_len: usize,
|
||||||
|
ret_addr: usize,
|
||||||
|
) ?[*]u8 {
|
||||||
|
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
|
||||||
|
const result = self.parent_allocator.rawRemap(memory, alignment, new_len, ret_addr);
|
||||||
|
self.reallocation_count += 1; // TODO: only if result is not null?
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
168
src/testing.zig
168
src/testing.zig
@@ -17,15 +17,15 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
pub const allocator = std.testing.allocator;
|
pub const allocator = std.testing.allocator;
|
||||||
pub const expectError = std.testing.expectError;
|
pub const expectError = std.testing.expectError;
|
||||||
pub const expectString = std.testing.expectEqualStrings;
|
pub const expectString = std.testing.expectEqualStrings;
|
||||||
pub const expectEqualSlices = std.testing.expectEqualSlices;
|
pub const expectEqualSlices = std.testing.expectEqualSlices;
|
||||||
|
|
||||||
const App = @import("app.zig").App;
|
const App = @import("app.zig").App;
|
||||||
const Allocator = std.mem.Allocator;
|
const parser = @import("browser/netsurf.zig");
|
||||||
|
|
||||||
// Merged std.testing.expectEqual and std.testing.expectString
|
// Merged std.testing.expectEqual and std.testing.expectString
|
||||||
// can be useful when testing fields of an anytype an you don't know
|
// can be useful when testing fields of an anytype an you don't know
|
||||||
@@ -217,13 +217,13 @@ pub const Document = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelectorAll(self: *Document, selector: []const u8) ![]const *parser.Node {
|
pub fn querySelectorAll(self: *Document, selector: []const u8) ![]const *parser.Node {
|
||||||
const css = @import("dom/css.zig");
|
const css = @import("browser/dom/css.zig");
|
||||||
const node_list = try css.querySelectorAll(self.arena.allocator(), self.asNode(), selector);
|
const node_list = try css.querySelectorAll(self.arena.allocator(), self.asNode(), selector);
|
||||||
return node_list.nodes.items;
|
return node_list.nodes.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelector(self: *Document, selector: []const u8) !?*parser.Node {
|
pub fn querySelector(self: *Document, selector: []const u8) !?*parser.Node {
|
||||||
const css = @import("dom/css.zig");
|
const css = @import("browser/dom/css.zig");
|
||||||
return css.querySelector(self.arena.allocator(), self.asNode(), selector);
|
return css.querySelector(self.arena.allocator(), self.asNode(), selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,3 +350,163 @@ fn isJsonValue(a: std.json.Value, b: std.json.Value) bool {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const tracking_allocator = @import("root").tracking_allocator.allocator();
|
||||||
|
pub const JsRunner = struct {
|
||||||
|
const URL = @import("url.zig").URL;
|
||||||
|
const Env = @import("browser/env.zig").Env;
|
||||||
|
const Loop = @import("runtime/loop.zig").Loop;
|
||||||
|
const HttpClient = @import("http/client.zig").Client;
|
||||||
|
const storage = @import("browser/storage/storage.zig");
|
||||||
|
const Window = @import("browser/html/window.zig").Window;
|
||||||
|
const Renderer = @import("browser/browser.zig").Renderer;
|
||||||
|
const SessionState = @import("browser/env.zig").SessionState;
|
||||||
|
|
||||||
|
url: URL,
|
||||||
|
env: *Env,
|
||||||
|
loop: Loop,
|
||||||
|
window: Window,
|
||||||
|
state: SessionState,
|
||||||
|
arena: Allocator,
|
||||||
|
renderer: Renderer,
|
||||||
|
http_client: HttpClient,
|
||||||
|
executor: *Env.Executor,
|
||||||
|
storage_shelf: storage.Shelf,
|
||||||
|
cookie_jar: storage.CookieJar,
|
||||||
|
|
||||||
|
fn init(parent_allocator: Allocator, opts: RunnerOpts) !*JsRunner {
|
||||||
|
parser.deinit();
|
||||||
|
try parser.init();
|
||||||
|
|
||||||
|
const aa = try parent_allocator.create(std.heap.ArenaAllocator);
|
||||||
|
aa.* = std.heap.ArenaAllocator.init(parent_allocator);
|
||||||
|
errdefer aa.deinit();
|
||||||
|
|
||||||
|
const arena = aa.allocator();
|
||||||
|
const runner = try arena.create(JsRunner);
|
||||||
|
runner.arena = arena;
|
||||||
|
|
||||||
|
runner.env = try Env.init(arena, .{});
|
||||||
|
errdefer runner.env.deinit();
|
||||||
|
|
||||||
|
runner.url = try URL.parse("https://lightpanda.io/opensource-browser/", null);
|
||||||
|
|
||||||
|
runner.renderer = Renderer.init(arena);
|
||||||
|
runner.cookie_jar = storage.CookieJar.init(arena);
|
||||||
|
runner.loop = try Loop.init(arena);
|
||||||
|
errdefer runner.loop.deinit();
|
||||||
|
|
||||||
|
var html = std.io.fixedBufferStream(opts.html);
|
||||||
|
const document = try parser.documentHTMLParse(html.reader(), "UTF-8");
|
||||||
|
|
||||||
|
runner.state = .{
|
||||||
|
.arena = arena,
|
||||||
|
.loop = &runner.loop,
|
||||||
|
.document = document,
|
||||||
|
.url = &runner.url,
|
||||||
|
.renderer = &runner.renderer,
|
||||||
|
.cookie_jar = &runner.cookie_jar,
|
||||||
|
.http_client = &runner.http_client,
|
||||||
|
};
|
||||||
|
|
||||||
|
runner.window = .{};
|
||||||
|
try runner.window.replaceDocument(document);
|
||||||
|
try runner.window.replaceLocation(.{
|
||||||
|
.url = try runner.url.toWebApi(arena),
|
||||||
|
});
|
||||||
|
|
||||||
|
runner.storage_shelf = storage.Shelf.init(arena);
|
||||||
|
runner.window.setStorageShelf(&runner.storage_shelf);
|
||||||
|
|
||||||
|
runner.http_client = try HttpClient.init(arena, 1, .{
|
||||||
|
.tls_verify_host = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
runner.executor = try runner.env.startExecutor(Window, &runner.state, runner);
|
||||||
|
errdefer runner.env.stopExecutor(runner.executor);
|
||||||
|
|
||||||
|
try runner.executor.startScope(&runner.window);
|
||||||
|
return runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *JsRunner) void {
|
||||||
|
self.loop.deinit();
|
||||||
|
self.executor.endScope();
|
||||||
|
self.env.deinit();
|
||||||
|
self.http_client.deinit();
|
||||||
|
self.storage_shelf.deinit();
|
||||||
|
|
||||||
|
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(self.arena.ptr));
|
||||||
|
arena.deinit();
|
||||||
|
arena.child_allocator.destroy(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RunOpts = struct {};
|
||||||
|
pub const Case = std.meta.Tuple(&.{ []const u8, []const u8 });
|
||||||
|
pub fn testCases(self: *JsRunner, cases: []const Case, _: RunOpts) !void {
|
||||||
|
const start = try std.time.Instant.now();
|
||||||
|
|
||||||
|
for (cases, 0..) |case, i| {
|
||||||
|
var try_catch: Env.TryCatch = undefined;
|
||||||
|
try_catch.init(self.executor);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
const value = self.executor.exec(case.@"0", null) catch |err| {
|
||||||
|
if (try try_catch.err(self.arena)) |msg| {
|
||||||
|
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
try self.loop.run();
|
||||||
|
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
|
||||||
|
|
||||||
|
const actual = try value.toString(self.arena);
|
||||||
|
if (std.mem.eql(u8, case.@"1", actual) == false) {
|
||||||
|
std.debug.print("Expected:\n{s}\n\nGot:\n{s}\n\nCase: {d}\n{s}\n", .{ case.@"1", actual, i + 1, case.@"0" });
|
||||||
|
return error.UnexpectedResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(self: *JsRunner, src: []const u8, name: ?[]const u8, err_msg: *?[]const u8) !void {
|
||||||
|
_ = try self.eval(src, name, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(self: *JsRunner, src: []const u8, name: ?[]const u8, err_msg: *?[]const u8) !Env.Value {
|
||||||
|
var try_catch: Env.TryCatch = undefined;
|
||||||
|
try_catch.init(self.executor);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
return self.executor.exec(src, name) catch |err| {
|
||||||
|
if (try try_catch.err(self.arena)) |msg| {
|
||||||
|
err_msg.* = msg;
|
||||||
|
std.debug.print("Error running script: {s}\n", .{msg});
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) ![]const u8 {
|
||||||
|
_ = ctx;
|
||||||
|
_ = specifier;
|
||||||
|
return error.DummyModuleLoader;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RunnerOpts = struct {
|
||||||
|
html: []const u8 =
|
||||||
|
\\ <div id="content">
|
||||||
|
\\ <a id="link" href="foo" class="ok">OK</a>
|
||||||
|
\\ <p id="para-empty" class="ok empty">
|
||||||
|
\\ <span id="para-empty-child"></span>
|
||||||
|
\\ </p>
|
||||||
|
\\ <p id="para"> And</p>
|
||||||
|
\\ <!--comment-->
|
||||||
|
\\ </div>
|
||||||
|
\\
|
||||||
|
,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn jsRunner(alloc: Allocator, opts: RunnerOpts) !*JsRunner {
|
||||||
|
return JsRunner.init(alloc, opts);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
|||||||
|
|
||||||
const Uri = std.Uri;
|
const Uri = std.Uri;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const WebApiURL = @import("url/url.zig").URL;
|
const WebApiURL = @import("browser/url/url.zig").URL;
|
||||||
|
|
||||||
pub const URL = struct {
|
pub const URL = struct {
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const parser = @import("netsurf");
|
|
||||||
const URL = @import("url.zig").URL;
|
|
||||||
const storage = @import("storage/storage.zig");
|
|
||||||
const Client = @import("http/client.zig").Client;
|
|
||||||
const Renderer = @import("browser/browser.zig").Renderer;
|
|
||||||
|
|
||||||
pub const UserContext = struct {
|
|
||||||
url: *const URL,
|
|
||||||
http_client: *Client,
|
|
||||||
document: *parser.DocumentHTML,
|
|
||||||
cookie_jar: *storage.CookieJar,
|
|
||||||
renderer: *Renderer,
|
|
||||||
};
|
|
||||||
184
src/wpt/run.zig
184
src/wpt/run.zig
@@ -18,112 +18,55 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const fspath = std.fs.path;
|
const fspath = std.fs.path;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Env = @import("../browser/env.zig").Env;
|
||||||
const FileLoader = @import("fileloader.zig").FileLoader;
|
const FileLoader = @import("fileloader.zig").FileLoader;
|
||||||
|
const Window = @import("../browser/html/window.zig").Window;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("../browser/netsurf.zig");
|
||||||
|
const polyfill = @import("../browser/polyfill/polyfill.zig");
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
const Loop = jsruntime.Loop;
|
|
||||||
const Env = jsruntime.Env;
|
|
||||||
const URL = @import("../url.zig").URL;
|
|
||||||
const browser = @import("../browser/browser.zig");
|
|
||||||
const Window = @import("../html/window.zig").Window;
|
|
||||||
const storage = @import("../storage/storage.zig");
|
|
||||||
const HttpClient = @import("../http/client.zig").Client;
|
|
||||||
|
|
||||||
const Types = @import("../main_wpt.zig").Types;
|
|
||||||
const UserContext = @import("../main_wpt.zig").UserContext;
|
|
||||||
|
|
||||||
const polyfill = @import("../polyfill/polyfill.zig");
|
|
||||||
|
|
||||||
// runWPT parses the given HTML file, starts a js env and run the first script
|
// runWPT parses the given HTML file, starts a js env and run the first script
|
||||||
// tags containing javascript sources.
|
// tags containing javascript sources.
|
||||||
// It loads first the js libs files.
|
// It loads first the js libs files.
|
||||||
pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const u8, loader: *FileLoader) !Res {
|
pub fn run(arena: Allocator, comptime dir: []const u8, f: []const u8, loader: *FileLoader, err_msg: *?[]const u8) ![]const u8 {
|
||||||
const alloc = arena.allocator();
|
|
||||||
try parser.init();
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
// document
|
// document
|
||||||
const file = try std.fs.cwd().openFile(f, .{});
|
const html = blk: {
|
||||||
defer file.close();
|
const file = try std.fs.cwd().openFile(f, .{});
|
||||||
|
defer file.close();
|
||||||
const html_doc = try parser.documentHTMLParse(file.reader(), "UTF-8");
|
break :blk try file.readToEndAlloc(arena, 128 * 1024);
|
||||||
|
};
|
||||||
|
|
||||||
const dirname = fspath.dirname(f[dir.len..]) orelse unreachable;
|
const dirname = fspath.dirname(f[dir.len..]) orelse unreachable;
|
||||||
|
|
||||||
// create JS env
|
var runner = try @import("../testing.zig").jsRunner(arena, .{
|
||||||
var loop = try Loop.init(alloc);
|
.html = html,
|
||||||
defer loop.deinit();
|
|
||||||
|
|
||||||
var http_client = try HttpClient.init(alloc, 2, .{});
|
|
||||||
defer http_client.deinit();
|
|
||||||
|
|
||||||
var cookie_jar = storage.CookieJar.init(alloc);
|
|
||||||
defer cookie_jar.deinit();
|
|
||||||
|
|
||||||
var renderer = browser.Renderer.init(alloc);
|
|
||||||
defer renderer.elements.deinit(alloc);
|
|
||||||
defer renderer.positions.deinit(alloc);
|
|
||||||
|
|
||||||
const url = try URL.parse("https://lightpanda.io", null);
|
|
||||||
|
|
||||||
var js_env: Env = undefined;
|
|
||||||
Env.init(&js_env, alloc, &loop, UserContext{
|
|
||||||
.url = &url,
|
|
||||||
.document = html_doc,
|
|
||||||
.cookie_jar = &cookie_jar,
|
|
||||||
.http_client = &http_client,
|
|
||||||
.renderer = &renderer,
|
|
||||||
});
|
});
|
||||||
defer js_env.deinit();
|
defer runner.deinit();
|
||||||
|
try polyfill.load(arena, runner.executor);
|
||||||
var storageShelf = storage.Shelf.init(alloc);
|
|
||||||
defer storageShelf.deinit();
|
|
||||||
|
|
||||||
// load user-defined types in JS env
|
|
||||||
var js_types: [Types.len]usize = undefined;
|
|
||||||
try js_env.load(&js_types);
|
|
||||||
|
|
||||||
// start JS env
|
|
||||||
try js_env.start();
|
|
||||||
defer js_env.stop();
|
|
||||||
|
|
||||||
// load polyfills
|
|
||||||
try polyfill.load(alloc, &js_env);
|
|
||||||
|
|
||||||
// display console logs
|
// display console logs
|
||||||
defer {
|
defer {
|
||||||
const res = evalJS(&js_env, alloc, "console.join('\\n');", "console") catch unreachable;
|
const res = runner.eval("console.join('\\n');", "console", err_msg) catch unreachable;
|
||||||
defer res.deinit(alloc);
|
const log = res.toString(arena) catch unreachable;
|
||||||
|
if (log.len > 0) {
|
||||||
if (res.msg != null and res.msg.?.len > 0) {
|
std.debug.print("-- CONSOLE LOG\n{s}\n--\n", .{log});
|
||||||
std.debug.print("-- CONSOLE LOG\n{s}\n--\n", .{res.msg.?});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup global env vars.
|
try runner.exec(
|
||||||
var window = Window.create(null, null);
|
\\ console = [];
|
||||||
try window.replaceDocument(html_doc);
|
\\ console.log = function () {
|
||||||
window.setStorageShelf(&storageShelf);
|
\\ console.push(...arguments);
|
||||||
try js_env.bindGlobal(&window);
|
\\ };
|
||||||
|
\\ console.debug = function () {
|
||||||
|
\\ console.push("debug", ...arguments);
|
||||||
|
\\ };
|
||||||
|
, "init", err_msg);
|
||||||
|
|
||||||
const init =
|
// loop over the scripts.
|
||||||
\\console = [];
|
const doc = parser.documentHTMLToDocument(runner.state.document.?);
|
||||||
\\console.log = function () {
|
|
||||||
\\ console.push(...arguments);
|
|
||||||
\\};
|
|
||||||
\\console.debug = function () {
|
|
||||||
\\ console.push("debug", ...arguments);
|
|
||||||
\\};
|
|
||||||
;
|
|
||||||
var res = try evalJS(&js_env, alloc, init, "init");
|
|
||||||
if (!res.ok) return res;
|
|
||||||
res.deinit(alloc);
|
|
||||||
|
|
||||||
// loop hover the scripts.
|
|
||||||
const doc = parser.documentHTMLToDocument(html_doc);
|
|
||||||
const scripts = try parser.documentGetElementsByTagName(doc, "script");
|
const scripts = try parser.documentGetElementsByTagName(doc, "script");
|
||||||
const slen = try parser.nodeListLength(scripts);
|
const slen = try parser.nodeListLength(scripts);
|
||||||
for (0..slen) |i| {
|
for (0..slen) |i| {
|
||||||
@@ -134,19 +77,14 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
|
|||||||
var path = src;
|
var path = src;
|
||||||
if (!std.mem.startsWith(u8, src, "/")) {
|
if (!std.mem.startsWith(u8, src, "/")) {
|
||||||
// no need to free path, thanks to the arena.
|
// no need to free path, thanks to the arena.
|
||||||
path = try fspath.join(alloc, &.{ "/", dirname, path });
|
path = try fspath.join(arena, &.{ "/", dirname, path });
|
||||||
}
|
}
|
||||||
|
try runner.exec(try loader.get(path), src, err_msg);
|
||||||
res = try evalJS(&js_env, alloc, try loader.get(path), src);
|
|
||||||
if (!res.ok) return res;
|
|
||||||
res.deinit(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the script as a source text, execute it.
|
// If the script as a source text, execute it.
|
||||||
const src = try parser.nodeTextContent(s) orelse continue;
|
const src = try parser.nodeTextContent(s) orelse continue;
|
||||||
res = try evalJS(&js_env, alloc, src, "");
|
try runner.exec(src, null, err_msg);
|
||||||
if (!res.ok) return res;
|
|
||||||
res.deinit(alloc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark tests as ready to run.
|
// Mark tests as ready to run.
|
||||||
@@ -155,57 +93,29 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
|
|||||||
|
|
||||||
try parser.eventInit(loadevt, "load", .{});
|
try parser.eventInit(loadevt, "load", .{});
|
||||||
_ = try parser.eventTargetDispatchEvent(
|
_ = try parser.eventTargetDispatchEvent(
|
||||||
parser.toEventTarget(Window, &window),
|
parser.toEventTarget(@TypeOf(runner.window), &runner.window),
|
||||||
loadevt,
|
loadevt,
|
||||||
);
|
);
|
||||||
|
|
||||||
// wait for all async executions
|
// wait for all async executions
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
{
|
||||||
try_catch.init(&js_env);
|
var try_catch: Env.TryCatch = undefined;
|
||||||
defer try_catch.deinit();
|
try_catch.init(runner.executor);
|
||||||
js_env.wait() catch {
|
defer try_catch.deinit();
|
||||||
return .{
|
runner.loop.run() catch |err| {
|
||||||
.ok = false,
|
if (try try_catch.err(arena)) |msg| {
|
||||||
.msg = try try_catch.err(alloc, &js_env),
|
err_msg.* = msg;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
// Check the final test status.
|
// Check the final test status.
|
||||||
res = try evalJS(&js_env, alloc, "report.status;", "teststatus");
|
try runner.exec("report.status", "teststatus", err_msg);
|
||||||
if (!res.ok) return res;
|
|
||||||
res.deinit(alloc);
|
|
||||||
|
|
||||||
// return the detailed result.
|
// return the detailed result.
|
||||||
return try evalJS(&js_env, alloc, "report.log", "teststatus");
|
const res = try runner.eval("report.log", "report", err_msg);
|
||||||
}
|
return res.toString(arena);
|
||||||
|
|
||||||
pub const Res = struct {
|
|
||||||
ok: bool,
|
|
||||||
msg: ?[]const u8,
|
|
||||||
|
|
||||||
pub fn deinit(res: Res, alloc: std.mem.Allocator) void {
|
|
||||||
if (res.msg) |msg| {
|
|
||||||
alloc.free(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn evalJS(env: *const jsruntime.Env, alloc: std.mem.Allocator, script: []const u8, name: ?[]const u8) !Res {
|
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
|
||||||
try_catch.init(env);
|
|
||||||
defer try_catch.deinit();
|
|
||||||
|
|
||||||
const v = env.exec(script, name) catch {
|
|
||||||
return .{
|
|
||||||
.ok = false,
|
|
||||||
.msg = try try_catch.err(alloc, env),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.ok = true,
|
|
||||||
.msg = try v.toString(alloc, env),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// browse the path to find the tests list.
|
// browse the path to find the tests list.
|
||||||
|
|||||||
1
vendor/zig-js-runtime
vendored
1
vendor/zig-js-runtime
vendored
Submodule vendor/zig-js-runtime deleted from 9b87782f1e
Reference in New Issue
Block a user