mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
Compare commits
1 Commits
v0.2.0
...
zigdom-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6e8aff2c9 |
2
.github/actions/install/action.yml
vendored
2
.github/actions/install/action.yml
vendored
@@ -13,7 +13,7 @@ inputs:
|
|||||||
zig-v8:
|
zig-v8:
|
||||||
description: 'zig v8 version to install'
|
description: 'zig v8 version to install'
|
||||||
required: false
|
required: false
|
||||||
default: 'v0.2.2'
|
default: 'v0.1.37'
|
||||||
v8:
|
v8:
|
||||||
description: 'v8 version to install'
|
description: 'v8 version to install'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
32
.github/workflows/build.yml
vendored
32
.github/workflows/build.yml
vendored
@@ -5,12 +5,8 @@ env:
|
|||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NIGHTLY_BUILD_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.NIGHTLY_BUILD_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_BUCKET: ${{ vars.NIGHTLY_BUILD_AWS_BUCKET }}
|
AWS_BUCKET: ${{ vars.NIGHTLY_BUILD_AWS_BUCKET }}
|
||||||
AWS_REGION: ${{ vars.NIGHTLY_BUILD_AWS_REGION }}
|
AWS_REGION: ${{ vars.NIGHTLY_BUILD_AWS_REGION }}
|
||||||
RELEASE: ${{ github.ref_type == 'tag' && github.ref_name || 'nightly' }}
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "2 2 * * *"
|
- cron: "2 2 * * *"
|
||||||
|
|
||||||
@@ -42,11 +38,8 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
mode: 'release'
|
mode: 'release'
|
||||||
|
|
||||||
- name: v8 snapshot
|
|
||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -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 }}
|
||||||
@@ -61,7 +54,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
tag: ${{ env.RELEASE }}
|
tag: nightly
|
||||||
|
|
||||||
build-linux-aarch64:
|
build-linux-aarch64:
|
||||||
env:
|
env:
|
||||||
@@ -84,11 +77,8 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
mode: 'release'
|
mode: 'release'
|
||||||
|
|
||||||
- name: v8 snapshot
|
|
||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=generic -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseSafe -Dcpu=generic -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 }}
|
||||||
@@ -103,7 +93,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
tag: ${{ env.RELEASE }}
|
tag: nightly
|
||||||
|
|
||||||
build-macos-aarch64:
|
build-macos-aarch64:
|
||||||
env:
|
env:
|
||||||
@@ -128,11 +118,8 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
mode: 'release'
|
mode: 'release'
|
||||||
|
|
||||||
- name: v8 snapshot
|
|
||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -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 }}
|
||||||
@@ -147,7 +134,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
tag: ${{ env.RELEASE }}
|
tag: nightly
|
||||||
|
|
||||||
build-macos-x86_64:
|
build-macos-x86_64:
|
||||||
env:
|
env:
|
||||||
@@ -170,11 +157,8 @@ jobs:
|
|||||||
arch: ${{env.ARCH}}
|
arch: ${{env.ARCH}}
|
||||||
mode: 'release'
|
mode: 'release'
|
||||||
|
|
||||||
- name: v8 snapshot
|
|
||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dgit_commit=$(git rev-parse --short ${{ github.sha }})
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -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 }}
|
||||||
@@ -189,4 +173,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
artifacts: lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
tag: ${{ env.RELEASE }}
|
tag: nightly
|
||||||
|
|||||||
12
Dockerfile
12
Dockerfile
@@ -3,7 +3,7 @@ FROM debian:stable-slim
|
|||||||
ARG MINISIG=0.12
|
ARG MINISIG=0.12
|
||||||
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
|
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
|
||||||
ARG V8=14.0.365.4
|
ARG V8=14.0.365.4
|
||||||
ARG ZIG_V8=v0.2.2
|
ARG ZIG_V8=v0.1.37
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
RUN apt-get update -yq && \
|
RUN apt-get update -yq && \
|
||||||
@@ -48,16 +48,8 @@ RUN case $TARGETPLATFORM in \
|
|||||||
mkdir -p v8/ && \
|
mkdir -p v8/ && \
|
||||||
mv libc_v8.a v8/libc_v8.a
|
mv libc_v8.a v8/libc_v8.a
|
||||||
|
|
||||||
# build v8 snapshot
|
|
||||||
RUN zig build -Doptimize=ReleaseFast \
|
|
||||||
-Dprebuilt_v8_path=v8/libc_v8.a \
|
|
||||||
snapshot_creator -- src/snapshot.bin
|
|
||||||
|
|
||||||
# build release
|
# build release
|
||||||
RUN zig build -Doptimize=ReleaseFast \
|
RUN zig build -Doptimize=ReleaseFast -Dprebuilt_v8_path=v8/libc_v8.a -Dgit_commit=$(git rev-parse --short HEAD)
|
||||||
-Dsnapshot_path=../../snapshot.bin \
|
|
||||||
-Dprebuilt_v8_path=v8/libc_v8.a \
|
|
||||||
-Dgit_commit=$(git rev-parse --short HEAD)
|
|
||||||
|
|
||||||
FROM debian:stable-slim
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
|||||||
14
Makefile
14
Makefile
@@ -47,18 +47,12 @@ help:
|
|||||||
|
|
||||||
# $(ZIG) commands
|
# $(ZIG) commands
|
||||||
# ------------
|
# ------------
|
||||||
.PHONY: build build-v8-snapshot build-dev run run-release shell test bench wpt data end2end
|
.PHONY: build build-dev run run-release shell test bench wpt data end2end
|
||||||
|
|
||||||
## Build v8 snapshot
|
## Build in release-safe mode
|
||||||
build-v8-snapshot:
|
build:
|
||||||
@printf "\033[36mBuilding v8 snapshot (release safe)...\033[0m\n"
|
|
||||||
@$(ZIG) build -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
|
||||||
@printf "\033[33mBuild OK\033[0m\n"
|
|
||||||
|
|
||||||
## Build in release-fast mode
|
|
||||||
build: build-v8-snapshot
|
|
||||||
@printf "\033[36mBuilding (release safe)...\033[0m\n"
|
@printf "\033[36mBuilding (release safe)...\033[0m\n"
|
||||||
@$(ZIG) build -Doptimize=ReleaseFast -Dsnapshot_path=../../snapshot.bin -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
@$(ZIG) build -Doptimize=ReleaseSafe -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
||||||
@printf "\033[33mBuild OK\033[0m\n"
|
@printf "\033[33mBuild OK\033[0m\n"
|
||||||
|
|
||||||
## Build in debug mode
|
## Build in debug mode
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -211,23 +211,6 @@ env.
|
|||||||
|
|
||||||
But you can directly use the zig command: `zig build run`.
|
But you can directly use the zig command: `zig build run`.
|
||||||
|
|
||||||
#### Embed v8 snapshot
|
|
||||||
|
|
||||||
Lighpanda uses v8 snapshot. By default, it is created on startup but you can
|
|
||||||
embed it by using the following commands:
|
|
||||||
|
|
||||||
Generate the snapshot.
|
|
||||||
```
|
|
||||||
zig build snapshot_creator -- src/snapshot.bin
|
|
||||||
```
|
|
||||||
|
|
||||||
Build using the snapshot binary.
|
|
||||||
```
|
|
||||||
zig build -Dsnapshot_path=../../snapshot.bin
|
|
||||||
```
|
|
||||||
|
|
||||||
See [#1279](https://github.com/lightpanda-io/browser/pull/1279) for more details.
|
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
### Unit Tests
|
### Unit Tests
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.v8 = .{
|
.v8 = .{
|
||||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/d6b5f89cfc7feece29359e8c848bb916e8ecfab6.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/0d64a3d5b36ac94067df3e13fddbf715caa6f391.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH6_0gBABrJc5cL6-P2wGvvweTTCgWdpmClr9r-C-s",
|
.hash = "v8-0.0.0-xddH65sfBAC8o3q41YxhOms5uY2fvMzBrsgN8IeCXZgE",
|
||||||
},
|
},
|
||||||
//.v8 = .{ .path = "../zig-v8-fork" },
|
//.v8 = .{ .path = "../zig-v8-fork" },
|
||||||
.@"boringssl-zig" = .{
|
.@"boringssl-zig" = .{
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ arena: Allocator,
|
|||||||
listener_pool: std.heap.MemoryPool(Listener),
|
listener_pool: std.heap.MemoryPool(Listener),
|
||||||
list_pool: std.heap.MemoryPool(std.DoublyLinkedList),
|
list_pool: std.heap.MemoryPool(std.DoublyLinkedList),
|
||||||
lookup: std.AutoHashMapUnmanaged(usize, *std.DoublyLinkedList),
|
lookup: std.AutoHashMapUnmanaged(usize, *std.DoublyLinkedList),
|
||||||
dispatch_depth: usize,
|
dispatch_depth: u32 = 0,
|
||||||
deferred_removals: std.ArrayList(struct { list: *std.DoublyLinkedList, listener: *Listener }),
|
|
||||||
|
|
||||||
pub fn init(page: *Page) EventManager {
|
pub fn init(page: *Page) EventManager {
|
||||||
return .{
|
return .{
|
||||||
@@ -51,7 +50,6 @@ pub fn init(page: *Page) EventManager {
|
|||||||
.list_pool = std.heap.MemoryPool(std.DoublyLinkedList).init(page.arena),
|
.list_pool = std.heap.MemoryPool(std.DoublyLinkedList).init(page.arena),
|
||||||
.listener_pool = std.heap.MemoryPool(Listener).init(page.arena),
|
.listener_pool = std.heap.MemoryPool(Listener).init(page.arena),
|
||||||
.dispatch_depth = 0,
|
.dispatch_depth = 0,
|
||||||
.deferred_removals = .{},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +119,9 @@ pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, call
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(self: *EventManager, target: *EventTarget, typ: []const u8, callback: Callback, use_capture: bool) void {
|
pub fn remove(self: *EventManager, target: *EventTarget, typ: []const u8, callback: Callback, use_capture: bool) void {
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
log.debug(.event, "eventManager.remove", .{ .type = typ, .capture = use_capture, .target = target });
|
||||||
|
}
|
||||||
const list = self.lookup.get(@intFromPtr(target)) orelse return;
|
const list = self.lookup.get(@intFromPtr(target)) orelse return;
|
||||||
if (findListener(list, typ, callback, use_capture)) |listener| {
|
if (findListener(list, typ, callback, use_capture)) |listener| {
|
||||||
self.removeListener(list, listener);
|
self.removeListener(list, listener);
|
||||||
@@ -185,7 +186,7 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
|||||||
|
|
||||||
if (function_) |func| {
|
if (function_) |func| {
|
||||||
event._current_target = target;
|
event._current_target = target;
|
||||||
if (func.callWithThis(void, target, .{event})) {
|
if (func.call(void, .{event})) {
|
||||||
was_dispatched = true;
|
was_dispatched = true;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
// a non-JS error
|
// a non-JS error
|
||||||
@@ -298,53 +299,38 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
const page = self.page;
|
const page = self.page;
|
||||||
const typ = event._type_string;
|
const typ = event._type_string;
|
||||||
|
|
||||||
// Track dispatch depth for deferred removal
|
// Track that we're dispatching to prevent immediate removal
|
||||||
self.dispatch_depth += 1;
|
self.dispatch_depth += 1;
|
||||||
defer {
|
defer {
|
||||||
const dispatch_depth = self.dispatch_depth;
|
self.dispatch_depth -= 1;
|
||||||
// Only destroy deferred listeners when we exit the outermost dispatch
|
// Clean up any marked listeners in this target's list after this phase
|
||||||
if (dispatch_depth == 1) {
|
// We do this regardless of depth to handle cross-target removals correctly
|
||||||
for (self.deferred_removals.items) |removal| {
|
self.cleanupMarkedListeners(list);
|
||||||
removal.list.remove(&removal.listener.node);
|
|
||||||
self.listener_pool.destroy(removal.listener);
|
|
||||||
}
|
|
||||||
self.deferred_removals.clearRetainingCapacity();
|
|
||||||
} else {
|
|
||||||
self.dispatch_depth = dispatch_depth - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the last listener in the list as sentinel - listeners added during dispatch will be after it
|
|
||||||
const last_node = list.last orelse return;
|
|
||||||
const last_listener: *Listener = @alignCast(@fieldParentPtr("node", last_node));
|
|
||||||
|
|
||||||
// Iterate through the list, stopping after we've encountered the last_listener
|
|
||||||
var node = list.first;
|
var node = list.first;
|
||||||
var is_done = false;
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (is_done) {
|
// do this now, in case we need to remove n (once: true or aborted signal)
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
|
|
||||||
is_done = (listener == last_listener);
|
|
||||||
node = n.next;
|
node = n.next;
|
||||||
|
|
||||||
// Skip non-matching listeners
|
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
|
||||||
|
|
||||||
|
// Skip listeners that were marked for removal
|
||||||
|
if (listener.marked_for_removal) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!listener.typ.eql(typ)) {
|
if (!listener.typ.eql(typ)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Can be null when dispatching to the target itself
|
||||||
if (comptime capture_only) |capture| {
|
if (comptime capture_only) |capture| {
|
||||||
if (listener.capture != capture) {
|
if (listener.capture != capture) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip removed listeners
|
|
||||||
if (listener.removed) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the listener has an aborted signal, remove it and skip
|
// If the listener has an aborted signal, remove it and skip
|
||||||
if (listener.signal) |signal| {
|
if (listener.signal) |signal| {
|
||||||
if (signal.getAborted()) {
|
if (signal.getAborted()) {
|
||||||
@@ -353,11 +339,6 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove "once" listeners BEFORE calling them so nested dispatches don't see them
|
|
||||||
if (listener.once) {
|
|
||||||
self.removeListener(list, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
was_handled.* = true;
|
was_handled.* = true;
|
||||||
event._current_target = current_target;
|
event._current_target = current_target;
|
||||||
|
|
||||||
@@ -368,7 +349,7 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (listener.function) {
|
switch (listener.function) {
|
||||||
.value => |value| try value.callWithThis(void, current_target, .{event}),
|
.value => |value| try value.call(void, .{event}),
|
||||||
.string => |string| {
|
.string => |string| {
|
||||||
const str = try page.call_arena.dupeZ(u8, string.str());
|
const str = try page.call_arena.dupeZ(u8, string.str());
|
||||||
try self.page.js.eval(str, null);
|
try self.page.js.eval(str, null);
|
||||||
@@ -385,6 +366,10 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
event._target = original_target;
|
event._target = original_target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (listener.once) {
|
||||||
|
self.removeListener(list, listener);
|
||||||
|
}
|
||||||
|
|
||||||
if (event._stop_immediate_propagation) {
|
if (event._stop_immediate_propagation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -397,17 +382,29 @@ fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn removeListener(self: *EventManager, list: *std.DoublyLinkedList, listener: *Listener) void {
|
fn removeListener(self: *EventManager, list: *std.DoublyLinkedList, listener: *Listener) void {
|
||||||
// If we're in a dispatch, defer removal to avoid invalidating iteration
|
|
||||||
if (self.dispatch_depth > 0) {
|
if (self.dispatch_depth > 0) {
|
||||||
listener.removed = true;
|
// We're in the middle of dispatching, just mark for removal
|
||||||
self.deferred_removals.append(self.arena, .{ .list = list, .listener = listener }) catch unreachable;
|
// This prevents invalidating the linked list during iteration
|
||||||
|
listener.marked_for_removal = true;
|
||||||
} else {
|
} else {
|
||||||
// Outside dispatch, remove immediately
|
// Safe to remove immediately
|
||||||
list.remove(&listener.node);
|
list.remove(&listener.node);
|
||||||
self.listener_pool.destroy(listener);
|
self.listener_pool.destroy(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cleanupMarkedListeners(self: *EventManager, list: *std.DoublyLinkedList) void {
|
||||||
|
var node = list.first;
|
||||||
|
while (node) |n| {
|
||||||
|
node = n.next;
|
||||||
|
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
|
||||||
|
if (listener.marked_for_removal) {
|
||||||
|
list.remove(&listener.node);
|
||||||
|
self.listener_pool.destroy(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn findListener(list: *const std.DoublyLinkedList, typ: []const u8, callback: Callback, capture: bool) ?*Listener {
|
fn findListener(list: *const std.DoublyLinkedList, typ: []const u8, callback: Callback, capture: bool) ?*Listener {
|
||||||
var node = list.first;
|
var node = list.first;
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
@@ -439,7 +436,7 @@ const Listener = struct {
|
|||||||
function: Function,
|
function: Function,
|
||||||
signal: ?*@import("webapi/AbortSignal.zig") = null,
|
signal: ?*@import("webapi/AbortSignal.zig") = null,
|
||||||
node: std.DoublyLinkedList.Node,
|
node: std.DoublyLinkedList.Node,
|
||||||
removed: bool = false,
|
marked_for_removal: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Function = union(enum) {
|
const Function = union(enum) {
|
||||||
|
|||||||
@@ -168,18 +168,6 @@ pub fn eventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
|
|||||||
return chain.get(1);
|
return chain.get(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eventInit(typ: []const u8, value: anytype, page: *Page) !Event {
|
|
||||||
// Round to 2ms for privacy (browsers do this)
|
|
||||||
const raw_timestamp = @import("../datetime.zig").milliTimestamp(.monotonic);
|
|
||||||
const time_stamp = (raw_timestamp / 2) * 2;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
._type = unionInit(Event.Type, value),
|
|
||||||
._type_string = try String.init(page.arena, typ, .{}),
|
|
||||||
._time_stamp = time_stamp,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is a root object
|
// this is a root object
|
||||||
pub fn event(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
|
pub fn event(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
|
||||||
const allocator = self._slab.allocator();
|
const allocator = self._slab.allocator();
|
||||||
@@ -190,7 +178,10 @@ pub fn event(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
|
|||||||
|
|
||||||
// Special case: Event has a _type_string field, so we need manual setup
|
// Special case: Event has a _type_string field, so we need manual setup
|
||||||
const event_ptr = chain.get(0);
|
const event_ptr = chain.get(0);
|
||||||
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
|
event_ptr.* = .{
|
||||||
|
._type = unionInit(Event.Type, chain.get(1)),
|
||||||
|
._type_string = try String.init(self._page.arena, typ, .{}),
|
||||||
|
};
|
||||||
chain.setLeaf(1, child);
|
chain.setLeaf(1, child);
|
||||||
|
|
||||||
return chain.get(1);
|
return chain.get(1);
|
||||||
@@ -205,7 +196,10 @@ pub fn uiEvent(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child)
|
|||||||
|
|
||||||
// Special case: Event has a _type_string field, so we need manual setup
|
// Special case: Event has a _type_string field, so we need manual setup
|
||||||
const event_ptr = chain.get(0);
|
const event_ptr = chain.get(0);
|
||||||
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
|
event_ptr.* = .{
|
||||||
|
._type = unionInit(Event.Type, chain.get(1)),
|
||||||
|
._type_string = try String.init(self._page.arena, typ, .{}),
|
||||||
|
};
|
||||||
chain.setMiddle(1, UIEvent.Type);
|
chain.setMiddle(1, UIEvent.Type);
|
||||||
chain.setLeaf(2, child);
|
chain.setLeaf(2, child);
|
||||||
|
|
||||||
|
|||||||
@@ -93,9 +93,7 @@ _attribute_named_node_map_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attri
|
|||||||
_element_styles: Element.StyleLookup = .{},
|
_element_styles: Element.StyleLookup = .{},
|
||||||
_element_datasets: Element.DatasetLookup = .{},
|
_element_datasets: Element.DatasetLookup = .{},
|
||||||
_element_class_lists: Element.ClassListLookup = .{},
|
_element_class_lists: Element.ClassListLookup = .{},
|
||||||
_element_rel_lists: Element.RelListLookup = .{},
|
|
||||||
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
||||||
_node_owner_documents: Node.OwnerDocumentLookup = .{},
|
|
||||||
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||||
|
|
||||||
_script_manager: ScriptManager,
|
_script_manager: ScriptManager,
|
||||||
@@ -265,9 +263,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._element_styles = .{};
|
self._element_styles = .{};
|
||||||
self._element_datasets = .{};
|
self._element_datasets = .{};
|
||||||
self._element_class_lists = .{};
|
self._element_class_lists = .{};
|
||||||
self._element_rel_lists = .{};
|
|
||||||
self._element_shadow_roots = .{};
|
self._element_shadow_roots = .{};
|
||||||
self._node_owner_documents = .{};
|
|
||||||
self._element_assigned_slots = .{};
|
self._element_assigned_slots = .{};
|
||||||
self._notified_network_idle = .init;
|
self._notified_network_idle = .init;
|
||||||
self._notified_network_almost_idle = .init;
|
self._notified_network_almost_idle = .init;
|
||||||
@@ -997,32 +993,21 @@ pub fn domChanged(self: *Page) void {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ElementIdMaps = struct { lookup: *std.StringHashMapUnmanaged(*Element), removed_ids: *std.StringHashMapUnmanaged(void) };
|
fn getElementIdMap(page: *Page, node: *Node) *std.StringHashMapUnmanaged(*Element) {
|
||||||
|
|
||||||
fn getElementIdMap(page: *Page, node: *Node) ElementIdMaps {
|
|
||||||
// Walk up the tree checking for ShadowRoot and tracking the root
|
// Walk up the tree checking for ShadowRoot and tracking the root
|
||||||
var current = node;
|
var current = node;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (current.is(ShadowRoot)) |shadow_root| {
|
if (current.is(ShadowRoot)) |shadow_root| {
|
||||||
return .{
|
return &shadow_root._elements_by_id;
|
||||||
.lookup = &shadow_root._elements_by_id,
|
|
||||||
.removed_ids = &shadow_root._removed_ids,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = current._parent orelse {
|
const parent = current._parent orelse {
|
||||||
if (current._type == .document) {
|
if (current._type == .document) {
|
||||||
return .{
|
return ¤t._type.document._elements_by_id;
|
||||||
.lookup = ¤t._type.document._elements_by_id,
|
|
||||||
.removed_ids = ¤t._type.document._removed_ids,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
// Detached nodes should not have IDs registered
|
// Detached nodes should not have IDs registered
|
||||||
std.debug.assert(false);
|
std.debug.assert(false);
|
||||||
return .{
|
return &page.document._elements_by_id;
|
||||||
.lookup = &page.document._elements_by_id,
|
|
||||||
.removed_ids = &page.document._removed_ids,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
current = parent;
|
current = parent;
|
||||||
@@ -1030,35 +1015,22 @@ fn getElementIdMap(page: *Page, node: *Node) ElementIdMaps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn addElementId(self: *Page, parent: *Node, element: *Element, id: []const u8) !void {
|
pub fn addElementId(self: *Page, parent: *Node, element: *Element, id: []const u8) !void {
|
||||||
var id_maps = self.getElementIdMap(parent);
|
var id_map = self.getElementIdMap(parent);
|
||||||
const gop = try id_maps.lookup.getOrPut(self.arena, id);
|
const gop = try id_map.getOrPut(self.arena, id);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
gop.value_ptr.* = element;
|
gop.value_ptr.* = element;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = gop.value_ptr.*.asNode();
|
|
||||||
switch (element.asNode().compareDocumentPosition(existing)) {
|
|
||||||
0x04 => gop.value_ptr.* = element,
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeElementId(self: *Page, element: *Element, id: []const u8) void {
|
pub fn removeElementId(self: *Page, element: *Element, id: []const u8) void {
|
||||||
const node = element.asNode();
|
var id_map = self.getElementIdMap(element.asNode());
|
||||||
self.removeElementIdWithMaps(self.getElementIdMap(node), id);
|
_ = id_map.remove(id);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn removeElementIdWithMaps(self: *Page, id_maps: ElementIdMaps, id: []const u8) void {
|
|
||||||
if (id_maps.lookup.remove(id)) {
|
|
||||||
id_maps.removed_ids.put(self.arena, id, {}) catch {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Element {
|
pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Element {
|
||||||
if (node.isConnected() or node.isInShadowTree()) {
|
if (node.isConnected() or node.isInShadowTree()) {
|
||||||
const lookup = self.getElementIdMap(node).lookup;
|
const id_map = self.getElementIdMap(node);
|
||||||
return lookup.get(id);
|
return id_map.get(id);
|
||||||
}
|
}
|
||||||
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(node, .{});
|
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(node, .{});
|
||||||
while (tw.next()) |el| {
|
while (tw.next()) |el| {
|
||||||
@@ -1315,26 +1287,6 @@ pub fn nodeComplete(self: *Page, node: *Node) !void {
|
|||||||
return self.nodeIsReady(true, node);
|
return self.nodeIsReady(true, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the owner document for a node. Only stores entries for nodes whose owner
|
|
||||||
// is NOT page.document to minimize memory overhead.
|
|
||||||
pub fn setNodeOwnerDocument(self: *Page, node: *Node, owner: *Document) !void {
|
|
||||||
if (owner == self.document) {
|
|
||||||
// No need to store if it's the main document - remove if present
|
|
||||||
_ = self._node_owner_documents.remove(node);
|
|
||||||
} else {
|
|
||||||
try self._node_owner_documents.put(self.arena, node, owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively sets the owner document for a node and all its descendants
|
|
||||||
pub fn adoptNodeTree(self: *Page, node: *Node, new_owner: *Document) !void {
|
|
||||||
try self.setNodeOwnerDocument(node, new_owner);
|
|
||||||
var it = node.childrenIterator();
|
|
||||||
while (it.next()) |child| {
|
|
||||||
try self.adoptNodeTree(child, new_owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_iterator: anytype) !*Node {
|
pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_iterator: anytype) !*Node {
|
||||||
const namespace: Element.Namespace = blk: {
|
const namespace: Element.Namespace = blk: {
|
||||||
const ns = ns_ orelse break :blk .html;
|
const ns = ns_ orelse break :blk .html;
|
||||||
@@ -2143,7 +2095,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
|
|||||||
// grab this before we null the parent
|
// grab this before we null the parent
|
||||||
const was_connected = child.isConnected();
|
const was_connected = child.isConnected();
|
||||||
// Capture the ID map before disconnecting, so we can remove IDs from the correct document
|
// Capture the ID map before disconnecting, so we can remove IDs from the correct document
|
||||||
const id_maps = if (was_connected) self.getElementIdMap(child) else null;
|
const id_map = if (was_connected) self.getElementIdMap(child) else null;
|
||||||
|
|
||||||
child._parent = null;
|
child._parent = null;
|
||||||
child._child_link = .{};
|
child._child_link = .{};
|
||||||
@@ -2194,7 +2146,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
|
|||||||
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{});
|
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{});
|
||||||
while (tw.next()) |el| {
|
while (tw.next()) |el| {
|
||||||
if (el.getAttributeSafe("id")) |id| {
|
if (el.getAttributeSafe("id")) |id| {
|
||||||
self.removeElementIdWithMaps(id_maps.?, id);
|
_ = id_map.?.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Element.Html.Custom.invokeDisconnectedCallbackOnElement(el, self);
|
Element.Html.Custom.invokeDisconnectedCallbackOnElement(el, self);
|
||||||
@@ -2218,9 +2170,9 @@ pub fn appendAllChildren(self: *Page, parent: *Node, target: *Node) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insertAllChildrenBefore(self: *Page, fragment: *Node, parent: *Node, ref_node: *Node) !void {
|
pub fn insertAllChildrenBefore(self: *Page, fragment: *Node, target: *Node, ref_node: *Node) !void {
|
||||||
self.domChanged();
|
self.domChanged();
|
||||||
const dest_connected = parent.isConnected();
|
const dest_connected = target.isConnected();
|
||||||
|
|
||||||
var it = fragment.childrenIterator();
|
var it = fragment.childrenIterator();
|
||||||
while (it.next()) |child| {
|
while (it.next()) |child| {
|
||||||
@@ -2228,7 +2180,7 @@ pub fn insertAllChildrenBefore(self: *Page, fragment: *Node, parent: *Node, ref_
|
|||||||
const child_was_connected = child.isConnected();
|
const child_was_connected = child.isConnected();
|
||||||
self.removeNode(fragment, child, .{ .will_be_reconnected = dest_connected });
|
self.removeNode(fragment, child, .{ .will_be_reconnected = dest_connected });
|
||||||
try self.insertNodeRelative(
|
try self.insertNodeRelative(
|
||||||
parent,
|
target,
|
||||||
child,
|
child,
|
||||||
.{ .before = ref_node },
|
.{ .before = ref_node },
|
||||||
.{ .child_already_connected = child_was_connected },
|
.{ .child_already_connected = child_was_connected },
|
||||||
@@ -2236,6 +2188,10 @@ pub fn insertAllChildrenBefore(self: *Page, fragment: *Node, parent: *Node, ref_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn _appendNode(self: *Page, comptime from_parser: bool, parent: *Node, child: *Node, opts: InsertNodeOpts) !void {
|
||||||
|
self._insertNodeRelative(from_parser, parent, child, .append, opts);
|
||||||
|
}
|
||||||
|
|
||||||
const InsertNodeRelative = union(enum) {
|
const InsertNodeRelative = union(enum) {
|
||||||
append,
|
append,
|
||||||
after: *Node,
|
after: *Node,
|
||||||
|
|||||||
@@ -239,17 +239,8 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
};
|
};
|
||||||
|
|
||||||
const is_blocking = script.mode == .normal;
|
const is_blocking = script.mode == .normal;
|
||||||
if (is_blocking == false) {
|
|
||||||
self.scriptList(script).append(&script.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remote_url) |url| {
|
if (remote_url) |url| {
|
||||||
errdefer {
|
errdefer script.deinit(true);
|
||||||
if (is_blocking == false) {
|
|
||||||
self.scriptList(script).remove(&script.node);
|
|
||||||
}
|
|
||||||
script.deinit(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var headers = try self.client.newHeaders();
|
var headers = try self.client.newHeaders();
|
||||||
try page.requestCookie(.{}).headersForRequest(page.arena, url, &headers);
|
try page.requestCookie(.{}).headersForRequest(page.arena, url, &headers);
|
||||||
@@ -280,6 +271,8 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_blocking == false) {
|
if (is_blocking == false) {
|
||||||
|
const list = self.scriptList(script);
|
||||||
|
list.append(&script.node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,295 +0,0 @@
|
|||||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
|
||||||
//
|
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as
|
|
||||||
// published by the Free Software Foundation, either version 3 of the
|
|
||||||
// License, or (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Tokenizer = @import("Tokenizer.zig");
|
|
||||||
|
|
||||||
pub const Declaration = struct {
|
|
||||||
name: []const u8,
|
|
||||||
value: []const u8,
|
|
||||||
important: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TokenSpan = struct {
|
|
||||||
token: Tokenizer.Token,
|
|
||||||
start: usize,
|
|
||||||
end: usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TokenStream = struct {
|
|
||||||
tokenizer: Tokenizer,
|
|
||||||
peeked: ?TokenSpan = null,
|
|
||||||
|
|
||||||
fn init(input: []const u8) TokenStream {
|
|
||||||
return .{ .tokenizer = .{ .input = input } };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nextRaw(self: *TokenStream) ?TokenSpan {
|
|
||||||
const start = self.tokenizer.position;
|
|
||||||
const token = self.tokenizer.next() orelse return null;
|
|
||||||
const end = self.tokenizer.position;
|
|
||||||
return .{ .token = token, .start = start, .end = end };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(self: *TokenStream) ?TokenSpan {
|
|
||||||
if (self.peeked) |token| {
|
|
||||||
self.peeked = null;
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
return self.nextRaw();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peek(self: *TokenStream) ?TokenSpan {
|
|
||||||
if (self.peeked == null) {
|
|
||||||
self.peeked = self.nextRaw();
|
|
||||||
}
|
|
||||||
return self.peeked;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn parseDeclarationsList(input: []const u8) DeclarationsIterator {
|
|
||||||
return DeclarationsIterator.init(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DeclarationsIterator = struct {
|
|
||||||
input: []const u8,
|
|
||||||
stream: TokenStream,
|
|
||||||
|
|
||||||
pub fn init(input: []const u8) DeclarationsIterator {
|
|
||||||
return .{
|
|
||||||
.input = input,
|
|
||||||
.stream = TokenStream.init(input),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *DeclarationsIterator) ?Declaration {
|
|
||||||
while (true) {
|
|
||||||
self.skipTriviaAndSemicolons();
|
|
||||||
const peeked = self.stream.peek() orelse return null;
|
|
||||||
|
|
||||||
switch (peeked.token) {
|
|
||||||
.at_keyword => {
|
|
||||||
_ = self.stream.next();
|
|
||||||
self.skipAtRule();
|
|
||||||
},
|
|
||||||
.ident => |name| {
|
|
||||||
_ = self.stream.next();
|
|
||||||
if (self.consumeDeclaration(name)) |declaration| {
|
|
||||||
return declaration;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
_ = self.stream.next();
|
|
||||||
self.skipInvalidDeclaration();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consumeDeclaration(self: *DeclarationsIterator, name: []const u8) ?Declaration {
|
|
||||||
self.skipTrivia();
|
|
||||||
|
|
||||||
const colon = self.stream.next() orelse return null;
|
|
||||||
if (!isColon(colon.token)) {
|
|
||||||
self.skipInvalidDeclaration();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = self.consumeValue() orelse return null;
|
|
||||||
return .{
|
|
||||||
.name = name,
|
|
||||||
.value = value.value,
|
|
||||||
.important = value.important,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ValueResult = struct {
|
|
||||||
value: []const u8,
|
|
||||||
important: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn consumeValue(self: *DeclarationsIterator) ?ValueResult {
|
|
||||||
self.skipTrivia();
|
|
||||||
|
|
||||||
var depth: usize = 0;
|
|
||||||
var start: ?usize = null;
|
|
||||||
var last_sig: ?TokenSpan = null;
|
|
||||||
var prev_sig: ?TokenSpan = null;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const peeked = self.stream.peek() orelse break;
|
|
||||||
if (isSemicolon(peeked.token) and depth == 0) {
|
|
||||||
_ = self.stream.next();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const span = self.stream.next() orelse break;
|
|
||||||
if (isWhitespaceOrComment(span.token)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start == null) start = span.start;
|
|
||||||
prev_sig = last_sig;
|
|
||||||
last_sig = span;
|
|
||||||
updateDepth(span.token, &depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
const value_start = start orelse return null;
|
|
||||||
const last = last_sig orelse return null;
|
|
||||||
|
|
||||||
var important = false;
|
|
||||||
var end_pos = last.end;
|
|
||||||
|
|
||||||
if (isImportantPair(prev_sig, last)) {
|
|
||||||
important = true;
|
|
||||||
const bang = prev_sig orelse return null;
|
|
||||||
if (value_start >= bang.start) return null;
|
|
||||||
end_pos = bang.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value_slice = self.input[value_start..end_pos];
|
|
||||||
value_slice = std.mem.trim(u8, value_slice, &std.ascii.whitespace);
|
|
||||||
if (value_slice.len == 0) return null;
|
|
||||||
|
|
||||||
return .{ .value = value_slice, .important = important };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skipTrivia(self: *DeclarationsIterator) void {
|
|
||||||
while (self.stream.peek()) |peeked| {
|
|
||||||
if (!isWhitespaceOrComment(peeked.token)) break;
|
|
||||||
_ = self.stream.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skipTriviaAndSemicolons(self: *DeclarationsIterator) void {
|
|
||||||
while (self.stream.peek()) |peeked| {
|
|
||||||
if (isWhitespaceOrComment(peeked.token) or isSemicolon(peeked.token)) {
|
|
||||||
_ = self.stream.next();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skipAtRule(self: *DeclarationsIterator) void {
|
|
||||||
var depth: usize = 0;
|
|
||||||
var saw_block = false;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const peeked = self.stream.peek() orelse return;
|
|
||||||
if (!saw_block and isSemicolon(peeked.token) and depth == 0) {
|
|
||||||
_ = self.stream.next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const span = self.stream.next() orelse return;
|
|
||||||
if (isWhitespaceOrComment(span.token)) continue;
|
|
||||||
|
|
||||||
if (isBlockStart(span.token)) {
|
|
||||||
depth += 1;
|
|
||||||
saw_block = true;
|
|
||||||
} else if (isBlockEnd(span.token)) {
|
|
||||||
if (depth > 0) depth -= 1;
|
|
||||||
if (saw_block and depth == 0) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skipInvalidDeclaration(self: *DeclarationsIterator) void {
|
|
||||||
var depth: usize = 0;
|
|
||||||
|
|
||||||
while (self.stream.peek()) |peeked| {
|
|
||||||
if (isSemicolon(peeked.token) and depth == 0) {
|
|
||||||
_ = self.stream.next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const span = self.stream.next() orelse return;
|
|
||||||
if (isWhitespaceOrComment(span.token)) continue;
|
|
||||||
updateDepth(span.token, &depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn isWhitespaceOrComment(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.white_space, .comment => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isSemicolon(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.semicolon => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isColon(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.colon => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isBlockStart(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.curly_bracket_block, .square_bracket_block, .parenthesis_block, .function => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isBlockEnd(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.close_curly_bracket, .close_parenthesis, .close_square_bracket => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn updateDepth(token: Tokenizer.Token, depth: *usize) void {
|
|
||||||
if (isBlockStart(token)) {
|
|
||||||
depth.* += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBlockEnd(token)) {
|
|
||||||
if (depth.* > 0) depth.* -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isImportantPair(prev_sig: ?TokenSpan, last_sig: TokenSpan) bool {
|
|
||||||
if (!isIdentImportant(last_sig.token)) return false;
|
|
||||||
const prev = prev_sig orelse return false;
|
|
||||||
return isBang(prev.token);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isIdentImportant(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.ident => |name| std.ascii.eqlIgnoreCase(name, "important"),
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isBang(token: Tokenizer.Token) bool {
|
|
||||||
return switch (token) {
|
|
||||||
.delim => |c| c == '!',
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -644,10 +644,8 @@ fn consumeNumeric(self: *Tokenizer) Token {
|
|||||||
fn consumeUnquotedUrl(self: *Tokenizer) ?Token {
|
fn consumeUnquotedUrl(self: *Tokenizer) ?Token {
|
||||||
// TODO: true url parser
|
// TODO: true url parser
|
||||||
if (self.nextByte()) |it| {
|
if (self.nextByte()) |it| {
|
||||||
return self.consumeString(it == '\'');
|
self.consumeString(it == '\'');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consumeIdentLike(self: *Tokenizer) Token {
|
fn consumeIdentLike(self: *Tokenizer) Token {
|
||||||
|
|||||||
@@ -89,10 +89,6 @@ pub const CallOpts = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn constructor(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
pub fn constructor(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
if (!info.isConstructCall()) {
|
|
||||||
self.handleError(T, @TypeOf(func), error.InvalidArgument, info, opts);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self._constructor(func, info) catch |err| {
|
self._constructor(func, info) catch |err| {
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -203,6 +203,27 @@ fn trackCallback(self: *Context, pf: PersistentFunction) !void {
|
|||||||
return self.callbacks.append(self.arena, pf);
|
return self.callbacks.append(self.arena, pf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an anytype, turns it into a v8.Object. The anytype could be:
|
||||||
|
// 1 - A V8.object already
|
||||||
|
// 2 - Our js.Object wrapper around a V8.Object
|
||||||
|
// 3 - A zig instance that has previously been given to V8
|
||||||
|
// (i.e., the value has to be known to the executor)
|
||||||
|
pub fn valueToExistingObject(self: *const Context, value: anytype) !v8.Object {
|
||||||
|
if (@TypeOf(value) == v8.Object) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@TypeOf(value) == js.Object) {
|
||||||
|
return value.js_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistent_object = self.identity_map.get(@intFromPtr(value)) orelse {
|
||||||
|
return error.InvalidThisForCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
return persistent_object.castToObject();
|
||||||
|
}
|
||||||
|
|
||||||
// == Executors ==
|
// == Executors ==
|
||||||
pub fn eval(self: *Context, src: []const u8, name: ?[]const u8) !void {
|
pub fn eval(self: *Context, src: []const u8, name: ?[]const u8) !void {
|
||||||
_ = try self.exec(src, name);
|
_ = try self.exec(src, name);
|
||||||
@@ -431,7 +452,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
|
|||||||
var js_arr = v8.Array.init(isolate, value.len);
|
var js_arr = v8.Array.init(isolate, value.len);
|
||||||
var js_obj = js_arr.castTo(v8.Object);
|
var js_obj = js_arr.castTo(v8.Object);
|
||||||
for (value, 0..) |v, i| {
|
for (value, 0..) |v, i| {
|
||||||
const js_val = try self.zigValueToJs(v, opts);
|
const js_val = try self.zigValueToJs(v, .{});
|
||||||
if (js_obj.setValueAtIndex(v8_context, @intCast(i), js_val) == false) {
|
if (js_obj.setValueAtIndex(v8_context, @intCast(i), js_val) == false) {
|
||||||
return error.FailedToCreateArray;
|
return error.FailedToCreateArray;
|
||||||
}
|
}
|
||||||
@@ -556,7 +577,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
|
|||||||
},
|
},
|
||||||
.optional => {
|
.optional => {
|
||||||
if (value) |v| {
|
if (value) |v| {
|
||||||
return self.zigValueToJs(v, opts);
|
return self.zigValueToJs(v, .{});
|
||||||
}
|
}
|
||||||
// would be handled by simpleZigValueToJs
|
// would be handled by simpleZigValueToJs
|
||||||
unreachable;
|
unreachable;
|
||||||
|
|||||||
@@ -74,23 +74,26 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
|||||||
|
|
||||||
const env = self.env;
|
const env = self.env;
|
||||||
const isolate = env.isolate;
|
const isolate = env.isolate;
|
||||||
const arena = self.context_arena.allocator();
|
|
||||||
|
|
||||||
var v8_context: v8.Context = blk: {
|
var v8_context: v8.Context = blk: {
|
||||||
var temp_scope: v8.HandleScope = undefined;
|
var temp_scope: v8.HandleScope = undefined;
|
||||||
v8.HandleScope.init(&temp_scope, isolate);
|
v8.HandleScope.init(&temp_scope, isolate);
|
||||||
defer temp_scope.deinit();
|
defer temp_scope.deinit();
|
||||||
|
|
||||||
// Creates a global template that inherits from Window.
|
if (comptime IS_DEBUG) {
|
||||||
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate, env.templates);
|
// Getting this into the snapshot is tricky (anything involving the
|
||||||
|
// global is tricky). Easier to do here, and in debug mode, we're
|
||||||
|
// fine with paying the small perf hit.
|
||||||
|
const js_global = v8.FunctionTemplate.initDefault(isolate);
|
||||||
|
const global_template = js_global.getInstanceTemplate();
|
||||||
|
|
||||||
// Add the named property handler
|
|
||||||
global_template.setNamedProperty(v8.NamedPropertyHandlerConfiguration{
|
global_template.setNamedProperty(v8.NamedPropertyHandlerConfiguration{
|
||||||
.getter = unknownPropertyCallback,
|
.getter = unknownPropertyCallback,
|
||||||
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
|
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
|
||||||
}, null);
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
const context_local = v8.Context.init(isolate, global_template, null);
|
const context_local = v8.Context.init(isolate, null, null);
|
||||||
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
|
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
|
||||||
break :blk v8_context;
|
break :blk v8_context;
|
||||||
};
|
};
|
||||||
@@ -121,7 +124,7 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
|||||||
.handle_scope = handle_scope,
|
.handle_scope = handle_scope,
|
||||||
.script_manager = &page._script_manager,
|
.script_manager = &page._script_manager,
|
||||||
.call_arena = page.call_arena,
|
.call_arena = page.call_arena,
|
||||||
.arena = arena,
|
.arena = self.context_arena.allocator(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var context = &self.context.?;
|
var context = &self.context.?;
|
||||||
@@ -156,9 +159,9 @@ pub fn resumeExecution(self: *const ExecutionWorld) void {
|
|||||||
|
|
||||||
pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
|
||||||
const context = Context.fromIsolate(info.getIsolate());
|
const context = Context.fromIsolate(info.getIsolate());
|
||||||
const maybe_property: ?[]u8 = context.valueToString(.{ .handle = c_name.? }, .{}) catch null;
|
|
||||||
|
const property = context.valueToString(.{ .handle = c_name.? }, .{}) catch "???";
|
||||||
|
|
||||||
const ignored = std.StaticStringMap(void).initComptime(.{
|
const ignored = std.StaticStringMap(void).initComptime(.{
|
||||||
.{ "process", {} },
|
.{ "process", {} },
|
||||||
@@ -182,27 +185,13 @@ pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C
|
|||||||
.{ "CLOSURE_FLAGS", {} },
|
.{ "CLOSURE_FLAGS", {} },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (maybe_property) |prop| {
|
if (!ignored.has(property)) {
|
||||||
if (!ignored.has(prop)) {
|
log.debug(.unknown_prop, "unkown global property", .{
|
||||||
const page = context.page;
|
|
||||||
const document = page.document;
|
|
||||||
|
|
||||||
if (document.getElementById(prop, page)) |el| {
|
|
||||||
const js_value = context.zigValueToJs(el, .{}) catch {
|
|
||||||
return v8.Intercepted.No;
|
|
||||||
};
|
|
||||||
|
|
||||||
info.getReturnValue().set(js_value);
|
|
||||||
return v8.Intercepted.Yes;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug(.unknown_prop, "unknown global property", .{
|
|
||||||
.info = "but the property can exist in pure JS",
|
.info = "but the property can exist in pure JS",
|
||||||
.stack = context.stackTrace() catch "???",
|
.stack = context.stackTrace() catch "???",
|
||||||
.property = prop,
|
.property = property,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return v8.Intercepted.No;
|
return v8.Intercepted.No;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,29 +116,7 @@ pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, a
|
|||||||
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
|
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
|
||||||
const context = self.context;
|
const context = self.context;
|
||||||
|
|
||||||
// When we're calling a function from within JavaScript itself, this isn't
|
const js_this = try context.valueToExistingObject(this);
|
||||||
// necessary. We're within a Caller instantiation, which will already have
|
|
||||||
// incremented the call_depth and it won't decrement it until the Caller is
|
|
||||||
// done.
|
|
||||||
// But some JS functions are initiated from Zig code, and not v8. For
|
|
||||||
// example, Observers, some event and window callbacks. In those cases, we
|
|
||||||
// need to increase the call_depth so that the call_arena remains valid for
|
|
||||||
// the duration of the function call. If we don't do this, the call_arena
|
|
||||||
// will be reset after each statement of the function which executes Zig code.
|
|
||||||
const call_depth = context.call_depth;
|
|
||||||
context.call_depth = call_depth + 1;
|
|
||||||
defer context.call_depth = call_depth;
|
|
||||||
|
|
||||||
const js_this = blk: {
|
|
||||||
if (@TypeOf(this) == v8.Object) {
|
|
||||||
break :blk this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@TypeOf(this) == js.Object) {
|
|
||||||
break :blk this.js_obj;
|
|
||||||
}
|
|
||||||
break :blk try context.zigValueToJs(this, .{});
|
|
||||||
};
|
|
||||||
|
|
||||||
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) error{
|
|||||||
const context = self.context;
|
const context = self.context;
|
||||||
|
|
||||||
const js_key = v8.String.initUtf8(context.isolate, key);
|
const js_key = v8.String.initUtf8(context.isolate, key);
|
||||||
const js_value = try context.zigValueToJs(value, .{});
|
const js_value = try context.zigValueToJs(value);
|
||||||
|
|
||||||
const res = self.js_obj.defineOwnProperty(context.v8_context, js_key.toName(), js_value, @bitCast(opts)) orelse false;
|
const res = self.js_obj.defineOwnProperty(context.v8_context, js_key.toName(), js_value, @bitCast(opts)) orelse false;
|
||||||
if (!res) {
|
if (!res) {
|
||||||
|
|||||||
@@ -113,17 +113,6 @@ fn isValid(self: Snapshot) bool {
|
|||||||
return v8.SnapshotCreator.startupDataIsValid(self.startup_data);
|
return v8.SnapshotCreator.startupDataIsValid(self.startup_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createGlobalTemplate(isolate: v8.Isolate, templates: []const v8.FunctionTemplate) v8.ObjectTemplate {
|
|
||||||
// Set up the global template to inherit from Window's template
|
|
||||||
// This way the global object gets all Window properties through inheritance
|
|
||||||
const js_global = v8.FunctionTemplate.initDefault(isolate);
|
|
||||||
js_global.setClassName(v8.String.initUtf8(isolate, "Window"));
|
|
||||||
// Find Window in JsApis by name (avoids circular import)
|
|
||||||
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
|
|
||||||
js_global.inherit(templates[window_index]);
|
|
||||||
return js_global.getInstanceTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create(allocator: Allocator) !Snapshot {
|
pub fn create(allocator: Allocator) !Snapshot {
|
||||||
var external_references = collectExternalReferences();
|
var external_references = collectExternalReferences();
|
||||||
|
|
||||||
@@ -165,7 +154,14 @@ pub fn create(allocator: Allocator) !Snapshot {
|
|||||||
|
|
||||||
// Set up the global template to inherit from Window's template
|
// Set up the global template to inherit from Window's template
|
||||||
// This way the global object gets all Window properties through inheritance
|
// This way the global object gets all Window properties through inheritance
|
||||||
const global_template = createGlobalTemplate(isolate, templates[0..]);
|
const js_global = v8.FunctionTemplate.initDefault(isolate);
|
||||||
|
js_global.setClassName(v8.String.initUtf8(isolate, "Window"));
|
||||||
|
|
||||||
|
// Find Window in JsApis by name (avoids circular import)
|
||||||
|
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
|
||||||
|
js_global.inherit(templates[window_index]);
|
||||||
|
|
||||||
|
const global_template = js_global.getInstanceTemplate();
|
||||||
|
|
||||||
const context = v8.Context.init(isolate, global_template, null);
|
const context = v8.Context.init(isolate, global_template, null);
|
||||||
context.enter();
|
context.enter();
|
||||||
@@ -411,7 +407,7 @@ fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.FunctionT
|
|||||||
},
|
},
|
||||||
bridge.Function => {
|
bridge.Function => {
|
||||||
const function_template = v8.FunctionTemplate.initCallback(isolate, value.func);
|
const function_template = v8.FunctionTemplate.initCallback(isolate, value.func);
|
||||||
const js_name = v8.String.initUtf8(isolate, name).toName();
|
const js_name: v8.Name = v8.String.initUtf8(isolate, name).toName();
|
||||||
if (value.static) {
|
if (value.static) {
|
||||||
template.set(js_name, function_template, v8.PropertyAttribute.None);
|
template.set(js_name, function_template, v8.PropertyAttribute.None);
|
||||||
} else {
|
} else {
|
||||||
@@ -460,12 +456,6 @@ fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.FunctionT
|
|||||||
instance_template.markAsUndetectable();
|
instance_template.markAsUndetectable();
|
||||||
instance_template.setCallAsFunctionHandler(JsApi.Meta.callable.func);
|
instance_template.setCallAsFunctionHandler(JsApi.Meta.callable.func);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (@hasDecl(JsApi.Meta, "name")) {
|
|
||||||
const js_name = v8.Symbol.getToStringTag(isolate).toName();
|
|
||||||
const instance_template = template.getInstanceTemplate();
|
|
||||||
instance_template.set(js_name, v8.String.initUtf8(isolate, JsApi.Meta.name), v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt {
|
fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt {
|
||||||
|
|||||||
@@ -41,14 +41,6 @@ pub fn isArray(self: Value) bool {
|
|||||||
return self.js_val.isArray();
|
return self.js_val.isArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isNull(self: Value) bool {
|
|
||||||
return self.js_val.isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isUndefined(self: Value) bool {
|
|
||||||
return self.js_val.isUndefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toString(self: Value, allocator: Allocator) ![]const u8 {
|
pub fn toString(self: Value, allocator: Allocator) ![]const u8 {
|
||||||
return self.context.valueToString(self.js_val, .{ .allocator = allocator });
|
return self.context.valueToString(self.js_val, .{ .allocator = allocator });
|
||||||
}
|
}
|
||||||
@@ -69,10 +61,6 @@ pub fn persist(self: Value) !Value {
|
|||||||
return Value{ .context = context, .js_val = persisted.toValue() };
|
return Value{ .context = context, .js_val = persisted.toValue() };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toZig(self: Value, comptime T: type) !T {
|
|
||||||
return self.context.jsValueToZig(T, self.js_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toObject(self: Value) js.Object {
|
pub fn toObject(self: Value) js.Object {
|
||||||
return .{
|
return .{
|
||||||
.context = self.context,
|
.context = self.context,
|
||||||
|
|||||||
@@ -98,29 +98,6 @@ pub fn parse(self: *Parser, html: []const u8) void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parseXML(self: *Parser, xml: []const u8) void {
|
|
||||||
h5e.xml5ever_parse_document(
|
|
||||||
xml.ptr,
|
|
||||||
xml.len,
|
|
||||||
&self.container,
|
|
||||||
self,
|
|
||||||
createElementCallback,
|
|
||||||
getDataCallback,
|
|
||||||
appendCallback,
|
|
||||||
parseErrorCallback,
|
|
||||||
popCallback,
|
|
||||||
createCommentCallback,
|
|
||||||
createProcessingInstruction,
|
|
||||||
appendDoctypeToDocument,
|
|
||||||
addAttrsIfMissingCallback,
|
|
||||||
getTemplateContentsCallback,
|
|
||||||
removeFromParentCallback,
|
|
||||||
reparentChildrenCallback,
|
|
||||||
appendBeforeSiblingCallback,
|
|
||||||
appendBasedOnParentNodeCallback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parseFragment(self: *Parser, html: []const u8) void {
|
pub fn parseFragment(self: *Parser, html: []const u8) void {
|
||||||
h5e.html5ever_parse_fragment(
|
h5e.html5ever_parse_fragment(
|
||||||
html.ptr,
|
html.ptr,
|
||||||
|
|||||||
@@ -171,24 +171,3 @@ pub const NodeOrText = extern struct {
|
|||||||
text: []const u8,
|
text: []const u8,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub extern "c" fn xml5ever_parse_document(
|
|
||||||
html: [*c]const u8,
|
|
||||||
len: usize,
|
|
||||||
doc: *anyopaque,
|
|
||||||
ctx: *anyopaque,
|
|
||||||
createElementCallback: *const fn (ctx: *anyopaque, data: *anyopaque, QualName, AttributeIterator) callconv(.c) ?*anyopaque,
|
|
||||||
elemNameCallback: *const fn (node_ref: *anyopaque) callconv(.c) *anyopaque,
|
|
||||||
appendCallback: *const fn (ctx: *anyopaque, parent_ref: *anyopaque, NodeOrText) callconv(.c) void,
|
|
||||||
parseErrorCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) void,
|
|
||||||
popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void,
|
|
||||||
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
|
|
||||||
createProcessingInstruction: *const fn (ctx: *anyopaque, StringSlice, StringSlice) callconv(.c) ?*anyopaque,
|
|
||||||
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
|
|
||||||
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
|
|
||||||
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
|
|
||||||
removeFromParentCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) void,
|
|
||||||
reparentChildrenCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void,
|
|
||||||
appendBeforeSiblingCallback: *const fn (ctx: *anyopaque, sibling_ref: *anyopaque, NodeOrText) callconv(.c) void,
|
|
||||||
appendBasedOnParentNodeCallback: *const fn (ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, NodeOrText) callconv(.c) void,
|
|
||||||
) void;
|
|
||||||
|
|||||||
@@ -107,6 +107,19 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=unsupportedMimeType>
|
||||||
|
{
|
||||||
|
const parser = new DOMParser();
|
||||||
|
|
||||||
|
// Should throw an error for unsupported MIME types
|
||||||
|
testing.withError((err) => {
|
||||||
|
testing.expectEqual('NotSupported', err.message);
|
||||||
|
}, () => {
|
||||||
|
parser.parseFromString('<div>test</div>', 'application/xml');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script id=getElementById>
|
<script id=getElementById>
|
||||||
{
|
{
|
||||||
const doc = new DOMParser().parseFromString('<div id="new-node">new-node</div>', 'text/html');
|
const doc = new DOMParser().parseFromString('<div id="new-node">new-node</div>', 'text/html');
|
||||||
@@ -231,161 +244,3 @@
|
|||||||
testing.expectEqual('<html><head></head><body>spice</body></html>', new DOMParser().parseFromString('spice', "text/html").documentElement.outerHTML);
|
testing.expectEqual('<html><head></head><body>spice</body></html>', new DOMParser().parseFromString('spice', "text/html").documentElement.outerHTML);
|
||||||
testing.expectEqual('<html><head></head><body></body></html>', new DOMParser().parseFromString('<html></html>', "text/html").documentElement.outerHTML);
|
testing.expectEqual('<html><head></head><body></body></html>', new DOMParser().parseFromString('<html></html>', "text/html").documentElement.outerHTML);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id=parse-xml>
|
|
||||||
{
|
|
||||||
const sampleXML = `<?xml version="1.0"?>
|
|
||||||
<catalog>
|
|
||||||
<book id="bk101">
|
|
||||||
<author>Gambardella, Matthew</author>
|
|
||||||
<title>XML Developer's Guide</title>
|
|
||||||
<genre>Computer</genre>
|
|
||||||
<price>44.95</price>
|
|
||||||
<publish_date>2000-10-01</publish_date>
|
|
||||||
<description>An in-depth look at creating applications
|
|
||||||
with XML.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk102">
|
|
||||||
<author>Ralls, Kim</author>
|
|
||||||
<title>Midnight Rain</title>
|
|
||||||
<genre>Fantasy</genre>
|
|
||||||
<price>5.95</price>
|
|
||||||
<publish_date>2000-12-16</publish_date>
|
|
||||||
<description>A former architect battles corporate zombies,
|
|
||||||
an evil sorceress, and her own childhood to become queen
|
|
||||||
of the world.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk103">
|
|
||||||
<author>Corets, Eva</author>
|
|
||||||
<title>Maeve Ascendant</title>
|
|
||||||
<genre>Fantasy</genre>
|
|
||||||
<price>5.95</price>
|
|
||||||
<publish_date>2000-11-17</publish_date>
|
|
||||||
<description>After the collapse of a nanotechnology
|
|
||||||
society in England, the young survivors lay the
|
|
||||||
foundation for a new society.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk104">
|
|
||||||
<author>Corets, Eva</author>
|
|
||||||
<title>Oberon's Legacy</title>
|
|
||||||
<genre>Fantasy</genre>
|
|
||||||
<price>5.95</price>
|
|
||||||
<publish_date>2001-03-10</publish_date>
|
|
||||||
<description>In post-apocalypse England, the mysterious
|
|
||||||
agent known only as Oberon helps to create a new life
|
|
||||||
for the inhabitants of London. Sequel to Maeve
|
|
||||||
Ascendant.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk105">
|
|
||||||
<author>Corets, Eva</author>
|
|
||||||
<title>The Sundered Grail</title>
|
|
||||||
<genre>Fantasy</genre>
|
|
||||||
<price>5.95</price>
|
|
||||||
<publish_date>2001-09-10</publish_date>
|
|
||||||
<description>The two daughters of Maeve, half-sisters,
|
|
||||||
battle one another for control of England. Sequel to
|
|
||||||
Oberon's Legacy.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk106">
|
|
||||||
<author>Randall, Cynthia</author>
|
|
||||||
<title>Lover Birds</title>
|
|
||||||
<genre>Romance</genre>
|
|
||||||
<price>4.95</price>
|
|
||||||
<publish_date>2000-09-02</publish_date>
|
|
||||||
<description>When Carla meets Paul at an ornithology
|
|
||||||
conference, tempers fly as feathers get ruffled.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk107">
|
|
||||||
<author>Thurman, Paula</author>
|
|
||||||
<title>Splish Splash</title>
|
|
||||||
<genre>Romance</genre>
|
|
||||||
<price>4.95</price>
|
|
||||||
<publish_date>2000-11-02</publish_date>
|
|
||||||
<description>A deep sea diver finds true love twenty
|
|
||||||
thousand leagues beneath the sea.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk108">
|
|
||||||
<author>Knorr, Stefan</author>
|
|
||||||
<title>Creepy Crawlies</title>
|
|
||||||
<genre>Horror</genre>
|
|
||||||
<price>4.95</price>
|
|
||||||
<publish_date>2000-12-06</publish_date>
|
|
||||||
<description>An anthology of horror stories about roaches,
|
|
||||||
centipedes, scorpions and other insects.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk109">
|
|
||||||
<author>Kress, Peter</author>
|
|
||||||
<title>Paradox Lost</title>
|
|
||||||
<genre>Science Fiction</genre>
|
|
||||||
<price>6.95</price>
|
|
||||||
<publish_date>2000-11-02</publish_date>
|
|
||||||
<description>After an inadvertant trip through a Heisenberg
|
|
||||||
Uncertainty Device, James Salway discovers the problems
|
|
||||||
of being quantum.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk110">
|
|
||||||
<author>O'Brien, Tim</author>
|
|
||||||
<title>Microsoft .NET: The Programming Bible</title>
|
|
||||||
<genre>Computer</genre>
|
|
||||||
<price>36.95</price>
|
|
||||||
<publish_date>2000-12-09</publish_date>
|
|
||||||
<description>Microsoft's .NET initiative is explored in
|
|
||||||
detail in this deep programmer's reference.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk111">
|
|
||||||
<author>O'Brien, Tim</author>
|
|
||||||
<title>MSXML3: A Comprehensive Guide</title>
|
|
||||||
<genre>Computer</genre>
|
|
||||||
<price>36.95</price>
|
|
||||||
<publish_date>2000-12-01</publish_date>
|
|
||||||
<description>The Microsoft MSXML3 parser is covered in
|
|
||||||
detail, with attention to XML DOM interfaces, XSLT processing,
|
|
||||||
SAX and more.</description>
|
|
||||||
</book>
|
|
||||||
<book id="bk112">
|
|
||||||
<author>Galos, Mike</author>
|
|
||||||
<title>Visual Studio 7: A Comprehensive Guide</title>
|
|
||||||
<genre>Computer</genre>
|
|
||||||
<price>49.95</price>
|
|
||||||
<publish_date>2001-04-16</publish_date>
|
|
||||||
<description>Microsoft Visual Studio 7 is explored in depth,
|
|
||||||
looking at how Visual Basic, Visual C++, C#, and ASP+ are
|
|
||||||
integrated into a comprehensive development
|
|
||||||
environment.</description>
|
|
||||||
</book>
|
|
||||||
</catalog>`;
|
|
||||||
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const mimes = [
|
|
||||||
"text/xml",
|
|
||||||
"application/xml",
|
|
||||||
"application/xhtml+xml",
|
|
||||||
"image/svg+xml",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const mime of mimes) {
|
|
||||||
const doc = parser.parseFromString(sampleXML, "text/xml");
|
|
||||||
const { firstChild: { childNodes, children: collection, tagName }, children } = doc;
|
|
||||||
// doc.
|
|
||||||
testing.expectEqual(true, doc instanceof XMLDocument);
|
|
||||||
testing.expectEqual(1, children.length);
|
|
||||||
// firstChild.
|
|
||||||
// TODO: Modern browsers expect this in lowercase.
|
|
||||||
testing.expectEqual("CATALOG", tagName);
|
|
||||||
testing.expectEqual(25, childNodes.length);
|
|
||||||
testing.expectEqual(12, collection.length);
|
|
||||||
// Check children of first child.
|
|
||||||
for (let i = 0; i < collection.length; i++) {
|
|
||||||
const {children: elements, id} = collection.item(i);
|
|
||||||
testing.expectEqual("bk" + (100 + i + 1), id);
|
|
||||||
// TODO: Modern browsers expect these in lowercase.
|
|
||||||
testing.expectEqual("AUTHOR", elements.item(0).tagName);
|
|
||||||
testing.expectEqual("TITLE", elements.item(1).tagName);
|
|
||||||
testing.expectEqual("GENRE", elements.item(2).tagName);
|
|
||||||
testing.expectEqual("PRICE", elements.item(3).tagName);
|
|
||||||
testing.expectEqual("PUBLISH_DATE", elements.item(4).tagName);
|
|
||||||
testing.expectEqual("DESCRIPTION", elements.item(5).tagName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<script src="../testing.js"></script>
|
|
||||||
|
|
||||||
<div id="test">first</div>
|
|
||||||
<div id="test">second</div>
|
|
||||||
|
|
||||||
<script id=duplicateIds>
|
|
||||||
const first = document.getElementById('test');
|
|
||||||
testing.expectEqual('first', first.textContent);
|
|
||||||
|
|
||||||
first.remove();
|
|
||||||
|
|
||||||
const second = document.getElementById('test');
|
|
||||||
testing.expectEqual('second', second.textContent);
|
|
||||||
|
|
||||||
// second.remove();
|
|
||||||
|
|
||||||
// testing.expectEqual(null, document.getElementById('test'));
|
|
||||||
</script>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<script src="../testing.js"></script>
|
|
||||||
<div id=d1>hello <em>world</em></div>
|
|
||||||
|
|
||||||
<script id=outerHTML>
|
|
||||||
const d1 = $('#d1');
|
|
||||||
testing.expectEqual('<div id=\"d1\">hello <em>world</em></div>', d1.outerHTML);
|
|
||||||
d1.outerHTML = '<p id=p1>spice</p>';
|
|
||||||
// setting outerHTML doesn't update what d1 points to
|
|
||||||
testing.expectEqual('<div id="d1">hello <em>world</em></div>', d1.outerHTML);
|
|
||||||
|
|
||||||
// but it does update the document
|
|
||||||
testing.expectEqual(null, document.getElementById('d1'));
|
|
||||||
testing.expectEqual(true, document.getElementById('p1') != null);
|
|
||||||
testing.expectEqual('<p id="p1">spice</p>', document.getElementById('p1').outerHTML);
|
|
||||||
// testing.expectEqual(true, document.body.outerHTML.replaceAll(/\n/g, '').startsWith('<body><p id="p1">spice</p><script id="outerHTML">'));
|
|
||||||
|
|
||||||
// document.getElementById('p1').outerHTML = '';
|
|
||||||
// testing.expectEqual(null, document.getElementById('p1'));
|
|
||||||
// testing.expectEqual(true, document.body.outerHTML.replaceAll(/\n/g, '').startsWith('<body><script id="outerHTML">'));
|
|
||||||
</script>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<script src="../testing.js"></script>
|
|
||||||
<script id="removeListenerDuringDispatch">
|
|
||||||
const target = document.createElement("div");
|
|
||||||
|
|
||||||
let listener1Called = 0;
|
|
||||||
let listener2Called = 0;
|
|
||||||
let listener3Called = 0;
|
|
||||||
|
|
||||||
function listener1() {
|
|
||||||
listener1Called++;
|
|
||||||
console.warn("listener1 called, removing listener2 and adding listener3");
|
|
||||||
target.removeEventListener("foo", listener2);
|
|
||||||
target.addEventListener("foo", listener3);
|
|
||||||
}
|
|
||||||
|
|
||||||
function listener2() {
|
|
||||||
listener2Called++;
|
|
||||||
console.warn("listener2 called (SHOULD NOT HAPPEN)");
|
|
||||||
}
|
|
||||||
|
|
||||||
function listener3() {
|
|
||||||
listener3Called++;
|
|
||||||
console.warn("listener3 called (SHOULD NOT HAPPEN IN FIRST DISPATCH)");
|
|
||||||
}
|
|
||||||
|
|
||||||
target.addEventListener("foo", listener1);
|
|
||||||
target.addEventListener("foo", listener2);
|
|
||||||
|
|
||||||
console.warn("Dispatching first event");
|
|
||||||
target.dispatchEvent(new Event("foo"));
|
|
||||||
|
|
||||||
console.warn("After first dispatch:");
|
|
||||||
console.warn(" listener1Called:", listener1Called);
|
|
||||||
console.warn(" listener2Called:", listener2Called);
|
|
||||||
console.warn(" listener3Called:", listener3Called);
|
|
||||||
|
|
||||||
testing.expectEqual(1, listener1Called);
|
|
||||||
testing.expectEqual(0, listener2Called);
|
|
||||||
testing.expectEqual(0, listener3Called);
|
|
||||||
|
|
||||||
console.warn("Dispatching second event");
|
|
||||||
target.dispatchEvent(new Event("foo"));
|
|
||||||
|
|
||||||
console.warn("After second dispatch:");
|
|
||||||
console.warn(" listener1Called:", listener1Called);
|
|
||||||
console.warn(" listener2Called:", listener2Called);
|
|
||||||
console.warn(" listener3Called:", listener3Called);
|
|
||||||
|
|
||||||
testing.expectEqual(2, listener1Called);
|
|
||||||
testing.expectEqual(0, listener2Called);
|
|
||||||
testing.expectEqual(1, listener3Called);
|
|
||||||
</script>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<script src="../testing.js"></script>
|
|
||||||
<body></body>
|
|
||||||
<script id="adoptNode">
|
|
||||||
const old = document.implementation.createHTMLDocument("");
|
|
||||||
const div = old.createElement("div");
|
|
||||||
div.appendChild(old.createTextNode("text"));
|
|
||||||
|
|
||||||
testing.expectEqual(old, div.ownerDocument);
|
|
||||||
testing.expectEqual(old, div.firstChild.ownerDocument);
|
|
||||||
|
|
||||||
document.body.appendChild(div);
|
|
||||||
|
|
||||||
testing.expectEqual(document, div.ownerDocument);
|
|
||||||
testing.expectEqual(document, div.firstChild.ownerDocument);
|
|
||||||
</script>
|
|
||||||
@@ -37,7 +37,4 @@
|
|||||||
testing.expectEqual(null, c2.parentNode);
|
testing.expectEqual(null, c2.parentNode);
|
||||||
assertChildren([c3, c4], d1)
|
assertChildren([c3, c4], d1)
|
||||||
assertChildren([], d2)
|
assertChildren([], d2)
|
||||||
|
|
||||||
testing.expectEqual(c3, d1.replaceChild(c3, c3));
|
|
||||||
assertChildren([c3, c4], d1)
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -376,447 +376,3 @@
|
|||||||
testing.expectEqual('Bold', fragment.childNodes[0].textContent);
|
testing.expectEqual('Bold', fragment.childNodes[0].textContent);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id=offset_validation_setStart>
|
|
||||||
{
|
|
||||||
const range = document.createRange();
|
|
||||||
const p1 = $('#p1');
|
|
||||||
|
|
||||||
// Test setStart with offset beyond node length
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.setStart(p1, 999);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test with negative offset (wraps to large u32)
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.setStart(p1.firstChild, -1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=offset_validation_setEnd>
|
|
||||||
{
|
|
||||||
const range = document.createRange();
|
|
||||||
const p1 = $('#p1');
|
|
||||||
|
|
||||||
// Test setEnd with offset beyond node length
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.setEnd(p1, 999);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test with text node
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.setEnd(p1.firstChild, 9999);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=comparePoint_basic>
|
|
||||||
{
|
|
||||||
// Create fresh elements to avoid DOM pollution from other tests
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
const p2 = document.createElement('p');
|
|
||||||
p1.textContent = 'First paragraph text';
|
|
||||||
p2.textContent = 'Second paragraph text';
|
|
||||||
div.appendChild(p1);
|
|
||||||
div.appendChild(p2);
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1.firstChild, 0);
|
|
||||||
range.setEnd(p2.firstChild, 5);
|
|
||||||
|
|
||||||
// Point before range
|
|
||||||
testing.expectEqual(-1, range.comparePoint(div, 0));
|
|
||||||
|
|
||||||
// Point at start boundary
|
|
||||||
testing.expectEqual(0, range.comparePoint(p1.firstChild, 0));
|
|
||||||
|
|
||||||
// Point inside range (in p1)
|
|
||||||
testing.expectEqual(0, range.comparePoint(p1.firstChild, 3));
|
|
||||||
|
|
||||||
// Point inside range (in p2)
|
|
||||||
testing.expectEqual(0, range.comparePoint(p2.firstChild, 2));
|
|
||||||
|
|
||||||
// Point at end boundary
|
|
||||||
testing.expectEqual(0, range.comparePoint(p2.firstChild, 5));
|
|
||||||
|
|
||||||
// Point after range
|
|
||||||
testing.expectEqual(1, range.comparePoint(p2.firstChild, 10));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=comparePoint_validation>
|
|
||||||
{
|
|
||||||
// Create fresh element
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Test content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
|
|
||||||
// Test comparePoint with invalid offset
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.comparePoint(p1, 20);
|
|
||||||
});
|
|
||||||
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.comparePoint(p1.firstChild, -1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=different_document_collapse>
|
|
||||||
{
|
|
||||||
// Create fresh element in current document
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Local content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
|
|
||||||
// Create a foreign document
|
|
||||||
const foreignDoc = document.implementation.createHTMLDocument('');
|
|
||||||
const foreignP = foreignDoc.createElement('p');
|
|
||||||
foreignP.textContent = 'Foreign';
|
|
||||||
foreignDoc.body.appendChild(foreignP);
|
|
||||||
|
|
||||||
// Set up range in current document
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
testing.expectEqual(false, range.collapsed);
|
|
||||||
|
|
||||||
// Setting start to foreign document should collapse to that point
|
|
||||||
range.setStart(foreignP, 0);
|
|
||||||
testing.expectEqual(true, range.collapsed);
|
|
||||||
testing.expectEqual(foreignP, range.startContainer);
|
|
||||||
testing.expectEqual(foreignP, range.endContainer);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=detached_node_collapse>
|
|
||||||
{
|
|
||||||
// Create fresh element
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Attached content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
|
|
||||||
// Create a detached element
|
|
||||||
const detached = document.createElement('div');
|
|
||||||
detached.textContent = 'Detached';
|
|
||||||
|
|
||||||
// Set up range in document
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
testing.expectEqual(false, range.collapsed);
|
|
||||||
|
|
||||||
// Setting end to detached node should collapse
|
|
||||||
range.setEnd(detached.firstChild, 0);
|
|
||||||
testing.expectEqual(true, range.collapsed);
|
|
||||||
testing.expectEqual(detached.firstChild, range.startContainer);
|
|
||||||
testing.expectEqual(detached.firstChild, range.endContainer);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=isPointInRange_basic>
|
|
||||||
{
|
|
||||||
// Create fresh elements
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
const p2 = document.createElement('p');
|
|
||||||
p1.textContent = 'First paragraph';
|
|
||||||
p2.textContent = 'Second paragraph';
|
|
||||||
div.appendChild(p1);
|
|
||||||
div.appendChild(p2);
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1.firstChild, 5);
|
|
||||||
range.setEnd(p2.firstChild, 6);
|
|
||||||
|
|
||||||
// Point before range
|
|
||||||
testing.expectEqual(false, range.isPointInRange(p1.firstChild, 0));
|
|
||||||
|
|
||||||
// Point at start boundary
|
|
||||||
testing.expectEqual(true, range.isPointInRange(p1.firstChild, 5));
|
|
||||||
|
|
||||||
// Point inside range
|
|
||||||
testing.expectEqual(true, range.isPointInRange(p1.firstChild, 7));
|
|
||||||
testing.expectEqual(true, range.isPointInRange(p2.firstChild, 3));
|
|
||||||
|
|
||||||
// Point at end boundary
|
|
||||||
testing.expectEqual(true, range.isPointInRange(p2.firstChild, 6));
|
|
||||||
|
|
||||||
// Point after range
|
|
||||||
testing.expectEqual(false, range.isPointInRange(p2.firstChild, 10));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=isPointInRange_different_root>
|
|
||||||
{
|
|
||||||
// Create element in current document
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Local content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
|
|
||||||
// Create element in different document
|
|
||||||
const foreignDoc = document.implementation.createHTMLDocument('');
|
|
||||||
const foreignP = foreignDoc.createElement('p');
|
|
||||||
foreignP.textContent = 'Foreign';
|
|
||||||
|
|
||||||
// Point in different root should return false (not throw)
|
|
||||||
testing.expectEqual(false, range.isPointInRange(foreignP, 0));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=isPointInRange_validation>
|
|
||||||
{
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Test content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
|
|
||||||
// Invalid offset should throw IndexSizeError
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.isPointInRange(p1, 999);
|
|
||||||
});
|
|
||||||
|
|
||||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => {
|
|
||||||
range.isPointInRange(p1.firstChild, 9999);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=intersectsNode_basic>
|
|
||||||
{
|
|
||||||
// Create fresh elements
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
const p2 = document.createElement('p');
|
|
||||||
const p3 = document.createElement('p');
|
|
||||||
p1.textContent = 'First';
|
|
||||||
p2.textContent = 'Second';
|
|
||||||
p3.textContent = 'Third';
|
|
||||||
div.appendChild(p1);
|
|
||||||
div.appendChild(p2);
|
|
||||||
div.appendChild(p3);
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1.firstChild, 2);
|
|
||||||
range.setEnd(p2.firstChild, 3);
|
|
||||||
|
|
||||||
// Node that intersects (p1 contains the start)
|
|
||||||
testing.expectEqual(true, range.intersectsNode(p1));
|
|
||||||
|
|
||||||
// Node that intersects (p2 contains the end)
|
|
||||||
testing.expectEqual(true, range.intersectsNode(p2));
|
|
||||||
|
|
||||||
// Node that doesn't intersect (p3 is after the range)
|
|
||||||
testing.expectEqual(false, range.intersectsNode(p3));
|
|
||||||
|
|
||||||
// Container intersects
|
|
||||||
testing.expectEqual(true, range.intersectsNode(div));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=intersectsNode_detached>
|
|
||||||
{
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Content';
|
|
||||||
div.appendChild(p1);
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
|
|
||||||
// The root node (div) should return true when it has no parent
|
|
||||||
// (Note: div is detached, so it's in the same tree as the range)
|
|
||||||
testing.expectEqual(true, range.intersectsNode(div));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=intersectsNode_different_root>
|
|
||||||
{
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Local';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1, 0);
|
|
||||||
range.setEnd(p1, 1);
|
|
||||||
|
|
||||||
// Node in different document should return false
|
|
||||||
const foreignDoc = document.implementation.createHTMLDocument('');
|
|
||||||
const foreignP = foreignDoc.createElement('p');
|
|
||||||
testing.expectEqual(false, range.intersectsNode(foreignP));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=commonAncestorContainer_same_node>
|
|
||||||
{
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.textContent = 'Content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p.firstChild, 0);
|
|
||||||
range.setEnd(p.firstChild, 3);
|
|
||||||
|
|
||||||
// Both boundaries in same text node, so that's the common ancestor
|
|
||||||
testing.expectEqual(p.firstChild, range.commonAncestorContainer);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=commonAncestorContainer_siblings>
|
|
||||||
{
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
const p2 = document.createElement('p');
|
|
||||||
p1.textContent = 'First';
|
|
||||||
p2.textContent = 'Second';
|
|
||||||
div.appendChild(p1);
|
|
||||||
div.appendChild(p2);
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p1.firstChild, 0);
|
|
||||||
range.setEnd(p2.firstChild, 3);
|
|
||||||
|
|
||||||
// Start and end in different siblings, common ancestor is the parent div
|
|
||||||
testing.expectEqual(div, range.commonAncestorContainer);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=commonAncestorContainer_nested>
|
|
||||||
{
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const section = document.createElement('section');
|
|
||||||
const p = document.createElement('p');
|
|
||||||
const span = document.createElement('span');
|
|
||||||
p.textContent = 'Text';
|
|
||||||
span.textContent = 'Span';
|
|
||||||
|
|
||||||
div.appendChild(section);
|
|
||||||
section.appendChild(p);
|
|
||||||
div.appendChild(span);
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p.firstChild, 0);
|
|
||||||
range.setEnd(span.firstChild, 2);
|
|
||||||
|
|
||||||
// Common ancestor of deeply nested p and sibling span is div
|
|
||||||
testing.expectEqual(div, range.commonAncestorContainer);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=compareBoundaryPoints_constants>
|
|
||||||
{
|
|
||||||
// Test that the constants are defined
|
|
||||||
testing.expectEqual(0, Range.START_TO_START);
|
|
||||||
testing.expectEqual(1, Range.START_TO_END);
|
|
||||||
testing.expectEqual(2, Range.END_TO_END);
|
|
||||||
testing.expectEqual(3, Range.END_TO_START);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=compareBoundaryPoints_basic>
|
|
||||||
{
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
const p2 = document.createElement('p');
|
|
||||||
p1.textContent = 'First paragraph';
|
|
||||||
p2.textContent = 'Second paragraph';
|
|
||||||
div.appendChild(p1);
|
|
||||||
div.appendChild(p2);
|
|
||||||
|
|
||||||
const range1 = document.createRange();
|
|
||||||
range1.setStart(p1.firstChild, 0);
|
|
||||||
range1.setEnd(p1.firstChild, 5);
|
|
||||||
|
|
||||||
const range2 = document.createRange();
|
|
||||||
range2.setStart(p1.firstChild, 3);
|
|
||||||
range2.setEnd(p2.firstChild, 5);
|
|
||||||
|
|
||||||
// range1 start is before range2 start
|
|
||||||
testing.expectEqual(-1, range1.compareBoundaryPoints(Range.START_TO_START, range2));
|
|
||||||
|
|
||||||
// range1 start is before range2 end
|
|
||||||
testing.expectEqual(-1, range1.compareBoundaryPoints(Range.START_TO_END, range2));
|
|
||||||
|
|
||||||
// range1 end is after range2 start
|
|
||||||
testing.expectEqual(1, range1.compareBoundaryPoints(Range.END_TO_START, range2));
|
|
||||||
|
|
||||||
// range1 end is before range2 end
|
|
||||||
testing.expectEqual(-1, range1.compareBoundaryPoints(Range.END_TO_END, range2));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=compareBoundaryPoints_same_range>
|
|
||||||
{
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.textContent = 'Content';
|
|
||||||
|
|
||||||
const range = document.createRange();
|
|
||||||
range.setStart(p.firstChild, 2);
|
|
||||||
range.setEnd(p.firstChild, 5);
|
|
||||||
|
|
||||||
// Comparing a range to itself should return 0
|
|
||||||
testing.expectEqual(0, range.compareBoundaryPoints(Range.START_TO_START, range));
|
|
||||||
testing.expectEqual(0, range.compareBoundaryPoints(Range.END_TO_END, range));
|
|
||||||
|
|
||||||
// Start is before end
|
|
||||||
testing.expectEqual(-1, range.compareBoundaryPoints(Range.START_TO_END, range));
|
|
||||||
|
|
||||||
// End is after start
|
|
||||||
testing.expectEqual(1, range.compareBoundaryPoints(Range.END_TO_START, range));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=compareBoundaryPoints_invalid_how>
|
|
||||||
{
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.textContent = 'Test';
|
|
||||||
|
|
||||||
const range1 = document.createRange();
|
|
||||||
const range2 = document.createRange();
|
|
||||||
range1.setStart(p, 0);
|
|
||||||
range2.setStart(p, 0);
|
|
||||||
|
|
||||||
// Invalid how parameter should throw NotSupportedError
|
|
||||||
testing.expectError('NotSupportedError: Not Supported', () => {
|
|
||||||
range1.compareBoundaryPoints(4, range2);
|
|
||||||
});
|
|
||||||
|
|
||||||
testing.expectError('NotSupportedError: Not Supported', () => {
|
|
||||||
range1.compareBoundaryPoints(99, range2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=compareBoundaryPoints_different_root>
|
|
||||||
{
|
|
||||||
const p1 = document.createElement('p');
|
|
||||||
p1.textContent = 'Local';
|
|
||||||
|
|
||||||
const range1 = document.createRange();
|
|
||||||
range1.setStart(p1, 0);
|
|
||||||
range1.setEnd(p1, 1);
|
|
||||||
|
|
||||||
// Create range in different document
|
|
||||||
const foreignDoc = document.implementation.createHTMLDocument('');
|
|
||||||
const foreignP = foreignDoc.createElement('p');
|
|
||||||
foreignP.textContent = 'Foreign';
|
|
||||||
|
|
||||||
const range2 = foreignDoc.createRange();
|
|
||||||
range2.setStart(foreignP, 0);
|
|
||||||
range2.setEnd(foreignP, 1);
|
|
||||||
|
|
||||||
// Comparing ranges in different documents should throw WrongDocumentError
|
|
||||||
testing.expectError('WrongDocumentError: wrong_document_error', () => {
|
|
||||||
range1.compareBoundaryPoints(Range.START_TO_START, range2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<script src="../testing.js"></script>
|
|
||||||
|
|
||||||
<div id=i></div>
|
|
||||||
<div id=testDiv></div>
|
|
||||||
<span id=mySpan></span>
|
|
||||||
<p id=paragraph></p>
|
|
||||||
|
|
||||||
<script id=named_access_global>
|
|
||||||
testing.expectEqual('i', i.id);
|
|
||||||
testing.expectEqual('testDiv',testDiv.id);
|
|
||||||
testing.expectEqual('mySpan', mySpan.id);
|
|
||||||
testing.expectEqual('paragraph', paragraph.id);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=named_access_window>
|
|
||||||
testing.expectEqual('i', window.i.id);
|
|
||||||
testing.expectEqual('testDiv', window.testDiv.id);
|
|
||||||
testing.expectEqual('mySpan', window.mySpan.id);
|
|
||||||
testing.expectEqual('paragraph', window.paragraph.id);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id=named_access_shadowing>
|
|
||||||
const i = 100;
|
|
||||||
testing.expectEqual(100, i);
|
|
||||||
</script>
|
|
||||||
@@ -69,19 +69,6 @@ pub fn getCollapsed(self: *const AbstractRange) bool {
|
|||||||
self._start_offset == self._end_offset;
|
self._start_offset == self._end_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCommonAncestorContainer(self: *const AbstractRange) *Node {
|
|
||||||
// Let container be start container
|
|
||||||
var container = self._start_container;
|
|
||||||
|
|
||||||
// While container is not an inclusive ancestor of end container
|
|
||||||
while (!isInclusiveAncestorOf(container, self._end_container)) {
|
|
||||||
// Let container be container's parent
|
|
||||||
container = container.parentNode() orelse break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isStartAfterEnd(self: *const AbstractRange) bool {
|
pub fn isStartAfterEnd(self: *const AbstractRange) bool {
|
||||||
return compareBoundaryPoints(
|
return compareBoundaryPoints(
|
||||||
self._start_container,
|
self._start_container,
|
||||||
@@ -97,7 +84,7 @@ const BoundaryComparison = enum {
|
|||||||
after,
|
after,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn compareBoundaryPoints(
|
fn compareBoundaryPoints(
|
||||||
node_a: *Node,
|
node_a: *Node,
|
||||||
offset_a: u32,
|
offset_a: u32,
|
||||||
node_b: *Node,
|
node_b: *Node,
|
||||||
@@ -208,13 +195,6 @@ fn isAncestorOf(potential_ancestor: *Node, node: *Node) bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isInclusiveAncestorOf(potential_ancestor: *Node, node: *Node) bool {
|
|
||||||
if (potential_ancestor == node) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return isAncestorOf(potential_ancestor, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
pub const bridge = js.Bridge(AbstractRange);
|
pub const bridge = js.Bridge(AbstractRange);
|
||||||
|
|
||||||
@@ -229,5 +209,4 @@ pub const JsApi = struct {
|
|||||||
pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{});
|
pub const endContainer = bridge.accessor(AbstractRange.getEndContainer, null, .{});
|
||||||
pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{});
|
pub const endOffset = bridge.accessor(AbstractRange.getEndOffset, null, .{});
|
||||||
pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{});
|
pub const collapsed = bridge.accessor(AbstractRange.getCollapsed, null, .{});
|
||||||
pub const commonAncestorContainer = bridge.accessor(AbstractRange.getCommonAncestorContainer, null, .{});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ pub fn format(self: *const CData, writer: *std.io.Writer) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getLength(self: *const CData) usize {
|
pub fn getLength(self: *const CData) usize {
|
||||||
return std.unicode.utf8CountCodepoints(self._data) catch self._data.len;
|
return self._data.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isEqualNode(self: *const CData, other: *const CData) bool {
|
pub fn isEqualNode(self: *const CData, other: *const CData) bool {
|
||||||
|
|||||||
@@ -120,6 +120,11 @@ pub const JsApi = struct {
|
|||||||
pub const createDocument = bridge.function(DOMImplementation.createDocument, .{});
|
pub const createDocument = bridge.function(DOMImplementation.createDocument, .{});
|
||||||
pub const createHTMLDocument = bridge.function(DOMImplementation.createHTMLDocument, .{});
|
pub const createHTMLDocument = bridge.function(DOMImplementation.createHTMLDocument, .{});
|
||||||
pub const hasFeature = bridge.function(DOMImplementation.hasFeature, .{});
|
pub const hasFeature = bridge.function(DOMImplementation.hasFeature, .{});
|
||||||
|
|
||||||
|
pub const toString = bridge.function(_toString, .{});
|
||||||
|
fn _toString(_: *const DOMImplementation) []const u8 {
|
||||||
|
return "[object DOMImplementation]";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
|
|||||||
@@ -19,13 +19,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
|
|
||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
const Parser = @import("../parser/Parser.zig");
|
|
||||||
|
|
||||||
const HTMLDocument = @import("HTMLDocument.zig");
|
const HTMLDocument = @import("HTMLDocument.zig");
|
||||||
const XMLDocument = @import("XMLDocument.zig");
|
|
||||||
const ProcessingInstruction = @import("../webapi/cdata/ProcessingInstruction.zig");
|
|
||||||
|
|
||||||
const DOMParser = @This();
|
const DOMParser = @This();
|
||||||
|
|
||||||
@@ -33,27 +28,14 @@ pub fn init() DOMParser {
|
|||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const HTMLDocumentOrXMLDocument = union(enum) {
|
pub fn parseFromString(self: *const DOMParser, html: []const u8, mime_type: []const u8, page: *Page) !*HTMLDocument {
|
||||||
html_document: *HTMLDocument,
|
_ = self;
|
||||||
xml_document: *XMLDocument,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn parseFromString(
|
// For now, only support text/html
|
||||||
_: *const DOMParser,
|
if (!std.mem.eql(u8, mime_type, "text/html")) {
|
||||||
html: []const u8,
|
return error.NotSupported;
|
||||||
mime_type: []const u8,
|
}
|
||||||
page: *Page,
|
|
||||||
) !HTMLDocumentOrXMLDocument {
|
|
||||||
const maybe_target_mime = std.meta.stringToEnum(enum {
|
|
||||||
@"text/html",
|
|
||||||
@"text/xml",
|
|
||||||
@"application/xml",
|
|
||||||
@"application/xhtml+xml",
|
|
||||||
@"image/svg+xml",
|
|
||||||
}, mime_type);
|
|
||||||
|
|
||||||
if (maybe_target_mime) |target_mime| switch (target_mime) {
|
|
||||||
.@"text/html" => {
|
|
||||||
// Create a new HTMLDocument
|
// Create a new HTMLDocument
|
||||||
const doc = try page._factory.document(HTMLDocument{
|
const doc = try page._factory.document(HTMLDocument{
|
||||||
._proto = undefined,
|
._proto = undefined,
|
||||||
@@ -65,6 +47,7 @@ pub fn parseFromString(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse HTML into the document
|
// Parse HTML into the document
|
||||||
|
const Parser = @import("../parser/Parser.zig");
|
||||||
var parser = Parser.init(page.arena, doc.asNode(), page);
|
var parser = Parser.init(page.arena, doc.asNode(), page);
|
||||||
parser.parse(normalized);
|
parser.parse(normalized);
|
||||||
|
|
||||||
@@ -72,39 +55,7 @@ pub fn parseFromString(
|
|||||||
return pe.err;
|
return pe.err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{ .html_document = doc };
|
return doc;
|
||||||
},
|
|
||||||
else => {
|
|
||||||
// Create a new XMLDocument.
|
|
||||||
const doc = try page._factory.document(XMLDocument{
|
|
||||||
._proto = undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Parse XML into XMLDocument.
|
|
||||||
const doc_node = doc.asNode();
|
|
||||||
var parser = Parser.init(page.arena, doc_node, page);
|
|
||||||
parser.parseXML(html);
|
|
||||||
|
|
||||||
if (parser.err) |pe| {
|
|
||||||
return pe.err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If first node is a `ProcessingInstruction`, skip it.
|
|
||||||
const first_child = doc_node.firstChild() orelse {
|
|
||||||
// Parsing should fail if there aren't any nodes.
|
|
||||||
unreachable;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (first_child.getNodeType() == 7) {
|
|
||||||
// We're sure that firstChild exist, this cannot fail.
|
|
||||||
_ = doc_node.removeChild(first_child, page) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .xml_document = doc };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return error.NotSupported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -79,81 +79,25 @@ pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
|
|
||||||
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
||||||
var node = self._current.firstChild();
|
var node = self._current.firstChild();
|
||||||
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
const filter_result = try self.acceptNode(n);
|
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||||
|
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
node = self.nextSiblingOrNull(n);
|
||||||
if (filter_result == NodeFilter.FILTER_SKIP) {
|
|
||||||
// Descend into children of this skipped node
|
|
||||||
if (n.firstChild()) |child| {
|
|
||||||
node = child;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// REJECT or SKIP with no children - find next sibling, walking up if necessary
|
|
||||||
var current_node = n;
|
|
||||||
while (true) {
|
|
||||||
if (current_node.nextSibling()) |sibling| {
|
|
||||||
node = sibling;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No sibling, go up to parent
|
|
||||||
const parent = current_node._parent orelse return null;
|
|
||||||
if (parent == self._current) {
|
|
||||||
// We've exhausted all children of self._current
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
current_node = parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
||||||
var node = self._current.lastChild();
|
var node = self._current.lastChild();
|
||||||
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
const filter_result = try self.acceptNode(n);
|
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||||
|
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
node = self.previousSiblingOrNull(n);
|
||||||
if (filter_result == NodeFilter.FILTER_SKIP) {
|
|
||||||
// Descend into children of this skipped node
|
|
||||||
if (n.lastChild()) |child| {
|
|
||||||
node = child;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// REJECT or SKIP with no children - find previous sibling, walking up if necessary
|
|
||||||
var current_node = n;
|
|
||||||
while (true) {
|
|
||||||
if (current_node.previousSibling()) |sibling| {
|
|
||||||
node = sibling;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No sibling, go up to parent
|
|
||||||
const parent = current_node._parent orelse return null;
|
|
||||||
if (parent == self._current) {
|
|
||||||
// We've exhausted all children of self._current
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
current_node = parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,39 +131,15 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
var sibling = self.previousSiblingOrNull(node);
|
var sibling = self.previousSiblingOrNull(node);
|
||||||
while (sibling) |sib| {
|
while (sibling) |sib| {
|
||||||
node = sib;
|
node = sib;
|
||||||
|
|
||||||
// Check if this sibling is rejected before descending into it
|
|
||||||
const sib_result = try self.acceptNode(node);
|
|
||||||
if (sib_result == NodeFilter.FILTER_REJECT) {
|
|
||||||
// Skip this sibling and its descendants entirely
|
|
||||||
sibling = self.previousSiblingOrNull(node);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Descend to the deepest last child, but respect FILTER_REJECT
|
|
||||||
while (true) {
|
|
||||||
var child = self.lastChildOrNull(node);
|
var child = self.lastChildOrNull(node);
|
||||||
|
|
||||||
// Find the rightmost non-rejected child
|
|
||||||
while (child) |c| {
|
while (child) |c| {
|
||||||
if (!self.isInSubtree(c)) break;
|
if (self.isInSubtree(c)) {
|
||||||
|
node = c;
|
||||||
const filter_result = try self.acceptNode(c);
|
child = self.lastChildOrNull(node);
|
||||||
if (filter_result == NodeFilter.FILTER_REJECT) {
|
|
||||||
// Skip this child and try its previous sibling
|
|
||||||
child = self.previousSiblingOrNull(c);
|
|
||||||
} else {
|
} else {
|
||||||
// ACCEPT or SKIP - use this child
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child == null) break; // No acceptable children
|
|
||||||
|
|
||||||
// Descend into this child
|
|
||||||
node = child.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ _location: ?*Location = null,
|
|||||||
_ready_state: ReadyState = .loading,
|
_ready_state: ReadyState = .loading,
|
||||||
_current_script: ?*Element.Html.Script = null,
|
_current_script: ?*Element.Html.Script = null,
|
||||||
_elements_by_id: std.StringHashMapUnmanaged(*Element) = .empty,
|
_elements_by_id: std.StringHashMapUnmanaged(*Element) = .empty,
|
||||||
// Track IDs that were removed from the map - they might have duplicates in the tree
|
|
||||||
_removed_ids: std.StringHashMapUnmanaged(void) = .empty,
|
|
||||||
_active_element: ?*Element = null,
|
_active_element: ?*Element = null,
|
||||||
_style_sheets: ?*StyleSheetList = null,
|
_style_sheets: ?*StyleSheetList = null,
|
||||||
_write_insertion_point: ?*Node = null,
|
_write_insertion_point: ?*Node = null,
|
||||||
@@ -123,15 +121,10 @@ const CreateElementOptions = struct {
|
|||||||
is: ?[]const u8 = null,
|
is: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
pub fn createElement(_: *const Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
||||||
const node = try page.createElement(null, name, null);
|
const node = try page.createElement(null, name, null);
|
||||||
const element = node.as(Element);
|
const element = node.as(Element);
|
||||||
|
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(node, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = options_ orelse return element;
|
const options = options_ orelse return element;
|
||||||
if (options.is) |is_value| {
|
if (options.is) |is_value| {
|
||||||
try element.setAttribute("is", is_value, page);
|
try element.setAttribute("is", is_value, page);
|
||||||
@@ -141,13 +134,8 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element {
|
pub fn createElementNS(_: *const Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element {
|
||||||
const node = try page.createElement(namespace, name, null);
|
const node = try page.createElement(namespace, name, null);
|
||||||
|
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(node, self);
|
|
||||||
}
|
|
||||||
return node.as(Element);
|
return node.as(Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,32 +163,9 @@ pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: []cons
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
|
pub fn getElementById(self: *const Document, id_: ?[]const u8) ?*Element {
|
||||||
if (id.len == 0) {
|
const id = id_ orelse return null;
|
||||||
return null;
|
return self._elements_by_id.get(id);
|
||||||
}
|
|
||||||
|
|
||||||
if (self._elements_by_id.get(id)) |element| {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ID was removed but might have duplicates
|
|
||||||
if (self._removed_ids.remove(id)) {
|
|
||||||
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
|
|
||||||
while (tw.next()) |el| {
|
|
||||||
const element_id = el.getAttributeSafe("id") orelse continue;
|
|
||||||
if (std.mem.eql(u8, element_id, id)) {
|
|
||||||
// we ignore this error to keep getElementById easy to call
|
|
||||||
// if it really failed, then we're out of memory and nothing's
|
|
||||||
// going to work like it should anyways.
|
|
||||||
const owned_id = page.dupeString(id) catch return null;
|
|
||||||
self._elements_by_id.put(page.arena, owned_id, el) catch return null;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetElementsByTagNameResult = union(enum) {
|
const GetElementsByTagNameResult = union(enum) {
|
||||||
@@ -287,53 +252,28 @@ pub fn getImplementation(_: *const Document) DOMImplementation {
|
|||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createDocumentFragment(self: *Document, page: *Page) !*Node.DocumentFragment {
|
pub fn createDocumentFragment(_: *const Document, page: *Page) !*Node.DocumentFragment {
|
||||||
const frag = try Node.DocumentFragment.init(page);
|
return Node.DocumentFragment.init(page);
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(frag.asNode(), self);
|
|
||||||
}
|
|
||||||
return frag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createComment(self: *Document, data: []const u8, page: *Page) !*Node {
|
pub fn createComment(_: *const Document, data: []const u8, page: *Page) !*Node {
|
||||||
const node = try page.createComment(data);
|
return page.createComment(data);
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(node, self);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createTextNode(self: *Document, data: []const u8, page: *Page) !*Node {
|
pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node {
|
||||||
const node = try page.createTextNode(data);
|
return page.createTextNode(data);
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(node, self);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createCDATASection(self: *Document, data: []const u8, page: *Page) !*Node {
|
pub fn createCDATASection(self: *const Document, data: []const u8, page: *Page) !*Node {
|
||||||
const node = switch (self._type) {
|
switch (self._type) {
|
||||||
.html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
.html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
||||||
.xml => try page.createCDATASection(data),
|
.xml => return page.createCDATASection(data),
|
||||||
.generic => try page.createCDATASection(data),
|
.generic => return page.createCDATASection(data),
|
||||||
};
|
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(node, self);
|
|
||||||
}
|
}
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createProcessingInstruction(self: *Document, target: []const u8, data: []const u8, page: *Page) !*Node {
|
pub fn createProcessingInstruction(_: *const Document, target: []const u8, data: []const u8, page: *Page) !*Node {
|
||||||
const node = try page.createProcessingInstruction(target, data);
|
return page.createProcessingInstruction(target, data);
|
||||||
// Track owner document if it's not the main document
|
|
||||||
if (self != page.document) {
|
|
||||||
try page.setNodeOwnerDocument(node, self);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Range = @import("Range.zig");
|
const Range = @import("Range.zig");
|
||||||
@@ -361,26 +301,14 @@ pub fn createEvent(_: *const Document, event_type: []const u8, page: *Page) !*@i
|
|||||||
return error.NotSupported;
|
return error.NotSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
||||||
return DOMTreeWalker.init(root, try whatToShow(what_to_show), filter, page);
|
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
||||||
|
return DOMTreeWalker.init(root, show, filter, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?u32, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||||
return DOMNodeIterator.init(root, try whatToShow(what_to_show), filter, page);
|
const show = what_to_show orelse NodeFilter.SHOW_ALL;
|
||||||
}
|
return DOMNodeIterator.init(root, show, filter, page);
|
||||||
|
|
||||||
fn whatToShow(value_: ?js.Value) !u32 {
|
|
||||||
const value = value_ orelse return 4294967295; // show all when undefined
|
|
||||||
if (value.isUndefined()) {
|
|
||||||
// undefined explicitly passed
|
|
||||||
return 4294967295;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isNull()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.toZig(u32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getReadyState(self: *const Document) []const u8 {
|
pub fn getReadyState(self: *const Document) []const u8 {
|
||||||
@@ -745,17 +673,7 @@ pub const JsApi = struct {
|
|||||||
pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true });
|
pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true });
|
||||||
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
|
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
|
||||||
pub const createNodeIterator = bridge.function(Document.createNodeIterator, .{});
|
pub const createNodeIterator = bridge.function(Document.createNodeIterator, .{});
|
||||||
pub const getElementById = bridge.function(_getElementById, .{});
|
pub const getElementById = bridge.function(Document.getElementById, .{});
|
||||||
fn _getElementById(self: *Document, value_: ?js.Value, page: *Page) !?*Element {
|
|
||||||
const value = value_ orelse return null;
|
|
||||||
if (value.isNull()) {
|
|
||||||
return self.getElementById("null", page);
|
|
||||||
}
|
|
||||||
if (value.isUndefined()) {
|
|
||||||
return self.getElementById("undefined", page);
|
|
||||||
}
|
|
||||||
return self.getElementById(try value.toZig([]const u8), page);
|
|
||||||
}
|
|
||||||
pub const querySelector = bridge.function(Document.querySelector, .{ .dom_exception = true });
|
pub const querySelector = bridge.function(Document.querySelector, .{ .dom_exception = true });
|
||||||
pub const querySelectorAll = bridge.function(Document.querySelectorAll, .{ .dom_exception = true });
|
pub const querySelectorAll = bridge.function(Document.querySelectorAll, .{ .dom_exception = true });
|
||||||
pub const getElementsByTagName = bridge.function(Document.getElementsByTagName, .{});
|
pub const getElementsByTagName = bridge.function(Document.getElementsByTagName, .{});
|
||||||
|
|||||||
@@ -71,10 +71,8 @@ pub fn className(_: *const DocumentFragment) []const u8 {
|
|||||||
return "[object DocumentFragment]";
|
return "[object DocumentFragment]";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getElementById(self: *DocumentFragment, id: []const u8) ?*Element {
|
pub fn getElementById(self: *DocumentFragment, id_: ?[]const u8) ?*Element {
|
||||||
if (id.len == 0) {
|
const id = id_ orelse return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
|
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
|
||||||
while (tw.next()) |el| {
|
while (tw.next()) |el| {
|
||||||
@@ -158,12 +156,6 @@ pub fn replaceChildren(self: *DocumentFragment, nodes: []const Node.NodeOrText,
|
|||||||
const parent_is_connected = parent.isConnected();
|
const parent_is_connected = parent.isConnected();
|
||||||
for (nodes) |node_or_text| {
|
for (nodes) |node_or_text| {
|
||||||
const child = try node_or_text.toNode(page);
|
const child = try node_or_text.toNode(page);
|
||||||
|
|
||||||
// If the new children has already a parent, remove from it.
|
|
||||||
if (child._parent) |p| {
|
|
||||||
page.removeNode(p, child, .{ .will_be_reconnected = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
try page.appendNode(parent, child, .{ .child_already_connected = parent_is_connected });
|
try page.appendNode(parent, child, .{ .child_already_connected = parent_is_connected });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,18 +233,7 @@ pub const JsApi = struct {
|
|||||||
|
|
||||||
pub const constructor = bridge.constructor(DocumentFragment.init, .{});
|
pub const constructor = bridge.constructor(DocumentFragment.init, .{});
|
||||||
|
|
||||||
pub const getElementById = bridge.function(_getElementById, .{});
|
pub const getElementById = bridge.function(DocumentFragment.getElementById, .{});
|
||||||
fn _getElementById(self: *DocumentFragment, value_: ?js.Value) !?*Element {
|
|
||||||
const value = value_ orelse return null;
|
|
||||||
if (value.isNull()) {
|
|
||||||
return self.getElementById("null");
|
|
||||||
}
|
|
||||||
if (value.isUndefined()) {
|
|
||||||
return self.getElementById("undefined");
|
|
||||||
}
|
|
||||||
return self.getElementById(try value.toZig([]const u8));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const querySelector = bridge.function(DocumentFragment.querySelector, .{ .dom_exception = true });
|
pub const querySelector = bridge.function(DocumentFragment.querySelector, .{ .dom_exception = true });
|
||||||
pub const querySelectorAll = bridge.function(DocumentFragment.querySelectorAll, .{ .dom_exception = true });
|
pub const querySelectorAll = bridge.function(DocumentFragment.querySelectorAll, .{ .dom_exception = true });
|
||||||
pub const children = bridge.accessor(DocumentFragment.getChildren, null, .{});
|
pub const children = bridge.accessor(DocumentFragment.getChildren, null, .{});
|
||||||
|
|||||||
@@ -70,4 +70,9 @@ pub const JsApi = struct {
|
|||||||
pub const name = bridge.accessor(DocumentType.getName, null, .{});
|
pub const name = bridge.accessor(DocumentType.getName, null, .{});
|
||||||
pub const publicId = bridge.accessor(DocumentType.getPublicId, null, .{});
|
pub const publicId = bridge.accessor(DocumentType.getPublicId, null, .{});
|
||||||
pub const systemId = bridge.accessor(DocumentType.getSystemId, null, .{});
|
pub const systemId = bridge.accessor(DocumentType.getSystemId, null, .{});
|
||||||
|
|
||||||
|
pub const toString = bridge.function(_toString, .{});
|
||||||
|
fn _toString(self: *const DocumentType) []const u8 {
|
||||||
|
return self.className();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ const Element = @This();
|
|||||||
pub const DatasetLookup = std.AutoHashMapUnmanaged(*Element, *DOMStringMap);
|
pub const DatasetLookup = std.AutoHashMapUnmanaged(*Element, *DOMStringMap);
|
||||||
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
pub const StyleLookup = std.AutoHashMapUnmanaged(*Element, *CSSStyleProperties);
|
||||||
pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
||||||
pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
|
||||||
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
||||||
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
||||||
|
|
||||||
@@ -317,20 +316,6 @@ pub fn getOuterHTML(self: *Element, writer: *std.Io.Writer, page: *Page) !void {
|
|||||||
return dump.deep(self.asNode(), .{ .shadow = .skip }, writer, page);
|
return dump.deep(self.asNode(), .{ .shadow = .skip }, writer, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setOuterHTML(self: *Element, html: []const u8, page: *Page) !void {
|
|
||||||
const node = self.asNode();
|
|
||||||
const parent = node._parent orelse return;
|
|
||||||
|
|
||||||
page.domChanged();
|
|
||||||
if (html.len > 0) {
|
|
||||||
const fragment = (try Node.DocumentFragment.init(page)).asNode();
|
|
||||||
try page.parseHtmlAsChildren(fragment, html);
|
|
||||||
try page.insertAllChildrenBefore(fragment, parent, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
page.removeNode(parent, node, .{ .will_be_reconnected = false });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getInnerHTML(self: *Element, writer: *std.Io.Writer, page: *Page) !void {
|
pub fn getInnerHTML(self: *Element, writer: *std.Io.Writer, page: *Page) !void {
|
||||||
const dump = @import("../dump.zig");
|
const dump = @import("../dump.zig");
|
||||||
return dump.children(self.asNode(), .{ .shadow = .skip }, writer, page);
|
return dump.children(self.asNode(), .{ .shadow = .skip }, writer, page);
|
||||||
@@ -565,17 +550,6 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
|||||||
return gop.value_ptr.*;
|
return gop.value_ptr.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
|
||||||
const gop = try page._element_rel_lists.getOrPut(page.arena, self);
|
|
||||||
if (!gop.found_existing) {
|
|
||||||
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
|
|
||||||
._element = self,
|
|
||||||
._attribute_name = "rel",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return gop.value_ptr.*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getDataset(self: *Element, page: *Page) !*DOMStringMap {
|
pub fn getDataset(self: *Element, page: *Page) !*DOMStringMap {
|
||||||
const gop = try page._element_datasets.getOrPut(page.arena, self);
|
const gop = try page._element_datasets.getOrPut(page.arena, self);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
@@ -992,7 +966,7 @@ pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Pag
|
|||||||
var class_names: std.ArrayList([]const u8) = .empty;
|
var class_names: std.ArrayList([]const u8) = .empty;
|
||||||
var it = std.mem.tokenizeAny(u8, class_name, &std.ascii.whitespace);
|
var it = std.mem.tokenizeAny(u8, class_name, &std.ascii.whitespace);
|
||||||
while (it.next()) |name| {
|
while (it.next()) |name| {
|
||||||
try class_names.append(arena, try page.dupeString(name));
|
try class_names.append(arena, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections.NodeLive(.class_name).init(self.asNode(), class_names.items, page);
|
return collections.NodeLive(.class_name).init(self.asNode(), class_names.items, page);
|
||||||
@@ -1004,11 +978,6 @@ pub fn cloneElement(self: *Element, deep: bool, page: *Page) !*Node {
|
|||||||
|
|
||||||
const node = try page.createElement(namespace_uri, tag_name, self._attributes);
|
const node = try page.createElement(namespace_uri, tag_name, self._attributes);
|
||||||
|
|
||||||
// Allow element-specific types to copy their runtime state
|
|
||||||
_ = Element.Build.call(node.as(Element), "cloned", .{ self, node.as(Element), page }) catch |err| {
|
|
||||||
log.err(.dom, "element.clone.failed", .{ .err = err });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (deep) {
|
if (deep) {
|
||||||
var child_it = self.asNode().childrenIterator();
|
var child_it = self.asNode().childrenIterator();
|
||||||
while (child_it.next()) |child| {
|
while (child_it.next()) |child| {
|
||||||
@@ -1239,7 +1208,7 @@ pub const JsApi = struct {
|
|||||||
return buf.written();
|
return buf.written();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const outerHTML = bridge.accessor(_outerHTML, Element.setOuterHTML, .{});
|
pub const outerHTML = bridge.accessor(_outerHTML, null, .{});
|
||||||
fn _outerHTML(self: *Element, page: *Page) ![]const u8 {
|
fn _outerHTML(self: *Element, page: *Page) ![]const u8 {
|
||||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||||
try self.getOuterHTML(&buf.writer, page);
|
try self.getOuterHTML(&buf.writer, page);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ _prevent_default: bool = false,
|
|||||||
_stop_propagation: bool = false,
|
_stop_propagation: bool = false,
|
||||||
_stop_immediate_propagation: bool = false,
|
_stop_immediate_propagation: bool = false,
|
||||||
_event_phase: EventPhase = .none,
|
_event_phase: EventPhase = .none,
|
||||||
_time_stamp: u64,
|
_time_stamp: u64 = 0,
|
||||||
_needs_retargeting: bool = false,
|
_needs_retargeting: bool = false,
|
||||||
_isTrusted: bool = false,
|
_isTrusted: bool = false,
|
||||||
|
|
||||||
@@ -105,14 +105,9 @@ pub fn initEvent(
|
|||||||
cancelable: ?bool,
|
cancelable: ?bool,
|
||||||
page: *Page,
|
page: *Page,
|
||||||
) !void {
|
) !void {
|
||||||
if (self._event_phase != .none) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self._type_string = try String.init(page.arena, event_string, .{});
|
self._type_string = try String.init(page.arena, event_string, .{});
|
||||||
self._bubbles = bubbles orelse false;
|
self._bubbles = bubbles orelse false;
|
||||||
self._cancelable = cancelable orelse false;
|
self._cancelable = cancelable orelse false;
|
||||||
self._stop_propagation = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as(self: *Event, comptime T: type) *T {
|
pub fn as(self: *Event, comptime T: type) *T {
|
||||||
@@ -181,22 +176,6 @@ pub fn getDefaultPrevented(self: *const Event) bool {
|
|||||||
return self._prevent_default;
|
return self._prevent_default;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getReturnValue(self: *const Event) bool {
|
|
||||||
return !self._prevent_default;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setReturnValue(self: *Event, v: bool) void {
|
|
||||||
self._prevent_default = !v;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getCancelBubble(self: *const Event) bool {
|
|
||||||
return self._stop_propagation;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setCancelBubble(self: *Event) void {
|
|
||||||
self.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getEventPhase(self: *const Event) u8 {
|
pub fn getEventPhase(self: *const Event) u8 {
|
||||||
return @intFromEnum(self._event_phase);
|
return @intFromEnum(self._event_phase);
|
||||||
}
|
}
|
||||||
@@ -393,7 +372,6 @@ pub const JsApi = struct {
|
|||||||
pub const cancelable = bridge.accessor(Event.getCancelable, null, .{});
|
pub const cancelable = bridge.accessor(Event.getCancelable, null, .{});
|
||||||
pub const composed = bridge.accessor(Event.getComposed, null, .{});
|
pub const composed = bridge.accessor(Event.getComposed, null, .{});
|
||||||
pub const target = bridge.accessor(Event.getTarget, null, .{});
|
pub const target = bridge.accessor(Event.getTarget, null, .{});
|
||||||
pub const srcElement = bridge.accessor(Event.getTarget, null, .{});
|
|
||||||
pub const currentTarget = bridge.accessor(Event.getCurrentTarget, null, .{});
|
pub const currentTarget = bridge.accessor(Event.getCurrentTarget, null, .{});
|
||||||
pub const eventPhase = bridge.accessor(Event.getEventPhase, null, .{});
|
pub const eventPhase = bridge.accessor(Event.getEventPhase, null, .{});
|
||||||
pub const defaultPrevented = bridge.accessor(Event.getDefaultPrevented, null, .{});
|
pub const defaultPrevented = bridge.accessor(Event.getDefaultPrevented, null, .{});
|
||||||
@@ -404,10 +382,6 @@ pub const JsApi = struct {
|
|||||||
pub const stopImmediatePropagation = bridge.function(Event.stopImmediatePropagation, .{});
|
pub const stopImmediatePropagation = bridge.function(Event.stopImmediatePropagation, .{});
|
||||||
pub const composedPath = bridge.function(Event.composedPath, .{});
|
pub const composedPath = bridge.function(Event.composedPath, .{});
|
||||||
pub const initEvent = bridge.function(Event.initEvent, .{});
|
pub const initEvent = bridge.function(Event.initEvent, .{});
|
||||||
// deprecated
|
|
||||||
pub const returnValue = bridge.accessor(Event.getReturnValue, Event.setReturnValue, .{});
|
|
||||||
// deprecated
|
|
||||||
pub const cancelBubble = bridge.accessor(Event.getCancelBubble, Event.setCancelBubble, .{});
|
|
||||||
|
|
||||||
// Event phase constants
|
// Event phase constants
|
||||||
pub const NONE = bridge.property(@intFromEnum(EventPhase.none));
|
pub const NONE = bridge.property(@intFromEnum(EventPhase.none));
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ pub const JsApi = struct {
|
|||||||
pub const dispatchEvent = bridge.function(EventTarget.dispatchEvent, .{});
|
pub const dispatchEvent = bridge.function(EventTarget.dispatchEvent, .{});
|
||||||
pub const addEventListener = bridge.function(EventTarget.addEventListener, .{});
|
pub const addEventListener = bridge.function(EventTarget.addEventListener, .{});
|
||||||
pub const removeEventListener = bridge.function(EventTarget.removeEventListener, .{});
|
pub const removeEventListener = bridge.function(EventTarget.removeEventListener, .{});
|
||||||
|
pub const toString = bridge.function(EventTarget.toString, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
|
|||||||
@@ -74,76 +74,30 @@ pub fn getBody(self: *HTMLDocument) ?*Element.Html.Body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTitle(self: *HTMLDocument, page: *Page) ![]const u8 {
|
pub fn getTitle(self: *HTMLDocument, page: *Page) ![]const u8 {
|
||||||
// Search the entire document for the first <title> element
|
const head = self.getHead() orelse return "";
|
||||||
const root = self._proto.getDocumentElement() orelse return "";
|
var it = head.asNode().childrenIterator();
|
||||||
const title_element = blk: {
|
while (it.next()) |node| {
|
||||||
var walker = @import("TreeWalker.zig").Full.init(root.asNode(), .{});
|
|
||||||
while (walker.next()) |node| {
|
|
||||||
if (node.is(Element.Html.Title)) |title| {
|
if (node.is(Element.Html.Title)) |title| {
|
||||||
break :blk title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||||
try title_element.asNode().getTextContent(&buf.writer);
|
try title.asElement().getInnerText(&buf.writer);
|
||||||
const text = buf.written();
|
return buf.written();
|
||||||
|
}
|
||||||
if (text.len == 0) {
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
var started = false;
|
|
||||||
var in_whitespace = false;
|
|
||||||
var result: std.ArrayList(u8) = .empty;
|
|
||||||
try result.ensureTotalCapacity(page.call_arena, text.len);
|
|
||||||
|
|
||||||
for (text) |c| {
|
|
||||||
const is_ascii_ws = c == ' ' or c == '\t' or c == '\n' or c == '\r' or c == '\x0C';
|
|
||||||
|
|
||||||
if (is_ascii_ws) {
|
|
||||||
if (started) {
|
|
||||||
in_whitespace = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (in_whitespace) {
|
|
||||||
result.appendAssumeCapacity(' ');
|
|
||||||
in_whitespace = false;
|
|
||||||
}
|
|
||||||
result.appendAssumeCapacity(c);
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTitle(self: *HTMLDocument, title: []const u8, page: *Page) !void {
|
pub fn setTitle(self: *HTMLDocument, title: []const u8, page: *Page) !void {
|
||||||
const head = self.getHead() orelse return;
|
const head = self.getHead() orelse return;
|
||||||
|
|
||||||
// Find existing title element in head
|
|
||||||
var it = head.asNode().childrenIterator();
|
var it = head.asNode().childrenIterator();
|
||||||
while (it.next()) |node| {
|
while (it.next()) |node| {
|
||||||
if (node.is(Element.Html.Title)) |title_element| {
|
if (node.is(Element.Html.Title)) |title_element| {
|
||||||
// Replace children, but don't create text node for empty string
|
|
||||||
if (title.len == 0) {
|
|
||||||
return title_element.asElement().replaceChildren(&.{}, page);
|
|
||||||
} else {
|
|
||||||
return title_element.asElement().replaceChildren(&.{.{ .text = title }}, page);
|
return title_element.asElement().replaceChildren(&.{.{ .text = title }}, page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No title element found, create one
|
|
||||||
const title_node = try page.createElement(null, "title", null);
|
const title_node = try page.createElement(null, "title", null);
|
||||||
const title_element = title_node.as(Element);
|
const title_element = title_node.as(Element);
|
||||||
|
|
||||||
// Only add text if non-empty
|
|
||||||
if (title.len > 0) {
|
|
||||||
try title_element.replaceChildren(&.{.{ .text = title }}, page);
|
try title_element.replaceChildren(&.{.{ .text = title }}, page);
|
||||||
}
|
|
||||||
|
|
||||||
_ = try head.asNode().appendChild(title_node, page);
|
_ = try head.asNode().appendChild(title_node, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry {
|
pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry {
|
||||||
const entries = try page.call_arena.dupe(*IntersectionObserverEntry, self._pending_entries.items);
|
const entries = try page.arena.dupe(*IntersectionObserverEntry, self._pending_entries.items);
|
||||||
self._pending_entries.clearRetainingCapacity();
|
self._pending_entries.clearRetainingCapacity();
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ _parent: ?*Node = null,
|
|||||||
_children: ?*Children = null,
|
_children: ?*Children = null,
|
||||||
_child_link: LinkedList.Node = .{},
|
_child_link: LinkedList.Node = .{},
|
||||||
|
|
||||||
// Lookup for nodes that have a different owner document than page.document
|
|
||||||
pub const OwnerDocumentLookup = std.AutoHashMapUnmanaged(*Node, *Document);
|
|
||||||
|
|
||||||
pub const Type = union(enum) {
|
pub const Type = union(enum) {
|
||||||
cdata: *CData,
|
cdata: *CData,
|
||||||
element: *Element,
|
element: *Element,
|
||||||
@@ -204,10 +201,6 @@ fn validateNodeInsertion(parent: *Node, node: *Node) !void {
|
|||||||
if (node.contains(parent)) {
|
if (node.contains(parent)) {
|
||||||
return error.HierarchyError;
|
return error.HierarchyError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node._type == .attribute) {
|
|
||||||
return error.HierarchyError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
||||||
@@ -224,11 +217,10 @@ pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
|||||||
// then we can remove + add a bit more efficiently (we don't have to fully
|
// then we can remove + add a bit more efficiently (we don't have to fully
|
||||||
// disconnect then reconnect)
|
// disconnect then reconnect)
|
||||||
const child_connected = child.isConnected();
|
const child_connected = child.isConnected();
|
||||||
|
|
||||||
// Check if we're adopting the node to a different document
|
// Check if we're adopting the node to a different document
|
||||||
const child_owner = child.ownerDocument(page);
|
const child_root = child.getRootNode(null);
|
||||||
const parent_owner = self.ownerDocument(page) orelse self.as(Document);
|
const parent_root = self.getRootNode(null);
|
||||||
const adopting_to_new_document = child_owner != null and child_owner.? != parent_owner;
|
const adopting_to_new_document = child_connected and child_root != parent_root;
|
||||||
|
|
||||||
if (child._parent) |parent| {
|
if (child._parent) |parent| {
|
||||||
// we can signal removeNode that the child will remain connected
|
// we can signal removeNode that the child will remain connected
|
||||||
@@ -236,11 +228,6 @@ pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
|||||||
page.removeNode(parent, child, .{ .will_be_reconnected = self.isConnected() });
|
page.removeNode(parent, child, .{ .will_be_reconnected = self.isConnected() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adopt the node tree if moving between documents
|
|
||||||
if (adopting_to_new_document) {
|
|
||||||
try page.adoptNodeTree(child, parent_owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
try page.appendNode(self, child, .{
|
try page.appendNode(self, child, .{
|
||||||
.child_already_connected = child_connected,
|
.child_already_connected = child_connected,
|
||||||
.adopting_to_new_document = adopting_to_new_document,
|
.adopting_to_new_document = adopting_to_new_document,
|
||||||
@@ -440,13 +427,8 @@ pub fn ownerDocument(self: *const Node, page: *const Page) ?*Document {
|
|||||||
return current._type.document;
|
return current._type.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, this is a detached node. Check if it has a specific owner
|
// Otherwise, this is a detached node. The owner is the document that
|
||||||
// document registered (for nodes created via non-main documents).
|
// created it. For now, we only have one document.
|
||||||
if (page._node_owner_documents.get(@constCast(self))) |owner| {
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to the main document for detached nodes without a specific owner.
|
|
||||||
return page.document;
|
return page.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,21 +457,6 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
|||||||
return self.appendChild(new_node, page);
|
return self.appendChild(new_node, page);
|
||||||
};
|
};
|
||||||
|
|
||||||
// special case: if nodes are the same, ignore the change.
|
|
||||||
if (new_node == ref_node_) {
|
|
||||||
page.domChanged();
|
|
||||||
|
|
||||||
if (page.hasMutationObservers()) {
|
|
||||||
const parent = new_node._parent.?;
|
|
||||||
const previous_sibling = new_node.previousSibling();
|
|
||||||
const next_sibling = new_node.nextSibling();
|
|
||||||
const replaced = [_]*Node{new_node};
|
|
||||||
page.childListChange(parent, &replaced, &replaced, previous_sibling, next_sibling);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref_node._parent == null or ref_node._parent.? != self) {
|
if (ref_node._parent == null or ref_node._parent.? != self) {
|
||||||
return error.NotFound;
|
return error.NotFound;
|
||||||
}
|
}
|
||||||
@@ -502,11 +469,10 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
|||||||
try validateNodeInsertion(self, new_node);
|
try validateNodeInsertion(self, new_node);
|
||||||
|
|
||||||
const child_already_connected = new_node.isConnected();
|
const child_already_connected = new_node.isConnected();
|
||||||
|
|
||||||
// Check if we're adopting the node to a different document
|
// Check if we're adopting the node to a different document
|
||||||
const child_owner = new_node.ownerDocument(page);
|
const child_root = new_node.getRootNode(null);
|
||||||
const parent_owner = self.ownerDocument(page) orelse self.as(Document);
|
const parent_root = self.getRootNode(null);
|
||||||
const adopting_to_new_document = child_owner != null and child_owner.? != parent_owner;
|
const adopting_to_new_document = child_already_connected and child_root != parent_root;
|
||||||
|
|
||||||
page.domChanged();
|
page.domChanged();
|
||||||
const will_be_reconnected = self.isConnected();
|
const will_be_reconnected = self.isConnected();
|
||||||
@@ -514,11 +480,6 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
|||||||
page.removeNode(parent, new_node, .{ .will_be_reconnected = will_be_reconnected });
|
page.removeNode(parent, new_node, .{ .will_be_reconnected = will_be_reconnected });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adopt the node tree if moving between documents
|
|
||||||
if (adopting_to_new_document) {
|
|
||||||
try page.adoptNodeTree(new_node, parent_owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
try page.insertNodeRelative(
|
try page.insertNodeRelative(
|
||||||
self,
|
self,
|
||||||
new_node,
|
new_node,
|
||||||
@@ -540,13 +501,7 @@ pub fn replaceChild(self: *Node, new_child: *Node, old_child: *Node, page: *Page
|
|||||||
try validateNodeInsertion(self, new_child);
|
try validateNodeInsertion(self, new_child);
|
||||||
|
|
||||||
_ = try self.insertBefore(new_child, old_child, page);
|
_ = try self.insertBefore(new_child, old_child, page);
|
||||||
|
|
||||||
// Special case: if we replace a node by itself, we don't remove it.
|
|
||||||
// insertBefore is an noop in this case.
|
|
||||||
if (new_child != old_child) {
|
|
||||||
page.removeNode(self, old_child, .{ .will_be_reconnected = false });
|
page.removeNode(self, old_child, .{ .will_be_reconnected = false });
|
||||||
}
|
|
||||||
|
|
||||||
return old_child;
|
return old_child;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,14 +826,11 @@ pub const JsApi = struct {
|
|||||||
pub const ATTRIBUTE_NODE = bridge.property(2);
|
pub const ATTRIBUTE_NODE = bridge.property(2);
|
||||||
pub const TEXT_NODE = bridge.property(3);
|
pub const TEXT_NODE = bridge.property(3);
|
||||||
pub const CDATA_SECTION_NODE = bridge.property(4);
|
pub const CDATA_SECTION_NODE = bridge.property(4);
|
||||||
pub const ENTITY_REFERENCE_NODE = bridge.property(5);
|
|
||||||
pub const ENTITY_NODE = bridge.property(6);
|
|
||||||
pub const PROCESSING_INSTRUCTION_NODE = bridge.property(7);
|
pub const PROCESSING_INSTRUCTION_NODE = bridge.property(7);
|
||||||
pub const COMMENT_NODE = bridge.property(8);
|
pub const COMMENT_NODE = bridge.property(8);
|
||||||
pub const DOCUMENT_NODE = bridge.property(9);
|
pub const DOCUMENT_NODE = bridge.property(9);
|
||||||
pub const DOCUMENT_TYPE_NODE = bridge.property(10);
|
pub const DOCUMENT_TYPE_NODE = bridge.property(10);
|
||||||
pub const DOCUMENT_FRAGMENT_NODE = bridge.property(11);
|
pub const DOCUMENT_FRAGMENT_NODE = bridge.property(11);
|
||||||
pub const NOTATION_NODE = bridge.property(12);
|
|
||||||
|
|
||||||
pub const DOCUMENT_POSITION_DISCONNECTED = bridge.property(0x01);
|
pub const DOCUMENT_POSITION_DISCONNECTED = bridge.property(0x01);
|
||||||
pub const DOCUMENT_POSITION_PRECEDING = bridge.property(0x02);
|
pub const DOCUMENT_POSITION_PRECEDING = bridge.property(0x02);
|
||||||
@@ -934,6 +886,11 @@ pub const JsApi = struct {
|
|||||||
pub const getRootNode = bridge.function(Node.getRootNode, .{});
|
pub const getRootNode = bridge.function(Node.getRootNode, .{});
|
||||||
pub const isEqualNode = bridge.function(Node.isEqualNode, .{});
|
pub const isEqualNode = bridge.function(Node.isEqualNode, .{});
|
||||||
|
|
||||||
|
pub const toString = bridge.function(_toString, .{});
|
||||||
|
fn _toString(self: *const Node) []const u8 {
|
||||||
|
return self.className();
|
||||||
|
}
|
||||||
|
|
||||||
fn _baseURI(_: *Node, page: *const Page) []const u8 {
|
fn _baseURI(_: *Node, page: *const Page) []const u8 {
|
||||||
return page.base();
|
return page.base();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ pub fn disconnect(self: *PerformanceObserver, page: *Page) void {
|
|||||||
/// Returns the current list of PerformanceEntry objects
|
/// Returns the current list of PerformanceEntry objects
|
||||||
/// stored in the performance observer, emptying it out.
|
/// stored in the performance observer, emptying it out.
|
||||||
pub fn takeRecords(self: *PerformanceObserver, page: *Page) ![]*Performance.Entry {
|
pub fn takeRecords(self: *PerformanceObserver, page: *Page) ![]*Performance.Entry {
|
||||||
const records = try page.call_arena.dupe(*Performance.Entry, self._entries.items);
|
const records = try page.arena.dupe(*Performance.Entry, self._entries.items);
|
||||||
self._entries.clearRetainingCapacity();
|
self._entries.clearRetainingCapacity();
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,35 +37,22 @@ pub fn init(page: *Page) !*Range {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
|
pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
|
||||||
if (offset > node.getLength()) {
|
|
||||||
return error.IndexSizeError;
|
|
||||||
}
|
|
||||||
|
|
||||||
self._proto._start_container = node;
|
self._proto._start_container = node;
|
||||||
self._proto._start_offset = offset;
|
self._proto._start_offset = offset;
|
||||||
|
|
||||||
// If start is now after end, or nodes are in different trees, collapse to start
|
// If start is now after end, collapse to start
|
||||||
const end_root = self._proto._end_container.getRootNode(null);
|
if (self._proto.isStartAfterEnd()) {
|
||||||
const start_root = node.getRootNode(null);
|
|
||||||
if (end_root != start_root or self._proto.isStartAfterEnd()) {
|
|
||||||
self._proto._end_container = self._proto._start_container;
|
self._proto._end_container = self._proto._start_container;
|
||||||
self._proto._end_offset = self._proto._start_offset;
|
self._proto._end_offset = self._proto._start_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setEnd(self: *Range, node: *Node, offset: u32) !void {
|
pub fn setEnd(self: *Range, node: *Node, offset: u32) !void {
|
||||||
// Validate offset
|
|
||||||
if (offset > node.getLength()) {
|
|
||||||
return error.IndexSizeError;
|
|
||||||
}
|
|
||||||
|
|
||||||
self._proto._end_container = node;
|
self._proto._end_container = node;
|
||||||
self._proto._end_offset = offset;
|
self._proto._end_offset = offset;
|
||||||
|
|
||||||
// If end is now before start, or nodes are in different trees, collapse to end
|
// If end is now before start, collapse to end
|
||||||
const start_root = self._proto._start_container.getRootNode(null);
|
if (self._proto.isStartAfterEnd()) {
|
||||||
const end_root = node.getRootNode(null);
|
|
||||||
if (start_root != end_root or self._proto.isStartAfterEnd()) {
|
|
||||||
self._proto._start_container = self._proto._end_container;
|
self._proto._start_container = self._proto._end_container;
|
||||||
self._proto._start_offset = self._proto._end_offset;
|
self._proto._start_offset = self._proto._end_offset;
|
||||||
}
|
}
|
||||||
@@ -118,181 +105,6 @@ pub fn collapse(self: *Range, to_start: ?bool) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detach(_: *Range) void {
|
|
||||||
// Legacy no-op method kept for backwards compatibility
|
|
||||||
// Modern spec: "The detach() method must do nothing."
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compareBoundaryPoints(self: *const Range, how_raw: i32, source_range: *const Range) !i16 {
|
|
||||||
// Convert how parameter per WebIDL unsigned short conversion
|
|
||||||
// This handles negative numbers and out-of-range values
|
|
||||||
const how_mod = @mod(how_raw, 65536);
|
|
||||||
const how: u16 = if (how_mod < 0) @intCast(@as(i32, how_mod) + 65536) else @intCast(how_mod);
|
|
||||||
|
|
||||||
// If how is not one of 0, 1, 2, or 3, throw NotSupportedError
|
|
||||||
if (how > 3) {
|
|
||||||
return error.NotSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the two ranges' root is different, throw WrongDocumentError
|
|
||||||
const this_root = self._proto._start_container.getRootNode(null);
|
|
||||||
const source_root = source_range._proto._start_container.getRootNode(null);
|
|
||||||
if (this_root != source_root) {
|
|
||||||
return error.WrongDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine which boundary points to compare based on how parameter
|
|
||||||
const result = switch (how) {
|
|
||||||
0 => AbstractRange.compareBoundaryPoints( // START_TO_START
|
|
||||||
self._proto._start_container,
|
|
||||||
self._proto._start_offset,
|
|
||||||
source_range._proto._start_container,
|
|
||||||
source_range._proto._start_offset,
|
|
||||||
),
|
|
||||||
1 => AbstractRange.compareBoundaryPoints( // START_TO_END
|
|
||||||
self._proto._start_container,
|
|
||||||
self._proto._start_offset,
|
|
||||||
source_range._proto._end_container,
|
|
||||||
source_range._proto._end_offset,
|
|
||||||
),
|
|
||||||
2 => AbstractRange.compareBoundaryPoints( // END_TO_END
|
|
||||||
self._proto._end_container,
|
|
||||||
self._proto._end_offset,
|
|
||||||
source_range._proto._end_container,
|
|
||||||
source_range._proto._end_offset,
|
|
||||||
),
|
|
||||||
3 => AbstractRange.compareBoundaryPoints( // END_TO_START
|
|
||||||
self._proto._end_container,
|
|
||||||
self._proto._end_offset,
|
|
||||||
source_range._proto._start_container,
|
|
||||||
source_range._proto._start_offset,
|
|
||||||
),
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
|
|
||||||
return switch (result) {
|
|
||||||
.before => -1,
|
|
||||||
.equal => 0,
|
|
||||||
.after => 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn comparePoint(self: *const Range, node: *Node, offset: u32) !i16 {
|
|
||||||
if (offset > node.getLength()) {
|
|
||||||
return error.IndexSizeError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if node is in a different tree than the range
|
|
||||||
const node_root = node.getRootNode(null);
|
|
||||||
const start_root = self._proto._start_container.getRootNode(null);
|
|
||||||
if (node_root != start_root) {
|
|
||||||
return error.WrongDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare point with start boundary
|
|
||||||
const cmp_start = AbstractRange.compareBoundaryPoints(
|
|
||||||
node,
|
|
||||||
offset,
|
|
||||||
self._proto._start_container,
|
|
||||||
self._proto._start_offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cmp_start == .before) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cmp_end = AbstractRange.compareBoundaryPoints(
|
|
||||||
node,
|
|
||||||
offset,
|
|
||||||
self._proto._end_container,
|
|
||||||
self._proto._end_offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
return if (cmp_end == .after) 1 else 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isPointInRange(self: *const Range, node: *Node, offset: u32) !bool {
|
|
||||||
// If node's root is different from the context object's root, return false
|
|
||||||
const node_root = node.getRootNode(null);
|
|
||||||
const start_root = self._proto._start_container.getRootNode(null);
|
|
||||||
if (node_root != start_root) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node._type == .document_type) {
|
|
||||||
return error.InvalidNodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If offset is greater than node's length, throw IndexSizeError
|
|
||||||
if (offset > node.getLength()) {
|
|
||||||
return error.IndexSizeError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If (node, offset) is before start or after end, return false
|
|
||||||
const cmp_start = AbstractRange.compareBoundaryPoints(
|
|
||||||
node,
|
|
||||||
offset,
|
|
||||||
self._proto._start_container,
|
|
||||||
self._proto._start_offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cmp_start == .before) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cmp_end = AbstractRange.compareBoundaryPoints(
|
|
||||||
node,
|
|
||||||
offset,
|
|
||||||
self._proto._end_container,
|
|
||||||
self._proto._end_offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
return cmp_end != .after;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intersectsNode(self: *const Range, node: *Node) bool {
|
|
||||||
// If node's root is different from the context object's root, return false
|
|
||||||
const node_root = node.getRootNode(null);
|
|
||||||
const start_root = self._proto._start_container.getRootNode(null);
|
|
||||||
if (node_root != start_root) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let parent be node's parent
|
|
||||||
const parent = node.parentNode() orelse {
|
|
||||||
// If parent is null, return true
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Let offset be node's index
|
|
||||||
const offset = parent.getChildIndex(node) orelse {
|
|
||||||
// Should not happen if node has a parent
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// If (parent, offset) is before end and (parent, offset + 1) is after start, return true
|
|
||||||
const before_end = AbstractRange.compareBoundaryPoints(
|
|
||||||
parent,
|
|
||||||
offset,
|
|
||||||
self._proto._end_container,
|
|
||||||
self._proto._end_offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
const after_start = AbstractRange.compareBoundaryPoints(
|
|
||||||
parent,
|
|
||||||
offset + 1,
|
|
||||||
self._proto._start_container,
|
|
||||||
self._proto._start_offset,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (before_end == .before and after_start == .after) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
||||||
const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page);
|
const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page);
|
||||||
clone._proto._end_offset = self._proto._end_offset;
|
clone._proto._end_offset = self._proto._end_offset;
|
||||||
@@ -496,35 +308,24 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constants for compareBoundaryPoints
|
|
||||||
pub const START_TO_START = bridge.property(0);
|
|
||||||
pub const START_TO_END = bridge.property(1);
|
|
||||||
pub const END_TO_END = bridge.property(2);
|
|
||||||
pub const END_TO_START = bridge.property(3);
|
|
||||||
|
|
||||||
pub const constructor = bridge.constructor(Range.init, .{});
|
pub const constructor = bridge.constructor(Range.init, .{});
|
||||||
pub const setStart = bridge.function(Range.setStart, .{ .dom_exception = true });
|
pub const setStart = bridge.function(Range.setStart, .{});
|
||||||
pub const setEnd = bridge.function(Range.setEnd, .{ .dom_exception = true });
|
pub const setEnd = bridge.function(Range.setEnd, .{});
|
||||||
pub const setStartBefore = bridge.function(Range.setStartBefore, .{ .dom_exception = true });
|
pub const setStartBefore = bridge.function(Range.setStartBefore, .{});
|
||||||
pub const setStartAfter = bridge.function(Range.setStartAfter, .{ .dom_exception = true });
|
pub const setStartAfter = bridge.function(Range.setStartAfter, .{});
|
||||||
pub const setEndBefore = bridge.function(Range.setEndBefore, .{ .dom_exception = true });
|
pub const setEndBefore = bridge.function(Range.setEndBefore, .{});
|
||||||
pub const setEndAfter = bridge.function(Range.setEndAfter, .{ .dom_exception = true });
|
pub const setEndAfter = bridge.function(Range.setEndAfter, .{});
|
||||||
pub const selectNode = bridge.function(Range.selectNode, .{ .dom_exception = true });
|
pub const selectNode = bridge.function(Range.selectNode, .{});
|
||||||
pub const selectNodeContents = bridge.function(Range.selectNodeContents, .{});
|
pub const selectNodeContents = bridge.function(Range.selectNodeContents, .{});
|
||||||
pub const collapse = bridge.function(Range.collapse, .{ .dom_exception = true });
|
pub const collapse = bridge.function(Range.collapse, .{});
|
||||||
pub const detach = bridge.function(Range.detach, .{});
|
pub const cloneRange = bridge.function(Range.cloneRange, .{});
|
||||||
pub const compareBoundaryPoints = bridge.function(Range.compareBoundaryPoints, .{ .dom_exception = true });
|
pub const insertNode = bridge.function(Range.insertNode, .{});
|
||||||
pub const comparePoint = bridge.function(Range.comparePoint, .{ .dom_exception = true });
|
pub const deleteContents = bridge.function(Range.deleteContents, .{});
|
||||||
pub const isPointInRange = bridge.function(Range.isPointInRange, .{ .dom_exception = true });
|
pub const cloneContents = bridge.function(Range.cloneContents, .{});
|
||||||
pub const intersectsNode = bridge.function(Range.intersectsNode, .{});
|
pub const extractContents = bridge.function(Range.extractContents, .{});
|
||||||
pub const cloneRange = bridge.function(Range.cloneRange, .{ .dom_exception = true });
|
pub const surroundContents = bridge.function(Range.surroundContents, .{});
|
||||||
pub const insertNode = bridge.function(Range.insertNode, .{ .dom_exception = true });
|
pub const createContextualFragment = bridge.function(Range.createContextualFragment, .{});
|
||||||
pub const deleteContents = bridge.function(Range.deleteContents, .{ .dom_exception = true });
|
pub const toString = bridge.function(Range.toString, .{});
|
||||||
pub const cloneContents = bridge.function(Range.cloneContents, .{ .dom_exception = true });
|
|
||||||
pub const extractContents = bridge.function(Range.extractContents, .{ .dom_exception = true });
|
|
||||||
pub const surroundContents = bridge.function(Range.surroundContents, .{ .dom_exception = true });
|
|
||||||
pub const createContextualFragment = bridge.function(Range.createContextualFragment, .{ .dom_exception = true });
|
|
||||||
pub const toString = bridge.function(Range.toString, .{ .dom_exception = true });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ _proto: *DocumentFragment,
|
|||||||
_mode: Mode,
|
_mode: Mode,
|
||||||
_host: *Element,
|
_host: *Element,
|
||||||
_elements_by_id: std.StringHashMapUnmanaged(*Element) = .{},
|
_elements_by_id: std.StringHashMapUnmanaged(*Element) = .{},
|
||||||
_removed_ids: std.StringHashMapUnmanaged(void) = .{},
|
|
||||||
|
|
||||||
pub fn init(host: *Element, mode: Mode, page: *Page) !*ShadowRoot {
|
pub fn init(host: *Element, mode: Mode, page: *Page) !*ShadowRoot {
|
||||||
return page._factory.documentFragment(ShadowRoot{
|
return page._factory.documentFragment(ShadowRoot{
|
||||||
@@ -73,34 +72,9 @@ pub fn getHost(self: *const ShadowRoot) *Element {
|
|||||||
return self._host;
|
return self._host;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element {
|
pub fn getElementById(self: *ShadowRoot, id_: ?[]const u8) ?*Element {
|
||||||
if (id.len == 0) {
|
const id = id_ orelse return null;
|
||||||
return null;
|
return self._elements_by_id.get(id);
|
||||||
}
|
|
||||||
|
|
||||||
// Fast path: ID is in the map
|
|
||||||
if (self._elements_by_id.get(id)) |element| {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow path: ID was removed but might have duplicates
|
|
||||||
if (self._removed_ids.remove(id)) {
|
|
||||||
// Do a tree walk to find another element with this ID
|
|
||||||
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
|
|
||||||
while (tw.next()) |el| {
|
|
||||||
const element_id = el.getAttributeSafe("id") orelse continue;
|
|
||||||
if (std.mem.eql(u8, element_id, id)) {
|
|
||||||
// we ignore this error to keep getElementById easy to call
|
|
||||||
// if it really failed, then we're out of memory and nothing's
|
|
||||||
// going to work like it should anyways.
|
|
||||||
const owned_id = page.dupeString(id) catch return null;
|
|
||||||
self._elements_by_id.put(page.arena, owned_id, el) catch return null;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
@@ -114,17 +88,7 @@ pub const JsApi = struct {
|
|||||||
|
|
||||||
pub const mode = bridge.accessor(ShadowRoot.getMode, null, .{});
|
pub const mode = bridge.accessor(ShadowRoot.getMode, null, .{});
|
||||||
pub const host = bridge.accessor(ShadowRoot.getHost, null, .{});
|
pub const host = bridge.accessor(ShadowRoot.getHost, null, .{});
|
||||||
pub const getElementById = bridge.function(_getElementById, .{});
|
pub const getElementById = bridge.function(ShadowRoot.getElementById, .{});
|
||||||
fn _getElementById(self: *ShadowRoot, value_: ?js.Value, page: *Page) !?*Element {
|
|
||||||
const value = value_ orelse return null;
|
|
||||||
if (value.isNull()) {
|
|
||||||
return self.getElementById("null", page);
|
|
||||||
}
|
|
||||||
if (value.isUndefined()) {
|
|
||||||
return self.getElementById("undefined", page);
|
|
||||||
}
|
|
||||||
return self.getElementById(try value.toZig([]const u8), page);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const Mode = enum {
|
|||||||
tag,
|
tag,
|
||||||
tag_name,
|
tag_name,
|
||||||
class_name,
|
class_name,
|
||||||
|
name,
|
||||||
all_elements,
|
all_elements,
|
||||||
child_elements,
|
child_elements,
|
||||||
child_tag,
|
child_tag,
|
||||||
@@ -43,6 +44,7 @@ _data: union(Mode) {
|
|||||||
tag: NodeLive(.tag),
|
tag: NodeLive(.tag),
|
||||||
tag_name: NodeLive(.tag_name),
|
tag_name: NodeLive(.tag_name),
|
||||||
class_name: NodeLive(.class_name),
|
class_name: NodeLive(.class_name),
|
||||||
|
name: NodeLive(.name),
|
||||||
all_elements: NodeLive(.all_elements),
|
all_elements: NodeLive(.all_elements),
|
||||||
child_elements: NodeLive(.child_elements),
|
child_elements: NodeLive(.child_elements),
|
||||||
child_tag: NodeLive(.child_tag),
|
child_tag: NodeLive(.child_tag),
|
||||||
@@ -77,6 +79,7 @@ pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator {
|
|||||||
.tag => |*impl| .{ .tag = impl._tw.clone() },
|
.tag => |*impl| .{ .tag = impl._tw.clone() },
|
||||||
.tag_name => |*impl| .{ .tag_name = impl._tw.clone() },
|
.tag_name => |*impl| .{ .tag_name = impl._tw.clone() },
|
||||||
.class_name => |*impl| .{ .class_name = impl._tw.clone() },
|
.class_name => |*impl| .{ .class_name = impl._tw.clone() },
|
||||||
|
.name => |*impl| .{ .name = impl._tw.clone() },
|
||||||
.all_elements => |*impl| .{ .all_elements = impl._tw.clone() },
|
.all_elements => |*impl| .{ .all_elements = impl._tw.clone() },
|
||||||
.child_elements => |*impl| .{ .child_elements = impl._tw.clone() },
|
.child_elements => |*impl| .{ .child_elements = impl._tw.clone() },
|
||||||
.child_tag => |*impl| .{ .child_tag = impl._tw.clone() },
|
.child_tag => |*impl| .{ .child_tag = impl._tw.clone() },
|
||||||
@@ -95,6 +98,7 @@ pub const Iterator = GenericIterator(struct {
|
|||||||
tag: TreeWalker.FullExcludeSelf,
|
tag: TreeWalker.FullExcludeSelf,
|
||||||
tag_name: TreeWalker.FullExcludeSelf,
|
tag_name: TreeWalker.FullExcludeSelf,
|
||||||
class_name: TreeWalker.FullExcludeSelf,
|
class_name: TreeWalker.FullExcludeSelf,
|
||||||
|
name: TreeWalker.FullExcludeSelf,
|
||||||
all_elements: TreeWalker.FullExcludeSelf,
|
all_elements: TreeWalker.FullExcludeSelf,
|
||||||
child_elements: TreeWalker.Children,
|
child_elements: TreeWalker.Children,
|
||||||
child_tag: TreeWalker.Children,
|
child_tag: TreeWalker.Children,
|
||||||
@@ -109,6 +113,7 @@ pub const Iterator = GenericIterator(struct {
|
|||||||
.tag => |*impl| impl.nextTw(&self.tw.tag),
|
.tag => |*impl| impl.nextTw(&self.tw.tag),
|
||||||
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name),
|
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name),
|
||||||
.class_name => |*impl| impl.nextTw(&self.tw.class_name),
|
.class_name => |*impl| impl.nextTw(&self.tw.class_name),
|
||||||
|
.name => |*impl| impl.nextTw(&self.tw.name),
|
||||||
.all_elements => |*impl| impl.nextTw(&self.tw.all_elements),
|
.all_elements => |*impl| impl.nextTw(&self.tw.all_elements),
|
||||||
.child_elements => |*impl| impl.nextTw(&self.tw.child_elements),
|
.child_elements => |*impl| impl.nextTw(&self.tw.child_elements),
|
||||||
.child_tag => |*impl| impl.nextTw(&self.tw.child_tag),
|
.child_tag => |*impl| impl.nextTw(&self.tw.child_tag),
|
||||||
|
|||||||
@@ -26,13 +26,11 @@ const Node = @import("../Node.zig");
|
|||||||
const ChildNodes = @import("ChildNodes.zig");
|
const ChildNodes = @import("ChildNodes.zig");
|
||||||
const RadioNodeList = @import("RadioNodeList.zig");
|
const RadioNodeList = @import("RadioNodeList.zig");
|
||||||
const SelectorList = @import("../selector/List.zig");
|
const SelectorList = @import("../selector/List.zig");
|
||||||
const NodeLive = @import("node_live.zig").NodeLive;
|
|
||||||
|
|
||||||
const Mode = enum {
|
const Mode = enum {
|
||||||
child_nodes,
|
child_nodes,
|
||||||
selector_list,
|
selector_list,
|
||||||
radio_node_list,
|
radio_node_list,
|
||||||
name,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const NodeList = @This();
|
const NodeList = @This();
|
||||||
@@ -41,7 +39,6 @@ data: union(Mode) {
|
|||||||
child_nodes: *ChildNodes,
|
child_nodes: *ChildNodes,
|
||||||
selector_list: *SelectorList,
|
selector_list: *SelectorList,
|
||||||
radio_node_list: *RadioNodeList,
|
radio_node_list: *RadioNodeList,
|
||||||
name: NodeLive(.name),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
pub fn length(self: *NodeList, page: *Page) !u32 {
|
pub fn length(self: *NodeList, page: *Page) !u32 {
|
||||||
@@ -49,7 +46,6 @@ pub fn length(self: *NodeList, page: *Page) !u32 {
|
|||||||
.child_nodes => |impl| impl.length(page),
|
.child_nodes => |impl| impl.length(page),
|
||||||
.selector_list => |impl| @intCast(impl.getLength()),
|
.selector_list => |impl| @intCast(impl.getLength()),
|
||||||
.radio_node_list => |impl| impl.getLength(),
|
.radio_node_list => |impl| impl.getLength(),
|
||||||
.name => |*impl| impl.length(page),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +54,6 @@ pub fn getAtIndex(self: *NodeList, index: usize, page: *Page) !?*Node {
|
|||||||
.child_nodes => |impl| impl.getAtIndex(index, page),
|
.child_nodes => |impl| impl.getAtIndex(index, page),
|
||||||
.selector_list => |impl| impl.getAtIndex(index),
|
.selector_list => |impl| impl.getAtIndex(index),
|
||||||
.radio_node_list => |impl| impl.getAtIndex(index, page),
|
.radio_node_list => |impl| impl.getAtIndex(index, page),
|
||||||
.name => |*impl| if (impl.getAtIndex(index, page)) |el| el.asNode() else null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getByName(self: *Self, name: []const u8, page: *Page) ?*Element {
|
pub fn getByName(self: *Self, name: []const u8, page: *Page) ?*Element {
|
||||||
if (page.document.getElementById(name, page)) |element| {
|
if (page.document.getElementById(name)) |element| {
|
||||||
const node = element.asNode();
|
const node = element.asNode();
|
||||||
if (self._tw.contains(node) and self.matches(node)) {
|
if (self._tw.contains(node) and self.matches(node)) {
|
||||||
return element;
|
return element;
|
||||||
@@ -320,14 +320,12 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HTMLCollection = @import("HTMLCollection.zig");
|
const HTMLCollection = @import("HTMLCollection.zig");
|
||||||
const NodeList = @import("NodeList.zig");
|
pub fn runtimeGenericWrap(self: Self, page: *Page) !*HTMLCollection {
|
||||||
|
|
||||||
pub fn runtimeGenericWrap(self: Self, page: *Page) !if (mode == .name) *NodeList else *HTMLCollection {
|
|
||||||
const collection = switch (mode) {
|
const collection = switch (mode) {
|
||||||
.name => return page._factory.create(NodeList{ .data = .{ .name = self } }),
|
|
||||||
.tag => HTMLCollection{ ._data = .{ .tag = self } },
|
.tag => HTMLCollection{ ._data = .{ .tag = self } },
|
||||||
.tag_name => HTMLCollection{ ._data = .{ .tag_name = self } },
|
.tag_name => HTMLCollection{ ._data = .{ .tag_name = self } },
|
||||||
.class_name => HTMLCollection{ ._data = .{ .class_name = self } },
|
.class_name => HTMLCollection{ ._data = .{ .class_name = self } },
|
||||||
|
.name => HTMLCollection{ ._data = .{ .name = self } },
|
||||||
.all_elements => HTMLCollection{ ._data = .{ .all_elements = self } },
|
.all_elements => HTMLCollection{ ._data = .{ .all_elements = self } },
|
||||||
.child_elements => HTMLCollection{ ._data = .{ .child_elements = self } },
|
.child_elements => HTMLCollection{ ._data = .{ .child_elements = self } },
|
||||||
.child_tag => HTMLCollection{ ._data = .{ .child_tag = self } },
|
.child_tag => HTMLCollection{ ._data = .{ .child_tag = self } },
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ const std = @import("std");
|
|||||||
const log = @import("../../../log.zig");
|
const log = @import("../../../log.zig");
|
||||||
const String = @import("../../../string.zig").String;
|
const String = @import("../../../string.zig").String;
|
||||||
|
|
||||||
const CssParser = @import("../../css/Parser.zig");
|
|
||||||
|
|
||||||
const js = @import("../../js/js.zig");
|
const js = @import("../../js/js.zig");
|
||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
const Element = @import("../Element.zig");
|
const Element = @import("../Element.zig");
|
||||||
@@ -151,10 +149,30 @@ pub fn setCssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse and set new properties
|
// Parse and set new properties
|
||||||
var it = CssParser.parseDeclarationsList(text);
|
// This is a simple parser - a full implementation would use a proper CSS parser
|
||||||
|
var it = std.mem.splitScalar(u8, text, ';');
|
||||||
while (it.next()) |declaration| {
|
while (it.next()) |declaration| {
|
||||||
const priority: ?[]const u8 = if (declaration.important) "important" else null;
|
const trimmed = std.mem.trim(u8, declaration, &std.ascii.whitespace);
|
||||||
try self.setProperty(declaration.name, declaration.value, priority, page);
|
if (trimmed.len == 0) continue;
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, trimmed, ':')) |colon_pos| {
|
||||||
|
const name = std.mem.trim(u8, trimmed[0..colon_pos], &std.ascii.whitespace);
|
||||||
|
const value_part = std.mem.trim(u8, trimmed[colon_pos + 1 ..], &std.ascii.whitespace);
|
||||||
|
|
||||||
|
var value = value_part;
|
||||||
|
var priority: ?[]const u8 = null;
|
||||||
|
|
||||||
|
// Check for !important
|
||||||
|
if (std.mem.lastIndexOfScalar(u8, value_part, '!')) |bang_pos| {
|
||||||
|
const after_bang = std.mem.trim(u8, value_part[bang_pos + 1 ..], &std.ascii.whitespace);
|
||||||
|
if (std.mem.eql(u8, after_bang, "important")) {
|
||||||
|
value = std.mem.trimRight(u8, value_part[0..bang_pos], &std.ascii.whitespace);
|
||||||
|
priority = "important";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.setProperty(name, value, priority, page);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -477,19 +477,11 @@ pub const NamedNodeMap = struct {
|
|||||||
return self._list.getAttribute(name, self._element, page);
|
return self._list.getAttribute(name, self._element, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(self: *const NamedNodeMap, attribute: *Attribute, page: *Page) !?*Attribute {
|
pub fn setByName(self: *const NamedNodeMap, attribute: *Attribute, page: *Page) !?*Attribute {
|
||||||
attribute._element = null; // just a requirement of list.putAttribute, it'll re-set it.
|
attribute._element = null; // just a requirement of list.putAttribute, it'll re-set it.
|
||||||
return self._list.putAttribute(attribute, self._element, page);
|
return self._list.putAttribute(attribute, self._element, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeByName(self: *const NamedNodeMap, name: []const u8, page: *Page) !?*Attribute {
|
|
||||||
// this 2-step process (get then delete) isn't efficient. But we don't
|
|
||||||
// expect this to be called often, and this lets us keep delete straightforward.
|
|
||||||
const attr = (try self.getByName(name, page)) orelse return null;
|
|
||||||
try self._list.delete(name, self._element, page);
|
|
||||||
return attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iterator(self: *const NamedNodeMap, page: *Page) !*Iterator {
|
pub fn iterator(self: *const NamedNodeMap, page: *Page) !*Iterator {
|
||||||
return .init(.{ .list = self }, page);
|
return .init(.{ .list = self }, page);
|
||||||
}
|
}
|
||||||
@@ -518,8 +510,7 @@ pub const NamedNodeMap = struct {
|
|||||||
pub const @"[int]" = bridge.indexed(NamedNodeMap.getAtIndex, .{ .null_as_undefined = true });
|
pub const @"[int]" = bridge.indexed(NamedNodeMap.getAtIndex, .{ .null_as_undefined = true });
|
||||||
pub const @"[str]" = bridge.namedIndexed(NamedNodeMap.getByName, null, null, .{ .null_as_undefined = true });
|
pub const @"[str]" = bridge.namedIndexed(NamedNodeMap.getByName, null, null, .{ .null_as_undefined = true });
|
||||||
pub const getNamedItem = bridge.function(NamedNodeMap.getByName, .{});
|
pub const getNamedItem = bridge.function(NamedNodeMap.getByName, .{});
|
||||||
pub const setNamedItem = bridge.function(NamedNodeMap.set, .{});
|
pub const setNamedItem = bridge.function(NamedNodeMap.setByName, .{});
|
||||||
pub const removeNamedItem = bridge.function(NamedNodeMap.removeByName, .{});
|
|
||||||
pub const item = bridge.function(_item, .{});
|
pub const item = bridge.function(_item, .{});
|
||||||
fn _item(self: *const NamedNodeMap, index: i32, page: *Page) !?*Attribute {
|
fn _item(self: *const NamedNodeMap, index: i32, page: *Page) !?*Attribute {
|
||||||
// the bridge.indexed handles this, so if we want
|
// the bridge.indexed handles this, so if we want
|
||||||
|
|||||||
@@ -220,18 +220,7 @@ pub const JsApi = struct {
|
|||||||
pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{});
|
pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{});
|
||||||
pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{});
|
pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{});
|
||||||
pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{});
|
pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{});
|
||||||
pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true });
|
|
||||||
pub const toString = bridge.function(Anchor.getHref, .{});
|
pub const toString = bridge.function(Anchor.getHref, .{});
|
||||||
|
|
||||||
fn _getRelList(self: *Anchor, page: *Page) !?*@import("../../collections.zig").DOMTokenList {
|
|
||||||
const element = self.asElement();
|
|
||||||
// relList is only valid for HTML and SVG <a> elements
|
|
||||||
const namespace = element._namespace;
|
|
||||||
if (namespace != .html and namespace != .svg) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return element.getRelList(page);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../../../testing.zig");
|
const testing = @import("../../../../testing.zig");
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ pub fn getForm(self: *Button, page: *Page) ?*Form {
|
|||||||
|
|
||||||
// If form attribute exists, ONLY use that (even if it references nothing)
|
// If form attribute exists, ONLY use that (even if it references nothing)
|
||||||
if (element.getAttributeSafe("form")) |form_id| {
|
if (element.getAttributeSafe("form")) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
// form attribute present but invalid - no form owner
|
// form attribute present but invalid - no form owner
|
||||||
|
|||||||
@@ -46,16 +46,14 @@ pub const Type = enum {
|
|||||||
range,
|
range,
|
||||||
date,
|
date,
|
||||||
time,
|
time,
|
||||||
@"datetime-local",
|
datetime_local,
|
||||||
month,
|
month,
|
||||||
week,
|
week,
|
||||||
color,
|
color,
|
||||||
|
|
||||||
pub fn fromString(str: []const u8) Type {
|
pub fn fromString(str: []const u8) Type {
|
||||||
// Longest type name is "datetime-local" at 14 chars
|
// Longest type name is "datetime-local" at 14 chars
|
||||||
if (str.len > 32) {
|
if (str.len > 32) return .text;
|
||||||
return .text;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf: [32]u8 = undefined;
|
var buf: [32]u8 = undefined;
|
||||||
const lower = std.ascii.lowerString(&buf, str);
|
const lower = std.ascii.lowerString(&buf, str);
|
||||||
@@ -63,7 +61,10 @@ pub const Type = enum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toString(self: Type) []const u8 {
|
pub fn toString(self: Type) []const u8 {
|
||||||
return @tagName(self);
|
return switch (self) {
|
||||||
|
.datetime_local => "datetime-local",
|
||||||
|
else => @tagName(self),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,7 +76,6 @@ _checked: bool = false,
|
|||||||
_checked_dirty: bool = false,
|
_checked_dirty: bool = false,
|
||||||
_input_type: Type = .text,
|
_input_type: Type = .text,
|
||||||
_selected: bool = false,
|
_selected: bool = false,
|
||||||
_indeterminate: bool = false,
|
|
||||||
|
|
||||||
pub fn asElement(self: *Input) *Element {
|
pub fn asElement(self: *Input) *Element {
|
||||||
return self._proto._proto;
|
return self._proto._proto;
|
||||||
@@ -129,14 +129,6 @@ pub fn setChecked(self: *Input, checked: bool, page: *Page) !void {
|
|||||||
self._checked_dirty = true;
|
self._checked_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getIndeterminate(self: *const Input) bool {
|
|
||||||
return self._indeterminate;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setIndeterminate(self: *Input, value: bool) !void {
|
|
||||||
self._indeterminate = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getDefaultChecked(self: *const Input) bool {
|
pub fn getDefaultChecked(self: *const Input) bool {
|
||||||
return self._default_checked;
|
return self._default_checked;
|
||||||
}
|
}
|
||||||
@@ -264,7 +256,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form {
|
|||||||
|
|
||||||
// If form attribute exists, ONLY use that (even if it references nothing)
|
// If form attribute exists, ONLY use that (even if it references nothing)
|
||||||
if (element.getAttributeSafe("form")) |form_id| {
|
if (element.getAttributeSafe("form")) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
// form attribute present but invalid - no form owner
|
// form attribute present but invalid - no form owner
|
||||||
@@ -350,7 +342,6 @@ pub const JsApi = struct {
|
|||||||
pub const size = bridge.accessor(Input.getSize, Input.setSize, .{});
|
pub const size = bridge.accessor(Input.getSize, Input.setSize, .{});
|
||||||
pub const src = bridge.accessor(Input.getSrc, Input.setSrc, .{});
|
pub const src = bridge.accessor(Input.getSrc, Input.setSrc, .{});
|
||||||
pub const form = bridge.accessor(Input.getForm, null, .{});
|
pub const form = bridge.accessor(Input.getForm, null, .{});
|
||||||
pub const indeterminate = bridge.accessor(Input.getIndeterminate, Input.setIndeterminate, .{});
|
|
||||||
pub const select = bridge.function(Input.select, .{});
|
pub const select = bridge.function(Input.select, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -413,18 +404,6 @@ pub const Build = struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cloned(source_element: *Element, cloned_element: *Element, _: *Page) !void {
|
|
||||||
const source = source_element.as(Input);
|
|
||||||
const clone = cloned_element.as(Input);
|
|
||||||
|
|
||||||
// Copy runtime state from source to clone
|
|
||||||
clone._value = source._value;
|
|
||||||
clone._checked = source._checked;
|
|
||||||
clone._checked_dirty = source._checked_dirty;
|
|
||||||
clone._selected = source._selected;
|
|
||||||
clone._indeterminate = source._indeterminate;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../../../testing.zig");
|
const testing = @import("../../../../testing.zig");
|
||||||
|
|||||||
@@ -68,16 +68,6 @@ pub const JsApi = struct {
|
|||||||
|
|
||||||
pub const rel = bridge.accessor(Link.getRel, Link.setRel, .{});
|
pub const rel = bridge.accessor(Link.getRel, Link.setRel, .{});
|
||||||
pub const href = bridge.accessor(Link.getHref, Link.setHref, .{});
|
pub const href = bridge.accessor(Link.getHref, Link.setHref, .{});
|
||||||
pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true });
|
|
||||||
|
|
||||||
fn _getRelList(self: *Link, page: *Page) !?*@import("../../collections.zig").DOMTokenList {
|
|
||||||
const element = self.asElement();
|
|
||||||
// relList is only valid for HTML <link> elements, not SVG or MathML
|
|
||||||
if (element._namespace != .html) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return element.getRelList(page);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../../../testing.zig");
|
const testing = @import("../../../../testing.zig");
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ pub fn getForm(self: *Select, page: *Page) ?*Form {
|
|||||||
|
|
||||||
// If form attribute exists, ONLY use that (even if it references nothing)
|
// If form attribute exists, ONLY use that (even if it references nothing)
|
||||||
if (element.getAttributeSafe("form")) |form_id| {
|
if (element.getAttributeSafe("form")) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
// form attribute present but invalid - no form owner
|
// form attribute present but invalid - no form owner
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ pub fn getForm(self: *TextArea, page: *Page) ?*Form {
|
|||||||
|
|
||||||
// If form attribute exists, ONLY use that (even if it references nothing)
|
// If form attribute exists, ONLY use that (even if it references nothing)
|
||||||
if (element.getAttributeSafe("form")) |form_id| {
|
if (element.getAttributeSafe("form")) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
// form attribute present but invalid - no form owner
|
// form attribute present but invalid - no form owner
|
||||||
|
|||||||
11
src/html5ever/Cargo.lock
generated
11
src/html5ever/Cargo.lock
generated
@@ -72,7 +72,6 @@ dependencies = [
|
|||||||
"tikv-jemalloc-ctl",
|
"tikv-jemalloc-ctl",
|
||||||
"tikv-jemallocator",
|
"tikv-jemallocator",
|
||||||
"typed-arena",
|
"typed-arena",
|
||||||
"xml5ever",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -477,13 +476,3 @@ name = "windows_x86_64_msvc"
|
|||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xml5ever"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"markup5ever",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ string_cache = "0.9.0"
|
|||||||
typed-arena = "2.0.2"
|
typed-arena = "2.0.2"
|
||||||
tikv-jemallocator = {version = "0.6.0", features = ["stats"]}
|
tikv-jemallocator = {version = "0.6.0", features = ["stats"]}
|
||||||
tikv-jemalloc-ctl = {version = "0.6.0", features = ["stats"]}
|
tikv-jemalloc-ctl = {version = "0.6.0", features = ["stats"]}
|
||||||
xml5ever = "0.35.0"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -16,20 +16,20 @@
|
|||||||
// 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/>.
|
||||||
|
|
||||||
mod sink;
|
|
||||||
mod types;
|
mod types;
|
||||||
|
mod sink;
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||||
|
|
||||||
|
use types::*;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::os::raw::{c_uchar, c_void};
|
use std::os::raw::{c_uchar, c_void};
|
||||||
use types::*;
|
|
||||||
|
|
||||||
|
use html5ever::{parse_document, parse_fragment, QualName, LocalName, ns, ParseOpts, Parser};
|
||||||
|
use html5ever::tendril::{TendrilSink, StrTendril};
|
||||||
use html5ever::interface::tree_builder::QuirksMode;
|
use html5ever::interface::tree_builder::QuirksMode;
|
||||||
use html5ever::tendril::{StrTendril, TendrilSink};
|
|
||||||
use html5ever::{ns, parse_document, parse_fragment, LocalName, ParseOpts, Parser, QualName};
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn html5ever_parse_document(
|
pub extern "C" fn html5ever_parse_document(
|
||||||
@@ -135,8 +135,7 @@ pub extern "C" fn html5ever_parse_fragment(
|
|||||||
|
|
||||||
let bytes = unsafe { std::slice::from_raw_parts(html, len) };
|
let bytes = unsafe { std::slice::from_raw_parts(html, len) };
|
||||||
parse_fragment(
|
parse_fragment(
|
||||||
sink,
|
sink, Default::default(),
|
||||||
Default::default(),
|
|
||||||
QualName::new(None, ns!(html), LocalName::from("body")),
|
QualName::new(None, ns!(html), LocalName::from("body")),
|
||||||
vec![], // attributes
|
vec![], // attributes
|
||||||
false, // context_element_allows_scripting
|
false, // context_element_allows_scripting
|
||||||
@@ -183,7 +182,7 @@ pub struct Memory {
|
|||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn html5ever_get_memory_usage() -> Memory {
|
pub extern "C" fn html5ever_get_memory_usage() -> Memory {
|
||||||
use tikv_jemalloc_ctl::{epoch, stats};
|
use tikv_jemalloc_ctl::{stats, epoch};
|
||||||
|
|
||||||
// many statistics are cached and only updated when the epoch is advanced.
|
// many statistics are cached and only updated when the epoch is advanced.
|
||||||
epoch::advance().unwrap();
|
epoch::advance().unwrap();
|
||||||
@@ -191,7 +190,7 @@ pub extern "C" fn html5ever_get_memory_usage() -> Memory {
|
|||||||
return Memory{
|
return Memory{
|
||||||
resident: stats::resident::read().unwrap(),
|
resident: stats::resident::read().unwrap(),
|
||||||
allocated: stats::allocated::read().unwrap(),
|
allocated: stats::allocated::read().unwrap(),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streaming parser API
|
// Streaming parser API
|
||||||
@@ -226,8 +225,9 @@ pub extern "C" fn html5ever_streaming_parser_create(
|
|||||||
// SAFETY: We're creating a self-referential structure here.
|
// SAFETY: We're creating a self-referential structure here.
|
||||||
// The arena is stored in the StreamingParser and lives as long as the parser.
|
// The arena is stored in the StreamingParser and lives as long as the parser.
|
||||||
// The sink contains a reference to the arena that's valid for the parser's lifetime.
|
// The sink contains a reference to the arena that's valid for the parser's lifetime.
|
||||||
let arena_ref: &'static typed_arena::Arena<sink::ElementData> =
|
let arena_ref: &'static typed_arena::Arena<sink::ElementData> = unsafe {
|
||||||
unsafe { std::mem::transmute(arena.as_ref()) };
|
std::mem::transmute(arena.as_ref())
|
||||||
|
};
|
||||||
|
|
||||||
let sink = sink::Sink {
|
let sink = sink::Sink {
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -281,8 +281,7 @@ pub extern "C" fn html5ever_streaming_parser_feed(
|
|||||||
|
|
||||||
// Feed the chunk to the parser
|
// Feed the chunk to the parser
|
||||||
// The Parser implements TendrilSink, so we can call process() on it
|
// The Parser implements TendrilSink, so we can call process() on it
|
||||||
let parser = streaming_parser
|
let parser = streaming_parser.parser
|
||||||
.parser
|
|
||||||
.downcast_mut::<Parser<sink::Sink>>()
|
.downcast_mut::<Parser<sink::Sink>>()
|
||||||
.expect("Invalid parser type");
|
.expect("Invalid parser type");
|
||||||
|
|
||||||
@@ -305,8 +304,7 @@ pub extern "C" fn html5ever_streaming_parser_finish(parser_ptr: *mut c_void) {
|
|||||||
let streaming_parser = unsafe { Box::from_raw(parser_ptr as *mut StreamingParser) };
|
let streaming_parser = unsafe { Box::from_raw(parser_ptr as *mut StreamingParser) };
|
||||||
|
|
||||||
// Extract and finish the parser
|
// Extract and finish the parser
|
||||||
let parser = streaming_parser
|
let parser = streaming_parser.parser
|
||||||
.parser
|
|
||||||
.downcast::<Parser<sink::Sink>>()
|
.downcast::<Parser<sink::Sink>>()
|
||||||
.expect("Invalid parser type");
|
.expect("Invalid parser type");
|
||||||
|
|
||||||
@@ -328,57 +326,3 @@ pub extern "C" fn html5ever_streaming_parser_destroy(parser_ptr: *mut c_void) {
|
|||||||
let _ = Box::from_raw(parser_ptr as *mut StreamingParser);
|
let _ = Box::from_raw(parser_ptr as *mut StreamingParser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn xml5ever_parse_document(
|
|
||||||
xml: *mut c_uchar,
|
|
||||||
len: usize,
|
|
||||||
document: Ref,
|
|
||||||
ctx: Ref,
|
|
||||||
create_element_callback: CreateElementCallback,
|
|
||||||
get_data_callback: GetDataCallback,
|
|
||||||
append_callback: AppendCallback,
|
|
||||||
parse_error_callback: ParseErrorCallback,
|
|
||||||
pop_callback: PopCallback,
|
|
||||||
create_comment_callback: CreateCommentCallback,
|
|
||||||
create_processing_instruction: CreateProcessingInstruction,
|
|
||||||
append_doctype_to_document: AppendDoctypeToDocumentCallback,
|
|
||||||
add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
|
|
||||||
get_template_contents_callback: GetTemplateContentsCallback,
|
|
||||||
remove_from_parent_callback: RemoveFromParentCallback,
|
|
||||||
reparent_children_callback: ReparentChildrenCallback,
|
|
||||||
append_before_sibling_callback: AppendBeforeSiblingCallback,
|
|
||||||
append_based_on_parent_node_callback: AppendBasedOnParentNodeCallback,
|
|
||||||
) -> () {
|
|
||||||
if xml.is_null() || len == 0 {
|
|
||||||
return ();
|
|
||||||
}
|
|
||||||
|
|
||||||
let arena = typed_arena::Arena::new();
|
|
||||||
|
|
||||||
let sink = sink::Sink {
|
|
||||||
ctx: ctx,
|
|
||||||
arena: &arena,
|
|
||||||
document: document,
|
|
||||||
quirks_mode: Cell::new(QuirksMode::NoQuirks),
|
|
||||||
pop_callback: pop_callback,
|
|
||||||
append_callback: append_callback,
|
|
||||||
get_data_callback: get_data_callback,
|
|
||||||
parse_error_callback: parse_error_callback,
|
|
||||||
create_element_callback: create_element_callback,
|
|
||||||
create_comment_callback: create_comment_callback,
|
|
||||||
create_processing_instruction: create_processing_instruction,
|
|
||||||
append_doctype_to_document: append_doctype_to_document,
|
|
||||||
add_attrs_if_missing_callback: add_attrs_if_missing_callback,
|
|
||||||
get_template_contents_callback: get_template_contents_callback,
|
|
||||||
remove_from_parent_callback: remove_from_parent_callback,
|
|
||||||
reparent_children_callback: reparent_children_callback,
|
|
||||||
append_before_sibling_callback: append_before_sibling_callback,
|
|
||||||
append_based_on_parent_node_callback: append_based_on_parent_node_callback,
|
|
||||||
};
|
|
||||||
|
|
||||||
let bytes = unsafe { std::slice::from_raw_parts(xml, len) };
|
|
||||||
xml5ever::driver::parse_document(sink, xml5ever::driver::XmlParseOpts::default())
|
|
||||||
.from_utf8()
|
|
||||||
.one(bytes);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user