mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-04-03 16:10:29 +00:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f02fc95958 | ||
|
|
175edca8c7 | ||
|
|
f1f0a66f41 | ||
|
|
496c6905af | ||
|
|
232e7a1759 | ||
|
|
c440d41d57 | ||
|
|
dfe5c24404 | ||
|
|
eba5773d56 | ||
|
|
5d56fea2d3 | ||
|
|
946f02b7a2 | ||
|
|
d02d974cd0 | ||
|
|
0a68be695d | ||
|
|
335e781d0c | ||
|
|
9f5c2e4ca7 | ||
|
|
76a53bedbe | ||
|
|
b0bc84ed21 | ||
|
|
ae298fc2e6 | ||
|
|
3b809b2910 | ||
|
|
68fbc0bde3 | ||
|
|
9d8e5263a6 | ||
|
|
7eb026cc0d | ||
|
|
e51e6aa2b0 | ||
|
|
bc700d2044 | ||
|
|
30ed58ff07 | ||
|
|
066069baad | ||
|
|
068ec68917 | ||
|
|
560f028bda | ||
|
|
fd1e77df8f | ||
|
|
864ac08f16 | ||
|
|
6ad1a11593 | ||
|
|
89174ba0b6 | ||
|
|
fc5496e570 | ||
|
|
fd21d952ac | ||
|
|
073fea2bde | ||
|
|
e548712f5e | ||
|
|
c3ba83ff93 | ||
|
|
451dd0fd64 | ||
|
|
aa805c2428 | ||
|
|
58a7590aff | ||
|
|
563ab30564 | ||
|
|
5050b34361 | ||
|
|
3bb86f196b | ||
|
|
51dca3be11 | ||
|
|
adeda6cd75 | ||
|
|
09665c3a4a | ||
|
|
8f5f6212d2 | ||
|
|
a11ae912b4 | ||
|
|
3b12240615 | ||
|
|
862520e4b1 | ||
|
|
a3d2dd8366 | ||
|
|
16ef487871 | ||
|
|
54c45a0cfd | ||
|
|
1f14eb62d4 | ||
|
|
0db86a8b3d | ||
|
|
c63c85071a | ||
|
|
b63d93e325 | ||
|
|
12c6e50e16 | ||
|
|
53ccc2e04c | ||
|
|
2d3234b54d | ||
|
|
9a57c2a0d4 | ||
|
|
fc64abee8f | ||
|
|
d5f26f6d15 | ||
|
|
97f9c2991b | ||
|
|
81378d4353 | ||
|
|
9f0c902030 | ||
|
|
3c0c75be10 | ||
|
|
90d23abe18 | ||
|
|
82eccf36d4 | ||
|
|
342cb52887 | ||
|
|
cafa4f5173 | ||
|
|
67cff5af8b | ||
|
|
6d23d91aa5 | ||
|
|
3a0699fc1d | ||
|
|
027e569087 | ||
|
|
830f759f0b | ||
|
|
969891c71c | ||
|
|
4eb5c3e907 | ||
|
|
23303a759b | ||
|
|
d1e7f46994 | ||
|
|
65ea70ae90 | ||
|
|
7522b71c86 | ||
|
|
70625c86c3 | ||
|
|
74354d2027 | ||
|
|
f6397e2731 | ||
|
|
065ca39d60 | ||
|
|
b4759ae261 | ||
|
|
c095950ef9 | ||
|
|
24b7035b1b | ||
|
|
7b1f157cf8 | ||
|
|
8b8bee4e9c | ||
|
|
c27ab35600 | ||
|
|
446b4dc461 | ||
|
|
ff8ed24622 | ||
|
|
ae2d6a122b | ||
|
|
3cac375f21 | ||
|
|
7d806dd161 | ||
|
|
db037c704e | ||
|
|
954184f742 | ||
|
|
7650e0b61a | ||
|
|
4a5c93988f | ||
|
|
8ceaf0ac66 | ||
|
|
ca60aa1cc6 | ||
|
|
596d5906a0 | ||
|
|
c02db94522 | ||
|
|
3970803575 | ||
|
|
43805ad698 | ||
|
|
2498e12f19 | ||
|
|
6f3cb4b48e | ||
|
|
fbd047599e | ||
|
|
da00117622 | ||
|
|
e44c73bdf6 | ||
|
|
e3cb7bd9f0 | ||
|
|
08f5889ee5 | ||
|
|
d5bfe74e1a | ||
|
|
d7015fa3b6 | ||
|
|
9092651b5b | ||
|
|
2c53b48e0a | ||
|
|
319a1c3367 | ||
|
|
80dd590e8f | ||
|
|
992a8e8774 | ||
|
|
f56d3bd193 | ||
|
|
4ecc59d0c0 | ||
|
|
5ebf82874b | ||
|
|
12670a3153 | ||
|
|
fa3a23134e | ||
|
|
8291044abc | ||
|
|
505e0799da | ||
|
|
be1d463775 | ||
|
|
a6fc5aa345 | ||
|
|
0e6e4db08b | ||
|
|
0edc1fcec7 | ||
|
|
b46d3b22e2 | ||
|
|
412c881cd4 | ||
|
|
48f07a110f | ||
|
|
5c1b7935e2 | ||
|
|
62aa564df1 | ||
|
|
798ee4a4d5 | ||
|
|
7d87fb80ec |
4
.github/actions/install/action.yml
vendored
4
.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.4'
|
default: 'v0.2.6'
|
||||||
v8:
|
v8:
|
||||||
description: 'v8 version to install'
|
description: 'v8 version to install'
|
||||||
required: false
|
required: false
|
||||||
@@ -32,7 +32,7 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y wget xz-utils python3 ca-certificates git pkg-config libglib2.0-dev gperf libexpat1-dev cmake clang
|
sudo apt-get install -y wget xz-utils ca-certificates clang make git
|
||||||
|
|
||||||
# Zig version used from the `minimum_zig_version` field in build.zig.zon
|
# Zig version used from the `minimum_zig_version` field in build.zig.zon
|
||||||
- uses: mlugg/setup-zig@v2
|
- uses: mlugg/setup-zig@v2
|
||||||
|
|||||||
4
.github/workflows/e2e-test.yml
vendored
4
.github/workflows/e2e-test.yml
vendored
@@ -124,8 +124,8 @@ jobs:
|
|||||||
needs: zig-build-release
|
needs: zig-build-release
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MAX_MEMORY: 28000
|
MAX_MEMORY: 26000
|
||||||
MAX_AVG_DURATION: 23
|
MAX_AVG_DURATION: 17
|
||||||
LIGHTPANDA_DISABLE_TELEMETRY: true
|
LIGHTPANDA_DISABLE_TELEMETRY: true
|
||||||
|
|
||||||
# use a self host runner.
|
# use a self host runner.
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ 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.4
|
ARG ZIG_V8=v0.2.6
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
RUN apt-get update -yq && \
|
RUN apt-get update -yq && \
|
||||||
apt-get install -yq xz-utils ca-certificates \
|
apt-get install -yq xz-utils ca-certificates \
|
||||||
|
pkg-config libglib2.0-dev \
|
||||||
clang make curl git
|
clang make curl git
|
||||||
|
|
||||||
# Get Rust
|
# Get Rust
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ For **Debian/Ubuntu based Linux**:
|
|||||||
|
|
||||||
```
|
```
|
||||||
sudo apt install xz-utils ca-certificates \
|
sudo apt install xz-utils ca-certificates \
|
||||||
|
pkg-config libglib2.0-dev \
|
||||||
clang make curl git
|
clang make curl git
|
||||||
```
|
```
|
||||||
You also need to [install Rust](https://rust-lang.org/tools/install/).
|
You also need to [install Rust](https://rust-lang.org/tools/install/).
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
.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/v0.2.4.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/v0.2.6.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH66YvBAD0YI9xr6F0Xgnw9wN30FdZ10FLyuoV3e66",
|
.hash = "v8-0.0.0-xddH60NRBAAWmpZq9nWdfFAEqVJ9zqJnvr1Nl9m2AbcY",
|
||||||
},
|
},
|
||||||
// .v8 = .{ .path = "../zig-v8-fork" },
|
//.v8 = .{ .path = "../zig-v8-fork" },
|
||||||
.@"boringssl-zig" = .{
|
.@"boringssl-zig" = .{
|
||||||
.url = "git+https://github.com/Syndica/boringssl-zig.git#c53df00d06b02b755ad88bbf4d1202ed9687b096",
|
.url = "git+https://github.com/Syndica/boringssl-zig.git#c53df00d06b02b755ad88bbf4d1202ed9687b096",
|
||||||
.hash = "boringssl-0.1.0-VtJeWehMAAA4RNnwRnzEvKcS9rjsR1QVRw1uJrwXxmVK",
|
.hash = "boringssl-0.1.0-VtJeWehMAAA4RNnwRnzEvKcS9rjsR1QVRw1uJrwXxmVK",
|
||||||
|
|||||||
12
src/App.zig
12
src/App.zig
@@ -21,13 +21,14 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
const Http = @import("http/Http.zig");
|
|
||||||
const Snapshot = @import("browser/js/Snapshot.zig");
|
const Snapshot = @import("browser/js/Snapshot.zig");
|
||||||
const Platform = @import("browser/js/Platform.zig");
|
const Platform = @import("browser/js/Platform.zig");
|
||||||
|
|
||||||
const Notification = @import("Notification.zig");
|
|
||||||
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
||||||
|
|
||||||
|
pub const Http = @import("http/Http.zig");
|
||||||
|
pub const ArenaPool = @import("ArenaPool.zig");
|
||||||
|
pub const Notification = @import("Notification.zig");
|
||||||
|
|
||||||
// Container for global state / objects that various parts of the system
|
// Container for global state / objects that various parts of the system
|
||||||
// might need.
|
// might need.
|
||||||
const App = @This();
|
const App = @This();
|
||||||
@@ -38,6 +39,7 @@ platform: Platform,
|
|||||||
snapshot: Snapshot,
|
snapshot: Snapshot,
|
||||||
telemetry: Telemetry,
|
telemetry: Telemetry,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
arena_pool: ArenaPool,
|
||||||
app_dir_path: ?[]const u8,
|
app_dir_path: ?[]const u8,
|
||||||
notification: *Notification,
|
notification: *Notification,
|
||||||
shutdown: bool = false,
|
shutdown: bool = false,
|
||||||
@@ -96,6 +98,9 @@ pub fn init(allocator: Allocator, config: Config) !*App {
|
|||||||
|
|
||||||
try app.telemetry.register(app.notification);
|
try app.telemetry.register(app.notification);
|
||||||
|
|
||||||
|
app.arena_pool = ArenaPool.init(allocator);
|
||||||
|
errdefer app.arena_pool.deinit();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +119,7 @@ pub fn deinit(self: *App) void {
|
|||||||
self.http.deinit();
|
self.http.deinit();
|
||||||
self.snapshot.deinit();
|
self.snapshot.deinit();
|
||||||
self.platform.deinit();
|
self.platform.deinit();
|
||||||
|
self.arena_pool.deinit();
|
||||||
|
|
||||||
allocator.destroy(self);
|
allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/ArenaPool.zig
Normal file
84
src/ArenaPool.zig
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const ArenaPool = @This();
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
retain_bytes: usize,
|
||||||
|
free_list_len: u16 = 0,
|
||||||
|
free_list: ?*Entry = null,
|
||||||
|
free_list_max: u16,
|
||||||
|
entry_pool: std.heap.MemoryPool(Entry),
|
||||||
|
|
||||||
|
const Entry = struct {
|
||||||
|
next: ?*Entry,
|
||||||
|
arena: ArenaAllocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) ArenaPool {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.free_list_max = 512, // TODO make configurable
|
||||||
|
.retain_bytes = 1024 * 16, // TODO make configurable
|
||||||
|
.entry_pool = std.heap.MemoryPool(Entry).init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *ArenaPool) void {
|
||||||
|
var entry = self.free_list;
|
||||||
|
while (entry) |e| {
|
||||||
|
entry = e.next;
|
||||||
|
e.arena.deinit();
|
||||||
|
}
|
||||||
|
self.entry_pool.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acquire(self: *ArenaPool) !Allocator {
|
||||||
|
if (self.free_list) |entry| {
|
||||||
|
self.free_list = entry.next;
|
||||||
|
return entry.arena.allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = try self.entry_pool.create();
|
||||||
|
entry.* = .{
|
||||||
|
.next = null,
|
||||||
|
.arena = ArenaAllocator.init(self.allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
return entry.arena.allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self: *ArenaPool, allocator: Allocator) void {
|
||||||
|
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
|
||||||
|
const entry: *Entry = @fieldParentPtr("arena", arena);
|
||||||
|
|
||||||
|
if (self.free_list_len == self.free_list_max) {
|
||||||
|
arena.deinit();
|
||||||
|
self.entry_pool.destroy(entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = arena.reset(.{ .retain_with_limit = self.retain_bytes });
|
||||||
|
entry.next = self.free_list;
|
||||||
|
self.free_list = entry;
|
||||||
|
}
|
||||||
@@ -24,8 +24,10 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
|||||||
const js = @import("js/js.zig");
|
const js = @import("js/js.zig");
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
const App = @import("../App.zig");
|
const App = @import("../App.zig");
|
||||||
const HttpClient = @import("../http/Client.zig");
|
|
||||||
const Notification = @import("../Notification.zig");
|
const ArenaPool = App.ArenaPool;
|
||||||
|
const HttpClient = App.Http.Client;
|
||||||
|
const Notification = App.Notification;
|
||||||
|
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ env: js.Env,
|
|||||||
app: *App,
|
app: *App,
|
||||||
session: ?Session,
|
session: ?Session,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
arena_pool: *ArenaPool,
|
||||||
http_client: *HttpClient,
|
http_client: *HttpClient,
|
||||||
call_arena: ArenaAllocator,
|
call_arena: ArenaAllocator,
|
||||||
page_arena: ArenaAllocator,
|
page_arena: ArenaAllocator,
|
||||||
@@ -64,6 +67,7 @@ pub fn init(app: *App) !Browser {
|
|||||||
.session = null,
|
.session = null,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.notification = notification,
|
.notification = notification,
|
||||||
|
.arena_pool = &app.arena_pool,
|
||||||
.http_client = app.http.client,
|
.http_client = app.http.client,
|
||||||
.call_arena = ArenaAllocator.init(allocator),
|
.call_arena = ArenaAllocator.init(allocator),
|
||||||
.page_arena = ArenaAllocator.init(allocator),
|
.page_arena = ArenaAllocator.init(allocator),
|
||||||
@@ -96,7 +100,7 @@ pub fn closeSession(self: *Browser) void {
|
|||||||
session.deinit();
|
session.deinit();
|
||||||
self.session = null;
|
self.session = null;
|
||||||
_ = self.session_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
|
_ = self.session_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
|
||||||
self.env.lowMemoryNotification();
|
self.env.memoryPressureNotification(.critical);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,10 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void
|
|||||||
var was_handled = false;
|
var was_handled = false;
|
||||||
|
|
||||||
defer if (was_handled) {
|
defer if (was_handled) {
|
||||||
self.page.js.runMicrotasks();
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
ls.local.runMicrotasks();
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (target._type) {
|
switch (target._type) {
|
||||||
@@ -180,7 +183,10 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
|||||||
|
|
||||||
var was_dispatched = false;
|
var was_dispatched = false;
|
||||||
defer if (was_dispatched) {
|
defer if (was_dispatched) {
|
||||||
self.page.js.runMicrotasks();
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
ls.local.runMicrotasks();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (function_) |func| {
|
if (function_) |func| {
|
||||||
@@ -367,14 +373,18 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
event._target = getAdjustedTarget(original_target, current_target);
|
event._target = getAdjustedTarget(original_target, current_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
switch (listener.function) {
|
switch (listener.function) {
|
||||||
.value => |value| try value.local().callWithThis(void, current_target, .{event}),
|
.value => |value| try ls.toLocal(value).callWithThis(void, current_target, .{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 ls.local.eval(str, null);
|
||||||
},
|
},
|
||||||
.object => |*obj_global| {
|
.object => |obj_global| {
|
||||||
const obj = obj_global.local();
|
const obj = ls.toLocal(obj_global);
|
||||||
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
||||||
try handleEvent.callWithThis(void, obj, .{event});
|
try handleEvent.callWithThis(void, obj, .{event});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
//
|
//
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
@@ -29,6 +29,7 @@ const Page = @import("Page.zig");
|
|||||||
const Node = @import("webapi/Node.zig");
|
const Node = @import("webapi/Node.zig");
|
||||||
const Event = @import("webapi/Event.zig");
|
const Event = @import("webapi/Event.zig");
|
||||||
const UIEvent = @import("webapi/event/UIEvent.zig");
|
const UIEvent = @import("webapi/event/UIEvent.zig");
|
||||||
|
const MouseEvent = @import("webapi/event/MouseEvent.zig");
|
||||||
const Element = @import("webapi/Element.zig");
|
const Element = @import("webapi/Element.zig");
|
||||||
const Document = @import("webapi/Document.zig");
|
const Document = @import("webapi/Document.zig");
|
||||||
const EventTarget = @import("webapi/EventTarget.zig");
|
const EventTarget = @import("webapi/EventTarget.zig");
|
||||||
@@ -36,6 +37,8 @@ const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.
|
|||||||
const Blob = @import("webapi/Blob.zig");
|
const Blob = @import("webapi/Blob.zig");
|
||||||
const AbstractRange = @import("webapi/AbstractRange.zig");
|
const AbstractRange = @import("webapi/AbstractRange.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const IS_DEBUG = builtin.mode == .Debug;
|
const IS_DEBUG = builtin.mode == .Debug;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
@@ -213,6 +216,29 @@ pub fn uiEvent(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child)
|
|||||||
return chain.get(2);
|
return chain.get(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mouseEvent(self: *Factory, typ: []const u8, mouse: MouseEvent, child: anytype) !*@TypeOf(child) {
|
||||||
|
const allocator = self._slab.allocator();
|
||||||
|
|
||||||
|
const chain = try PrototypeChain(
|
||||||
|
&.{ Event, UIEvent, MouseEvent, @TypeOf(child) },
|
||||||
|
).allocate(allocator);
|
||||||
|
|
||||||
|
// Special case: Event has a _type_string field, so we need manual setup
|
||||||
|
const event_ptr = chain.get(0);
|
||||||
|
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
|
||||||
|
chain.setMiddle(1, UIEvent.Type);
|
||||||
|
|
||||||
|
// Set MouseEvent with all its fields
|
||||||
|
const mouse_ptr = chain.get(2);
|
||||||
|
mouse_ptr.* = mouse;
|
||||||
|
mouse_ptr._proto = chain.get(1);
|
||||||
|
mouse_ptr._type = unionInit(MouseEvent.Type, chain.get(3));
|
||||||
|
|
||||||
|
chain.setLeaf(3, child);
|
||||||
|
|
||||||
|
return chain.get(3);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
|
pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||||
const allocator = self._slab.allocator();
|
const allocator = self._slab.allocator();
|
||||||
|
|
||||||
@@ -320,9 +346,7 @@ pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeO
|
|||||||
return chain.get(4);
|
return chain.get(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn xhrEventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
|
pub fn xhrEventTarget(_: *const Factory, allocator: Allocator, child: anytype) !*@TypeOf(child) {
|
||||||
const allocator = self._slab.allocator();
|
|
||||||
|
|
||||||
return try AutoPrototypeChain(
|
return try AutoPrototypeChain(
|
||||||
&.{ EventTarget, XMLHttpRequestEventTarget, @TypeOf(child) },
|
&.{ EventTarget, XMLHttpRequestEventTarget, @TypeOf(child) },
|
||||||
).create(allocator, child);
|
).create(allocator, child);
|
||||||
@@ -337,32 +361,6 @@ pub fn textTrackCue(self: *Factory, child: anytype) !*@TypeOf(child) {
|
|||||||
).create(allocator, child);
|
).create(allocator, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hasChainRoot(comptime T: type) bool {
|
|
||||||
// Check if this is a root
|
|
||||||
if (@hasDecl(T, "_prototype_root")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no _proto field, we're at the top but not a recognized root
|
|
||||||
if (!@hasField(T, "_proto")) return false;
|
|
||||||
|
|
||||||
// Get the _proto field's type and recurse
|
|
||||||
const fields = @typeInfo(T).@"struct".fields;
|
|
||||||
inline for (fields) |field| {
|
|
||||||
if (std.mem.eql(u8, field.name, "_proto")) {
|
|
||||||
const ProtoType = reflect.Struct(field.type);
|
|
||||||
return hasChainRoot(ProtoType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isChainType(comptime T: type) bool {
|
|
||||||
if (@hasField(T, "_proto")) return false;
|
|
||||||
return comptime hasChainRoot(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(self: *Factory, value: anytype) void {
|
pub fn destroy(self: *Factory, value: anytype) void {
|
||||||
const S = reflect.Struct(@TypeOf(value));
|
const S = reflect.Struct(@TypeOf(value));
|
||||||
|
|
||||||
@@ -379,7 +377,7 @@ pub fn destroy(self: *Factory, value: anytype) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime isChainType(S)) {
|
if (comptime @hasField(S, "_proto")) {
|
||||||
self.destroyChain(value, true, 0, std.mem.Alignment.@"1");
|
self.destroyChain(value, true, 0, std.mem.Alignment.@"1");
|
||||||
} else {
|
} else {
|
||||||
self.destroyStandalone(value);
|
self.destroyStandalone(value);
|
||||||
@@ -387,20 +385,7 @@ pub fn destroy(self: *Factory, value: anytype) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroyStandalone(self: *Factory, value: anytype) void {
|
pub fn destroyStandalone(self: *Factory, value: anytype) void {
|
||||||
const S = reflect.Struct(@TypeOf(value));
|
|
||||||
assert(!@hasDecl(S, "_prototype_root"));
|
|
||||||
|
|
||||||
const allocator = self._slab.allocator();
|
const allocator = self._slab.allocator();
|
||||||
|
|
||||||
if (@hasDecl(S, "deinit")) {
|
|
||||||
// And it has a deinit, we'll call it
|
|
||||||
switch (@typeInfo(@TypeOf(S.deinit)).@"fn".params.len) {
|
|
||||||
1 => value.deinit(),
|
|
||||||
2 => value.deinit(self._page),
|
|
||||||
else => @compileLog(@typeName(S) ++ " has an invalid deinit function"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allocator.destroy(value);
|
allocator.destroy(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,10 +401,8 @@ fn destroyChain(
|
|||||||
|
|
||||||
// aligns the old size to the alignment of this element
|
// aligns the old size to the alignment of this element
|
||||||
const current_size = std.mem.alignForward(usize, old_size, @alignOf(S));
|
const current_size = std.mem.alignForward(usize, old_size, @alignOf(S));
|
||||||
const alignment = std.mem.Alignment.fromByteUnits(@alignOf(S));
|
|
||||||
|
|
||||||
const new_align = std.mem.Alignment.max(old_align, alignment);
|
|
||||||
const new_size = current_size + @sizeOf(S);
|
const new_size = current_size + @sizeOf(S);
|
||||||
|
const new_align = std.mem.Alignment.max(old_align, std.mem.Alignment.of(S));
|
||||||
|
|
||||||
// This is initially called from a deinit. We don't want to call that
|
// This is initially called from a deinit. We don't want to call that
|
||||||
// same deinit. So when this is the first time destroyChain is called
|
// same deinit. So when this is the first time destroyChain is called
|
||||||
@@ -438,20 +421,15 @@ fn destroyChain(
|
|||||||
|
|
||||||
if (@hasField(S, "_proto")) {
|
if (@hasField(S, "_proto")) {
|
||||||
self.destroyChain(value._proto, false, new_size, new_align);
|
self.destroyChain(value._proto, false, new_size, new_align);
|
||||||
} else if (@hasDecl(S, "JsApi")) {
|
|
||||||
// Doesn't have a _proto, but has a JsApi.
|
|
||||||
if (self._page.js.removeTaggedMapping(@intFromPtr(value))) |tagged| {
|
|
||||||
allocator.destroy(tagged);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// no proto so this is the head of the chain.
|
// no proto so this is the head of the chain.
|
||||||
// we use this as the ptr to the start of the chain.
|
// we use this as the ptr to the start of the chain.
|
||||||
// and we have summed up the length.
|
// and we have summed up the length.
|
||||||
assert(@hasDecl(S, "_prototype_root"));
|
assert(@hasDecl(S, "_prototype_root"));
|
||||||
|
|
||||||
const memory_ptr: [*]const u8 = @ptrCast(value);
|
const memory_ptr: [*]u8 = @ptrCast(@constCast(value));
|
||||||
const len = std.mem.alignForward(usize, new_size, new_align.toByteUnits());
|
const len = std.mem.alignForward(usize, new_size, new_align.toByteUnits());
|
||||||
allocator.free(memory_ptr[0..len]);
|
allocator.rawFree(memory_ptr[0..len], new_align, @returnAddress());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const IS_DEBUG = builtin.mode == .Debug;
|
|||||||
|
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
|
|
||||||
const Http = @import("../http/Http.zig");
|
const App = @import("../App.zig");
|
||||||
const String = @import("../string.zig").String;
|
const String = @import("../string.zig").String;
|
||||||
|
|
||||||
const Mime = @import("Mime.zig");
|
const Mime = @import("Mime.zig");
|
||||||
@@ -59,6 +59,9 @@ const PageTransitionEvent = @import("webapi/event/PageTransitionEvent.zig");
|
|||||||
const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind;
|
const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind;
|
||||||
const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig");
|
const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig");
|
||||||
|
|
||||||
|
const Http = App.Http;
|
||||||
|
const ArenaPool = App.ArenaPool;
|
||||||
|
|
||||||
const timestamp = @import("../datetime.zig").timestamp;
|
const timestamp = @import("../datetime.zig").timestamp;
|
||||||
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
||||||
|
|
||||||
@@ -99,6 +102,20 @@ _element_shadow_roots: Element.ShadowRootLookup = .{},
|
|||||||
_node_owner_documents: Node.OwnerDocumentLookup = .{},
|
_node_owner_documents: Node.OwnerDocumentLookup = .{},
|
||||||
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||||
|
|
||||||
|
/// Lazily-created inline event listeners (or listeners provided as attributes).
|
||||||
|
/// Avoids bloating all elements with extra function fields for rare usage.
|
||||||
|
///
|
||||||
|
/// Use this when a listener provided like these:
|
||||||
|
///
|
||||||
|
/// ```html
|
||||||
|
/// <img onload="(() => { ... })()" />
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```js
|
||||||
|
/// img.onload = () => { ... };
|
||||||
|
/// ```
|
||||||
|
_element_attr_listeners: Element.AttrListenerLookup = .{},
|
||||||
|
|
||||||
_script_manager: ScriptManager,
|
_script_manager: ScriptManager,
|
||||||
|
|
||||||
// List of active MutationObservers
|
// List of active MutationObservers
|
||||||
@@ -168,6 +185,14 @@ arena: Allocator,
|
|||||||
// from JS. Best arena to use, when possible.
|
// from JS. Best arena to use, when possible.
|
||||||
call_arena: Allocator,
|
call_arena: Allocator,
|
||||||
|
|
||||||
|
arena_pool: *ArenaPool,
|
||||||
|
// In Debug, we use this to see if anything fails to release an arena back to
|
||||||
|
// the pool.
|
||||||
|
_arena_pool_leak_track: (if (IS_DEBUG) std.AutoHashMapUnmanaged(usize, struct {
|
||||||
|
owner: []const u8,
|
||||||
|
count: usize,
|
||||||
|
}) else void),
|
||||||
|
|
||||||
window: *Window,
|
window: *Window,
|
||||||
document: *Document,
|
document: *Document,
|
||||||
|
|
||||||
@@ -185,10 +210,14 @@ pub fn init(arena: Allocator, call_arena: Allocator, session: *Session) !*Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const page = try session.browser.allocator.create(Page);
|
const page = try session.browser.allocator.create(Page);
|
||||||
|
page._session = session;
|
||||||
|
|
||||||
page.arena = arena;
|
page.arena = arena;
|
||||||
page.call_arena = call_arena;
|
page.call_arena = call_arena;
|
||||||
page._session = session;
|
page.arena_pool = session.browser.arena_pool;
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
page._arena_pool_leak_track = .empty;
|
||||||
|
}
|
||||||
|
|
||||||
try page.reset(true);
|
try page.reset(true);
|
||||||
return page;
|
return page;
|
||||||
@@ -205,9 +234,14 @@ pub fn deinit(self: *Page) void {
|
|||||||
// stats.print(&stream) catch unreachable;
|
// stats.print(&stream) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// some MicroTasks might be referencing the page, we need to drain it while
|
{
|
||||||
// the page still exists
|
// some MicroTasks might be referencing the page, we need to drain it while
|
||||||
self.js.runMicrotasks();
|
// the page still exists
|
||||||
|
var ls: JS.Local.Scope = undefined;
|
||||||
|
self.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
ls.local.runMicrotasks();
|
||||||
|
}
|
||||||
|
|
||||||
const session = self._session;
|
const session = self._session;
|
||||||
session.executor.removeContext();
|
session.executor.removeContext();
|
||||||
@@ -215,12 +249,38 @@ pub fn deinit(self: *Page) void {
|
|||||||
self._script_manager.shutdown = true;
|
self._script_manager.shutdown = true;
|
||||||
session.browser.http_client.abort();
|
session.browser.http_client.abort();
|
||||||
self._script_manager.deinit();
|
self._script_manager.deinit();
|
||||||
|
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
var it = self._arena_pool_leak_track.valueIterator();
|
||||||
|
while (it.next()) |value_ptr| {
|
||||||
|
if (value_ptr.count > 0) {
|
||||||
|
log.err(.bug, "ArenaPool Leak", .{ .owner = value_ptr.owner });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
session.browser.allocator.destroy(self);
|
session.browser.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(self: *Page, comptime initializing: bool) !void {
|
fn reset(self: *Page, comptime initializing: bool) !void {
|
||||||
if (comptime initializing == false) {
|
if (comptime initializing == false) {
|
||||||
self._session.executor.removeContext();
|
self._session.executor.removeContext();
|
||||||
|
|
||||||
|
// removing a context can trigger finalizers, so we can only check for
|
||||||
|
// a leak after the above.
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
var it = self._arena_pool_leak_track.valueIterator();
|
||||||
|
while (it.next()) |value_ptr| {
|
||||||
|
log.err(.bug, "ArenaPool Leak", .{ .owner = value_ptr.* });
|
||||||
|
}
|
||||||
|
self._arena_pool_leak_track.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// We force a garbage collection between page navigations to keep v8
|
||||||
|
// memory usage as low as possible.
|
||||||
|
self._session.browser.env.memoryPressureNotification(.moderate);
|
||||||
|
|
||||||
self._script_manager.shutdown = true;
|
self._script_manager.shutdown = true;
|
||||||
self._session.browser.http_client.abort();
|
self._session.browser.http_client.abort();
|
||||||
self._script_manager.deinit();
|
self._script_manager.deinit();
|
||||||
@@ -250,7 +310,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self.window._location = &default_location;
|
self.window._location = &default_location;
|
||||||
|
|
||||||
self._parse_state = .pre;
|
self._parse_state = .pre;
|
||||||
self._load_state = .parsing;
|
self._load_state = .waiting;
|
||||||
self._queued_navigation = null;
|
self._queued_navigation = null;
|
||||||
self._parse_mode = .document;
|
self._parse_mode = .document;
|
||||||
self._attribute_lookup = .empty;
|
self._attribute_lookup = .empty;
|
||||||
@@ -270,6 +330,9 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._element_shadow_roots = .{};
|
self._element_shadow_roots = .{};
|
||||||
self._node_owner_documents = .{};
|
self._node_owner_documents = .{};
|
||||||
self._element_assigned_slots = .{};
|
self._element_assigned_slots = .{};
|
||||||
|
|
||||||
|
self._element_attr_listeners = .{};
|
||||||
|
|
||||||
self._notified_network_idle = .init;
|
self._notified_network_idle = .init;
|
||||||
self._notified_network_almost_idle = .init;
|
self._notified_network_almost_idle = .init;
|
||||||
|
|
||||||
@@ -287,6 +350,10 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._customized_builtin_disconnected_callback_invoked = .{};
|
self._customized_builtin_disconnected_callback_invoked = .{};
|
||||||
self._undefined_custom_elements = .{};
|
self._undefined_custom_elements = .{};
|
||||||
|
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
self._arena_pool_leak_track = .{};
|
||||||
|
}
|
||||||
|
|
||||||
try self.registerBackgroundTasks();
|
try self.registerBackgroundTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,6 +389,33 @@ pub fn getOrigin(self: *Page, allocator: Allocator) !?[]const u8 {
|
|||||||
return try URL.getOrigin(allocator, self.url);
|
return try URL.getOrigin(allocator, self.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetArenaOpts = struct {
|
||||||
|
debug: []const u8,
|
||||||
|
};
|
||||||
|
pub fn getArena(self: *Page, comptime opts: GetArenaOpts) !Allocator {
|
||||||
|
const allocator = try self.arena_pool.acquire();
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
const gop = try self._arena_pool_leak_track.getOrPut(self.arena, @intFromPtr(allocator.ptr));
|
||||||
|
if (gop.found_existing) {
|
||||||
|
std.debug.assert(gop.value_ptr.count == 0);
|
||||||
|
}
|
||||||
|
gop.value_ptr.* = .{ .owner = opts.debug, .count = 1 };
|
||||||
|
}
|
||||||
|
return allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn releaseArena(self: *Page, allocator: Allocator) void {
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
const found = self._arena_pool_leak_track.getPtr(@intFromPtr(allocator.ptr)).?;
|
||||||
|
if (found.count != 1) {
|
||||||
|
log.err(.bug, "ArenaPool Double Free", .{ .owner = found.owner, .count = found.count });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
found.count = 0;
|
||||||
|
}
|
||||||
|
return self.arena_pool.release(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
|
pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
|
||||||
const current_origin = (try URL.getOrigin(self.call_arena, self.url)) orelse return false;
|
const current_origin = (try URL.getOrigin(self.call_arena, self.url)) orelse return false;
|
||||||
return std.mem.startsWith(u8, url, current_origin);
|
return std.mem.startsWith(u8, url, current_origin);
|
||||||
@@ -329,11 +423,12 @@ pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
|
|||||||
|
|
||||||
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
||||||
const session = self._session;
|
const session = self._session;
|
||||||
if (self._parse_state != .pre) {
|
if (self._parse_state != .pre or self._load_state != .waiting) {
|
||||||
// it's possible for navigate to be called multiple times on the
|
// it's possible for navigate to be called multiple times on the
|
||||||
// same page (via CDP). We want to reset the page between each call.
|
// same page (via CDP). We want to reset the page between each call.
|
||||||
try self.reset(false);
|
try self.reset(false);
|
||||||
}
|
}
|
||||||
|
self._load_state = .parsing;
|
||||||
|
|
||||||
const req_id = self._session.browser.http_client.nextReqId();
|
const req_id = self._session.browser.http_client.nextReqId();
|
||||||
log.info(.page, "navigate", .{
|
log.info(.page, "navigate", .{
|
||||||
@@ -562,26 +657,29 @@ fn _documentIsComplete(self: *Page) !void {
|
|||||||
const event = try Event.initTrusted("load", .{}, self);
|
const event = try Event.initTrusted("load", .{}, self);
|
||||||
// this event is weird, it's dispatched directly on the window, but
|
// this event is weird, it's dispatched directly on the window, but
|
||||||
// with the document as the target
|
// with the document as the target
|
||||||
|
|
||||||
|
var ls: JS.Local.Scope = undefined;
|
||||||
|
self.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
event._target = self.document.asEventTarget();
|
event._target = self.document.asEventTarget();
|
||||||
const on_load = if (self.window._on_load) |*g| g.local() else null;
|
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchWithFunction(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
on_load,
|
ls.toLocal(self.window._on_load),
|
||||||
.{ .inject_target = false, .context = "page load" },
|
.{ .inject_target = false, .context = "page load" },
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageshow_event = try PageTransitionEvent.initTrusted("pageshow", .{}, self);
|
const pageshow_event = try PageTransitionEvent.initTrusted("pageshow", .{}, self);
|
||||||
const on_pageshow = if (self.window._on_pageshow) |*g| g.local() else null;
|
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchWithFunction(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
pageshow_event.asEvent(),
|
pageshow_event.asEvent(),
|
||||||
on_pageshow,
|
ls.toLocal(self.window._on_pageshow),
|
||||||
.{ .context = "page show" },
|
.{ .context = "page show" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pageHeaderDoneCallback(transfer: *Http.Transfer) !void {
|
fn pageHeaderDoneCallback(transfer: *Http.Transfer) !bool {
|
||||||
var self: *Page = @ptrCast(@alignCast(transfer.ctx));
|
var self: *Page = @ptrCast(@alignCast(transfer.ctx));
|
||||||
|
|
||||||
// would be different than self.url in the case of a redirect
|
// would be different than self.url in the case of a redirect
|
||||||
@@ -598,6 +696,8 @@ fn pageHeaderDoneCallback(transfer: *Http.Transfer) !void {
|
|||||||
.content_type = header.contentType(),
|
.content_type = header.contentType(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pageDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
fn pageDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||||
@@ -671,7 +771,10 @@ fn pageDoneCallback(ctx: *anyopaque) !void {
|
|||||||
|
|
||||||
switch (self._parse_state) {
|
switch (self._parse_state) {
|
||||||
.html => |buf| {
|
.html => |buf| {
|
||||||
var parser = Parser.init(self.arena, self.document.asNode(), self);
|
const parse_arena = try self.getArena(.{ .debug = "Page.parse" });
|
||||||
|
defer self.releaseArena(parse_arena);
|
||||||
|
|
||||||
|
var parser = Parser.init(parse_arena, self.document.asNode(), self);
|
||||||
parser.parse(buf.items);
|
parser.parse(buf.items);
|
||||||
self._script_manager.staticScriptsDone();
|
self._script_manager.staticScriptsDone();
|
||||||
if (self._script_manager.isDone()) {
|
if (self._script_manager.isDone()) {
|
||||||
@@ -683,7 +786,11 @@ fn pageDoneCallback(ctx: *anyopaque) !void {
|
|||||||
},
|
},
|
||||||
.text => |*buf| {
|
.text => |*buf| {
|
||||||
try buf.appendSlice(self.arena, "</pre></body></html>");
|
try buf.appendSlice(self.arena, "</pre></body></html>");
|
||||||
var parser = Parser.init(self.arena, self.document.asNode(), self);
|
|
||||||
|
const parse_arena = try self.getArena(.{ .debug = "Page.parse" });
|
||||||
|
defer self.releaseArena(parse_arena);
|
||||||
|
|
||||||
|
var parser = Parser.init(parse_arena, self.document.asNode(), self);
|
||||||
parser.parse(buf.items);
|
parser.parse(buf.items);
|
||||||
self.documentIsComplete();
|
self.documentIsComplete();
|
||||||
},
|
},
|
||||||
@@ -748,10 +855,6 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
|||||||
var timer = try std.time.Timer.start();
|
var timer = try std.time.Timer.start();
|
||||||
var ms_remaining = wait_ms;
|
var ms_remaining = wait_ms;
|
||||||
|
|
||||||
var try_catch: JS.TryCatch = undefined;
|
|
||||||
try_catch.init(self.js);
|
|
||||||
defer try_catch.deinit();
|
|
||||||
|
|
||||||
var scheduler = &self.scheduler;
|
var scheduler = &self.scheduler;
|
||||||
var http_client = self._session.browser.http_client;
|
var http_client = self._session.browser.http_client;
|
||||||
|
|
||||||
@@ -808,10 +911,6 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
|||||||
// it AFTER.
|
// it AFTER.
|
||||||
const ms_to_next_task = try scheduler.run();
|
const ms_to_next_task = try scheduler.run();
|
||||||
|
|
||||||
if (try_catch.caught(self.call_arena)) |caught| {
|
|
||||||
log.info(.js, "page wait", .{ .caught = caught, .src = "scheduler" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const http_active = http_client.active;
|
const http_active = http_client.active;
|
||||||
const total_network_activity = http_active + http_client.intercepted;
|
const total_network_activity = http_active + http_client.intercepted;
|
||||||
if (self._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
if (self._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||||
@@ -962,16 +1061,6 @@ fn printWaitAnalysis(self: *Page) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *Page) void {
|
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
log.debug(.page, "tick", .{});
|
|
||||||
}
|
|
||||||
_ = self.scheduler.run() catch |err| {
|
|
||||||
log.err(.page, "tick", .{ .err = err });
|
|
||||||
};
|
|
||||||
self.js.runMicrotasks();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isGoingAway(self: *const Page) bool {
|
pub fn isGoingAway(self: *const Page) bool {
|
||||||
return self._queued_navigation != null;
|
return self._queued_navigation != null;
|
||||||
}
|
}
|
||||||
@@ -985,7 +1074,7 @@ pub fn scriptAddedCallback(self: *Page, comptime from_parser: bool, script: *Ele
|
|||||||
self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| {
|
self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| {
|
||||||
log.err(.page, "page.scriptAddedCallback", .{
|
log.err(.page, "page.scriptAddedCallback", .{
|
||||||
.err = err,
|
.err = err,
|
||||||
.src = script.asElement().getAttributeSafe("src"),
|
.src = script.asElement().getAttributeSafe(comptime .wrap("src")),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1070,7 +1159,7 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen
|
|||||||
}
|
}
|
||||||
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| {
|
||||||
const element_id = el.getAttributeSafe("id") orelse continue;
|
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
|
||||||
if (std.mem.eql(u8, element_id, id)) {
|
if (std.mem.eql(u8, element_id, id)) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
@@ -1078,6 +1167,35 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets an inline event listener (`onload`, `onclick`, `onwheel` etc.);
|
||||||
|
/// overrides the listener if there's already one.
|
||||||
|
pub fn setAttrListener(
|
||||||
|
self: *Page,
|
||||||
|
element: *Element,
|
||||||
|
listener_type: Element.KnownListener,
|
||||||
|
listener_callback: JS.Function.Global,
|
||||||
|
) !void {
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
log.debug(.event, "Page.setAttrListener", .{
|
||||||
|
.element = element,
|
||||||
|
.listener_type = listener_type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = element.calcAttrListenerKey(listener_type);
|
||||||
|
const gop = try self._element_attr_listeners.getOrPut(self.arena, key);
|
||||||
|
gop.value_ptr.* = listener_callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inline event listener by an element and listener type.
|
||||||
|
pub fn getAttrListener(
|
||||||
|
self: *const Page,
|
||||||
|
element: *Element,
|
||||||
|
listener_type: Element.KnownListener,
|
||||||
|
) ?JS.Function.Global {
|
||||||
|
return self._element_attr_listeners.get(element.calcAttrListenerKey(listener_type));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn registerPerformanceObserver(self: *Page, observer: *PerformanceObserver) !void {
|
pub fn registerPerformanceObserver(self: *Page, observer: *PerformanceObserver) !void {
|
||||||
return self._performance_observers.append(self.arena, observer);
|
return self._performance_observers.append(self.arena, observer);
|
||||||
}
|
}
|
||||||
@@ -1644,7 +1762,7 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
|||||||
// If page's base url is not already set, fill it with the base
|
// If page's base url is not already set, fill it with the base
|
||||||
// tag.
|
// tag.
|
||||||
if (self.base_url == null) {
|
if (self.base_url == null) {
|
||||||
if (n.as(Element).getAttributeSafe("href")) |href| {
|
if (n.as(Element).getAttributeSafe(comptime .wrap("href"))) |href| {
|
||||||
self.base_url = try URL.resolve(self.arena, self.url, href, .{});
|
self.base_url = try URL.resolve(self.arena, self.url, href, .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2005,8 +2123,12 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
|||||||
self._upgrading_element = node;
|
self._upgrading_element = node;
|
||||||
defer self._upgrading_element = prev_upgrading;
|
defer self._upgrading_element = prev_upgrading;
|
||||||
|
|
||||||
|
var ls: JS.Local.Scope = undefined;
|
||||||
|
self.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
var caught: JS.TryCatch.Caught = undefined;
|
var caught: JS.TryCatch.Caught = undefined;
|
||||||
_ = def.constructor.local().newInstance(&caught) catch |err| {
|
_ = ls.toLocal(def.constructor).newInstance(&caught) catch |err| {
|
||||||
log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught });
|
log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught });
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
@@ -2018,9 +2140,9 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
|||||||
while (it.next()) |attr| {
|
while (it.next()) |attr| {
|
||||||
Element.Html.Custom.invokeAttributeChangedCallbackOnElement(
|
Element.Html.Custom.invokeAttributeChangedCallbackOnElement(
|
||||||
element,
|
element,
|
||||||
attr._name.str(),
|
attr._name,
|
||||||
null, // old_value is null for initial attributes
|
null, // old_value is null for initial attributes
|
||||||
attr._value.str(),
|
attr._value,
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2113,6 +2235,236 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi
|
|||||||
}
|
}
|
||||||
var attributes = try element.createAttributeList(self);
|
var attributes = try element.createAttributeList(self);
|
||||||
while (list.next()) |attr| {
|
while (list.next()) |attr| {
|
||||||
|
// Event handlers can be provided like attributes; here we check if there's such.
|
||||||
|
const name = attr.name.local;
|
||||||
|
lp.assert(name.len != 0, "populateElementAttributes: 0-length attr name", .{ .attr = attr });
|
||||||
|
// Idea here is to make this check as cheap as possible.
|
||||||
|
const has_on_prefix = @as(u16, @bitCast([2]u8{ name.ptr[0], name.ptr[1 % name.len] })) == asUint("on");
|
||||||
|
// We may have found an event handler.
|
||||||
|
if (has_on_prefix) {
|
||||||
|
// Must be usable as function.
|
||||||
|
const func = self.js.stringToPersistedFunction(attr.value.slice()) catch continue;
|
||||||
|
|
||||||
|
// Longest known listener kind is 32 bytes long.
|
||||||
|
const remaining: u6 = @truncate(name.len -| 2);
|
||||||
|
const unsafe = name.ptr + 2;
|
||||||
|
const Vec16x8 = @Vector(16, u8);
|
||||||
|
const Vec32x8 = @Vector(32, u8);
|
||||||
|
|
||||||
|
switch (remaining) {
|
||||||
|
3 => if (@as(u24, @bitCast(unsafe[0..3].*)) == asUint("cut")) {
|
||||||
|
try self.setAttrListener(element, .cut, func);
|
||||||
|
},
|
||||||
|
4 => switch (@as(u32, @bitCast(unsafe[0..4].*))) {
|
||||||
|
asUint("blur") => try self.setAttrListener(element, .blur, func),
|
||||||
|
asUint("copy") => try self.setAttrListener(element, .copy, func),
|
||||||
|
asUint("drag") => try self.setAttrListener(element, .drag, func),
|
||||||
|
asUint("drop") => try self.setAttrListener(element, .drop, func),
|
||||||
|
asUint("load") => try self.setAttrListener(element, .load, func),
|
||||||
|
asUint("play") => try self.setAttrListener(element, .play, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
5 => switch (@as(u40, @bitCast(unsafe[0..5].*))) {
|
||||||
|
asUint("abort") => try self.setAttrListener(element, .abort, func),
|
||||||
|
asUint("click") => try self.setAttrListener(element, .click, func),
|
||||||
|
asUint("close") => try self.setAttrListener(element, .close, func),
|
||||||
|
asUint("ended") => try self.setAttrListener(element, .ended, func),
|
||||||
|
asUint("error") => try self.setAttrListener(element, .@"error", func),
|
||||||
|
asUint("focus") => try self.setAttrListener(element, .focus, func),
|
||||||
|
asUint("input") => try self.setAttrListener(element, .input, func),
|
||||||
|
asUint("keyup") => try self.setAttrListener(element, .keyup, func),
|
||||||
|
asUint("paste") => try self.setAttrListener(element, .paste, func),
|
||||||
|
asUint("pause") => try self.setAttrListener(element, .pause, func),
|
||||||
|
asUint("reset") => try self.setAttrListener(element, .reset, func),
|
||||||
|
asUint("wheel") => try self.setAttrListener(element, .wheel, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
6 => switch (@as(u48, @bitCast(unsafe[0..6].*))) {
|
||||||
|
asUint("cancel") => try self.setAttrListener(element, .cancel, func),
|
||||||
|
asUint("change") => try self.setAttrListener(element, .change, func),
|
||||||
|
asUint("resize") => try self.setAttrListener(element, .resize, func),
|
||||||
|
asUint("scroll") => try self.setAttrListener(element, .scroll, func),
|
||||||
|
asUint("seeked") => try self.setAttrListener(element, .seeked, func),
|
||||||
|
asUint("select") => try self.setAttrListener(element, .select, func),
|
||||||
|
asUint("submit") => try self.setAttrListener(element, .submit, func),
|
||||||
|
asUint("toggle") => try self.setAttrListener(element, .toggle, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
7 => switch (@as(u56, @bitCast(unsafe[0..7].*))) {
|
||||||
|
asUint("canplay") => try self.setAttrListener(element, .canplay, func),
|
||||||
|
asUint("command") => try self.setAttrListener(element, .command, func),
|
||||||
|
asUint("dragend") => try self.setAttrListener(element, .dragend, func),
|
||||||
|
asUint("emptied") => try self.setAttrListener(element, .emptied, func),
|
||||||
|
asUint("invalid") => try self.setAttrListener(element, .invalid, func),
|
||||||
|
asUint("keydown") => try self.setAttrListener(element, .keydown, func),
|
||||||
|
asUint("mouseup") => try self.setAttrListener(element, .mouseup, func),
|
||||||
|
asUint("playing") => try self.setAttrListener(element, .playing, func),
|
||||||
|
asUint("seeking") => try self.setAttrListener(element, .seeking, func),
|
||||||
|
asUint("stalled") => try self.setAttrListener(element, .stalled, func),
|
||||||
|
asUint("suspend") => try self.setAttrListener(element, .@"suspend", func),
|
||||||
|
asUint("waiting") => try self.setAttrListener(element, .waiting, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
8 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("auxclick") => try self.setAttrListener(element, .auxclick, func),
|
||||||
|
asUint("dblclick") => try self.setAttrListener(element, .dblclick, func),
|
||||||
|
asUint("dragexit") => try self.setAttrListener(element, .dragexit, func),
|
||||||
|
asUint("dragover") => try self.setAttrListener(element, .dragover, func),
|
||||||
|
asUint("formdata") => try self.setAttrListener(element, .formdata, func),
|
||||||
|
asUint("keypress") => try self.setAttrListener(element, .keypress, func),
|
||||||
|
asUint("mouseout") => try self.setAttrListener(element, .mouseout, func),
|
||||||
|
asUint("progress") => try self.setAttrListener(element, .progress, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
// Won't fit to 64-bit integer; we do 2 checks.
|
||||||
|
9 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("cuechang") => if (unsafe[8] == 'e') try self.setAttrListener(element, .cuechange, func),
|
||||||
|
asUint("dragente") => if (unsafe[8] == 'r') try self.setAttrListener(element, .dragenter, func),
|
||||||
|
asUint("dragleav") => if (unsafe[8] == 'e') try self.setAttrListener(element, .dragleave, func),
|
||||||
|
asUint("dragstar") => if (unsafe[8] == 't') try self.setAttrListener(element, .dragstart, func),
|
||||||
|
asUint("loadstar") => if (unsafe[8] == 't') try self.setAttrListener(element, .loadstart, func),
|
||||||
|
asUint("mousedow") => if (unsafe[8] == 'n') try self.setAttrListener(element, .mousedown, func),
|
||||||
|
asUint("mousemov") => if (unsafe[8] == 'e') try self.setAttrListener(element, .mousemove, func),
|
||||||
|
asUint("mouseove") => if (unsafe[8] == 'r') try self.setAttrListener(element, .mouseover, func),
|
||||||
|
asUint("pointeru") => if (unsafe[8] == 'p') try self.setAttrListener(element, .pointerup, func),
|
||||||
|
asUint("scrollen") => if (unsafe[8] == 'd') try self.setAttrListener(element, .scrollend, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
10 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("loadedda") => if (asUint("ta") == @as(u16, @bitCast(unsafe[8..10].*)))
|
||||||
|
try self.setAttrListener(element, .loadeddata, func),
|
||||||
|
asUint("pointero") => if (asUint("ut") == @as(u16, @bitCast(unsafe[8..10].*)))
|
||||||
|
try self.setAttrListener(element, .pointerout, func),
|
||||||
|
asUint("ratechan") => if (asUint("ge") == @as(u16, @bitCast(unsafe[8..10].*)))
|
||||||
|
try self.setAttrListener(element, .ratechange, func),
|
||||||
|
asUint("slotchan") => if (asUint("ge") == @as(u16, @bitCast(unsafe[8..10].*)))
|
||||||
|
try self.setAttrListener(element, .slotchange, func),
|
||||||
|
asUint("timeupda") => if (asUint("te") == @as(u16, @bitCast(unsafe[8..10].*)))
|
||||||
|
try self.setAttrListener(element, .timeupdate, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
11 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("beforein") => if (asUint("put") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .beforeinput, func),
|
||||||
|
asUint("beforema") => if (asUint("tch") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .beforematch, func),
|
||||||
|
asUint("contextl") => if (asUint("ost") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .contextlost, func),
|
||||||
|
asUint("contextm") => if (asUint("enu") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .contextmenu, func),
|
||||||
|
asUint("pointerd") => if (asUint("own") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .pointerdown, func),
|
||||||
|
asUint("pointerm") => if (asUint("ove") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .pointermove, func),
|
||||||
|
asUint("pointero") => if (asUint("ver") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .pointerover, func),
|
||||||
|
asUint("selectst") => if (asUint("art") == @as(u24, @bitCast(unsafe[8..11].*)))
|
||||||
|
try self.setAttrListener(element, .selectstart, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
12 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("animatio") => if (asUint("nend") == @as(u32, @bitCast(unsafe[8..12].*)))
|
||||||
|
try self.setAttrListener(element, .animationend, func),
|
||||||
|
asUint("beforeto") => if (asUint("ggle") == @as(u32, @bitCast(unsafe[8..12].*)))
|
||||||
|
try self.setAttrListener(element, .beforetoggle, func),
|
||||||
|
asUint("pointere") => if (asUint("nter") == @as(u32, @bitCast(unsafe[8..12].*)))
|
||||||
|
try self.setAttrListener(element, .pointerenter, func),
|
||||||
|
asUint("pointerl") => if (asUint("eave") == @as(u32, @bitCast(unsafe[8..12].*)))
|
||||||
|
try self.setAttrListener(element, .pointerleave, func),
|
||||||
|
asUint("volumech") => if (asUint("ange") == @as(u32, @bitCast(unsafe[8..12].*)))
|
||||||
|
try self.setAttrListener(element, .volumechange, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
13 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("pointerc") => if (asUint("ancel") == @as(u40, @bitCast(unsafe[8..13].*)))
|
||||||
|
try self.setAttrListener(element, .pointercancel, func),
|
||||||
|
asUint("transiti") => switch (@as(u40, @bitCast(unsafe[8..13].*))) {
|
||||||
|
asUint("onend") => try self.setAttrListener(element, .transitionend, func),
|
||||||
|
asUint("onrun") => try self.setAttrListener(element, .transitionrun, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
14 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("animatio") => if (asUint("nstart") == @as(u48, @bitCast(unsafe[8..14].*)))
|
||||||
|
try self.setAttrListener(element, .animationstart, func),
|
||||||
|
asUint("canplayt") => if (asUint("hrough") == @as(u48, @bitCast(unsafe[8..14].*)))
|
||||||
|
try self.setAttrListener(element, .canplaythrough, func),
|
||||||
|
asUint("duration") => if (asUint("change") == @as(u48, @bitCast(unsafe[8..14].*)))
|
||||||
|
try self.setAttrListener(element, .durationchange, func),
|
||||||
|
asUint("loadedme") => if (asUint("tadata") == @as(u48, @bitCast(unsafe[8..14].*)))
|
||||||
|
try self.setAttrListener(element, .loadedmetadata, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
15 => switch (@as(u64, @bitCast(unsafe[0..8].*))) {
|
||||||
|
asUint("animatio") => if (asUint("ncancel") == @as(u56, @bitCast(unsafe[8..15].*)))
|
||||||
|
try self.setAttrListener(element, .animationcancel, func),
|
||||||
|
asUint("contextr") => if (asUint("estored") == @as(u56, @bitCast(unsafe[8..15].*)))
|
||||||
|
try self.setAttrListener(element, .contextrestored, func),
|
||||||
|
asUint("fullscre") => if (asUint("enerror") == @as(u56, @bitCast(unsafe[8..15].*)))
|
||||||
|
try self.setAttrListener(element, .fullscreenerror, func),
|
||||||
|
asUint("selectio") => if (asUint("nchange") == @as(u56, @bitCast(unsafe[8..15].*)))
|
||||||
|
try self.setAttrListener(element, .selectionchange, func),
|
||||||
|
asUint("transiti") => if (asUint("onstart") == @as(u56, @bitCast(unsafe[8..15].*)))
|
||||||
|
try self.setAttrListener(element, .transitionstart, func),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
// Can't switch on vector types.
|
||||||
|
16 => {
|
||||||
|
const as_vector: Vec16x8 = unsafe[0..16].*;
|
||||||
|
|
||||||
|
if (@reduce(.And, as_vector == @as(Vec16x8, "fullscreenchange".*))) {
|
||||||
|
try self.setAttrListener(element, .fullscreenchange, func);
|
||||||
|
} else if (@reduce(.And, as_vector == @as(Vec16x8, "pointerrawupdate".*))) {
|
||||||
|
try self.setAttrListener(element, .pointerrawupdate, func);
|
||||||
|
} else if (@reduce(.And, as_vector == @as(Vec16x8, "transitioncancel".*))) {
|
||||||
|
try self.setAttrListener(element, .transitioncancel, func);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
17 => {
|
||||||
|
const as_vector: Vec16x8 = unsafe[0..16].*;
|
||||||
|
|
||||||
|
const dirty = @reduce(.And, as_vector == @as(Vec16x8, "gotpointercaptur".*)) and
|
||||||
|
unsafe[16] == 'e';
|
||||||
|
if (dirty) {
|
||||||
|
try self.setAttrListener(element, .gotpointercapture, func);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
18 => {
|
||||||
|
const as_vector: Vec16x8 = unsafe[0..16].*;
|
||||||
|
|
||||||
|
const is_animationiteration = @reduce(.And, as_vector == @as(Vec16x8, "animationiterati".*)) and
|
||||||
|
asUint("on") == @as(u16, @bitCast(unsafe[16..18].*));
|
||||||
|
if (is_animationiteration) {
|
||||||
|
try self.setAttrListener(element, .animationiteration, func);
|
||||||
|
} else {
|
||||||
|
const is_lostpointercapture = @reduce(.And, as_vector == @as(Vec16x8, "lostpointercaptu".*)) and
|
||||||
|
asUint("re") == @as(u16, @bitCast(unsafe[16..18].*));
|
||||||
|
if (is_lostpointercapture) {
|
||||||
|
try self.setAttrListener(element, .lostpointercapture, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
23 => {
|
||||||
|
const as_vector: Vec16x8 = unsafe[0..16].*;
|
||||||
|
|
||||||
|
const dirty = @reduce(.And, as_vector == @as(Vec16x8, "securitypolicyvi".*)) and
|
||||||
|
asUint("olation") == @as(u56, @bitCast(unsafe[16..23].*));
|
||||||
|
if (dirty) {
|
||||||
|
try self.setAttrListener(element, .securitypolicyviolation, func);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
32 => {
|
||||||
|
const as_vector: Vec32x8 = unsafe[0..32].*;
|
||||||
|
|
||||||
|
if (@reduce(.And, as_vector == @as(Vec32x8, "contentvisibilityautostatechange".*))) {
|
||||||
|
try self.setAttrListener(element, .contentvisibilityautostatechange, func);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try attributes.putNew(attr.name.local.slice(), attr.value.slice(), self);
|
try attributes.putNew(attr.name.local.slice(), attr.value.slice(), self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2180,7 +2532,7 @@ pub fn createProcessingInstruction(self: *Page, target: []const u8, data: []cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate target follows XML name rules (similar to attribute name validation)
|
// Validate target follows XML name rules (similar to attribute name validation)
|
||||||
try Element.Attribute.validateAttributeName(target);
|
try Element.Attribute.validateAttributeName(.wrap(target));
|
||||||
|
|
||||||
const owned_target = try self.dupeString(target);
|
const owned_target = try self.dupeString(target);
|
||||||
const owned_data = try self.dupeString(data);
|
const owned_data = try self.dupeString(data);
|
||||||
@@ -2251,7 +2603,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
|
|||||||
if (parent.is(Element)) |parent_el| {
|
if (parent.is(Element)) |parent_el| {
|
||||||
if (self._element_shadow_roots.get(parent_el)) |shadow_root| {
|
if (self._element_shadow_roots.get(parent_el)) |shadow_root| {
|
||||||
// Signal slot changes for any affected slots
|
// Signal slot changes for any affected slots
|
||||||
const slot_name = el.getAttributeSafe("slot") orelse "";
|
const slot_name = el.getAttributeSafe(comptime .wrap("slot")) orelse "";
|
||||||
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(shadow_root.asNode(), .{});
|
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(shadow_root.asNode(), .{});
|
||||||
while (tw.next()) |slot_el| {
|
while (tw.next()) |slot_el| {
|
||||||
if (slot_el.is(Element.Html.Slot)) |slot| {
|
if (slot_el.is(Element.Html.Slot)) |slot| {
|
||||||
@@ -2290,7 +2642,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
|
|||||||
// the ID map and invoking disconnectedCallback for custom elements
|
// the ID map and invoking disconnectedCallback for custom elements
|
||||||
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(comptime .wrap("id"))) |id| {
|
||||||
self.removeElementIdWithMaps(id_maps.?, id);
|
self.removeElementIdWithMaps(id_maps.?, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2424,7 +2776,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
|
|||||||
// For main document parsing, we know nodes are connected (fast path)
|
// For main document parsing, we know nodes are connected (fast path)
|
||||||
// For fragment parsing (innerHTML), we need to check connectivity
|
// For fragment parsing (innerHTML), we need to check connectivity
|
||||||
if (child.isConnected() or child.isInShadowTree()) {
|
if (child.isConnected() or child.isInShadowTree()) {
|
||||||
if (el.getAttributeSafe("id")) |id| {
|
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||||
try self.addElementId(parent, el, id);
|
try self.addElementId(parent, el, id);
|
||||||
}
|
}
|
||||||
try Element.Html.Custom.invokeConnectedCallbackOnElement(true, el, self);
|
try Element.Html.Custom.invokeConnectedCallbackOnElement(true, el, self);
|
||||||
@@ -2463,7 +2815,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
|
|||||||
|
|
||||||
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(comptime .wrap("id"))) |id| {
|
||||||
try self.addElementId(el.asNode()._parent.?, el, id);
|
try self.addElementId(el.asNode()._parent.?, el, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2473,7 +2825,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value: []const u8, old_value: ?[]const u8) void {
|
pub fn attributeChange(self: *Page, element: *Element, name: String, value: String, old_value: ?String) void {
|
||||||
_ = Element.Build.call(element, "attributeChange", .{ element, name, value, self }) catch |err| {
|
_ = Element.Build.call(element, "attributeChange", .{ element, name, value, self }) catch |err| {
|
||||||
log.err(.bug, "build.attributeChange", .{ .tag = element.getTag(), .name = name, .value = value, .err = err });
|
log.err(.bug, "build.attributeChange", .{ .tag = element.getTag(), .name = name, .value = value, .err = err });
|
||||||
};
|
};
|
||||||
@@ -2489,9 +2841,9 @@ pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle slot assignment changes
|
// Handle slot assignment changes
|
||||||
if (std.mem.eql(u8, name, "slot")) {
|
if (name.eql(comptime .wrap("slot"))) {
|
||||||
self.updateSlotAssignments(element);
|
self.updateSlotAssignments(element);
|
||||||
} else if (std.mem.eql(u8, name, "name")) {
|
} else if (name.eql(comptime .wrap("name"))) {
|
||||||
// Check if this is a slot element
|
// Check if this is a slot element
|
||||||
if (element.is(Element.Html.Slot)) |slot| {
|
if (element.is(Element.Html.Slot)) |slot| {
|
||||||
self.signalSlotChange(slot);
|
self.signalSlotChange(slot);
|
||||||
@@ -2499,7 +2851,7 @@ pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_value: []const u8) void {
|
pub fn attributeRemove(self: *Page, element: *Element, name: String, old_value: String) void {
|
||||||
_ = Element.Build.call(element, "attributeRemove", .{ element, name, self }) catch |err| {
|
_ = Element.Build.call(element, "attributeRemove", .{ element, name, self }) catch |err| {
|
||||||
log.err(.bug, "build.attributeRemove", .{ .tag = element.getTag(), .name = name, .err = err });
|
log.err(.bug, "build.attributeRemove", .{ .tag = element.getTag(), .name = name, .err = err });
|
||||||
};
|
};
|
||||||
@@ -2515,9 +2867,9 @@ pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_val
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle slot assignment changes
|
// Handle slot assignment changes
|
||||||
if (std.mem.eql(u8, name, "slot")) {
|
if (name.eql(comptime .wrap("slot"))) {
|
||||||
self.updateSlotAssignments(element);
|
self.updateSlotAssignments(element);
|
||||||
} else if (std.mem.eql(u8, name, "name")) {
|
} else if (name.eql(comptime .wrap("name"))) {
|
||||||
// Check if this is a slot element
|
// Check if this is a slot element
|
||||||
if (element.is(Element.Html.Slot)) |slot| {
|
if (element.is(Element.Html.Slot)) |slot| {
|
||||||
self.signalSlotChange(slot);
|
self.signalSlotChange(slot);
|
||||||
@@ -2566,7 +2918,7 @@ fn updateElementAssignedSlot(self: *Page, element: *Element) void {
|
|||||||
const parent_el = parent.is(Element) orelse return;
|
const parent_el = parent.is(Element) orelse return;
|
||||||
const shadow_root = self._element_shadow_roots.get(parent_el) orelse return;
|
const shadow_root = self._element_shadow_roots.get(parent_el) orelse return;
|
||||||
|
|
||||||
const slot_name = element.getAttributeSafe("slot") orelse "";
|
const slot_name = element.getAttributeSafe(comptime .wrap("slot")) orelse "";
|
||||||
|
|
||||||
// Recursively search through the shadow root for a matching slot
|
// Recursively search through the shadow root for a matching slot
|
||||||
if (findMatchingSlot(shadow_root.asNode(), slot_name)) |slot| {
|
if (findMatchingSlot(shadow_root.asNode(), slot_name)) |slot| {
|
||||||
@@ -2713,6 +3065,9 @@ const ParseState = union(enum) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LoadState = enum {
|
const LoadState = enum {
|
||||||
|
// waiting for the main HTML
|
||||||
|
waiting,
|
||||||
|
|
||||||
// the main HTML is being parsed (or downloaded)
|
// the main HTML is being parsed (or downloaded)
|
||||||
parsing,
|
parsing,
|
||||||
|
|
||||||
@@ -2860,7 +3215,7 @@ pub fn handleClick(self: *Page, target: *Node) !void {
|
|||||||
|
|
||||||
switch (html_element._type) {
|
switch (html_element._type) {
|
||||||
.anchor => |anchor| {
|
.anchor => |anchor| {
|
||||||
const href = element.getAttributeSafe("href") orelse return;
|
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return;
|
||||||
if (href.len == 0) {
|
if (href.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2876,7 +3231,7 @@ pub fn handleClick(self: *Page, target: *Node) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (try element.hasAttribute("download", self)) {
|
if (try element.hasAttribute(comptime .wrap("download"), self)) {
|
||||||
log.warn(.browser, "a.download", .{});
|
log.warn(.browser, "a.download", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2933,16 +3288,7 @@ pub fn handleKeydown(self: *Page, target: *Node, event: *Event) !void {
|
|||||||
|
|
||||||
// Handle printable characters
|
// Handle printable characters
|
||||||
if (key.isPrintable()) {
|
if (key.isPrintable()) {
|
||||||
// if the input is selected, replace the content.
|
try input.innerInsert(key.asString(), self);
|
||||||
if (input._selected) {
|
|
||||||
const new_value = try self.arena.dupe(u8, key.asString());
|
|
||||||
try input.setValue(new_value, self);
|
|
||||||
input._selected = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const current_value = input.getValue();
|
|
||||||
const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, key.asString() });
|
|
||||||
try input.setValue(new_value, self);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2965,7 +3311,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
|
|||||||
const form = form_ orelse return;
|
const form = form_ orelse return;
|
||||||
|
|
||||||
if (submitter_) |submitter| {
|
if (submitter_) |submitter| {
|
||||||
if (submitter.getAttributeSafe("disabled") != null) {
|
if (submitter.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2978,13 +3324,13 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
|
|||||||
|
|
||||||
const transfer_arena = self._session.transfer_arena;
|
const transfer_arena = self._session.transfer_arena;
|
||||||
|
|
||||||
const encoding = form_element.getAttributeSafe("enctype");
|
const encoding = form_element.getAttributeSafe(comptime .wrap("enctype"));
|
||||||
|
|
||||||
var buf = std.Io.Writer.Allocating.init(transfer_arena);
|
var buf = std.Io.Writer.Allocating.init(transfer_arena);
|
||||||
try form_data.write(encoding, &buf.writer);
|
try form_data.write(encoding, &buf.writer);
|
||||||
|
|
||||||
const method = form_element.getAttributeSafe("method") orelse "";
|
const method = form_element.getAttributeSafe(comptime .wrap("method")) orelse "";
|
||||||
var action = form_element.getAttributeSafe("action") orelse self.url;
|
var action = form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url;
|
||||||
|
|
||||||
var opts = NavigateOpts{
|
var opts = NavigateOpts{
|
||||||
.reason = .form,
|
.reason = .form,
|
||||||
@@ -3011,18 +3357,7 @@ pub fn insertText(self: *Page, v: []const u8) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the input is selected, replace the existing value
|
try input.innerInsert(v, self);
|
||||||
if (input._selected) {
|
|
||||||
const new_value = try self.arena.dupe(u8, v);
|
|
||||||
try input.setValue(new_value, self);
|
|
||||||
input._selected = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or append the value
|
|
||||||
const current_value = input.getValue();
|
|
||||||
const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, v });
|
|
||||||
return input.setValue(new_value, self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (html_element.is(Element.Html.TextArea)) |textarea| {
|
if (html_element.is(Element.Html.TextArea)) |textarea| {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const js = @import("js/js.zig");
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
||||||
|
|
||||||
|
|||||||
@@ -152,14 +152,14 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
script_element._executed = true;
|
script_element._executed = true;
|
||||||
|
|
||||||
const element = script_element.asElement();
|
const element = script_element.asElement();
|
||||||
if (element.getAttributeSafe("nomodule") != null) {
|
if (element.getAttributeSafe(comptime .wrap("nomodule")) != null) {
|
||||||
// these scripts should only be loaded if we don't support modules
|
// these scripts should only be loaded if we don't support modules
|
||||||
// but since we do support modules, we can just skip them.
|
// but since we do support modules, we can just skip them.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const kind: Script.Kind = blk: {
|
const kind: Script.Kind = blk: {
|
||||||
const script_type = element.getAttributeSafe("type") orelse break :blk .javascript;
|
const script_type = element.getAttributeSafe(comptime .wrap("type")) orelse break :blk .javascript;
|
||||||
if (script_type.len == 0) {
|
if (script_type.len == 0) {
|
||||||
break :blk .javascript;
|
break :blk .javascript;
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
var source: Script.Source = undefined;
|
var source: Script.Source = undefined;
|
||||||
var remote_url: ?[:0]const u8 = null;
|
var remote_url: ?[:0]const u8 = null;
|
||||||
const base_url = page.base();
|
const base_url = page.base();
|
||||||
if (element.getAttributeSafe("src")) |src| {
|
if (element.getAttributeSafe(comptime .wrap("src"))) |src| {
|
||||||
if (try parseDataURI(page.arena, src)) |data_uri| {
|
if (try parseDataURI(page.arena, src)) |data_uri| {
|
||||||
source = .{ .@"inline" = data_uri };
|
source = .{ .@"inline" = data_uri };
|
||||||
} else {
|
} else {
|
||||||
@@ -217,12 +217,12 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
break :blk if (kind == .module) .@"defer" else .normal;
|
break :blk if (kind == .module) .@"defer" else .normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.getAttributeSafe("async") != null) {
|
if (element.getAttributeSafe(comptime .wrap("async")) != null) {
|
||||||
break :blk .async;
|
break :blk .async;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for defer or module (before checking dynamic script default)
|
// Check for defer or module (before checking dynamic script default)
|
||||||
if (kind == .module or element.getAttributeSafe("defer") != null) {
|
if (kind == .module or element.getAttributeSafe(comptime .wrap("defer")) != null) {
|
||||||
break :blk .@"defer";
|
break :blk .@"defer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,11 +271,15 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.url = remote_url.?,
|
.url = remote_url.?,
|
||||||
.element = element,
|
.element = element,
|
||||||
.stack = page.js.stackTrace() catch "???",
|
.stack = ls.local.stackTrace() catch "???",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,11 +361,15 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
|||||||
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
.url = url,
|
.url = url,
|
||||||
.ctx = "module",
|
.ctx = "module",
|
||||||
.referrer = referrer,
|
.referrer = referrer,
|
||||||
.stack = self.page.js.stackTrace() catch "???",
|
.stack = ls.local.stackTrace() catch "???",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,11 +456,15 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
|||||||
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
.url = url,
|
.url = url,
|
||||||
.ctx = "dynamic module",
|
.ctx = "dynamic module",
|
||||||
.referrer = referrer,
|
.referrer = referrer,
|
||||||
.stack = self.page.js.stackTrace() catch "???",
|
.stack = ls.local.stackTrace() catch "???",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,7 +663,7 @@ pub const Script = struct {
|
|||||||
log.debug(.http, "script fetch start", .{ .req = transfer });
|
log.debug(.http, "script fetch start", .{ .req = transfer });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn headerCallback(transfer: *Http.Transfer) !void {
|
fn headerCallback(transfer: *Http.Transfer) !bool {
|
||||||
const self: *Script = @ptrCast(@alignCast(transfer.ctx));
|
const self: *Script = @ptrCast(@alignCast(transfer.ctx));
|
||||||
const header = &transfer.response_header.?;
|
const header = &transfer.response_header.?;
|
||||||
self.status = header.status;
|
self.status = header.status;
|
||||||
@@ -661,7 +673,7 @@ pub const Script = struct {
|
|||||||
.status = header.status,
|
.status = header.status,
|
||||||
.content_type = header.contentType(),
|
.content_type = header.contentType(),
|
||||||
});
|
});
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
@@ -682,6 +694,7 @@ pub const Script = struct {
|
|||||||
try buffer.ensureTotalCapacity(self.manager.allocator, cl);
|
try buffer.ensureTotalCapacity(self.manager.allocator, cl);
|
||||||
}
|
}
|
||||||
self.source = .{ .remote = buffer };
|
self.source = .{ .remote = buffer };
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||||
@@ -721,7 +734,7 @@ pub const Script = struct {
|
|||||||
log.warn(.http, "script fetch error", .{
|
log.warn(.http, "script fetch error", .{
|
||||||
.err = err,
|
.err = err,
|
||||||
.req = self.url,
|
.req = self.url,
|
||||||
.mode = self.mode,
|
.mode = std.meta.activeTag(self.mode),
|
||||||
.kind = self.kind,
|
.kind = self.kind,
|
||||||
.status = self.status,
|
.status = self.status,
|
||||||
});
|
});
|
||||||
@@ -741,9 +754,13 @@ pub const Script = struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.mode == .import) {
|
switch (self.mode) {
|
||||||
const entry = self.manager.imported_modules.getPtr(self.url).?;
|
.import_async => |ia| ia.callback(ia.data, error.FailedToLoad),
|
||||||
entry.state = .err;
|
.import => {
|
||||||
|
const entry = manager.imported_modules.getPtr(self.url).?;
|
||||||
|
entry.state = .err;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
}
|
}
|
||||||
self.deinit(true);
|
self.deinit(true);
|
||||||
manager.evaluate();
|
manager.evaluate();
|
||||||
@@ -785,6 +802,12 @@ pub const Script = struct {
|
|||||||
.cacheable = cacheable,
|
.cacheable = cacheable,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
const local = &ls.local;
|
||||||
|
|
||||||
// Handle importmap special case here: the content is a JSON containing
|
// Handle importmap special case here: the content is a JSON containing
|
||||||
// imports.
|
// imports.
|
||||||
if (self.kind == .importmap) {
|
if (self.kind == .importmap) {
|
||||||
@@ -795,25 +818,24 @@ pub const Script = struct {
|
|||||||
.kind = self.kind,
|
.kind = self.kind,
|
||||||
.cacheable = cacheable,
|
.cacheable = cacheable,
|
||||||
});
|
});
|
||||||
self.executeCallback("error", script_element._on_error, page);
|
self.executeCallback("error", local.toLocal(script_element._on_error), page);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.executeCallback("load", script_element._on_load, page);
|
self.executeCallback("load", local.toLocal(script_element._on_load), page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const js_context = page.js;
|
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const success = blk: {
|
const success = blk: {
|
||||||
const content = self.source.content();
|
const content = self.source.content();
|
||||||
switch (self.kind) {
|
switch (self.kind) {
|
||||||
.javascript => _ = js_context.eval(content, url) catch break :blk false,
|
.javascript => _ = local.eval(content, url) catch break :blk false,
|
||||||
.module => {
|
.module => {
|
||||||
// We don't care about waiting for the evaluation here.
|
// We don't care about waiting for the evaluation here.
|
||||||
js_context.module(false, content, url, cacheable) catch break :blk false;
|
page.js.module(false, local, content, url, cacheable) catch break :blk false;
|
||||||
},
|
},
|
||||||
.importmap => unreachable, // handled before the try/catch.
|
.importmap => unreachable, // handled before the try/catch.
|
||||||
}
|
}
|
||||||
@@ -826,14 +848,14 @@ pub const Script = struct {
|
|||||||
|
|
||||||
defer {
|
defer {
|
||||||
// We should run microtasks even if script execution fails.
|
// We should run microtasks even if script execution fails.
|
||||||
page.js.runMicrotasks();
|
local.runMicrotasks();
|
||||||
_ = page.scheduler.run() catch |err| {
|
_ = page.scheduler.run() catch |err| {
|
||||||
log.err(.page, "scheduler", .{ .err = err });
|
log.err(.page, "scheduler", .{ .err = err });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
self.executeCallback("load", script_element._on_load, page);
|
self.executeCallback("load", local.toLocal(script_element._on_load), page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,12 +866,11 @@ pub const Script = struct {
|
|||||||
.cacheable = cacheable,
|
.cacheable = cacheable,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.executeCallback("error", script_element._on_error, page);
|
self.executeCallback("error", local.toLocal(script_element._on_error), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function.Global, page: *Page) void {
|
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function, page: *Page) void {
|
||||||
const cb_global = cb_ orelse return;
|
const cb = cb_ orelse return;
|
||||||
const cb = cb_global.local();
|
|
||||||
|
|
||||||
const Event = @import("webapi/Event.zig");
|
const Event = @import("webapi/Event.zig");
|
||||||
const event = Event.initTrusted(typ, .{}, page) catch |err| {
|
const event = Event.initTrusted(typ, .{}, page) catch |err| {
|
||||||
|
|||||||
@@ -77,8 +77,9 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
|
|||||||
}
|
}
|
||||||
|
|
||||||
// trailing space so that we always have space to append the null terminator
|
// trailing space so that we always have space to append the null terminator
|
||||||
var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " });
|
// and so that we can compare the next two characters without needing to length check
|
||||||
const end = out.len - 1;
|
var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " });
|
||||||
|
const end = out.len - 2;
|
||||||
|
|
||||||
const path_marker = path_start + 1;
|
const path_marker = path_start + 1;
|
||||||
|
|
||||||
@@ -88,33 +89,39 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
|
|||||||
var in_i: usize = 0;
|
var in_i: usize = 0;
|
||||||
var out_i: usize = 0;
|
var out_i: usize = 0;
|
||||||
while (in_i < end) {
|
while (in_i < end) {
|
||||||
if (std.mem.startsWith(u8, out[in_i..], "./")) {
|
if (out[in_i] == '.' and (out_i == 0 or out[out_i - 1] == '/')) {
|
||||||
in_i += 2;
|
if (out[in_i + 1] == '/') { // always safe, because we added a whitespace
|
||||||
continue;
|
// /./
|
||||||
}
|
in_i += 2;
|
||||||
|
continue;
|
||||||
if (std.mem.startsWith(u8, out[in_i..], "../")) {
|
}
|
||||||
lp.assert(out[out_i - 1] == '/', "URL.resolve", .{ .out = out });
|
if (out[in_i + 1] == '.' and out[in_i + 2] == '/') { // always safe, because we added two whitespaces
|
||||||
|
// /../
|
||||||
if (out_i > path_marker) {
|
if (out_i > path_marker) {
|
||||||
// go back before the /
|
// go back before the /
|
||||||
out_i -= 2;
|
out_i -= 2;
|
||||||
while (out_i > 1 and out[out_i - 1] != '/') {
|
while (out_i > 1 and out[out_i - 1] != '/') {
|
||||||
out_i -= 1;
|
out_i -= 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if out_i == path_marker, than we've reached the start of
|
// if out_i == path_marker, than we've reached the start of
|
||||||
// the path. We can't ../ any more. E.g.:
|
// the path. We can't ../ any more. E.g.:
|
||||||
// http://www.example.com/../hello.
|
// http://www.example.com/../hello.
|
||||||
// You might think that's an error, but, at least with
|
// You might think that's an error, but, at least with
|
||||||
// new URL('../hello', 'http://www.example.com/')
|
// new URL('../hello', 'http://www.example.com/')
|
||||||
// it just ignores the extra ../
|
// it just ignores the extra ../
|
||||||
|
}
|
||||||
|
in_i += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_i == end - 1) {
|
||||||
|
// ignore trailing dot
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
in_i += 3;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out[out_i] = out[in_i];
|
const c = out[in_i];
|
||||||
|
out[out_i] = c;
|
||||||
in_i += 1;
|
in_i += 1;
|
||||||
out_i += 1;
|
out_i += 1;
|
||||||
}
|
}
|
||||||
@@ -542,6 +549,21 @@ test "URL: resolve" {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cases = [_]Case{
|
const cases = [_]Case{
|
||||||
|
.{
|
||||||
|
.base = "https://example/dir",
|
||||||
|
.path = "abc../test",
|
||||||
|
.expected = "https://example/abc../test",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://example/dir",
|
||||||
|
.path = "abc.",
|
||||||
|
.expected = "https://example/abc.",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://example/dir",
|
||||||
|
.path = "abc/.",
|
||||||
|
.expected = "https://example/abc/",
|
||||||
|
},
|
||||||
.{
|
.{
|
||||||
.base = "https://example/xyz/abc/123",
|
.base = "https://example/xyz/abc/123",
|
||||||
.path = "something.js",
|
.path = "something.js",
|
||||||
|
|||||||
@@ -51,11 +51,22 @@ pub const Opts = struct {
|
|||||||
|
|
||||||
pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: *Page) !void {
|
pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: *Page) !void {
|
||||||
if (doc.is(Node.Document.HTMLDocument)) |html_doc| {
|
if (doc.is(Node.Document.HTMLDocument)) |html_doc| {
|
||||||
try writer.writeAll("<!DOCTYPE html>");
|
blk: {
|
||||||
|
// Ideally we just render the doctype which is part of the document
|
||||||
|
if (doc.asNode().firstChild()) |first| {
|
||||||
|
if (first._type == .document_type) {
|
||||||
|
break :blk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// But if the doc has no child, or the first child isn't a doctype
|
||||||
|
// well force it.
|
||||||
|
try writer.writeAll("<!DOCTYPE html>");
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.with_base) {
|
if (opts.with_base) {
|
||||||
const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode();
|
const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode();
|
||||||
const base = try doc.createElement("base", null, page);
|
const base = try doc.createElement("base", null, page);
|
||||||
try base.setAttributeSafe("base", page.base(), page);
|
try base.setAttributeSafe(comptime .wrap("base"), .wrap(page.base()), page);
|
||||||
_ = try parent.insertBefore(base.asNode(), parent.firstChild(), page);
|
_ = try parent.insertBefore(base.asNode(), parent.firstChild(), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +110,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
|||||||
// to render that "active" content, so when we're trying to render
|
// to render that "active" content, so when we're trying to render
|
||||||
// it, we don't want to skip it.
|
// it, we don't want to skip it.
|
||||||
if ((comptime force_slot == false) and opts.shadow == .rendered) {
|
if ((comptime force_slot == false) and opts.shadow == .rendered) {
|
||||||
if (el.getAttributeSafe("slot")) |_| {
|
if (el.getAttributeSafe(comptime .wrap("slot"))) |_| {
|
||||||
// Skip - will be rendered by the Slot if it's the active container
|
// Skip - will be rendered by the Slot if it's the active container
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -242,12 +253,12 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
|
|||||||
if (std.mem.eql(u8, tag_name, "noscript")) return true;
|
if (std.mem.eql(u8, tag_name, "noscript")) return true;
|
||||||
|
|
||||||
if (std.mem.eql(u8, tag_name, "link")) {
|
if (std.mem.eql(u8, tag_name, "link")) {
|
||||||
if (el.getAttributeSafe("as")) |as| {
|
if (el.getAttributeSafe(comptime .wrap("as"))) |as| {
|
||||||
if (std.mem.eql(u8, as, "script")) return true;
|
if (std.mem.eql(u8, as, "script")) return true;
|
||||||
}
|
}
|
||||||
if (el.getAttributeSafe("rel")) |rel| {
|
if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| {
|
||||||
if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) {
|
if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) {
|
||||||
if (el.getAttributeSafe("as")) |as| {
|
if (el.getAttributeSafe(comptime .wrap("as"))) |as| {
|
||||||
if (std.mem.eql(u8, as, "script")) return true;
|
if (std.mem.eql(u8, as, "script")) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,7 +270,7 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
|
|||||||
if (std.mem.eql(u8, tag_name, "style")) return true;
|
if (std.mem.eql(u8, tag_name, "style")) return true;
|
||||||
|
|
||||||
if (std.mem.eql(u8, tag_name, "link")) {
|
if (std.mem.eql(u8, tag_name, "link")) {
|
||||||
if (el.getAttributeSafe("rel")) |rel| {
|
if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| {
|
||||||
if (std.mem.eql(u8, rel, "stylesheet")) return true;
|
if (std.mem.eql(u8, rel, "stylesheet")) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const Array = @This();
|
const Array = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Array,
|
handle: *const v8.Array,
|
||||||
|
|
||||||
pub fn len(self: Array) usize {
|
pub fn len(self: Array) usize {
|
||||||
@@ -30,39 +30,37 @@ pub fn len(self: Array) usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: Array, index: u32) !js.Value {
|
pub fn get(self: Array, index: u32) !js.Value {
|
||||||
const ctx = self.ctx;
|
const ctx = self.local.ctx;
|
||||||
|
|
||||||
const idx = js.Integer.init(ctx.isolate.handle, index);
|
const idx = js.Integer.init(ctx.isolate.handle, index);
|
||||||
const handle = v8.v8__Object__Get(@ptrCast(self.handle), ctx.handle, idx.handle) orelse {
|
const handle = v8.v8__Object__Get(@ptrCast(self.handle), self.local.handle, idx.handle) orelse {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
|
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.Caller.CallOpts) !bool {
|
||||||
const ctx = self.ctx;
|
const js_value = try self.local.zigValueToJs(value, opts);
|
||||||
|
|
||||||
const js_value = try ctx.zigValueToJs(value, opts);
|
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Object__SetAtIndex(@ptrCast(self.handle), ctx.handle, index, js_value.handle, &out);
|
v8.v8__Object__SetAtIndex(@ptrCast(self.handle), self.local.handle, index, js_value.handle, &out);
|
||||||
return out.has_value;
|
return out.has_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toObject(self: Array) js.Object {
|
pub fn toObject(self: Array) js.Object {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toValue(self: Array) js.Value {
|
pub fn toValue(self: Array) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
585
src/browser/js/Caller.zig
Normal file
585
src/browser/js/Caller.zig
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
const string = @import("../../string.zig");
|
||||||
|
|
||||||
|
const Page = @import("../Page.zig");
|
||||||
|
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const bridge = @import("bridge.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||||
|
|
||||||
|
const v8 = js.v8;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const CALL_ARENA_RETAIN = 1024 * 16;
|
||||||
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
|
const Caller = @This();
|
||||||
|
local: js.Local,
|
||||||
|
prev_local: ?*const js.Local,
|
||||||
|
|
||||||
|
// Takes the raw v8 isolate and extracts the context from it.
|
||||||
|
pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void {
|
||||||
|
const v8_context_handle = v8.v8__Isolate__GetCurrentContext(v8_isolate);
|
||||||
|
|
||||||
|
const embedder_data = v8.v8__Context__GetEmbedderData(v8_context_handle, 1);
|
||||||
|
var lossless: bool = undefined;
|
||||||
|
const ctx: *Context = @ptrFromInt(v8.v8__BigInt__Uint64Value(embedder_data, &lossless));
|
||||||
|
|
||||||
|
ctx.call_depth += 1;
|
||||||
|
self.* = Caller{
|
||||||
|
.local = .{
|
||||||
|
.ctx = ctx,
|
||||||
|
.handle = v8_context_handle.?,
|
||||||
|
.call_arena = ctx.call_arena,
|
||||||
|
.isolate = .{ .handle = v8_isolate },
|
||||||
|
},
|
||||||
|
.prev_local = ctx.local,
|
||||||
|
};
|
||||||
|
ctx.local = &self.local;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Caller) void {
|
||||||
|
const ctx = self.local.ctx;
|
||||||
|
const call_depth = ctx.call_depth - 1;
|
||||||
|
|
||||||
|
// Because of callbacks, calls can be nested. Because of this, we
|
||||||
|
// can't clear the call_arena after _every_ call. Imagine we have
|
||||||
|
// arr.forEach((i) => { console.log(i); }
|
||||||
|
//
|
||||||
|
// First we call forEach. Inside of our forEach call,
|
||||||
|
// we call console.log. If we reset the call_arena after this call,
|
||||||
|
// it'll reset it for the `forEach` call after, which might still
|
||||||
|
// need the data.
|
||||||
|
//
|
||||||
|
// Therefore, we keep a call_depth, and only reset the call_arena
|
||||||
|
// when a top-level (call_depth == 0) function ends.
|
||||||
|
if (call_depth == 0) {
|
||||||
|
const arena: *ArenaAllocator = @ptrCast(@alignCast(ctx.call_arena.ptr));
|
||||||
|
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.call_depth = call_depth;
|
||||||
|
ctx.local = self.prev_local;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CallOpts = struct {
|
||||||
|
cache: ?[]const u8 = null,
|
||||||
|
dom_exception: bool = false,
|
||||||
|
null_as_undefined: bool = false,
|
||||||
|
as_typed_array: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn constructor(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = FunctionCallbackInfo{ .handle = handle };
|
||||||
|
|
||||||
|
if (!info.isConstructCall()) {
|
||||||
|
self.handleError(T, @TypeOf(func), error.InvalidArgument, info, opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._constructor(func, info) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _constructor(self: *Caller, func: anytype, info: FunctionCallbackInfo) !void {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
const args = try self.getArgs(F, 0, info);
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
|
||||||
|
const ReturnType = @typeInfo(F).@"fn".return_type orelse {
|
||||||
|
@compileError(@typeName(F) ++ " has a constructor without a return type");
|
||||||
|
};
|
||||||
|
|
||||||
|
const new_this_handle = info.getThis();
|
||||||
|
var this = js.Object{ .local = &self.local, .handle = new_this_handle };
|
||||||
|
if (@typeInfo(ReturnType) == .error_union) {
|
||||||
|
const non_error_res = res catch |err| return err;
|
||||||
|
this = try self.local.mapZigInstanceToJs(new_this_handle, non_error_res);
|
||||||
|
} else {
|
||||||
|
this = try self.local.mapZigInstanceToJs(new_this_handle, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got back a different object (existing wrapper), copy the prototype
|
||||||
|
// from new object. (this happens when we're upgrading an CustomElement)
|
||||||
|
if (this.handle != new_this_handle) {
|
||||||
|
const prototype_handle = v8.v8__Object__GetPrototype(new_this_handle).?;
|
||||||
|
var out: v8.MaybeBool = undefined;
|
||||||
|
v8.v8__Object__SetPrototype(this.handle, self.local.handle, prototype_handle, &out);
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
std.debug.assert(out.has_value and out.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.getReturnValue().set(this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = FunctionCallbackInfo{ .handle = handle };
|
||||||
|
self._method(T, func, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args = try self.getArgs(F, 1, info);
|
||||||
|
|
||||||
|
const js_this = info.getThis();
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, js_this);
|
||||||
|
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
|
||||||
|
const mapped = try self.local.zigValueToJs(res, opts);
|
||||||
|
const return_value = info.getReturnValue();
|
||||||
|
return_value.set(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn function(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = FunctionCallbackInfo{ .handle = handle };
|
||||||
|
self._function(func, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _function(self: *Caller, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
const args = try self.getArgs(F, 0, info);
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
info.getReturnValue().set(try self.local.zigValueToJs(res, opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._getIndex(T, func, idx, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args = try self.getArgs(F, 2, info);
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = idx;
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._getNamedIndex(T, func, name, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args = try self.getArgs(F, 2, info);
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = try self.nameToString(@TypeOf(args.@"1"), name);
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, js_value: *const v8.Value, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._setNamedIndex(T, func, name, .{ .local = &self.local, .handle = js_value }, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, js_value: js.Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args: ParameterTypes(F) = undefined;
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = try self.nameToString(@TypeOf(args.@"1"), name);
|
||||||
|
@field(args, "2") = try self.local.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
|
||||||
|
if (@typeInfo(F).@"fn".params.len == 4) {
|
||||||
|
@field(args, "3") = self.local.ctx.page;
|
||||||
|
}
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._deleteNamedIndex(T, func, name, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args: ParameterTypes(F) = undefined;
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = try self.nameToString(@TypeOf(args.@"1"), name);
|
||||||
|
if (@typeInfo(F).@"fn".params.len == 3) {
|
||||||
|
@field(args, "2") = self.local.ctx.page;
|
||||||
|
}
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, comptime getter: bool, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
// need to unwrap this error immediately for when opts.null_as_undefined == true
|
||||||
|
// and we need to compare it to null;
|
||||||
|
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
|
||||||
|
.error_union => |eu| blk: {
|
||||||
|
break :blk ret catch |err| {
|
||||||
|
// We can't compare err == error.NotHandled if error.NotHandled
|
||||||
|
// isn't part of the possible error set. So we first need to check
|
||||||
|
// if error.NotHandled is part of the error set.
|
||||||
|
if (isInErrorSet(error.NotHandled, eu.error_set)) {
|
||||||
|
if (err == error.NotHandled) {
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.handleError(T, F, err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => ret,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (comptime getter) {
|
||||||
|
info.getReturnValue().set(try self.local.zigValueToJs(non_error_ret, opts));
|
||||||
|
}
|
||||||
|
// intercepted
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isInErrorSet(err: anyerror, comptime T: type) bool {
|
||||||
|
inline for (@typeInfo(T).error_set.?) |e| {
|
||||||
|
if (err == @field(anyerror, e.name)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nameToString(self: *const Caller, comptime T: type, name: *const v8.Name) !T {
|
||||||
|
const v8_string = @as(*const v8.String, @ptrCast(name));
|
||||||
|
if (T == string.String) {
|
||||||
|
return self.local.jsStringToStringSSO(v8_string, .{});
|
||||||
|
}
|
||||||
|
if (T == string.Global) {
|
||||||
|
return self.local.jsStringToStringSSO(v8_string, .{ .allocator = self.local.ctx.allocator });
|
||||||
|
}
|
||||||
|
return try self.local.valueHandleToString(v8_string, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
||||||
|
const isolate = self.local.isolate;
|
||||||
|
|
||||||
|
if (comptime @import("builtin").mode == .Debug and @TypeOf(info) == FunctionCallbackInfo) {
|
||||||
|
if (log.enabled(.js, .warn)) {
|
||||||
|
self.logFunctionCallError(@typeName(T), @typeName(F), err, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const js_err: *const v8.Value = switch (err) {
|
||||||
|
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
||||||
|
error.OutOfMemory => isolate.createError("out of memory"),
|
||||||
|
error.IllegalConstructor => isolate.createError("Illegal Contructor"),
|
||||||
|
else => blk: {
|
||||||
|
if (comptime opts.dom_exception) {
|
||||||
|
const DOMException = @import("../webapi/DOMException.zig");
|
||||||
|
if (DOMException.fromError(err)) |ex| {
|
||||||
|
const value = self.local.zigValueToJs(ex, .{}) catch break :blk isolate.createError("internal error");
|
||||||
|
break :blk value.handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break :blk isolate.createError(@errorName(err));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const js_exception = isolate.throwException(js_err);
|
||||||
|
info.getReturnValue().setValueHandle(js_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we call a method in javascript: cat.lives('nine');
|
||||||
|
//
|
||||||
|
// Then we'd expect a Zig function with 2 parameters: a self and the string.
|
||||||
|
// In this case, offset == 1. Offset is always 1 for setters or methods.
|
||||||
|
//
|
||||||
|
// Offset is always 0 for constructors.
|
||||||
|
//
|
||||||
|
// For constructors, setters and methods, we can further increase offset + 1
|
||||||
|
// if the first parameter is an instance of Page.
|
||||||
|
//
|
||||||
|
// Finally, if the JS function is called with _more_ parameters and
|
||||||
|
// the last parameter in Zig is an array, we'll try to slurp the additional
|
||||||
|
// parameters into the array.
|
||||||
|
fn getArgs(self: *const Caller, comptime F: type, comptime offset: usize, info: anytype) !ParameterTypes(F) {
|
||||||
|
const local = &self.local;
|
||||||
|
var args: ParameterTypes(F) = undefined;
|
||||||
|
|
||||||
|
const params = @typeInfo(F).@"fn".params[offset..];
|
||||||
|
// Except for the constructor, the first parameter is always `self`
|
||||||
|
// This isn't something we'll bind from JS, so skip it.
|
||||||
|
const params_to_map = blk: {
|
||||||
|
if (params.len == 0) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last parameter is the Page, set it, and exclude it
|
||||||
|
// from our params slice, because we don't want to bind it to
|
||||||
|
// a JS argument
|
||||||
|
if (comptime isPage(params[params.len - 1].type.?)) {
|
||||||
|
@field(args, tupleFieldName(params.len - 1 + offset)) = local.ctx.page;
|
||||||
|
break :blk params[0 .. params.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have neither a Page nor a JsObject. All params must be
|
||||||
|
// bound to a JavaScript value.
|
||||||
|
break :blk params;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params_to_map.len == 0) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
const js_parameter_count = info.length();
|
||||||
|
const last_js_parameter = params_to_map.len - 1;
|
||||||
|
var is_variadic = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
// This is going to get complicated. If the last Zig parameter
|
||||||
|
// is a slice AND the corresponding javascript parameter is
|
||||||
|
// NOT an an array, then we'll treat it as a variadic.
|
||||||
|
|
||||||
|
const last_parameter_type = params_to_map[params_to_map.len - 1].type.?;
|
||||||
|
const last_parameter_type_info = @typeInfo(last_parameter_type);
|
||||||
|
if (last_parameter_type_info == .pointer and last_parameter_type_info.pointer.size == .slice) {
|
||||||
|
const slice_type = last_parameter_type_info.pointer.child;
|
||||||
|
const corresponding_js_value = info.getArg(@intCast(last_js_parameter), local);
|
||||||
|
if (corresponding_js_value.isArray() == false and corresponding_js_value.isTypedArray() == false and slice_type != u8) {
|
||||||
|
is_variadic = true;
|
||||||
|
if (js_parameter_count == 0) {
|
||||||
|
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
||||||
|
} else if (js_parameter_count >= params_to_map.len) {
|
||||||
|
const arr = try local.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1);
|
||||||
|
for (arr, last_js_parameter..) |*a, i| {
|
||||||
|
a.* = try local.jsValueToZig(slice_type, info.getArg(@intCast(i), local));
|
||||||
|
}
|
||||||
|
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = arr;
|
||||||
|
} else {
|
||||||
|
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline for (params_to_map, 0..) |param, i| {
|
||||||
|
const field_index = comptime i + offset;
|
||||||
|
if (comptime i == params_to_map.len - 1) {
|
||||||
|
if (is_variadic) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime isPage(param.type.?)) {
|
||||||
|
@compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F));
|
||||||
|
} else if (i >= js_parameter_count) {
|
||||||
|
if (@typeInfo(param.type.?) != .optional) {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
@field(args, tupleFieldName(field_index)) = null;
|
||||||
|
} else {
|
||||||
|
const js_val = info.getArg(@intCast(i), local);
|
||||||
|
@field(args, tupleFieldName(field_index)) = local.jsValueToZig(param.type.?, js_val) catch {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is extracted to speed up compilation. When left inlined in handleError,
|
||||||
|
// this can add as much as 10 seconds of compilation time.
|
||||||
|
fn logFunctionCallError(self: *Caller, type_name: []const u8, func: []const u8, err: anyerror, info: FunctionCallbackInfo) void {
|
||||||
|
const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
|
||||||
|
log.info(.js, "function call error", .{
|
||||||
|
.type = type_name,
|
||||||
|
.func = func,
|
||||||
|
.err = err,
|
||||||
|
.args = args_dump,
|
||||||
|
.stack = self.local.stackTrace() catch |err1| @errorName(err1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serializeFunctionArgs(self: *Caller, info: FunctionCallbackInfo) ![]const u8 {
|
||||||
|
const local = &self.local;
|
||||||
|
var buf = std.Io.Writer.Allocating.init(local.call_arena);
|
||||||
|
|
||||||
|
const separator = log.separator();
|
||||||
|
for (0..info.length()) |i| {
|
||||||
|
try buf.writer.print("{s}{d} - ", .{ separator, i + 1 });
|
||||||
|
const js_value = info.getArg(@intCast(i), local);
|
||||||
|
try local.debugValue(js_value, &buf.writer);
|
||||||
|
}
|
||||||
|
return buf.written();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a function, and returns a tuple for its argument. Used when we
|
||||||
|
// @call a function
|
||||||
|
fn ParameterTypes(comptime F: type) type {
|
||||||
|
const params = @typeInfo(F).@"fn".params;
|
||||||
|
var fields: [params.len]std.builtin.Type.StructField = undefined;
|
||||||
|
|
||||||
|
inline for (params, 0..) |param, i| {
|
||||||
|
fields[i] = .{
|
||||||
|
.name = tupleFieldName(i),
|
||||||
|
.type = param.type.?,
|
||||||
|
.default_value_ptr = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = @alignOf(param.type.?),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @Type(.{ .@"struct" = .{
|
||||||
|
.layout = .auto,
|
||||||
|
.decls = &.{},
|
||||||
|
.fields = &fields,
|
||||||
|
.is_tuple = true,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tupleFieldName(comptime i: usize) [:0]const u8 {
|
||||||
|
return switch (i) {
|
||||||
|
0 => "0",
|
||||||
|
1 => "1",
|
||||||
|
2 => "2",
|
||||||
|
3 => "3",
|
||||||
|
4 => "4",
|
||||||
|
5 => "5",
|
||||||
|
6 => "6",
|
||||||
|
7 => "7",
|
||||||
|
8 => "8",
|
||||||
|
9 => "9",
|
||||||
|
else => std.fmt.comptimePrint("{d}", .{i}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isPage(comptime T: type) bool {
|
||||||
|
return T == *Page or T == *const Page;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These wrap the raw v8 C API to provide a cleaner interface.
|
||||||
|
pub const FunctionCallbackInfo = struct {
|
||||||
|
handle: *const v8.FunctionCallbackInfo,
|
||||||
|
|
||||||
|
pub fn length(self: FunctionCallbackInfo) u32 {
|
||||||
|
return @intCast(v8.v8__FunctionCallbackInfo__Length(self.handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getArg(self: FunctionCallbackInfo, index: u32, local: *const js.Local) js.Value {
|
||||||
|
return .{ .local = local, .handle = v8.v8__FunctionCallbackInfo__INDEX(self.handle, @intCast(index)).? };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getThis(self: FunctionCallbackInfo) *const v8.Object {
|
||||||
|
return v8.v8__FunctionCallbackInfo__This(self.handle).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getReturnValue(self: FunctionCallbackInfo) ReturnValue {
|
||||||
|
var rv: v8.ReturnValue = undefined;
|
||||||
|
v8.v8__FunctionCallbackInfo__GetReturnValue(self.handle, &rv);
|
||||||
|
return .{ .handle = rv };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isConstructCall(self: FunctionCallbackInfo) bool {
|
||||||
|
return v8.v8__FunctionCallbackInfo__IsConstructCall(self.handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PropertyCallbackInfo = struct {
|
||||||
|
handle: *const v8.PropertyCallbackInfo,
|
||||||
|
|
||||||
|
pub fn getThis(self: PropertyCallbackInfo) *const v8.Object {
|
||||||
|
return v8.v8__PropertyCallbackInfo__This(self.handle).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getReturnValue(self: PropertyCallbackInfo) ReturnValue {
|
||||||
|
var rv: v8.ReturnValue = undefined;
|
||||||
|
v8.v8__PropertyCallbackInfo__GetReturnValue(self.handle, &rv);
|
||||||
|
return .{ .handle = rv };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReturnValue = struct {
|
||||||
|
handle: v8.ReturnValue,
|
||||||
|
|
||||||
|
pub fn set(self: ReturnValue, value: anytype) void {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
if (T == *const v8.Object) {
|
||||||
|
self.setValueHandle(@ptrCast(value));
|
||||||
|
} else if (T == *const v8.Value) {
|
||||||
|
self.setValueHandle(value);
|
||||||
|
} else if (T == js.Value) {
|
||||||
|
self.setValueHandle(value.handle);
|
||||||
|
} else {
|
||||||
|
@compileError("Unsupported type for ReturnValue.set: " ++ @typeName(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setValueHandle(self: ReturnValue, handle: *const v8.Value) void {
|
||||||
|
v8.v8__ReturnValue__Set(self.handle, handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -24,11 +24,14 @@ const log = @import("../../log.zig");
|
|||||||
|
|
||||||
const bridge = @import("bridge.zig");
|
const bridge = @import("bridge.zig");
|
||||||
const Context = @import("Context.zig");
|
const Context = @import("Context.zig");
|
||||||
|
const Isolate = @import("Isolate.zig");
|
||||||
const Platform = @import("Platform.zig");
|
const Platform = @import("Platform.zig");
|
||||||
const Snapshot = @import("Snapshot.zig");
|
const Snapshot = @import("Snapshot.zig");
|
||||||
const Inspector = @import("Inspector.zig");
|
const Inspector = @import("Inspector.zig");
|
||||||
const ExecutionWorld = @import("ExecutionWorld.zig");
|
const ExecutionWorld = @import("ExecutionWorld.zig");
|
||||||
|
|
||||||
|
const Window = @import("../webapi/Window.zig");
|
||||||
|
|
||||||
const JsApis = bridge.JsApis;
|
const JsApis = bridge.JsApis;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
@@ -59,6 +62,9 @@ eternal_function_templates: []v8.Eternal,
|
|||||||
// Dynamic slice to avoid circular dependency on JsApis.len at comptime
|
// Dynamic slice to avoid circular dependency on JsApis.len at comptime
|
||||||
templates: []*const v8.FunctionTemplate,
|
templates: []*const v8.FunctionTemplate,
|
||||||
|
|
||||||
|
// Global template created once per isolate and reused across all contexts
|
||||||
|
global_template: v8.Eternal,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot) !Env {
|
pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot) !Env {
|
||||||
var params = try allocator.create(v8.CreateParams);
|
var params = try allocator.create(v8.CreateParams);
|
||||||
errdefer allocator.destroy(params);
|
errdefer allocator.destroy(params);
|
||||||
@@ -91,6 +97,7 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot
|
|||||||
const templates = try allocator.alloc(*const v8.FunctionTemplate, JsApis.len);
|
const templates = try allocator.alloc(*const v8.FunctionTemplate, JsApis.len);
|
||||||
errdefer allocator.free(templates);
|
errdefer allocator.free(templates);
|
||||||
|
|
||||||
|
var global_eternal: v8.Eternal = undefined;
|
||||||
{
|
{
|
||||||
var temp_scope: js.HandleScope = undefined;
|
var temp_scope: js.HandleScope = undefined;
|
||||||
temp_scope.init(isolate);
|
temp_scope.init(isolate);
|
||||||
@@ -107,6 +114,29 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot
|
|||||||
const eternal_ptr = v8.v8__Eternal__Get(&eternal_function_templates[i], isolate.handle);
|
const eternal_ptr = v8.v8__Eternal__Get(&eternal_function_templates[i], isolate.handle);
|
||||||
templates[i] = @ptrCast(@alignCast(eternal_ptr.?));
|
templates[i] = @ptrCast(@alignCast(eternal_ptr.?));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create global template once per isolate
|
||||||
|
const js_global = v8.v8__FunctionTemplate__New__DEFAULT(isolate.handle);
|
||||||
|
const window_name = v8.v8__String__NewFromUtf8(isolate.handle, "Window", v8.kNormal, 6);
|
||||||
|
v8.v8__FunctionTemplate__SetClassName(js_global, window_name);
|
||||||
|
|
||||||
|
// Find Window in JsApis by name (avoids circular import)
|
||||||
|
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
|
||||||
|
v8.v8__FunctionTemplate__Inherit(js_global, templates[window_index]);
|
||||||
|
|
||||||
|
const global_template_local = v8.v8__FunctionTemplate__InstanceTemplate(js_global).?;
|
||||||
|
v8.v8__ObjectTemplate__SetNamedHandler(global_template_local, &.{
|
||||||
|
.getter = bridge.unknownPropertyCallback,
|
||||||
|
.setter = null,
|
||||||
|
.query = null,
|
||||||
|
.deleter = null,
|
||||||
|
.enumerator = null,
|
||||||
|
.definer = null,
|
||||||
|
.descriptor = null,
|
||||||
|
.data = null,
|
||||||
|
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
||||||
|
});
|
||||||
|
v8.v8__Eternal__New(isolate.handle, @ptrCast(global_template_local), &global_eternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
@@ -117,6 +147,7 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot
|
|||||||
.templates = templates,
|
.templates = templates,
|
||||||
.isolate_params = params,
|
.isolate_params = params,
|
||||||
.eternal_function_templates = eternal_function_templates,
|
.eternal_function_templates = eternal_function_templates,
|
||||||
|
.global_template = global_eternal,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +172,10 @@ pub fn runMicrotasks(self: *const Env) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pumpMessageLoop(self: *const Env) bool {
|
pub fn pumpMessageLoop(self: *const Env) bool {
|
||||||
|
var hs: v8.HandleScope = undefined;
|
||||||
|
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate.handle);
|
||||||
|
defer v8.v8__HandleScope__DESTRUCT(&hs);
|
||||||
|
|
||||||
return v8.v8__Platform__PumpMessageLoop(self.platform.handle, self.isolate.handle, false);
|
return v8.v8__Platform__PumpMessageLoop(self.platform.handle, self.isolate.handle, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +194,8 @@ pub fn newExecutionWorld(self: *Env) !ExecutionWorld {
|
|||||||
// a Context, it's managed by the garbage collector. We use the
|
// a Context, it's managed by the garbage collector. We use the
|
||||||
// `lowMemoryNotification` call on the isolate to encourage v8 to free
|
// `lowMemoryNotification` call on the isolate to encourage v8 to free
|
||||||
// any contexts which have been freed.
|
// any contexts which have been freed.
|
||||||
|
// This GC is very aggressive. Use memoryPressureNotification for less
|
||||||
|
// aggressive GC passes.
|
||||||
pub fn lowMemoryNotification(self: *Env) void {
|
pub fn lowMemoryNotification(self: *Env) void {
|
||||||
var handle_scope: js.HandleScope = undefined;
|
var handle_scope: js.HandleScope = undefined;
|
||||||
handle_scope.init(self.isolate);
|
handle_scope.init(self.isolate);
|
||||||
@@ -166,6 +203,21 @@ pub fn lowMemoryNotification(self: *Env) void {
|
|||||||
self.isolate.lowMemoryNotification();
|
self.isolate.lowMemoryNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V8 doesn't immediately free memory associated with
|
||||||
|
// a Context, it's managed by the garbage collector. We use the
|
||||||
|
// `memoryPressureNotification` call on the isolate to encourage v8 to free
|
||||||
|
// any contexts which have been freed.
|
||||||
|
// The level indicates the aggressivity of the GC required:
|
||||||
|
// moderate speeds up incremental GC
|
||||||
|
// critical runs one full GC
|
||||||
|
// For a more aggressive GC, use lowMemoryNotification.
|
||||||
|
pub fn memoryPressureNotification(self: *Env, level: Isolate.MemoryPressureLevel) void {
|
||||||
|
var handle_scope: js.HandleScope = undefined;
|
||||||
|
handle_scope.init(self.isolate);
|
||||||
|
defer handle_scope.deinit();
|
||||||
|
self.isolate.memoryPressureNotification(level);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dumpMemoryStats(self: *Env) void {
|
pub fn dumpMemoryStats(self: *Env) void {
|
||||||
const stats = self.isolate.getHeapStatistics();
|
const stats = self.isolate.getHeapStatistics();
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
@@ -189,19 +241,27 @@ pub fn dumpMemoryStats(self: *Env) void {
|
|||||||
|
|
||||||
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
|
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
|
||||||
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
|
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
|
||||||
const isolate_handle = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
|
const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
|
||||||
const js_isolate = js.Isolate{ .handle = isolate_handle };
|
const js_isolate = js.Isolate{ .handle = v8_isolate };
|
||||||
const context = Context.fromIsolate(js_isolate);
|
const ctx = Context.fromIsolate(js_isolate);
|
||||||
|
|
||||||
|
const local = js.Local{
|
||||||
|
.ctx = ctx,
|
||||||
|
.isolate = js_isolate,
|
||||||
|
.handle = v8.v8__Isolate__GetCurrentContext(v8_isolate).?,
|
||||||
|
.call_arena = ctx.call_arena,
|
||||||
|
};
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
if (v8.v8__PromiseRejectMessage__GetValue(&message_handle)) |v8_value|
|
if (v8.v8__PromiseRejectMessage__GetValue(&message_handle)) |v8_value|
|
||||||
context.valueToString(.{ .ctx = context, .handle = v8_value }, .{}) catch |err| @errorName(err)
|
// @HandleScope - no reason to create a js.Context here
|
||||||
|
local.valueHandleToString(v8_value, .{}) catch |err| @errorName(err)
|
||||||
else
|
else
|
||||||
"no value";
|
"no value";
|
||||||
|
|
||||||
log.debug(.js, "unhandled rejection", .{
|
log.debug(.js, "unhandled rejection", .{
|
||||||
.value = value,
|
.value = value,
|
||||||
.stack = context.stackTrace() catch |err| @errorName(err) orelse "???",
|
.stack = local.stackTrace() catch |err| @errorName(err) orelse "???",
|
||||||
.note = "This should be updated to call window.unhandledrejection",
|
.note = "This should be updated to call window.unhandledrejection",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ context_arena: ArenaAllocator,
|
|||||||
// does all the work, but having all page-specific data structures
|
// does all the work, but having all page-specific data structures
|
||||||
// grouped together helps keep things clean.
|
// grouped together helps keep things clean.
|
||||||
context: ?Context = null,
|
context: ?Context = null,
|
||||||
persisted_context: ?js.Global(Context) = null,
|
|
||||||
|
|
||||||
// no init, must be initialized via env.newExecutionWorld()
|
// no init, must be initialized via env.newExecutionWorld()
|
||||||
|
|
||||||
@@ -77,43 +76,28 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
|||||||
const isolate = env.isolate;
|
const isolate = env.isolate;
|
||||||
const arena = self.context_arena.allocator();
|
const arena = self.context_arena.allocator();
|
||||||
|
|
||||||
const persisted_context: js.Global(Context) = blk: {
|
var hs: js.HandleScope = undefined;
|
||||||
var temp_scope: js.HandleScope = undefined;
|
hs.init(isolate);
|
||||||
temp_scope.init(isolate);
|
defer hs.deinit();
|
||||||
defer temp_scope.deinit();
|
|
||||||
|
|
||||||
// Getting this into the snapshot is tricky (anything involving the
|
// Get the global template that was created once per isolate
|
||||||
// global is tricky). Easier to do here
|
const global_template: *const v8.ObjectTemplate = @ptrCast(@alignCast(v8.v8__Eternal__Get(&env.global_template, isolate.handle).?));
|
||||||
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate.handle, env.templates);
|
const v8_context = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
||||||
v8.v8__ObjectTemplate__SetNamedHandler(global_template, &.{
|
|
||||||
.getter = bridge.unknownPropertyCallback,
|
|
||||||
.setter = null,
|
|
||||||
.query = null,
|
|
||||||
.deleter = null,
|
|
||||||
.enumerator = null,
|
|
||||||
.definer = null,
|
|
||||||
.descriptor = null,
|
|
||||||
.data = null,
|
|
||||||
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
|
||||||
});
|
|
||||||
|
|
||||||
const context_handle = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
// Create the v8::Context and wrap it in a v8::Global
|
||||||
break :blk js.Global(Context).init(isolate.handle, context_handle);
|
var context_global: v8.Global = undefined;
|
||||||
};
|
v8.v8__Global__New(isolate.handle, v8_context, &context_global);
|
||||||
|
|
||||||
|
// our window wrapped in a v8::Global
|
||||||
|
const global_obj = v8.v8__Context__Global(v8_context).?;
|
||||||
|
var global_global: v8.Global = undefined;
|
||||||
|
v8.v8__Global__New(isolate.handle, global_obj, &global_global);
|
||||||
|
|
||||||
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
|
|
||||||
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
|
|
||||||
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
|
|
||||||
const v8_context = persisted_context.local();
|
|
||||||
var handle_scope: ?js.HandleScope = null;
|
|
||||||
if (enter) {
|
if (enter) {
|
||||||
handle_scope = @as(js.HandleScope, undefined);
|
|
||||||
handle_scope.?.init(isolate);
|
|
||||||
v8.v8__Context__Enter(v8_context);
|
v8.v8__Context__Enter(v8_context);
|
||||||
}
|
}
|
||||||
errdefer if (enter) {
|
errdefer if (enter) {
|
||||||
v8.v8__Context__Exit(v8_context);
|
v8.v8__Context__Exit(v8_context);
|
||||||
handle_scope.?.deinit();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const context_id = env.context_id;
|
const context_id = env.context_id;
|
||||||
@@ -122,24 +106,24 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
|||||||
self.context = Context{
|
self.context = Context{
|
||||||
.page = page,
|
.page = page,
|
||||||
.id = context_id,
|
.id = context_id,
|
||||||
|
.entered = enter,
|
||||||
.isolate = isolate,
|
.isolate = isolate,
|
||||||
.handle = v8_context,
|
.handle = context_global,
|
||||||
.templates = env.templates,
|
.templates = env.templates,
|
||||||
.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 = arena,
|
||||||
};
|
};
|
||||||
self.persisted_context = persisted_context;
|
|
||||||
|
|
||||||
var context = &self.context.?;
|
var context = &self.context.?;
|
||||||
|
try context.identity_map.putNoClobber(arena, @intFromPtr(page.window), global_global);
|
||||||
|
|
||||||
// Store a pointer to our context inside the v8 context so that, given
|
// Store a pointer to our context inside the v8 context so that, given
|
||||||
// a v8 context, we can get our context out
|
// a v8 context, we can get our context out
|
||||||
const data = isolate.initBigInt(@intFromPtr(context));
|
const data = isolate.initBigInt(@intFromPtr(&self.context.?));
|
||||||
v8.v8__Context__SetEmbedderData(context.handle, 1, @ptrCast(data.handle));
|
v8.v8__Context__SetEmbedderData(v8_context, 1, @ptrCast(data.handle));
|
||||||
|
|
||||||
try context.setupGlobal();
|
return &self.context.?;
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeContext(self: *ExecutionWorld) void {
|
pub fn removeContext(self: *ExecutionWorld) void {
|
||||||
@@ -147,17 +131,6 @@ pub fn removeContext(self: *ExecutionWorld) void {
|
|||||||
context.deinit();
|
context.deinit();
|
||||||
self.context = null;
|
self.context = null;
|
||||||
|
|
||||||
self.persisted_context.?.deinit();
|
|
||||||
self.persisted_context = null;
|
|
||||||
|
|
||||||
self.env.isolate.notifyContextDisposed();
|
self.env.isolate.notifyContextDisposed();
|
||||||
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
|
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminateExecution(self: *const ExecutionWorld) void {
|
|
||||||
self.env.isolate.terminateExecution();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resumeExecution(self: *const ExecutionWorld) void {
|
|
||||||
self.env.isolate.cancelTerminateExecution();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ const std = @import("std");
|
|||||||
const js = @import("js.zig");
|
const js = @import("js.zig");
|
||||||
const v8 = js.v8;
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Function = @This();
|
const Function = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
this: ?*const v8.Object = null,
|
this: ?*const v8.Object = null,
|
||||||
handle: *const v8.Function,
|
handle: *const v8.Function,
|
||||||
|
|
||||||
@@ -34,60 +36,66 @@ pub const Result = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn withThis(self: *const Function, value: anytype) !Function {
|
pub fn withThis(self: *const Function, value: anytype) !Function {
|
||||||
|
const local = self.local;
|
||||||
const this_obj = if (@TypeOf(value) == js.Object)
|
const this_obj = if (@TypeOf(value) == js.Object)
|
||||||
value.handle
|
value.handle
|
||||||
else
|
else
|
||||||
(try self.ctx.zigValueToJs(value, .{})).handle;
|
(try local.zigValueToJs(value, .{})).handle;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = local,
|
||||||
.this = this_obj,
|
.this = this_obj,
|
||||||
.handle = self.handle,
|
.handle = self.handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
|
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
|
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(ctx);
|
try_catch.init(local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
// This creates a new instance using this Function as a constructor.
|
// This creates a new instance using this Function as a constructor.
|
||||||
// const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{}));
|
// const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{}));
|
||||||
const handle = v8.v8__Function__NewInstance(self.handle, ctx.handle, 0, null) orelse {
|
const handle = v8.v8__Function__NewInstance(self.handle, local.handle, 0, null) orelse {
|
||||||
caught.* = try_catch.caughtOrError(ctx.call_arena, error.Unknown);
|
caught.* = try_catch.caughtOrError(local.call_arena, error.Unknown);
|
||||||
return error.JsConstructorFailed;
|
return error.JsConstructorFailed;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
|
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
|
||||||
return self.callWithThis(T, self.getThis(), args);
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
}
|
return self._tryCallWithThis(T, self.getThis(), args, &caught) catch |err| {
|
||||||
|
log.warn(.js, "call caught", .{ .err = err, .caught = caught });
|
||||||
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T {
|
|
||||||
return self.tryCallWithThis(T, self.getThis(), args, caught);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
|
|
||||||
var try_catch: js.TryCatch = undefined;
|
|
||||||
|
|
||||||
try_catch.init(self.ctx);
|
|
||||||
defer try_catch.deinit();
|
|
||||||
|
|
||||||
return self.callWithThis(T, this, args) catch |err| {
|
|
||||||
caught.* = try_catch.caughtOrError(self.ctx.call_arena, err);
|
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ctx = self.ctx;
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
|
return self._tryCallWithThis(T, this, args, &caught) catch |err| {
|
||||||
|
log.warn(.js, "callWithThis caught", .{ .err = err, .caught = caught });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T {
|
||||||
|
return self._tryCallWithThis(T, self.getThis(), args, caught);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
|
||||||
|
return self._tryCallWithThis(T, this, args, caught);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
|
||||||
|
caught.* = .{};
|
||||||
|
const local = self.local;
|
||||||
|
|
||||||
// When we're calling a function from within JavaScript itself, this isn't
|
// When we're calling a function from within JavaScript itself, this isn't
|
||||||
// necessary. We're within a Caller instantiation, which will already have
|
// necessary. We're within a Caller instantiation, which will already have
|
||||||
@@ -98,6 +106,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
|||||||
// need to increase the call_depth so that the call_arena remains valid for
|
// 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
|
// 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.
|
// will be reset after each statement of the function which executes Zig code.
|
||||||
|
const ctx = local.ctx;
|
||||||
const call_depth = ctx.call_depth;
|
const call_depth = ctx.call_depth;
|
||||||
ctx.call_depth = call_depth + 1;
|
ctx.call_depth = call_depth + 1;
|
||||||
defer ctx.call_depth = call_depth;
|
defer ctx.call_depth = call_depth;
|
||||||
@@ -106,7 +115,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
|||||||
if (@TypeOf(this) == js.Object) {
|
if (@TypeOf(this) == js.Object) {
|
||||||
break :blk this;
|
break :blk this;
|
||||||
}
|
}
|
||||||
break :blk try ctx.zigValueToJs(this, .{});
|
break :blk try local.zigValueToJs(this, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
||||||
@@ -116,15 +125,15 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
|||||||
const fields = s.fields;
|
const fields = s.fields;
|
||||||
var js_args: [fields.len]*const v8.Value = undefined;
|
var js_args: [fields.len]*const v8.Value = undefined;
|
||||||
inline for (fields, 0..) |f, i| {
|
inline for (fields, 0..) |f, i| {
|
||||||
js_args[i] = (try ctx.zigValueToJs(@field(aargs, f.name), .{})).handle;
|
js_args[i] = (try local.zigValueToJs(@field(aargs, f.name), .{})).handle;
|
||||||
}
|
}
|
||||||
const cargs: [fields.len]*const v8.Value = js_args;
|
const cargs: [fields.len]*const v8.Value = js_args;
|
||||||
break :blk &cargs;
|
break :blk &cargs;
|
||||||
},
|
},
|
||||||
.pointer => blk: {
|
.pointer => blk: {
|
||||||
var values = try ctx.call_arena.alloc(*const v8.Value, args.len);
|
var values = try local.call_arena.alloc(*const v8.Value, args.len);
|
||||||
for (args, 0..) |a, i| {
|
for (args, 0..) |a, i| {
|
||||||
values[i] = (try ctx.zigValueToJs(a, .{})).handle;
|
values[i] = (try local.zigValueToJs(a, .{})).handle;
|
||||||
}
|
}
|
||||||
break :blk values;
|
break :blk values;
|
||||||
},
|
},
|
||||||
@@ -132,54 +141,71 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
|||||||
};
|
};
|
||||||
|
|
||||||
const c_args = @as(?[*]const ?*v8.Value, @ptrCast(js_args.ptr));
|
const c_args = @as(?[*]const ?*v8.Value, @ptrCast(js_args.ptr));
|
||||||
const handle = v8.v8__Function__Call(self.handle, ctx.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
|
|
||||||
// std.debug.print("CB ERR: {s}\n", .{self.src() catch "???"});
|
var try_catch: js.TryCatch = undefined;
|
||||||
|
try_catch.init(local);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
const handle = v8.v8__Function__Call(self.handle, local.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
|
||||||
|
caught.* = try_catch.caughtOrError(local.call_arena, error.JSExecCallback);
|
||||||
return error.JSExecCallback;
|
return error.JSExecCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (@typeInfo(T) == .void) {
|
if (@typeInfo(T) == .void) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return ctx.jsValueToZig(T, .{ .ctx = ctx, .handle = handle });
|
return local.jsValueToZig(T, .{ .local = local, .handle = handle });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getThis(self: *const Function) js.Object {
|
fn getThis(self: *const Function) js.Object {
|
||||||
const handle = if (self.this) |t| t else v8.v8__Context__Global(self.ctx.handle).?;
|
const handle = if (self.this) |t| t else v8.v8__Context__Global(self.local.handle).?;
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn src(self: *const Function) ![]const u8 {
|
pub fn src(self: *const Function) ![]const u8 {
|
||||||
return self.context.valueToString(.{ .handle = @ptrCast(self.handle) }, .{});
|
return self.local.valueToString(.{ .local = self.local, .handle = @ptrCast(self.handle) }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
|
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
const key = ctx.isolate.initStringHandle(name);
|
const key = local.isolate.initStringHandle(name);
|
||||||
const handle = v8.v8__Object__Get(self.handle, ctx.handle, key) orelse {
|
const handle = v8.v8__Object__Get(self.handle, self.local.handle, key) orelse {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: *const Function) !Global {
|
pub fn persist(self: *const Function) !Global {
|
||||||
var ctx = self.ctx;
|
return self._persist(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn temp(self: *const Function) !Temp {
|
||||||
|
return self._persist(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Global else Temp) {
|
||||||
|
var ctx = self.local.ctx;
|
||||||
|
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
|
if (comptime is_global) {
|
||||||
|
try ctx.global_functions.append(ctx.arena, global);
|
||||||
|
} else {
|
||||||
|
try ctx.global_functions_temp.put(ctx.arena, global.data_ptr, global);
|
||||||
|
}
|
||||||
|
return .{ .handle = global };
|
||||||
|
}
|
||||||
|
|
||||||
try ctx.global_functions.append(ctx.arena, global);
|
pub fn tempWithThis(self: *const Function, value: anytype) !Temp {
|
||||||
|
const with_this = try self.withThis(value);
|
||||||
return .{
|
return with_this.temp();
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persistWithThis(self: *const Function, value: anytype) !Global {
|
pub fn persistWithThis(self: *const Function, value: anytype) !Global {
|
||||||
@@ -187,22 +213,31 @@ pub fn persistWithThis(self: *const Function, value: anytype) !Global {
|
|||||||
return with_this.persist();
|
return with_this.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Temp = G(0);
|
||||||
handle: v8.Global,
|
pub const Global = G(1);
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
fn G(comptime discriminator: u8) type {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
return struct {
|
||||||
}
|
handle: v8.Global,
|
||||||
|
|
||||||
pub fn local(self: *const Global) Function {
|
// makes the types different (G(0) != G(1)), without taking up space
|
||||||
return .{
|
comptime _: u8 = discriminator,
|
||||||
.ctx = self.ctx,
|
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isEqual(self: *const Global, other: Function) bool {
|
const Self = @This();
|
||||||
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
|
||||||
}
|
pub fn deinit(self: *Self) void {
|
||||||
};
|
v8.v8__Global__Reset(&self.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local(self: *const Self, l: *const js.Local) Function {
|
||||||
|
return .{
|
||||||
|
.local = l,
|
||||||
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEqual(self: *const Self, other: Function) bool {
|
||||||
|
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
|||||||
const js = @import("js.zig");
|
const js = @import("js.zig");
|
||||||
const v8 = js.v8;
|
const v8 = js.v8;
|
||||||
|
|
||||||
const Context = @import("Context.zig");
|
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const RndGen = std.Random.DefaultPrng;
|
const RndGen = std.Random.DefaultPrng;
|
||||||
@@ -36,7 +36,7 @@ client: Client,
|
|||||||
channel: Channel,
|
channel: Channel,
|
||||||
session: Session,
|
session: Session,
|
||||||
rnd: RndGen = RndGen.init(0),
|
rnd: RndGen = RndGen.init(0),
|
||||||
default_context: ?*const v8.Context = null,
|
default_context: ?v8.Global,
|
||||||
|
|
||||||
// We expect allocator to be an arena
|
// We expect allocator to be an arena
|
||||||
// Note: This initializes the pre-allocated inspector in-place
|
// Note: This initializes the pre-allocated inspector in-place
|
||||||
@@ -96,9 +96,9 @@ pub fn init(self: *Inspector, isolate: *v8.Isolate, ctx: anytype) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *const Inspector) void {
|
pub fn deinit(self: *const Inspector) void {
|
||||||
var temp_scope: v8.HandleScope = undefined;
|
var hs: v8.HandleScope = undefined;
|
||||||
v8.v8__HandleScope__CONSTRUCT(&temp_scope, self.isolate);
|
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate);
|
||||||
defer v8.v8__HandleScope__DESTRUCT(&temp_scope);
|
defer v8.v8__HandleScope__DESTRUCT(&hs);
|
||||||
|
|
||||||
self.session.deinit();
|
self.session.deinit();
|
||||||
self.client.deinit();
|
self.client.deinit();
|
||||||
@@ -128,7 +128,7 @@ pub fn send(self: *const Inspector, msg: []const u8) void {
|
|||||||
// - is_default_context: Whether the execution context is default, should match the auxData
|
// - is_default_context: Whether the execution context is default, should match the auxData
|
||||||
pub fn contextCreated(
|
pub fn contextCreated(
|
||||||
self: *Inspector,
|
self: *Inspector,
|
||||||
context: *const Context,
|
local: *const js.Local,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
origin: []const u8,
|
origin: []const u8,
|
||||||
aux_data: []const u8,
|
aux_data: []const u8,
|
||||||
@@ -143,14 +143,26 @@ pub fn contextCreated(
|
|||||||
aux_data.ptr,
|
aux_data.ptr,
|
||||||
aux_data.len,
|
aux_data.len,
|
||||||
CONTEXT_GROUP_ID,
|
CONTEXT_GROUP_ID,
|
||||||
context.handle,
|
local.handle,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (is_default_context) {
|
if (is_default_context) {
|
||||||
self.default_context = context.handle;
|
self.default_context = local.ctx.handle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contextDestroyed(self: *Inspector, local: *const js.Local) void {
|
||||||
|
v8.v8_inspector__Inspector__ContextDestroyed(self.handle, local.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resetContextGroup(self: *const Inspector) void {
|
||||||
|
var hs: v8.HandleScope = undefined;
|
||||||
|
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate);
|
||||||
|
defer v8.v8__HandleScope__DESTRUCT(&hs);
|
||||||
|
|
||||||
|
v8.v8_inspector__Inspector__ResetContextGroup(self.handle, CONTEXT_GROUP_ID);
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieves the RemoteObject for a given value.
|
// Retrieves the RemoteObject for a given value.
|
||||||
// The value is loaded through the ExecutionWorld's mapZigInstanceToJs function,
|
// The value is loaded through the ExecutionWorld's mapZigInstanceToJs function,
|
||||||
// just like a method return value. Therefore, if we've mapped this
|
// just like a method return value. Therefore, if we've mapped this
|
||||||
@@ -158,41 +170,42 @@ pub fn contextCreated(
|
|||||||
// we'll create it and track it for cleanup when the context ends.
|
// we'll create it and track it for cleanup when the context ends.
|
||||||
pub fn getRemoteObject(
|
pub fn getRemoteObject(
|
||||||
self: *const Inspector,
|
self: *const Inspector,
|
||||||
context: *Context,
|
local: *const js.Local,
|
||||||
group: []const u8,
|
group: []const u8,
|
||||||
value: anytype,
|
value: anytype,
|
||||||
) !RemoteObject {
|
) !RemoteObject {
|
||||||
const js_value = try context.zigValueToJs(value, .{});
|
const js_val = try local.zigValueToJs(value, .{});
|
||||||
|
|
||||||
// We do not want to expose this as a parameter for now
|
// We do not want to expose this as a parameter for now
|
||||||
const generate_preview = false;
|
const generate_preview = false;
|
||||||
return self.session.wrapObject(
|
return self.session.wrapObject(
|
||||||
context.isolate.handle,
|
local.isolate.handle,
|
||||||
context.handle,
|
local.handle,
|
||||||
js_value.handle,
|
js_val.handle,
|
||||||
group,
|
group,
|
||||||
generate_preview,
|
generate_preview,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets a value by object ID regardless of which context it is in.
|
// Gets a value by object ID regardless of which context it is in.
|
||||||
// Our TaggedAnyOpaque stores the "resolved" ptr value (the most specific _type,
|
// Our TaggedOpaque stores the "resolved" ptr value (the most specific _type,
|
||||||
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
|
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
|
||||||
// the pointer to the Node, so we need to use the same resolution mechanism which
|
// the pointer to the Node, so we need to use the same resolution mechanism which
|
||||||
// is used when we're calling a function to turn the Div into a Node, which is
|
// is used when we're calling a function to turn the Div into a Node, which is
|
||||||
// what Context.typeTaggedAnyOpaque does.
|
// what TaggedOpaque.fromJS does.
|
||||||
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8) !*anyopaque {
|
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8, local: *js.Local) !*anyopaque {
|
||||||
|
// just to indicate that the caller is responsible for ensure there's a local environment
|
||||||
|
_ = local;
|
||||||
const unwrapped = try self.session.unwrapObject(allocator, object_id);
|
const unwrapped = try self.session.unwrapObject(allocator, object_id);
|
||||||
// The values context and groupId are not used here
|
// The values context and groupId are not used here
|
||||||
const js_val = unwrapped.value;
|
const js_val = unwrapped.value;
|
||||||
if (!v8.v8__Value__IsObject(js_val)) {
|
if (!v8.v8__Value__IsObject(js_val)) {
|
||||||
return error.ObjectIdIsNotANode;
|
return error.ObjectIdIsNotANode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Node = @import("../webapi/Node.zig");
|
const Node = @import("../webapi/Node.zig");
|
||||||
// Cast to *const v8.Object for typeTaggedAnyOpaque
|
// Cast to *const v8.Object for typeTaggedAnyOpaque
|
||||||
return Context.typeTaggedAnyOpaque(*Node, @ptrCast(js_val)) catch {
|
return TaggedOpaque.fromJS(*Node, @ptrCast(js_val)) catch return error.ObjectIdIsNotANode;
|
||||||
return error.ObjectIdIsNotANode;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const RemoteObject = struct {
|
pub const RemoteObject = struct {
|
||||||
@@ -399,7 +412,7 @@ fn fromData(data: *anyopaque) *Inspector {
|
|||||||
return @ptrCast(@alignCast(data));
|
return @ptrCast(@alignCast(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTaggedAnyOpaque(value: *const v8.Value) ?*js.TaggedAnyOpaque {
|
pub fn getTaggedOpaque(value: *const v8.Value) ?*TaggedOpaque {
|
||||||
if (!v8.v8__Value__IsObject(value)) {
|
if (!v8.v8__Value__IsObject(value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -469,7 +482,8 @@ pub export fn v8_inspector__Client__IMPL__ensureDefaultContextInGroup(
|
|||||||
data: *anyopaque,
|
data: *anyopaque,
|
||||||
) callconv(.c) ?*const v8.Context {
|
) callconv(.c) ?*const v8.Context {
|
||||||
const inspector: *Inspector = @ptrCast(@alignCast(data));
|
const inspector: *Inspector = @ptrCast(@alignCast(data));
|
||||||
return inspector.default_context;
|
const global_handle = inspector.default_context orelse return null;
|
||||||
|
return v8.v8__Global__Get(&global_handle, inspector.isolate);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub export fn v8_inspector__Channel__IMPL__sendResponse(
|
pub export fn v8_inspector__Channel__IMPL__sendResponse(
|
||||||
|
|||||||
@@ -57,6 +57,16 @@ pub fn lowMemoryNotification(self: Isolate) void {
|
|||||||
v8.v8__Isolate__LowMemoryNotification(self.handle);
|
v8.v8__Isolate__LowMemoryNotification(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const MemoryPressureLevel = enum(u32) {
|
||||||
|
none = v8.kNone,
|
||||||
|
moderate = v8.kModerate,
|
||||||
|
critical = v8.kCritical,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn memoryPressureNotification(self: Isolate, level: MemoryPressureLevel) void {
|
||||||
|
v8.v8__Isolate__MemoryPressureNotification(self.handle, @intFromEnum(level));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn notifyContextDisposed(self: Isolate) void {
|
pub fn notifyContextDisposed(self: Isolate) void {
|
||||||
_ = v8.v8__Isolate__ContextDisposedNotification(self.handle);
|
_ = v8.v8__Isolate__ContextDisposedNotification(self.handle);
|
||||||
}
|
}
|
||||||
|
|||||||
1391
src/browser/js/Local.zig
Normal file
1391
src/browser/js/Local.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const Module = @This();
|
const Module = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Module,
|
handle: *const v8.Module,
|
||||||
|
|
||||||
pub const Status = enum(u32) {
|
pub const Status = enum(u32) {
|
||||||
@@ -39,21 +39,21 @@ pub fn getStatus(self: Module) Status {
|
|||||||
|
|
||||||
pub fn getException(self: Module) js.Value {
|
pub fn getException(self: Module) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = v8.v8__Module__GetException(self.handle).?,
|
.handle = v8.v8__Module__GetException(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getModuleRequests(self: Module) Requests {
|
pub fn getModuleRequests(self: Module) Requests {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx.handle,
|
.context_handle = self.local.handle,
|
||||||
.handle = v8.v8__Module__GetModuleRequests(self.handle).?,
|
.handle = v8.v8__Module__GetModuleRequests(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Module__InstantiateModule(self.handle, self.ctx.handle, cb, &out);
|
v8.v8__Module__InstantiateModule(self.handle, self.local.handle, cb, &out);
|
||||||
if (out.has_value) {
|
if (out.has_value) {
|
||||||
return out.value;
|
return out.value;
|
||||||
}
|
}
|
||||||
@@ -61,15 +61,14 @@ pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(self: Module) !js.Value {
|
pub fn evaluate(self: Module) !js.Value {
|
||||||
const ctx = self.ctx;
|
const res = v8.v8__Module__Evaluate(self.handle, self.local.handle) orelse return error.JsException;
|
||||||
const res = v8.v8__Module__Evaluate(self.handle, ctx.handle) orelse return error.JsException;
|
|
||||||
|
|
||||||
if (self.getStatus() == .kErrored) {
|
if (self.getStatus() == .kErrored) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = self.local,
|
||||||
.handle = res,
|
.handle = res,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -80,7 +79,7 @@ pub fn getIdentityHash(self: Module) u32 {
|
|||||||
|
|
||||||
pub fn getModuleNamespace(self: Module) js.Value {
|
pub fn getModuleNamespace(self: Module) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = v8.v8__Module__GetModuleNamespace(self.handle).?,
|
.handle = v8.v8__Module__GetModuleNamespace(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -90,28 +89,24 @@ pub fn getScriptId(self: Module) u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: Module) !Global {
|
pub fn persist(self: Module) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
try ctx.global_modules.append(ctx.arena, global);
|
try ctx.global_modules.append(ctx.arena, global);
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
v8.v8__Global__Reset(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(self: *const Global) Module {
|
pub fn local(self: *const Global, l: *const js.Local) Module {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,15 +116,15 @@ pub const Global = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Requests = struct {
|
const Requests = struct {
|
||||||
ctx: *const v8.Context,
|
|
||||||
handle: *const v8.FixedArray,
|
handle: *const v8.FixedArray,
|
||||||
|
context_handle: *const v8.Context,
|
||||||
|
|
||||||
pub fn len(self: Requests) usize {
|
pub fn len(self: Requests) usize {
|
||||||
return @intCast(v8.v8__FixedArray__Length(self.handle));
|
return @intCast(v8.v8__FixedArray__Length(self.handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: Requests, idx: usize) Request {
|
pub fn get(self: Requests, idx: usize) Request {
|
||||||
return .{ .handle = v8.v8__FixedArray__Get(self.handle, self.ctx, @intCast(idx)).? };
|
return .{ .handle = v8.v8__FixedArray__Get(self.handle, self.context_handle, @intCast(idx)).? };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,19 +28,15 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const Object = @This();
|
const Object = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Object,
|
handle: *const v8.Object,
|
||||||
|
|
||||||
pub fn getId(self: Object) u32 {
|
|
||||||
return @bitCast(v8.v8__Object__GetIdentityHash(self.handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has(self: Object, key: anytype) bool {
|
pub fn has(self: Object, key: anytype) bool {
|
||||||
const ctx = self.ctx;
|
const ctx = self.local.ctx;
|
||||||
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Object__Has(self.handle, self.ctx.handle, key_handle, &out);
|
v8.v8__Object__Has(self.handle, self.local.handle, key_handle, &out);
|
||||||
if (out.has_value) {
|
if (out.has_value) {
|
||||||
return out.value;
|
return out.value;
|
||||||
}
|
}
|
||||||
@@ -48,34 +44,34 @@ pub fn has(self: Object, key: anytype) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: Object, key: anytype) !js.Value {
|
pub fn get(self: Object, key: anytype) !js.Value {
|
||||||
const ctx = self.ctx;
|
const ctx = self.local.ctx;
|
||||||
|
|
||||||
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||||
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, key_handle) orelse return error.JsException;
|
const js_val_handle = v8.v8__Object__Get(self.handle, self.local.handle, key_handle) orelse return error.JsException;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = self.local,
|
||||||
.handle = js_val_handle,
|
.handle = js_val_handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
|
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.Caller.CallOpts) !bool {
|
||||||
const ctx = self.ctx;
|
const ctx = self.local.ctx;
|
||||||
|
|
||||||
const js_value = try ctx.zigValueToJs(value, opts);
|
const js_value = try self.local.zigValueToJs(value, opts);
|
||||||
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Object__Set(self.handle, ctx.handle, key_handle, js_value.handle, &out);
|
v8.v8__Object__Set(self.handle, self.local.handle, key_handle, js_value.handle, &out);
|
||||||
return out.has_value;
|
return out.has_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr: v8.PropertyAttribute) ?bool {
|
pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr: v8.PropertyAttribute) ?bool {
|
||||||
const ctx = self.ctx;
|
const ctx = self.local.ctx;
|
||||||
const name_handle = ctx.isolate.initStringHandle(name);
|
const name_handle = ctx.isolate.initStringHandle(name);
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(name_handle), value.handle, attr, &out);
|
v8.v8__Object__DefineOwnProperty(self.handle, self.local.handle, @ptrCast(name_handle), value.handle, attr, &out);
|
||||||
|
|
||||||
if (out.has_value) {
|
if (out.has_value) {
|
||||||
return out.value;
|
return out.value;
|
||||||
@@ -85,52 +81,49 @@ pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr:
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toString(self: Object) ![]const u8 {
|
pub fn toString(self: Object) ![]const u8 {
|
||||||
return self.ctx.valueToString(self.toValue(), .{});
|
return self.local.ctx.valueToString(self.toValue(), .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toValue(self: Object) js.Value {
|
pub fn toValue(self: Object) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Object, writer: *std.Io.Writer) !void {
|
pub fn format(self: Object, writer: *std.Io.Writer) !void {
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
return self.ctx.debugValue(self.toValue(), writer);
|
return self.local.ctx.debugValue(self.toValue(), writer);
|
||||||
}
|
}
|
||||||
const str = self.toString() catch return error.WriteFailed;
|
const str = self.toString() catch return error.WriteFailed;
|
||||||
return writer.writeAll(str);
|
return writer.writeAll(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: Object) !Global {
|
pub fn persist(self: Object) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
|
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
|
|
||||||
try ctx.global_objects.append(ctx.arena, global);
|
try ctx.global_objects.append(ctx.arena, global);
|
||||||
|
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
|
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
|
||||||
if (self.isNullOrUndefined()) {
|
if (self.isNullOrUndefined()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
|
|
||||||
const js_name = ctx.isolate.initStringHandle(name);
|
const js_name = local.isolate.initStringHandle(name);
|
||||||
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, js_name) orelse return error.JsException;
|
const js_val_handle = v8.v8__Object__Get(self.handle, local.handle, js_name) orelse return error.JsException;
|
||||||
|
|
||||||
if (v8.v8__Value__IsFunction(js_val_handle) == false) {
|
if (v8.v8__Value__IsFunction(js_val_handle) == false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = @ptrCast(js_val_handle),
|
.handle = @ptrCast(js_val_handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -145,51 +138,48 @@ pub fn isNullOrUndefined(self: Object) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOwnPropertyNames(self: Object) js.Array {
|
pub fn getOwnPropertyNames(self: Object) js.Array {
|
||||||
const handle = v8.v8__Object__GetOwnPropertyNames(self.handle, self.ctx.handle).?;
|
const handle = v8.v8__Object__GetOwnPropertyNames(self.handle, self.local.handle).?;
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPropertyNames(self: Object) js.Array {
|
pub fn getPropertyNames(self: Object) js.Array {
|
||||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.ctx.handle).?;
|
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.local.handle).?;
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nameIterator(self: Object) NameIterator {
|
pub fn nameIterator(self: Object) NameIterator {
|
||||||
const ctx = self.ctx;
|
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.local.handle).?;
|
||||||
|
|
||||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, ctx.handle).?;
|
|
||||||
const count = v8.v8__Array__Length(handle);
|
const count = v8.v8__Array__Length(handle);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
.count = count,
|
.count = count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toZig(self: Object, comptime T: type) !T {
|
pub fn toZig(self: Object, comptime T: type) !T {
|
||||||
const js_value = js.Value{ .ctx = self.ctx, .handle = @ptrCast(self.handle) };
|
const js_value = js.Value{ .local = self.local, .handle = @ptrCast(self.handle) };
|
||||||
return self.ctx.jsValueToZig(T, js_value);
|
return self.local.jsValueToZig(T, js_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
v8.v8__Global__Reset(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(self: *const Global) Object {
|
pub fn local(self: *const Global, l: *const js.Local) Object {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +191,7 @@ pub const Global = struct {
|
|||||||
pub const NameIterator = struct {
|
pub const NameIterator = struct {
|
||||||
count: u32,
|
count: u32,
|
||||||
idx: u32 = 0,
|
idx: u32 = 0,
|
||||||
ctx: *Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Array,
|
handle: *const v8.Array,
|
||||||
|
|
||||||
pub fn next(self: *NameIterator) !?[]const u8 {
|
pub fn next(self: *NameIterator) !?[]const u8 {
|
||||||
@@ -211,8 +201,8 @@ pub const NameIterator = struct {
|
|||||||
}
|
}
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
|
|
||||||
const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.ctx.handle, idx) orelse return error.JsException;
|
const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.local.handle, idx) orelse return error.JsException;
|
||||||
const js_val = js.Value{ .ctx = self.ctx, .handle = js_val_handle };
|
const js_val = js.Value{ .local = self.local, .handle = js_val_handle };
|
||||||
return try self.ctx.valueToString(js_val, .{});
|
return try self.local.valueToString(js_val, .{});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,63 +21,51 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const Promise = @This();
|
const Promise = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Promise,
|
handle: *const v8.Promise,
|
||||||
|
|
||||||
pub fn toObject(self: Promise) js.Object {
|
pub fn toObject(self: Promise) js.Object {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toValue(self: Promise) js.Value {
|
pub fn toValue(self: Promise) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thenAndCatch(self: Promise, on_fulfilled: js.Function, on_rejected: js.Function) !Promise {
|
pub fn thenAndCatch(self: Promise, on_fulfilled: js.Function, on_rejected: js.Function) !Promise {
|
||||||
if (v8.v8__Promise__Then2(self.handle, self.ctx.handle, on_fulfilled.handle, on_rejected.handle)) |handle| {
|
if (v8.v8__Promise__Then2(self.handle, self.local.handle, on_fulfilled.handle, on_rejected.handle)) |handle| {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return error.PromiseChainFailed;
|
return error.PromiseChainFailed;
|
||||||
}
|
}
|
||||||
pub fn persist(self: Promise) !Global {
|
pub fn persist(self: Promise) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
try ctx.global_promises.append(ctx.arena, global);
|
try ctx.global_promises.append(ctx.arena, global);
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
v8.v8__Global__Reset(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(self: *const Global) Promise {
|
pub fn local(self: *const Global, l: *const js.Local) Promise {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isEqual(self: *const Global, other: Promise) bool {
|
|
||||||
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn promise(self: *const Global) Promise {
|
|
||||||
return self.local();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,19 +22,19 @@ const log = @import("../../log.zig");
|
|||||||
|
|
||||||
const PromiseResolver = @This();
|
const PromiseResolver = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.PromiseResolver,
|
handle: *const v8.PromiseResolver,
|
||||||
|
|
||||||
pub fn init(ctx: *js.Context) PromiseResolver {
|
pub fn init(local: *const js.Local) PromiseResolver {
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = v8.v8__Promise__Resolver__New(ctx.handle).?,
|
.handle = v8.v8__Promise__Resolver__New(local.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn promise(self: PromiseResolver) js.Promise {
|
pub fn promise(self: PromiseResolver) js.Promise {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = v8.v8__Promise__Resolver__GetPromise(self.handle).?,
|
.handle = v8.v8__Promise__Resolver__GetPromise(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -46,15 +46,15 @@ pub fn resolve(self: PromiseResolver, comptime source: []const u8, value: anytyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn _resolve(self: PromiseResolver, value: anytype) !void {
|
fn _resolve(self: PromiseResolver, value: anytype) !void {
|
||||||
const ctx: *js.Context = @constCast(self.ctx);
|
const local = self.local;
|
||||||
const js_value = try ctx.zigValueToJs(value, .{});
|
const js_val = try local.zigValueToJs(value, .{});
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Promise__Resolver__Resolve(self.handle, self.ctx.handle, js_value.handle, &out);
|
v8.v8__Promise__Resolver__Resolve(self.handle, self.local.handle, js_val.handle, &out);
|
||||||
if (!out.has_value or !out.value) {
|
if (!out.has_value or !out.value) {
|
||||||
return error.FailedToResolvePromise;
|
return error.FailedToResolvePromise;
|
||||||
}
|
}
|
||||||
ctx.runMicrotasks();
|
local.runMicrotasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void {
|
pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void {
|
||||||
@@ -64,44 +64,36 @@ pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn _reject(self: PromiseResolver, value: anytype) !void {
|
fn _reject(self: PromiseResolver, value: anytype) !void {
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
const js_value = try ctx.zigValueToJs(value, .{});
|
const js_val = try local.zigValueToJs(value, .{});
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Promise__Resolver__Reject(self.handle, ctx.handle, js_value.handle, &out);
|
v8.v8__Promise__Resolver__Reject(self.handle, local.handle, js_val.handle, &out);
|
||||||
if (!out.has_value or !out.value) {
|
if (!out.has_value or !out.value) {
|
||||||
return error.FailedToRejectPromise;
|
return error.FailedToRejectPromise;
|
||||||
}
|
}
|
||||||
ctx.runMicrotasks();
|
local.runMicrotasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: PromiseResolver) !Global {
|
pub fn persist(self: PromiseResolver) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
try ctx.global_promise_resolvers.append(ctx.arena, global);
|
try ctx.global_promise_resolvers.append(ctx.arena, global);
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
v8.v8__Global__Reset(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(self: *const Global) PromiseResolver {
|
pub fn local(self: *const Global, l: *const js.Local) PromiseResolver {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isEqual(self: *const Global, other: PromiseResolver) bool {
|
|
||||||
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const bridge = @import("bridge.zig");
|
|||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
const Window = @import("../webapi/Window.zig");
|
|
||||||
|
|
||||||
const v8 = js.v8;
|
const v8 = js.v8;
|
||||||
const JsApis = bridge.JsApis;
|
const JsApis = bridge.JsApis;
|
||||||
@@ -114,20 +113,6 @@ fn isValid(self: Snapshot) bool {
|
|||||||
return v8.v8__StartupData__IsValid(self.startup_data);
|
return v8.v8__StartupData__IsValid(self.startup_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createGlobalTemplate(isolate: *v8.Isolate, templates: anytype) *const v8.ObjectTemplate {
|
|
||||||
// Set up the global template to inherit from Window's template
|
|
||||||
// This way the global object gets all Window properties through inheritance
|
|
||||||
const js_global = v8.v8__FunctionTemplate__New__DEFAULT(isolate);
|
|
||||||
const window_name = v8.v8__String__NewFromUtf8(isolate, "Window", v8.kNormal, 6);
|
|
||||||
v8.v8__FunctionTemplate__SetClassName(js_global, window_name);
|
|
||||||
|
|
||||||
// Find Window in JsApis by name (avoids circular import)
|
|
||||||
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
|
|
||||||
v8.v8__FunctionTemplate__Inherit(js_global, templates[window_index]);
|
|
||||||
|
|
||||||
return v8.v8__FunctionTemplate__InstanceTemplate(js_global).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create() !Snapshot {
|
pub fn create() !Snapshot {
|
||||||
var external_references = collectExternalReferences();
|
var external_references = collectExternalReferences();
|
||||||
|
|
||||||
@@ -169,8 +154,7 @@ pub fn create() !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 context = v8.v8__Context__New(isolate, null, null);
|
||||||
const context = v8.v8__Context__New(isolate, global_template, null);
|
|
||||||
v8.v8__Context__Enter(context);
|
v8.v8__Context__Enter(context);
|
||||||
defer v8.v8__Context__Exit(context);
|
defer v8.v8__Context__Exit(context);
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const String = @This();
|
const String = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.String,
|
handle: *const v8.String,
|
||||||
|
|
||||||
pub const ToZigOpts = struct {
|
pub const ToZigOpts = struct {
|
||||||
@@ -41,8 +41,8 @@ pub fn toZigZ(self: String, opts: ToZigOpts) ![:0]u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
||||||
const isolate = self.ctx.isolate.handle;
|
const isolate = self.local.isolate.handle;
|
||||||
const allocator = opts.allocator orelse self.ctx.call_arena;
|
const allocator = opts.allocator orelse self.local.ctx.call_arena;
|
||||||
const len: u32 = @intCast(v8.v8__String__Utf8Length(self.handle, isolate));
|
const len: u32 = @intCast(v8.v8__String__Utf8Length(self.handle, isolate));
|
||||||
const buf = if (null_terminate) try allocator.allocSentinel(u8, len, 0) else try allocator.alloc(u8, len);
|
const buf = if (null_terminate) try allocator.allocSentinel(u8, len, 0) else try allocator.alloc(u8, len);
|
||||||
|
|
||||||
|
|||||||
156
src/browser/js/TaggedOpaque.zig
Normal file
156
src/browser/js/TaggedOpaque.zig
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
const bridge = js.bridge;
|
||||||
|
|
||||||
|
// When we return a Zig object to V8, we put it on the heap and pass it into
|
||||||
|
// v8 as an *anyopaque (i.e. void *). When V8 gives us back the value, say, as a
|
||||||
|
// function parameter, we know what type it _should_ be.
|
||||||
|
//
|
||||||
|
// In a simple/perfect world, we could use this knowledge to cast the *anyopaque
|
||||||
|
// to the parameter type:
|
||||||
|
// const arg: @typeInfo(@TypeOf(function)).@"fn".params[0] = @ptrCast(v8_data);
|
||||||
|
//
|
||||||
|
// But there are 2 reasons we can't do that.
|
||||||
|
//
|
||||||
|
// == Reason 1 ==
|
||||||
|
// The JS code might pass the wrong type:
|
||||||
|
//
|
||||||
|
// var cat = new Cat();
|
||||||
|
// cat.setOwner(new Cat());
|
||||||
|
//
|
||||||
|
// The zig_setOwner method expects the 2nd parameter to be an *Owner, but
|
||||||
|
// the JS code passed a *Cat.
|
||||||
|
//
|
||||||
|
// To solve this issue, we tag every returned value so that we can check what
|
||||||
|
// type it is. In the above case, we'd expect an *Owner, but the tag would tell
|
||||||
|
// us that we got a *Cat. We use the type index in our Types lookup as the tag.
|
||||||
|
//
|
||||||
|
// == Reason 2 ==
|
||||||
|
// Because of prototype inheritance, even "correct" code can be a challenge. For
|
||||||
|
// example, say the above JavaScript is fixed:
|
||||||
|
//
|
||||||
|
// var cat = new Cat();
|
||||||
|
// cat.setOwner(new Owner("Leto"));
|
||||||
|
//
|
||||||
|
// The issue is that setOwner might not expect an *Owner, but rather a
|
||||||
|
// *Person, which is the prototype for Owner. Now our Zig code is expecting
|
||||||
|
// a *Person, but it was (correctly) given an *Owner.
|
||||||
|
// For this reason, we also store the prototype chain.
|
||||||
|
const TaggedOpaque = @This();
|
||||||
|
|
||||||
|
prototype_len: u16,
|
||||||
|
prototype_chain: [*]const PrototypeChainEntry,
|
||||||
|
|
||||||
|
// Ptr to the Zig instance. Between the context where it's called (i.e.
|
||||||
|
// we have the comptime parameter info for all functions), and the index field
|
||||||
|
// we can figure out what type this is.
|
||||||
|
value: *anyopaque,
|
||||||
|
|
||||||
|
// When we're asked to describe an object via the Inspector, we _must_ include
|
||||||
|
// the proper subtype (and description) fields in the returned JSON.
|
||||||
|
// V8 will give us a Value and ask us for the subtype. From the js.Value we
|
||||||
|
// can get a js.Object, and from the js.Object, we can get out TaggedOpaque
|
||||||
|
// which is where we store the subtype.
|
||||||
|
subtype: ?bridge.SubType,
|
||||||
|
|
||||||
|
pub const PrototypeChainEntry = struct {
|
||||||
|
index: bridge.JsApiLookup.BackingInt,
|
||||||
|
offset: u16, // offset to the _proto field
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reverses the mapZigInstanceToJs, making sure that our TaggedOpaque
|
||||||
|
// contains a ptr to the correct type.
|
||||||
|
pub fn fromJS(comptime R: type, js_obj_handle: *const v8.Object) !R {
|
||||||
|
const ti = @typeInfo(R);
|
||||||
|
if (ti != .pointer) {
|
||||||
|
@compileError("non-pointer Zig parameter type: " ++ @typeName(R));
|
||||||
|
}
|
||||||
|
|
||||||
|
const T = ti.pointer.child;
|
||||||
|
const JsApi = bridge.Struct(T).JsApi;
|
||||||
|
|
||||||
|
if (@hasDecl(JsApi.Meta, "empty_with_no_proto")) {
|
||||||
|
// Empty structs aren't stored as TOAs and there's no data
|
||||||
|
// stored in the JSObject's IntenrnalField. Why bother when
|
||||||
|
// we can just return an empty struct here?
|
||||||
|
return @constCast(@as(*const T, &.{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const internal_field_count = v8.v8__Object__InternalFieldCount(js_obj_handle);
|
||||||
|
// Special case for Window: the global object doesn't have internal fields
|
||||||
|
// Window instance is stored in context.page.window instead
|
||||||
|
if (internal_field_count == 0) {
|
||||||
|
// Normally, this would be an error. All JsObject that map to a Zig type
|
||||||
|
// are either `empty_with_no_proto` (handled above) or have an
|
||||||
|
// interalFieldCount. The only exception to that is the Window...
|
||||||
|
const isolate = v8.v8__Object__GetIsolate(js_obj_handle).?;
|
||||||
|
const context = js.Context.fromIsolate(.{ .handle = isolate });
|
||||||
|
|
||||||
|
const Window = @import("../webapi/Window.zig");
|
||||||
|
if (T == Window) {
|
||||||
|
return context.page.window;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... Or the window's prototype.
|
||||||
|
// We could make this all comptime-fancy, but it's easier to hard-code
|
||||||
|
// the EventTarget
|
||||||
|
|
||||||
|
const EventTarget = @import("../webapi/EventTarget.zig");
|
||||||
|
if (T == EventTarget) {
|
||||||
|
return context.page.window._proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type not found in Window's prototype chain
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it isn't an empty struct, then the v8.Object should have an
|
||||||
|
// InternalFieldCount > 0, since our toa pointer should be embedded
|
||||||
|
// at index 0 of the internal field count.
|
||||||
|
if (internal_field_count == 0) {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bridge.JsApiLookup.has(JsApi)) {
|
||||||
|
@compileError("unknown Zig type: " ++ @typeName(R));
|
||||||
|
}
|
||||||
|
|
||||||
|
const internal_field_handle = v8.v8__Object__GetInternalField(js_obj_handle, 0).?;
|
||||||
|
const tao: *TaggedOpaque = @ptrCast(@alignCast(v8.v8__External__Value(internal_field_handle)));
|
||||||
|
const expected_type_index = bridge.JsApiLookup.getId(JsApi);
|
||||||
|
|
||||||
|
const prototype_chain = tao.prototype_chain[0..tao.prototype_len];
|
||||||
|
if (prototype_chain[0].index == expected_type_index) {
|
||||||
|
return @ptrCast(@alignCast(tao.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, let's walk up the chain
|
||||||
|
var ptr = @intFromPtr(tao.value);
|
||||||
|
for (prototype_chain[1..]) |proto| {
|
||||||
|
ptr += proto.offset; // the offset to the _proto field
|
||||||
|
const proto_ptr: **anyopaque = @ptrFromInt(ptr);
|
||||||
|
if (proto.index == expected_type_index) {
|
||||||
|
return @ptrCast(@alignCast(proto_ptr.*));
|
||||||
|
}
|
||||||
|
ptr = @intFromPtr(proto_ptr.*);
|
||||||
|
}
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
@@ -24,43 +24,34 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const TryCatch = @This();
|
const TryCatch = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
|
||||||
handle: v8.TryCatch,
|
handle: v8.TryCatch,
|
||||||
|
local: *const js.Local,
|
||||||
|
|
||||||
pub fn init(self: *TryCatch, ctx: *js.Context) void {
|
pub fn init(self: *TryCatch, l: *const js.Local) void {
|
||||||
self.ctx = ctx;
|
self.local = l;
|
||||||
v8.v8__TryCatch__CONSTRUCT(&self.handle, ctx.isolate.handle);
|
v8.v8__TryCatch__CONSTRUCT(&self.handle, l.isolate.handle);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hasCaught(self: TryCatch) bool {
|
|
||||||
return v8.v8__TryCatch__HasCaught(&self.handle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn caught(self: TryCatch, allocator: Allocator) ?Caught {
|
pub fn caught(self: TryCatch, allocator: Allocator) ?Caught {
|
||||||
if (!self.hasCaught()) {
|
if (!v8.v8__TryCatch__HasCaught(&self.handle)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = self.ctx;
|
const l = self.local;
|
||||||
|
|
||||||
var hs: js.HandleScope = undefined;
|
|
||||||
hs.init(ctx.isolate);
|
|
||||||
defer hs.deinit();
|
|
||||||
|
|
||||||
const line: ?u32 = blk: {
|
const line: ?u32 = blk: {
|
||||||
const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null;
|
const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null;
|
||||||
const l = v8.v8__Message__GetLineNumber(handle, ctx.handle);
|
const line = v8.v8__Message__GetLineNumber(handle, l.handle);
|
||||||
break :blk if (l < 0) null else @intCast(l);
|
break :blk if (line < 0) null else @intCast(line);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exception: ?[]const u8 = blk: {
|
const exception: ?[]const u8 = blk: {
|
||||||
const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null;
|
const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null;
|
||||||
break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err);
|
break :blk l.valueHandleToString(@ptrCast(handle), .{ .allocator = allocator }) catch |err| @errorName(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
const stack: ?[]const u8 = blk: {
|
const stack: ?[]const u8 = blk: {
|
||||||
const handle = v8.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse break :blk null;
|
const handle = v8.v8__TryCatch__StackTrace(&self.handle, l.handle) orelse break :blk null;
|
||||||
break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err);
|
break :blk l.valueHandleToString(@ptrCast(handle), .{ .allocator = allocator }) catch |err| @errorName(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
@@ -85,10 +76,10 @@ pub fn deinit(self: *TryCatch) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const Caught = struct {
|
pub const Caught = struct {
|
||||||
line: ?u32,
|
line: ?u32 = null,
|
||||||
caught: bool,
|
caught: bool = false,
|
||||||
stack: ?[]const u8,
|
stack: ?[]const u8 = null,
|
||||||
exception: ?[]const u8,
|
exception: ?[]const u8 = null,
|
||||||
|
|
||||||
pub fn format(self: Caught, writer: *std.Io.Writer) !void {
|
pub fn format(self: Caught, writer: *std.Io.Writer) !void {
|
||||||
const separator = @import("../../log.zig").separator();
|
const separator = @import("../../log.zig").separator();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const Value = @This();
|
const Value = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Value,
|
handle: *const v8.Value,
|
||||||
|
|
||||||
pub fn isObject(self: Value) bool {
|
pub fn isObject(self: Value) bool {
|
||||||
@@ -155,12 +155,12 @@ pub fn isPromise(self: Value) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toBool(self: Value) bool {
|
pub fn toBool(self: Value) bool {
|
||||||
return v8.v8__Value__BooleanValue(self.handle, self.ctx.isolate.handle);
|
return v8.v8__Value__BooleanValue(self.handle, self.local.isolate.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typeOf(self: Value) js.String {
|
pub fn typeOf(self: Value) js.String {
|
||||||
const str_handle = v8.v8__Value__TypeOf(self.handle, self.ctx.isolate.handle).?;
|
const str_handle = v8.v8__Value__TypeOf(self.handle, self.local.isolate.handle).?;
|
||||||
return js.String{ .ctx = self.ctx, .handle = str_handle };
|
return js.String{ .local = self.local, .handle = str_handle };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toF32(self: Value) !f32 {
|
pub fn toF32(self: Value) !f32 {
|
||||||
@@ -169,7 +169,7 @@ pub fn toF32(self: Value) !f32 {
|
|||||||
|
|
||||||
pub fn toF64(self: Value) !f64 {
|
pub fn toF64(self: Value) !f64 {
|
||||||
var maybe: v8.MaybeF64 = undefined;
|
var maybe: v8.MaybeF64 = undefined;
|
||||||
v8.v8__Value__NumberValue(self.handle, self.ctx.handle, &maybe);
|
v8.v8__Value__NumberValue(self.handle, self.local.handle, &maybe);
|
||||||
if (!maybe.has_value) {
|
if (!maybe.has_value) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ pub fn toF64(self: Value) !f64 {
|
|||||||
|
|
||||||
pub fn toI32(self: Value) !i32 {
|
pub fn toI32(self: Value) !i32 {
|
||||||
var maybe: v8.MaybeI32 = undefined;
|
var maybe: v8.MaybeI32 = undefined;
|
||||||
v8.v8__Value__Int32Value(self.handle, self.ctx.handle, &maybe);
|
v8.v8__Value__Int32Value(self.handle, self.local.handle, &maybe);
|
||||||
if (!maybe.has_value) {
|
if (!maybe.has_value) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ pub fn toI32(self: Value) !i32 {
|
|||||||
|
|
||||||
pub fn toU32(self: Value) !u32 {
|
pub fn toU32(self: Value) !u32 {
|
||||||
var maybe: v8.MaybeU32 = undefined;
|
var maybe: v8.MaybeU32 = undefined;
|
||||||
v8.v8__Value__Uint32Value(self.handle, self.ctx.handle, &maybe);
|
v8.v8__Value__Uint32Value(self.handle, self.local.handle, &maybe);
|
||||||
if (!maybe.has_value) {
|
if (!maybe.has_value) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ pub fn toPromise(self: Value) js.Promise {
|
|||||||
std.debug.assert(self.isPromise());
|
std.debug.assert(self.isPromise());
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -212,53 +212,52 @@ pub fn toStringZ(self: Value, opts: js.String.ToZigOpts) ![:0]u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toJson(self: Value, allocator: Allocator) ![]u8 {
|
pub fn toJson(self: Value, allocator: Allocator) ![]u8 {
|
||||||
const json_str_handle = v8.v8__JSON__Stringify(self.ctx.handle, self.handle, null) orelse return error.JsException;
|
const json_str_handle = v8.v8__JSON__Stringify(self.local.handle, self.handle, null) orelse return error.JsException;
|
||||||
return self.ctx.jsStringToZig(json_str_handle, .{ .allocator = allocator });
|
return self.local.jsStringToZig(json_str_handle, .{ .allocator = allocator });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
||||||
const ctx = self.ctx;
|
const l = self.local;
|
||||||
|
|
||||||
if (self.isSymbol()) {
|
if (self.isSymbol()) {
|
||||||
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?;
|
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), l.isolate.handle).?;
|
||||||
return _toString(.{ .handle = @ptrCast(sym_handle), .ctx = ctx }, null_terminate, opts);
|
return _toString(.{ .handle = @ptrCast(sym_handle), .local = l }, null_terminate, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
const str_handle = v8.v8__Value__ToString(self.handle, ctx.handle) orelse {
|
const str_handle = v8.v8__Value__ToString(self.handle, l.handle) orelse {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
};
|
};
|
||||||
|
|
||||||
const str = js.String{ .ctx = ctx, .handle = str_handle };
|
const str = js.String{ .local = l, .handle = str_handle };
|
||||||
if (comptime null_terminate) {
|
if (comptime null_terminate) {
|
||||||
return js.String.toZigZ(str, opts);
|
return js.String.toZigZ(str, opts);
|
||||||
}
|
}
|
||||||
return js.String.toZig(str, opts);
|
return js.String.toZig(str, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fromJson(ctx: *js.Context, json: []const u8) !Value {
|
pub fn persist(self: Value) !Global {
|
||||||
const v8_isolate = v8.Isolate{ .handle = ctx.isolate.handle };
|
return self._persist(true);
|
||||||
const json_string = v8.String.initUtf8(v8_isolate, json);
|
|
||||||
const v8_context = v8.Context{ .handle = ctx.handle };
|
|
||||||
const value = try v8.Json.parse(v8_context, json_string);
|
|
||||||
return .{ .ctx = ctx, .handle = value.handle };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: Value) !Global {
|
pub fn temp(self: Value) !Temp {
|
||||||
var ctx = self.ctx;
|
return self._persist(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Global else Temp) {
|
||||||
|
var ctx = self.local.ctx;
|
||||||
|
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
|
if (comptime is_global) {
|
||||||
try ctx.global_values.append(ctx.arena, global);
|
try ctx.global_values.append(ctx.arena, global);
|
||||||
|
} else {
|
||||||
return .{
|
try ctx.global_values_temp.put(ctx.arena, global.data_ptr, global);
|
||||||
.handle = global,
|
}
|
||||||
.ctx = ctx,
|
return .{ .handle = global };
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toZig(self: Value, comptime T: type) !T {
|
pub fn toZig(self: Value, comptime T: type) !T {
|
||||||
return self.ctx.jsValueToZig(T, self);
|
return self.local.jsValueToZig(T, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toObject(self: Value) js.Object {
|
pub fn toObject(self: Value) js.Object {
|
||||||
@@ -267,7 +266,7 @@ pub fn toObject(self: Value) js.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -278,7 +277,7 @@ pub fn toArray(self: Value) js.Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -295,28 +294,37 @@ pub fn toBigInt(self: Value) js.BigInt {
|
|||||||
|
|
||||||
pub fn format(self: Value, writer: *std.Io.Writer) !void {
|
pub fn format(self: Value, writer: *std.Io.Writer) !void {
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
return self.ctx.debugValue(self, writer);
|
return self.local.debugValue(self, writer);
|
||||||
}
|
}
|
||||||
const str = self.toString(.{}) catch return error.WriteFailed;
|
const str = self.toString(.{}) catch return error.WriteFailed;
|
||||||
return writer.writeAll(str);
|
return writer.writeAll(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Temp = G(0);
|
||||||
handle: v8.Global,
|
pub const Global = G(1);
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
fn G(comptime discriminator: u8) type {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
return struct {
|
||||||
}
|
handle: v8.Global,
|
||||||
|
|
||||||
pub fn local(self: *const Global) Value {
|
// makes the types different (G(0) != G(1)), without taking up space
|
||||||
return .{
|
comptime _: u8 = discriminator,
|
||||||
.ctx = self.ctx,
|
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isEqual(self: *const Global, other: Value) bool {
|
const Self = @This();
|
||||||
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
|
||||||
}
|
pub fn deinit(self: *Self) void {
|
||||||
};
|
v8.v8__Global__Reset(&self.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local(self: *const Self, l: *const js.Local) Value {
|
||||||
|
return .{
|
||||||
|
.local = l,
|
||||||
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEqual(self: *const Self, other: Value) bool {
|
||||||
|
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
//
|
//
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
@@ -20,555 +20,15 @@ const std = @import("std");
|
|||||||
const js = @import("js.zig");
|
const js = @import("js.zig");
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const Page = @import("../Page.zig");
|
||||||
|
|
||||||
const v8 = js.v8;
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Caller = @import("Caller.zig");
|
||||||
const Context = @import("Context.zig");
|
const Context = @import("Context.zig");
|
||||||
const Page = @import("../Page.zig");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
||||||
|
|
||||||
const CALL_ARENA_RETAIN = 1024 * 16;
|
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Internal Callback Info Wrappers
|
|
||||||
// ============================================================================
|
|
||||||
// These wrap the raw v8 C API to provide a cleaner interface.
|
|
||||||
// They are not exported - internal to this module only.
|
|
||||||
|
|
||||||
const Value = struct {
|
|
||||||
handle: *const v8.Value,
|
|
||||||
|
|
||||||
fn isArray(self: Value) bool {
|
|
||||||
return v8.v8__Value__IsArray(self.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isTypedArray(self: Value) bool {
|
|
||||||
return v8.v8__Value__IsTypedArray(self.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isFunction(self: Value) bool {
|
|
||||||
return v8.v8__Value__IsFunction(self.handle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Name = struct {
|
|
||||||
handle: *const v8.Name,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FunctionCallbackInfo = struct {
|
|
||||||
handle: *const v8.FunctionCallbackInfo,
|
|
||||||
|
|
||||||
fn length(self: FunctionCallbackInfo) u32 {
|
|
||||||
return @intCast(v8.v8__FunctionCallbackInfo__Length(self.handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getArg(self: FunctionCallbackInfo, index: u32) Value {
|
|
||||||
return .{ .handle = v8.v8__FunctionCallbackInfo__INDEX(self.handle, @intCast(index)).? };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getThis(self: FunctionCallbackInfo) *const v8.Object {
|
|
||||||
return v8.v8__FunctionCallbackInfo__This(self.handle).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getReturnValue(self: FunctionCallbackInfo) ReturnValue {
|
|
||||||
var rv: v8.ReturnValue = undefined;
|
|
||||||
v8.v8__FunctionCallbackInfo__GetReturnValue(self.handle, &rv);
|
|
||||||
return .{ .handle = rv };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isConstructCall(self: FunctionCallbackInfo) bool {
|
|
||||||
return v8.v8__FunctionCallbackInfo__IsConstructCall(self.handle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const PropertyCallbackInfo = struct {
|
|
||||||
handle: *const v8.PropertyCallbackInfo,
|
|
||||||
|
|
||||||
fn getThis(self: PropertyCallbackInfo) *const v8.Object {
|
|
||||||
return v8.v8__PropertyCallbackInfo__This(self.handle).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getReturnValue(self: PropertyCallbackInfo) ReturnValue {
|
|
||||||
var rv: v8.ReturnValue = undefined;
|
|
||||||
v8.v8__PropertyCallbackInfo__GetReturnValue(self.handle, &rv);
|
|
||||||
return .{ .handle = rv };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ReturnValue = struct {
|
|
||||||
handle: v8.ReturnValue,
|
|
||||||
|
|
||||||
fn set(self: ReturnValue, value: anytype) void {
|
|
||||||
const T = @TypeOf(value);
|
|
||||||
if (T == Value) {
|
|
||||||
self.setValueHandle(value.handle);
|
|
||||||
} else if (T == *const v8.Object) {
|
|
||||||
self.setValueHandle(@ptrCast(value));
|
|
||||||
} else if (T == *const v8.Value) {
|
|
||||||
self.setValueHandle(value);
|
|
||||||
} else if (T == js.Value) {
|
|
||||||
self.setValueHandle(value.handle);
|
|
||||||
} else {
|
|
||||||
@compileError("Unsupported type for ReturnValue.set: " ++ @typeName(T));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setValueHandle(self: ReturnValue, handle: *const v8.Value) void {
|
|
||||||
v8.v8__ReturnValue__Set(self.handle, handle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Caller - Responsible for calling Zig functions from JS invocations
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
pub const Caller = struct {
|
|
||||||
context: *Context,
|
|
||||||
isolate: js.Isolate,
|
|
||||||
call_arena: Allocator,
|
|
||||||
|
|
||||||
// Takes the raw v8 isolate and extracts the context from it.
|
|
||||||
pub fn init(v8_isolate: *v8.Isolate) Caller {
|
|
||||||
const isolate = js.Isolate{ .handle = v8_isolate };
|
|
||||||
const v8_context_handle = v8.v8__Isolate__GetCurrentContext(v8_isolate);
|
|
||||||
const embedder_data = v8.v8__Context__GetEmbedderData(v8_context_handle, 1);
|
|
||||||
var lossless: bool = undefined;
|
|
||||||
const context: *Context = @ptrFromInt(v8.v8__BigInt__Uint64Value(embedder_data, &lossless));
|
|
||||||
|
|
||||||
context.call_depth += 1;
|
|
||||||
return .{
|
|
||||||
.context = context,
|
|
||||||
.isolate = isolate,
|
|
||||||
.call_arena = context.call_arena,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Caller) void {
|
|
||||||
const context = self.context;
|
|
||||||
const call_depth = context.call_depth - 1;
|
|
||||||
|
|
||||||
// Because of callbacks, calls can be nested. Because of this, we
|
|
||||||
// can't clear the call_arena after _every_ call. Imagine we have
|
|
||||||
// arr.forEach((i) => { console.log(i); }
|
|
||||||
//
|
|
||||||
// First we call forEach. Inside of our forEach call,
|
|
||||||
// we call console.log. If we reset the call_arena after this call,
|
|
||||||
// it'll reset it for the `forEach` call after, which might still
|
|
||||||
// need the data.
|
|
||||||
//
|
|
||||||
// Therefore, we keep a call_depth, and only reset the call_arena
|
|
||||||
// when a top-level (call_depth == 0) function ends.
|
|
||||||
if (call_depth == 0) {
|
|
||||||
const arena: *ArenaAllocator = @ptrCast(@alignCast(context.call_arena.ptr));
|
|
||||||
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
|
|
||||||
}
|
|
||||||
|
|
||||||
context.call_depth = call_depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const CallOpts = struct {
|
|
||||||
dom_exception: bool = false,
|
|
||||||
null_as_undefined: bool = false,
|
|
||||||
as_typed_array: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn constructor(self: *Caller, comptime T: type, func: anytype, info: 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.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _constructor(self: *Caller, func: anytype, info: FunctionCallbackInfo) !void {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
const args = try self.getArgs(F, 0, info);
|
|
||||||
const res = @call(.auto, func, args);
|
|
||||||
|
|
||||||
const ReturnType = @typeInfo(F).@"fn".return_type orelse {
|
|
||||||
@compileError(@typeName(F) ++ " has a constructor without a return type");
|
|
||||||
};
|
|
||||||
|
|
||||||
const new_this_handle = info.getThis();
|
|
||||||
var this = js.Object{ .ctx = self.context, .handle = new_this_handle };
|
|
||||||
if (@typeInfo(ReturnType) == .error_union) {
|
|
||||||
const non_error_res = res catch |err| return err;
|
|
||||||
this = try self.context.mapZigInstanceToJs(new_this_handle, non_error_res);
|
|
||||||
} else {
|
|
||||||
this = try self.context.mapZigInstanceToJs(new_this_handle, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got back a different object (existing wrapper), copy the prototype
|
|
||||||
// from new object. (this happens when we're upgrading an CustomElement)
|
|
||||||
if (this.handle != new_this_handle) {
|
|
||||||
const prototype_handle = v8.v8__Object__GetPrototype(new_this_handle).?;
|
|
||||||
var out: v8.MaybeBool = undefined;
|
|
||||||
v8.v8__Object__SetPrototype(this.handle, self.context.handle, prototype_handle, &out);
|
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
std.debug.assert(out.has_value and out.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info.getReturnValue().set(this.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) void {
|
|
||||||
self._method(T, func, info, opts) catch |err| {
|
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
var handle_scope: js.HandleScope = undefined;
|
|
||||||
handle_scope.init(self.isolate);
|
|
||||||
defer handle_scope.deinit();
|
|
||||||
|
|
||||||
var args = try self.getArgs(F, 1, info);
|
|
||||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
|
||||||
const res = @call(.auto, func, args);
|
|
||||||
info.getReturnValue().set(try self.context.zigValueToJs(res, opts));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn function(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) void {
|
|
||||||
self._function(func, info, opts) catch |err| {
|
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _function(self: *Caller, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
const context = self.context;
|
|
||||||
const args = try self.getArgs(F, 0, info);
|
|
||||||
const res = @call(.auto, func, args);
|
|
||||||
info.getReturnValue().set(try context.zigValueToJs(res, opts));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
|
||||||
return self._getIndex(T, func, idx, info, opts) catch |err| {
|
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
// not intercepted
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
var args = try self.getArgs(F, 2, info);
|
|
||||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
|
||||||
@field(args, "1") = idx;
|
|
||||||
const ret = @call(.auto, func, args);
|
|
||||||
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
|
||||||
return self._getNamedIndex(T, func, name, info, opts) catch |err| {
|
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
// not intercepted
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
var args = try self.getArgs(F, 2, info);
|
|
||||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
|
||||||
@field(args, "1") = try self.nameToString(name);
|
|
||||||
const ret = @call(.auto, func, args);
|
|
||||||
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, js_value: Value, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
|
||||||
return self._setNamedIndex(T, func, name, js_value, info, opts) catch |err| {
|
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
// not intercepted
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, js_value: Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
var args: ParameterTypes(F) = undefined;
|
|
||||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
|
||||||
@field(args, "1") = try self.nameToString(name);
|
|
||||||
@field(args, "2") = try self.context.jsValueToZig(@TypeOf(@field(args, "2")), js.Value{ .ctx = self.context, .handle = js_value.handle });
|
|
||||||
if (@typeInfo(F).@"fn".params.len == 4) {
|
|
||||||
@field(args, "3") = self.context.page;
|
|
||||||
}
|
|
||||||
const ret = @call(.auto, func, args);
|
|
||||||
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
|
||||||
return self._deleteNamedIndex(T, func, name, info, opts) catch |err| {
|
|
||||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
|
||||||
const F = @TypeOf(func);
|
|
||||||
var args: ParameterTypes(F) = undefined;
|
|
||||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
|
||||||
@field(args, "1") = try self.nameToString(name);
|
|
||||||
if (@typeInfo(F).@"fn".params.len == 3) {
|
|
||||||
@field(args, "2") = self.context.page;
|
|
||||||
}
|
|
||||||
const ret = @call(.auto, func, args);
|
|
||||||
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, comptime getter: bool, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
|
||||||
// need to unwrap this error immediately for when opts.null_as_undefined == true
|
|
||||||
// and we need to compare it to null;
|
|
||||||
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
|
|
||||||
.error_union => |eu| blk: {
|
|
||||||
break :blk ret catch |err| {
|
|
||||||
// We can't compare err == error.NotHandled if error.NotHandled
|
|
||||||
// isn't part of the possible error set. So we first need to check
|
|
||||||
// if error.NotHandled is part of the error set.
|
|
||||||
if (isInErrorSet(error.NotHandled, eu.error_set)) {
|
|
||||||
if (err == error.NotHandled) {
|
|
||||||
// not intercepted
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.handleError(T, F, err, info, opts);
|
|
||||||
// not intercepted
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
else => ret,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (comptime getter) {
|
|
||||||
info.getReturnValue().set(try self.context.zigValueToJs(non_error_ret, opts));
|
|
||||||
}
|
|
||||||
// intercepted
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isInErrorSet(err: anyerror, comptime T: type) bool {
|
|
||||||
inline for (@typeInfo(T).error_set.?) |e| {
|
|
||||||
if (err == @field(anyerror, e.name)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nameToString(self: *Caller, name: Name) ![]const u8 {
|
|
||||||
return self.context.valueToString(js.Value{ .ctx = self.context, .handle = @ptrCast(name.handle) }, .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
|
||||||
const isolate = self.isolate;
|
|
||||||
|
|
||||||
if (comptime @import("builtin").mode == .Debug and @TypeOf(info) == FunctionCallbackInfo) {
|
|
||||||
if (log.enabled(.js, .warn)) {
|
|
||||||
self.logFunctionCallError(@typeName(T), @typeName(F), err, info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const js_err: *const v8.Value = switch (err) {
|
|
||||||
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
|
||||||
error.OutOfMemory => isolate.createError("out of memory"),
|
|
||||||
error.IllegalConstructor => isolate.createError("Illegal Contructor"),
|
|
||||||
else => blk: {
|
|
||||||
if (comptime opts.dom_exception) {
|
|
||||||
const DOMException = @import("../webapi/DOMException.zig");
|
|
||||||
if (DOMException.fromError(err)) |ex| {
|
|
||||||
const value = self.context.zigValueToJs(ex, .{}) catch break :blk isolate.createError("internal error");
|
|
||||||
break :blk value.handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break :blk isolate.createError(@errorName(err));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const js_exception = isolate.throwException(js_err);
|
|
||||||
info.getReturnValue().setValueHandle(js_exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we call a method in javascript: cat.lives('nine');
|
|
||||||
//
|
|
||||||
// Then we'd expect a Zig function with 2 parameters: a self and the string.
|
|
||||||
// In this case, offset == 1. Offset is always 1 for setters or methods.
|
|
||||||
//
|
|
||||||
// Offset is always 0 for constructors.
|
|
||||||
//
|
|
||||||
// For constructors, setters and methods, we can further increase offset + 1
|
|
||||||
// if the first parameter is an instance of Page.
|
|
||||||
//
|
|
||||||
// Finally, if the JS function is called with _more_ parameters and
|
|
||||||
// the last parameter in Zig is an array, we'll try to slurp the additional
|
|
||||||
// parameters into the array.
|
|
||||||
fn getArgs(self: *const Caller, comptime F: type, comptime offset: usize, info: anytype) !ParameterTypes(F) {
|
|
||||||
const context = self.context;
|
|
||||||
var args: ParameterTypes(F) = undefined;
|
|
||||||
|
|
||||||
const params = @typeInfo(F).@"fn".params[offset..];
|
|
||||||
// Except for the constructor, the first parameter is always `self`
|
|
||||||
// This isn't something we'll bind from JS, so skip it.
|
|
||||||
const params_to_map = blk: {
|
|
||||||
if (params.len == 0) {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last parameter is the Page, set it, and exclude it
|
|
||||||
// from our params slice, because we don't want to bind it to
|
|
||||||
// a JS argument
|
|
||||||
if (comptime isPage(params[params.len - 1].type.?)) {
|
|
||||||
@field(args, tupleFieldName(params.len - 1 + offset)) = self.context.page;
|
|
||||||
break :blk params[0 .. params.len - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have neither a Page nor a JsObject. All params must be
|
|
||||||
// bound to a JavaScript value.
|
|
||||||
break :blk params;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (params_to_map.len == 0) {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
const js_parameter_count = info.length();
|
|
||||||
const last_js_parameter = params_to_map.len - 1;
|
|
||||||
var is_variadic = false;
|
|
||||||
|
|
||||||
{
|
|
||||||
// This is going to get complicated. If the last Zig parameter
|
|
||||||
// is a slice AND the corresponding javascript parameter is
|
|
||||||
// NOT an an array, then we'll treat it as a variadic.
|
|
||||||
|
|
||||||
const last_parameter_type = params_to_map[params_to_map.len - 1].type.?;
|
|
||||||
const last_parameter_type_info = @typeInfo(last_parameter_type);
|
|
||||||
if (last_parameter_type_info == .pointer and last_parameter_type_info.pointer.size == .slice) {
|
|
||||||
const slice_type = last_parameter_type_info.pointer.child;
|
|
||||||
const corresponding_js_value = info.getArg(@as(u32, @intCast(last_js_parameter)));
|
|
||||||
if (corresponding_js_value.isArray() == false and corresponding_js_value.isTypedArray() == false and slice_type != u8) {
|
|
||||||
is_variadic = true;
|
|
||||||
if (js_parameter_count == 0) {
|
|
||||||
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
|
||||||
} else if (js_parameter_count >= params_to_map.len) {
|
|
||||||
const arr = try self.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1);
|
|
||||||
for (arr, last_js_parameter..) |*a, i| {
|
|
||||||
const js_value = info.getArg(@as(u32, @intCast(i)));
|
|
||||||
a.* = try context.jsValueToZig(slice_type, js.Value{ .ctx = context, .handle = js_value.handle });
|
|
||||||
}
|
|
||||||
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = arr;
|
|
||||||
} else {
|
|
||||||
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline for (params_to_map, 0..) |param, i| {
|
|
||||||
const field_index = comptime i + offset;
|
|
||||||
if (comptime i == params_to_map.len - 1) {
|
|
||||||
if (is_variadic) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime isPage(param.type.?)) {
|
|
||||||
@compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F));
|
|
||||||
} else if (i >= js_parameter_count) {
|
|
||||||
if (@typeInfo(param.type.?) != .optional) {
|
|
||||||
return error.InvalidArgument;
|
|
||||||
}
|
|
||||||
@field(args, tupleFieldName(field_index)) = null;
|
|
||||||
} else {
|
|
||||||
const js_value = info.getArg(@as(u32, @intCast(i)));
|
|
||||||
@field(args, tupleFieldName(field_index)) = context.jsValueToZig(param.type.?, js.Value{ .ctx = context, .handle = js_value.handle }) catch {
|
|
||||||
return error.InvalidArgument;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is extracted to speed up compilation. When left inlined in handleError,
|
|
||||||
// this can add as much as 10 seconds of compilation time.
|
|
||||||
fn logFunctionCallError(self: *Caller, type_name: []const u8, func: []const u8, err: anyerror, info: FunctionCallbackInfo) void {
|
|
||||||
const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
|
|
||||||
log.info(.js, "function call error", .{
|
|
||||||
.type = type_name,
|
|
||||||
.func = func,
|
|
||||||
.err = err,
|
|
||||||
.args = args_dump,
|
|
||||||
.stack = self.context.stackTrace() catch |err1| @errorName(err1),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serializeFunctionArgs(self: *Caller, info: FunctionCallbackInfo) ![]const u8 {
|
|
||||||
const context = self.context;
|
|
||||||
var buf = std.Io.Writer.Allocating.init(context.call_arena);
|
|
||||||
|
|
||||||
const separator = log.separator();
|
|
||||||
for (0..info.length()) |i| {
|
|
||||||
try buf.writer.print("{s}{d} - ", .{ separator, i + 1 });
|
|
||||||
const val = info.getArg(@intCast(i));
|
|
||||||
try context.debugValue(js.Value{ .ctx = context, .handle = val.handle }, &buf.writer);
|
|
||||||
}
|
|
||||||
return buf.written();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes a function, and returns a tuple for its argument. Used when we
|
|
||||||
// @call a function
|
|
||||||
fn ParameterTypes(comptime F: type) type {
|
|
||||||
const params = @typeInfo(F).@"fn".params;
|
|
||||||
var fields: [params.len]std.builtin.Type.StructField = undefined;
|
|
||||||
|
|
||||||
inline for (params, 0..) |param, i| {
|
|
||||||
fields[i] = .{
|
|
||||||
.name = tupleFieldName(i),
|
|
||||||
.type = param.type.?,
|
|
||||||
.default_value_ptr = null,
|
|
||||||
.is_comptime = false,
|
|
||||||
.alignment = @alignOf(param.type.?),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return @Type(.{ .@"struct" = .{
|
|
||||||
.layout = .auto,
|
|
||||||
.decls = &.{},
|
|
||||||
.fields = &fields,
|
|
||||||
.is_tuple = true,
|
|
||||||
} });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tupleFieldName(comptime i: usize) [:0]const u8 {
|
|
||||||
return switch (i) {
|
|
||||||
0 => "0",
|
|
||||||
1 => "1",
|
|
||||||
2 => "2",
|
|
||||||
3 => "3",
|
|
||||||
4 => "4",
|
|
||||||
5 => "5",
|
|
||||||
6 => "6",
|
|
||||||
7 => "7",
|
|
||||||
8 => "8",
|
|
||||||
9 => "9",
|
|
||||||
else => std.fmt.comptimePrint("{d}", .{i}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isPage(comptime T: type) bool {
|
|
||||||
return T == *Page or T == *const Page;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Bridge Builder Functions
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
pub fn Builder(comptime T: type) type {
|
pub fn Builder(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub const @"type" = T;
|
pub const @"type" = T;
|
||||||
@@ -610,8 +70,9 @@ pub fn Builder(comptime T: type) type {
|
|||||||
@compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet");
|
@compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prototypeChain() [prototypeChainLength(T)]js.PrototypeChainEntry {
|
const PrototypeChainEntry = @import("TaggedOpaque.zig").PrototypeChainEntry;
|
||||||
var entries: [prototypeChainLength(T)]js.PrototypeChainEntry = undefined;
|
pub fn prototypeChain() [prototypeChainLength(T)]PrototypeChainEntry {
|
||||||
|
var entries: [prototypeChainLength(T)]PrototypeChainEntry = undefined;
|
||||||
|
|
||||||
entries[0] = .{ .offset = 0, .index = JsApiLookup.getId(T.JsApi) };
|
entries[0] = .{ .offset = 0, .index = JsApiLookup.getId(T.JsApi) };
|
||||||
|
|
||||||
@@ -630,6 +91,36 @@ pub fn Builder(comptime T: type) type {
|
|||||||
}
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn finalizer(comptime func: *const fn (self: *T, shutdown: bool) void) Finalizer {
|
||||||
|
return .{
|
||||||
|
.from_zig = struct {
|
||||||
|
fn wrap(ptr: *anyopaque) void {
|
||||||
|
func(@ptrCast(@alignCast(ptr)), true);
|
||||||
|
}
|
||||||
|
}.wrap,
|
||||||
|
|
||||||
|
.from_v8 = struct {
|
||||||
|
fn wrap(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
|
||||||
|
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
|
||||||
|
const self: *T = @ptrCast(@alignCast(ptr));
|
||||||
|
// This is simply a requirement of any type that Finalizes:
|
||||||
|
// It must have a _page: *Page field. We need it because
|
||||||
|
// we need to check the item has already been cleared
|
||||||
|
// (There are all types of weird timing issues that seem
|
||||||
|
// to be possible between finalization and context shutdown,
|
||||||
|
// we need to be defensive).
|
||||||
|
// There _ARE_ alternatives to this. But this is simple.
|
||||||
|
const ctx = self._page.js;
|
||||||
|
if (!ctx.identity_map.contains(@intFromPtr(ptr))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
func(self, false);
|
||||||
|
ctx.release(ptr);
|
||||||
|
}
|
||||||
|
}.wrap,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,11 +135,11 @@ pub const Constructor = struct {
|
|||||||
return .{ .func = struct {
|
return .{ .func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
caller.constructor(T, func, handle.?, .{
|
||||||
caller.constructor(T, func, info, .{
|
|
||||||
.dom_exception = opts.dom_exception,
|
.dom_exception = opts.dom_exception,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -673,18 +164,18 @@ pub const Function = struct {
|
|||||||
.func = struct {
|
.func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
|
||||||
if (comptime opts.static) {
|
if (comptime opts.static) {
|
||||||
caller.function(T, func, info, .{
|
caller.function(T, func, handle.?, .{
|
||||||
.dom_exception = opts.dom_exception,
|
.dom_exception = opts.dom_exception,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
caller.method(T, func, info, .{
|
caller.method(T, func, handle.?, .{
|
||||||
.dom_exception = opts.dom_exception,
|
.dom_exception = opts.dom_exception,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
@@ -703,7 +194,7 @@ pub const Accessor = struct {
|
|||||||
|
|
||||||
const Opts = struct {
|
const Opts = struct {
|
||||||
static: bool = false,
|
static: bool = false,
|
||||||
cache: ?[]const u8 = null, // @ZIGDOM
|
cache: ?[]const u8 = null,
|
||||||
as_typed_array: bool = false,
|
as_typed_array: bool = false,
|
||||||
null_as_undefined: bool = false,
|
null_as_undefined: bool = false,
|
||||||
};
|
};
|
||||||
@@ -717,17 +208,19 @@ pub const Accessor = struct {
|
|||||||
accessor.getter = struct {
|
accessor.getter = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
|
||||||
if (comptime opts.static) {
|
if (comptime opts.static) {
|
||||||
caller.function(T, getter, info, .{
|
caller.function(T, getter, handle.?, .{
|
||||||
|
.cache = opts.cache,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
caller.method(T, getter, info, .{
|
caller.method(T, getter, handle.?, .{
|
||||||
|
.cache = opts.cache,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -740,15 +233,11 @@ pub const Accessor = struct {
|
|||||||
accessor.setter = struct {
|
accessor.setter = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
caller.method(T, setter, handle.?, .{
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
lp.assert(info.length() == 1, "bridge.setter", .{ .len = info.length() });
|
|
||||||
}
|
|
||||||
|
|
||||||
caller.method(T, setter, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -772,11 +261,11 @@ pub const Indexed = struct {
|
|||||||
return .{ .getter = struct {
|
return .{ .getter = struct {
|
||||||
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.getIndex(T, getter, idx, handle.?, .{
|
||||||
return caller.getIndex(T, getter, idx, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -799,11 +288,11 @@ pub const NamedIndexed = struct {
|
|||||||
const getter_fn = struct {
|
const getter_fn = struct {
|
||||||
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.getNamedIndex(T, getter, c_name.?, handle.?, .{
|
||||||
return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -813,11 +302,11 @@ pub const NamedIndexed = struct {
|
|||||||
const setter_fn = if (@typeInfo(@TypeOf(setter)) == .null) null else struct {
|
const setter_fn = if (@typeInfo(@TypeOf(setter)) == .null) null else struct {
|
||||||
fn wrap(c_name: ?*const v8.Name, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
fn wrap(c_name: ?*const v8.Name, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.setNamedIndex(T, setter, c_name.?, c_value.?, handle.?, .{
|
||||||
return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -827,11 +316,11 @@ pub const NamedIndexed = struct {
|
|||||||
const deleter_fn = if (@typeInfo(@TypeOf(deleter)) == .null) null else struct {
|
const deleter_fn = if (@typeInfo(@TypeOf(deleter)) == .null) null else struct {
|
||||||
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.deleteNamedIndex(T, deleter, c_name.?, handle.?, .{
|
||||||
return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -861,7 +350,7 @@ pub const Iterator = struct {
|
|||||||
.async = opts.async,
|
.async = opts.async,
|
||||||
.func = struct {
|
.func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
const info = Caller.FunctionCallbackInfo{ .handle = handle.? };
|
||||||
info.getReturnValue().set(info.getThis());
|
info.getReturnValue().set(info.getThis());
|
||||||
}
|
}
|
||||||
}.wrap,
|
}.wrap,
|
||||||
@@ -873,11 +362,10 @@ pub const Iterator = struct {
|
|||||||
.func = struct {
|
.func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
caller.method(T, struct_or_func, handle.?, .{});
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
|
||||||
caller.method(T, struct_or_func, info, .{});
|
|
||||||
}
|
}
|
||||||
}.wrap,
|
}.wrap,
|
||||||
};
|
};
|
||||||
@@ -895,11 +383,11 @@ pub const Callable = struct {
|
|||||||
return .{ .func = struct {
|
return .{ .func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||||
var caller = Caller.init(v8_isolate);
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
caller.method(T, func, handle.?, .{
|
||||||
caller.method(T, func, info, .{
|
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -911,53 +399,69 @@ pub const Property = union(enum) {
|
|||||||
int: i64,
|
int: i64,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn unknownPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
const Finalizer = struct {
|
||||||
const isolate_handle = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
// The finalizer wrapper when called fro Zig. This is only called on
|
||||||
const context = Context.fromIsolate(.{ .handle = isolate_handle });
|
// Context.deinit
|
||||||
|
from_zig: *const fn (ctx: *anyopaque) void,
|
||||||
|
|
||||||
const property: []const u8 = context.valueToString(.{ .ctx = context, .handle = c_name.? }, .{}) catch {
|
// The finalizer wrapper when called from V8. This may never be called
|
||||||
|
// (hence why we fallback to calling in Context.denit). If it is called,
|
||||||
|
// it is only ever called after we SetWeak on the Global.
|
||||||
|
from_v8: *const fn (?*const v8.WeakCallbackInfo) callconv(.c) void,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn unknownPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||||
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const local = &caller.local;
|
||||||
|
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const property: []const u8 = local.valueHandleToString(@ptrCast(c_name.?), .{}) catch {
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ignored = std.StaticStringMap(void).initComptime(.{
|
const page = local.ctx.page;
|
||||||
.{ "process", {} },
|
const document = page.document;
|
||||||
.{ "ShadyDOM", {} },
|
|
||||||
.{ "ShadyCSS", {} },
|
|
||||||
|
|
||||||
.{ "litNonce", {} },
|
if (document.getElementById(property, page)) |el| {
|
||||||
.{ "litHtmlVersions", {} },
|
const js_val = local.zigValueToJs(el, .{}) catch return 0;
|
||||||
.{ "litElementVersions", {} },
|
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
|
||||||
.{ "litHtmlPolyfillSupport", {} },
|
pc.getReturnValue().set(js_val);
|
||||||
.{ "litElementHydrateSupport", {} },
|
return 1;
|
||||||
.{ "litElementPolyfillSupport", {} },
|
}
|
||||||
.{ "reactiveElementVersions", {} },
|
|
||||||
|
|
||||||
.{ "recaptcha", {} },
|
if (comptime IS_DEBUG) {
|
||||||
.{ "grecaptcha", {} },
|
const ignored = std.StaticStringMap(void).initComptime(.{
|
||||||
.{ "___grecaptcha_cfg", {} },
|
.{ "process", {} },
|
||||||
.{ "__recaptcha_api", {} },
|
.{ "ShadyDOM", {} },
|
||||||
.{ "__google_recaptcha_client", {} },
|
.{ "ShadyCSS", {} },
|
||||||
|
|
||||||
.{ "CLOSURE_FLAGS", {} },
|
.{ "litNonce", {} },
|
||||||
});
|
.{ "litHtmlVersions", {} },
|
||||||
|
.{ "litElementVersions", {} },
|
||||||
|
.{ "litHtmlPolyfillSupport", {} },
|
||||||
|
.{ "litElementHydrateSupport", {} },
|
||||||
|
.{ "litElementPolyfillSupport", {} },
|
||||||
|
.{ "reactiveElementVersions", {} },
|
||||||
|
|
||||||
if (!ignored.has(property)) {
|
.{ "recaptcha", {} },
|
||||||
const page = context.page;
|
.{ "grecaptcha", {} },
|
||||||
const document = page.document;
|
.{ "___grecaptcha_cfg", {} },
|
||||||
|
.{ "__recaptcha_api", {} },
|
||||||
|
.{ "__google_recaptcha_client", {} },
|
||||||
|
|
||||||
if (document.getElementById(property, page)) |el| {
|
.{ "CLOSURE_FLAGS", {} },
|
||||||
const js_value = context.zigValueToJs(el, .{}) catch {
|
});
|
||||||
return 0;
|
if (!ignored.has(property)) {
|
||||||
};
|
|
||||||
var pc = PropertyCallbackInfo{ .handle = handle.? };
|
|
||||||
pc.getReturnValue().set(js_value);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
log.debug(.unknown_prop, "unknown global property", .{
|
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 = local.stackTrace() catch "???",
|
||||||
.property = property,
|
.property = property,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1231,6 +735,7 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/event/PopStateEvent.zig"),
|
@import("../webapi/event/PopStateEvent.zig"),
|
||||||
@import("../webapi/event/UIEvent.zig"),
|
@import("../webapi/event/UIEvent.zig"),
|
||||||
@import("../webapi/event/MouseEvent.zig"),
|
@import("../webapi/event/MouseEvent.zig"),
|
||||||
|
@import("../webapi/event/PointerEvent.zig"),
|
||||||
@import("../webapi/event/KeyboardEvent.zig"),
|
@import("../webapi/event/KeyboardEvent.zig"),
|
||||||
@import("../webapi/MessageChannel.zig"),
|
@import("../webapi/MessageChannel.zig"),
|
||||||
@import("../webapi/MessagePort.zig"),
|
@import("../webapi/MessagePort.zig"),
|
||||||
@@ -1272,4 +777,5 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/canvas/CanvasRenderingContext2D.zig"),
|
@import("../webapi/canvas/CanvasRenderingContext2D.zig"),
|
||||||
@import("../webapi/canvas/WebGLRenderingContext.zig"),
|
@import("../webapi/canvas/WebGLRenderingContext.zig"),
|
||||||
@import("../webapi/SubtleCrypto.zig"),
|
@import("../webapi/SubtleCrypto.zig"),
|
||||||
|
@import("../webapi/Selection.zig"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,48 +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 js = @import("js.zig");
|
|
||||||
|
|
||||||
const v8 = js.v8;
|
|
||||||
|
|
||||||
pub fn Global(comptime T: type) type {
|
|
||||||
const H = @FieldType(T, "handle");
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
global: v8.Global,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(isolate: *v8.Isolate, handle: H) Self {
|
|
||||||
var global: v8.Global = undefined;
|
|
||||||
v8.v8__Global__New(isolate, handle, &global);
|
|
||||||
return .{
|
|
||||||
.global = global,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
v8.v8__Global__Reset(&self.global);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn local(self: *const Self) H {
|
|
||||||
return @ptrCast(@alignCast(@as(*const anyopaque, @ptrFromInt(self.global.data_ptr))));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -20,11 +20,14 @@ const std = @import("std");
|
|||||||
pub const v8 = @import("v8").c;
|
pub const v8 = @import("v8").c;
|
||||||
|
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const string = @import("../../string.zig");
|
||||||
|
|
||||||
pub const Env = @import("Env.zig");
|
pub const Env = @import("Env.zig");
|
||||||
pub const bridge = @import("bridge.zig");
|
pub const bridge = @import("bridge.zig");
|
||||||
pub const ExecutionWorld = @import("ExecutionWorld.zig");
|
pub const ExecutionWorld = @import("ExecutionWorld.zig");
|
||||||
|
pub const Caller = @import("Caller.zig");
|
||||||
pub const Context = @import("Context.zig");
|
pub const Context = @import("Context.zig");
|
||||||
|
pub const Local = @import("Local.zig");
|
||||||
pub const Inspector = @import("Inspector.zig");
|
pub const Inspector = @import("Inspector.zig");
|
||||||
pub const Snapshot = @import("Snapshot.zig");
|
pub const Snapshot = @import("Snapshot.zig");
|
||||||
pub const Platform = @import("Platform.zig");
|
pub const Platform = @import("Platform.zig");
|
||||||
@@ -43,7 +46,6 @@ pub const Module = @import("Module.zig");
|
|||||||
pub const BigInt = @import("BigInt.zig");
|
pub const BigInt = @import("BigInt.zig");
|
||||||
pub const Number = @import("Number.zig");
|
pub const Number = @import("Number.zig");
|
||||||
pub const Integer = @import("Integer.zig");
|
pub const Integer = @import("Integer.zig");
|
||||||
pub const Global = @import("global.zig").Global;
|
|
||||||
pub const PromiseResolver = @import("PromiseResolver.zig");
|
pub const PromiseResolver = @import("PromiseResolver.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@@ -78,12 +80,8 @@ pub const ArrayBuffer = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Exception = struct {
|
pub const Exception = struct {
|
||||||
ctx: *const Context,
|
local: *const Local,
|
||||||
handle: *const v8.Value,
|
handle: *const v8.Value,
|
||||||
|
|
||||||
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
|
|
||||||
return self.context.valueToString(self.inner, .{ .allocator = allocator });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// These are simple types that we can convert to JS with only an isolate. This
|
// These are simple types that we can convert to JS with only an isolate. This
|
||||||
@@ -133,6 +131,7 @@ pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool,
|
|||||||
},
|
},
|
||||||
.@"struct" => {
|
.@"struct" => {
|
||||||
switch (@TypeOf(value)) {
|
switch (@TypeOf(value)) {
|
||||||
|
string.String => return isolate.initStringHandle(value.str()),
|
||||||
ArrayBuffer => {
|
ArrayBuffer => {
|
||||||
const values = value.values;
|
const values = value.values;
|
||||||
const len = values.len;
|
const len = values.len;
|
||||||
@@ -215,61 +214,6 @@ pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool,
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// When we return a Zig object to V8, we put it on the heap and pass it into
|
|
||||||
// v8 as an *anyopaque (i.e. void *). When V8 gives us back the value, say, as a
|
|
||||||
// function parameter, we know what type it _should_ be.
|
|
||||||
//
|
|
||||||
// In a simple/perfect world, we could use this knowledge to cast the *anyopaque
|
|
||||||
// to the parameter type:
|
|
||||||
// const arg: @typeInfo(@TypeOf(function)).@"fn".params[0] = @ptrCast(v8_data);
|
|
||||||
//
|
|
||||||
// But there are 2 reasons we can't do that.
|
|
||||||
//
|
|
||||||
// == Reason 1 ==
|
|
||||||
// The JS code might pass the wrong type:
|
|
||||||
//
|
|
||||||
// var cat = new Cat();
|
|
||||||
// cat.setOwner(new Cat());
|
|
||||||
//
|
|
||||||
// The zig_setOwner method expects the 2nd parameter to be an *Owner, but
|
|
||||||
// the JS code passed a *Cat.
|
|
||||||
//
|
|
||||||
// To solve this issue, we tag every returned value so that we can check what
|
|
||||||
// type it is. In the above case, we'd expect an *Owner, but the tag would tell
|
|
||||||
// us that we got a *Cat. We use the type index in our Types lookup as the tag.
|
|
||||||
//
|
|
||||||
// == Reason 2 ==
|
|
||||||
// Because of prototype inheritance, even "correct" code can be a challenge. For
|
|
||||||
// example, say the above JavaScript is fixed:
|
|
||||||
//
|
|
||||||
// var cat = new Cat();
|
|
||||||
// cat.setOwner(new Owner("Leto"));
|
|
||||||
//
|
|
||||||
// The issue is that setOwner might not expect an *Owner, but rather a
|
|
||||||
// *Person, which is the prototype for Owner. Now our Zig code is expecting
|
|
||||||
// a *Person, but it was (correctly) given an *Owner.
|
|
||||||
// For this reason, we also store the prototype chain.
|
|
||||||
pub const TaggedAnyOpaque = struct {
|
|
||||||
prototype_len: u16,
|
|
||||||
prototype_chain: [*]const PrototypeChainEntry,
|
|
||||||
|
|
||||||
// Ptr to the Zig instance. Between the context where it's called (i.e.
|
|
||||||
// we have the comptime parameter info for all functions), and the index field
|
|
||||||
// we can figure out what type this is.
|
|
||||||
value: *anyopaque,
|
|
||||||
|
|
||||||
// When we're asked to describe an object via the Inspector, we _must_ include
|
|
||||||
// the proper subtype (and description) fields in the returned JSON.
|
|
||||||
// V8 will give us a Value and ask us for the subtype. From the js.Value we
|
|
||||||
// can get a js.Object, and from the js.Object, we can get out TaggedAnyOpaque
|
|
||||||
// which is where we store the subtype.
|
|
||||||
subtype: ?bridge.SubType,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PrototypeChainEntry = struct {
|
|
||||||
index: bridge.JsApiLookup.BackingInt,
|
|
||||||
offset: u16, // offset to the _proto field
|
|
||||||
};
|
|
||||||
|
|
||||||
// These are here, and not in Inspector.zig, because Inspector.zig isn't always
|
// These are here, and not in Inspector.zig, because Inspector.zig isn't always
|
||||||
// included (e.g. in the wpt build).
|
// included (e.g. in the wpt build).
|
||||||
@@ -281,7 +225,7 @@ pub export fn v8_inspector__Client__IMPL__valueSubtype(
|
|||||||
_: *v8.InspectorClientImpl,
|
_: *v8.InspectorClientImpl,
|
||||||
c_value: *const v8.Value,
|
c_value: *const v8.Value,
|
||||||
) callconv(.c) [*c]const u8 {
|
) callconv(.c) [*c]const u8 {
|
||||||
const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null;
|
const external_entry = Inspector.getTaggedOpaque(c_value) orelse return null;
|
||||||
return if (external_entry.subtype) |st| @tagName(st) else null;
|
return if (external_entry.subtype) |st| @tagName(st) else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,11 +242,11 @@ pub export fn v8_inspector__Client__IMPL__descriptionForValueSubtype(
|
|||||||
|
|
||||||
// We _must_ include a non-null description in order for the subtype value
|
// We _must_ include a non-null description in order for the subtype value
|
||||||
// to be included. Besides that, I don't know if the value has any meaning
|
// to be included. Besides that, I don't know if the value has any meaning
|
||||||
const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null;
|
const external_entry = Inspector.getTaggedOpaque(c_value) orelse return null;
|
||||||
return if (external_entry.subtype == null) null else "";
|
return if (external_entry.subtype == null) null else "";
|
||||||
}
|
}
|
||||||
|
|
||||||
test "TaggedAnyOpaque" {
|
test "TaggedAnyOpaque" {
|
||||||
// If we grow this, fine, but it should be a conscious decision
|
// If we grow this, fine, but it should be a conscious decision
|
||||||
try std.testing.expectEqual(24, @sizeOf(TaggedAnyOpaque));
|
try std.testing.expectEqual(24, @sizeOf(@import("TaggedOpaque.zig")));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Page = @import("../Page.zig");
|
|||||||
const Node = @import("../webapi/Node.zig");
|
const Node = @import("../webapi/Node.zig");
|
||||||
const Element = @import("../webapi/Element.zig");
|
const Element = @import("../webapi/Element.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
pub const ParsedNode = struct {
|
pub const ParsedNode = struct {
|
||||||
node: *Node,
|
node: *Node,
|
||||||
@@ -373,6 +374,17 @@ fn _appendCallback(self: *Parser, parent: *Node, node_or_text: h5e.NodeOrText) !
|
|||||||
switch (node_or_text.toUnion()) {
|
switch (node_or_text.toUnion()) {
|
||||||
.node => |cpn| {
|
.node => |cpn| {
|
||||||
const child = getNode(cpn);
|
const child = getNode(cpn);
|
||||||
|
if (child._parent) |previous_parent| {
|
||||||
|
// html5ever says this can't happen, but we might be screwing up
|
||||||
|
// the node on our side. We shouldn't be, but we're seeing this
|
||||||
|
// in the wild, and I'm not sure why. In debug, let's crash so
|
||||||
|
// we can try to figure it out. In release, let's disconnect
|
||||||
|
// the child first.
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
self.page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent.isConnected() });
|
||||||
|
}
|
||||||
try self.page.appendNew(parent, .{ .node = child });
|
try self.page.appendNew(parent, .{ .node = child });
|
||||||
},
|
},
|
||||||
.text => |txt| try self.page.appendNew(parent, .{ .text = txt }),
|
.text => |txt| try self.page.appendNew(parent, .{ .text = txt }),
|
||||||
|
|||||||
@@ -41,4 +41,53 @@
|
|||||||
testing.expectEqual("DIV", newElement.tagName);
|
testing.expectEqual("DIV", newElement.tagName);
|
||||||
testing.expectEqual("after begin", newElement.innerText);
|
testing.expectEqual("after begin", newElement.innerText);
|
||||||
testing.expectEqual("afterbegin", newElement.className);
|
testing.expectEqual("afterbegin", newElement.className);
|
||||||
|
|
||||||
|
const fuzzWrapper = document.createElement("div");
|
||||||
|
fuzzWrapper.id = "fuzz-wrapper";
|
||||||
|
document.body.appendChild(fuzzWrapper);
|
||||||
|
|
||||||
|
const fuzzCases = [
|
||||||
|
// These cases have no <body> element (or empty body), so nothing is inserted
|
||||||
|
{ name: "empty string", html: "", expectElements: 0 },
|
||||||
|
{ name: "comment only", html: "<!-- comment -->", expectElements: 0 },
|
||||||
|
{ name: "doctype only", html: "<!DOCTYPE html>", expectElements: 0 },
|
||||||
|
{ name: "full empty doc", html: "<!DOCTYPE html><html><head></head><body></body></html>", expectElements: 0 },
|
||||||
|
|
||||||
|
{ name: "whitespace only", html: " ", expectElements: 0 },
|
||||||
|
{ name: "newlines only", html: "\n\n\n", expectElements: 0 },
|
||||||
|
{ name: "just text", html: "plain text", expectElements: 0 },
|
||||||
|
// Head-only elements: Extracted from <head> container
|
||||||
|
{ name: "empty meta", html: "<meta>", expectElements: 1 },
|
||||||
|
{ name: "empty title", html: "<title></title>", expectElements: 1 },
|
||||||
|
{ name: "empty head", html: "<head></head>", expectElements: 0 }, // Container with no children
|
||||||
|
{ name: "empty body", html: "<body></body>", expectElements: 0 }, // Container with no children
|
||||||
|
{ name: "empty html", html: "<html></html>", expectElements: 0 }, // Container with no children
|
||||||
|
{ name: "meta only", html: "<meta charset='utf-8'>", expectElements: 1 },
|
||||||
|
{ name: "title only", html: "<title>Test</title>", expectElements: 1 },
|
||||||
|
{ name: "link only", html: "<link rel='stylesheet' href='test.css'>", expectElements: 1 },
|
||||||
|
{ name: "meta and title", html: "<meta charset='utf-8'><title>Test</title>", expectElements: 2 },
|
||||||
|
{ name: "script only", html: "<script>console.log('hi')<\/script>", expectElements: 1 },
|
||||||
|
{ name: "style only", html: "<style>body { color: red; }<\/style>", expectElements: 1 },
|
||||||
|
{ name: "unclosed div", html: "<div>content", expectElements: 1 },
|
||||||
|
{ name: "unclosed span", html: "<span>text", expectElements: 1 },
|
||||||
|
{ name: "invalid tag", html: "<notarealtag>content</notarealtag>", expectElements: 1 },
|
||||||
|
{ name: "malformed", html: "<<div>>test<</div>>", expectElements: 1 }, // Parser handles it
|
||||||
|
{ name: "just closing tag", html: "</div>", expectElements: 0 },
|
||||||
|
{ name: "nested empty", html: "<div><div></div></div>", expectElements: 1 },
|
||||||
|
{ name: "multiple top-level", html: "<span>1</span><span>2</span><span>3</span>", expectElements: 3 },
|
||||||
|
{ name: "mixed text and elements", html: "Text before<b>bold</b>Text after", expectElements: 1 },
|
||||||
|
{ name: "deeply nested", html: "<div><div><div><span>deep</span></div></div></div>", expectElements: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
fuzzCases.forEach((tc, idx) => {
|
||||||
|
fuzzWrapper.innerHTML = "";
|
||||||
|
fuzzWrapper.insertAdjacentHTML("beforeend", tc.html);
|
||||||
|
if (tc.expectElements !== fuzzWrapper.childElementCount) {
|
||||||
|
console.warn(`Fuzz idx: ${idx}`);
|
||||||
|
testing.expectEqual(tc.expectElements, fuzzWrapper.childElementCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(fuzzWrapper);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
490
src/browser/tests/element/html/event_listeners.html
Normal file
490
src/browser/tests/element/html/event_listeners.html
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../../testing.js"></script>
|
||||||
|
|
||||||
|
<!-- Test inline event listeners set via HTML attributes -->
|
||||||
|
<div id="attr-click" onclick="window.x = 1"></div>
|
||||||
|
<div id="attr-load" onload="window.x = 1"></div>
|
||||||
|
<div id="attr-error" onerror="window.x = 1"></div>
|
||||||
|
<div id="attr-focus" onfocus="window.x = 1"></div>
|
||||||
|
<div id="attr-blur" onblur="window.x = 1"></div>
|
||||||
|
<div id="attr-keydown" onkeydown="window.x = 1"></div>
|
||||||
|
<div id="attr-mousedown" onmousedown="window.x = 1"></div>
|
||||||
|
<div id="attr-submit" onsubmit="window.x = 1"></div>
|
||||||
|
<div id="attr-wheel" onwheel="window.x = 1"></div>
|
||||||
|
<div id="attr-scroll" onscroll="window.x = 1"></div>
|
||||||
|
<div id="attr-contextmenu" oncontextmenu="window.x = 1"></div>
|
||||||
|
<div id="no-listeners"></div>
|
||||||
|
|
||||||
|
<script id="attr_listener_returns_function">
|
||||||
|
{
|
||||||
|
// Inline listeners set via HTML attributes should return a function
|
||||||
|
testing.expectEqual('function', typeof $('#attr-click').onclick);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-load').onload);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-error').onerror);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-focus').onfocus);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-blur').onblur);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-keydown').onkeydown);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-mousedown').onmousedown);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-submit').onsubmit);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-wheel').onwheel);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-scroll').onscroll);
|
||||||
|
testing.expectEqual('function', typeof $('#attr-contextmenu').oncontextmenu);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="no_attr_listener_returns_null">
|
||||||
|
{
|
||||||
|
// Elements without inline listeners should return null
|
||||||
|
const div = $('#no-listeners');
|
||||||
|
testing.expectEqual(null, div.onclick);
|
||||||
|
testing.expectEqual(null, div.onload);
|
||||||
|
testing.expectEqual(null, div.onerror);
|
||||||
|
testing.expectEqual(null, div.onfocus);
|
||||||
|
testing.expectEqual(null, div.onblur);
|
||||||
|
testing.expectEqual(null, div.onkeydown);
|
||||||
|
testing.expectEqual(null, div.onmousedown);
|
||||||
|
testing.expectEqual(null, div.onsubmit);
|
||||||
|
testing.expectEqual(null, div.onwheel);
|
||||||
|
testing.expectEqual(null, div.onscroll);
|
||||||
|
testing.expectEqual(null, div.oncontextmenu);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="js_setter_getter">
|
||||||
|
{
|
||||||
|
// Test setting and getting listeners via JavaScript property
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
// Initially null
|
||||||
|
testing.expectEqual(null, div.onclick);
|
||||||
|
testing.expectEqual(null, div.onload);
|
||||||
|
testing.expectEqual(null, div.onerror);
|
||||||
|
|
||||||
|
// Set listeners
|
||||||
|
const clickHandler = () => {};
|
||||||
|
const loadHandler = () => {};
|
||||||
|
const errorHandler = () => {};
|
||||||
|
|
||||||
|
div.onclick = clickHandler;
|
||||||
|
div.onload = loadHandler;
|
||||||
|
div.onerror = errorHandler;
|
||||||
|
|
||||||
|
// Verify they can be retrieved and are functions
|
||||||
|
testing.expectEqual('function', typeof div.onclick);
|
||||||
|
testing.expectEqual('function', typeof div.onload);
|
||||||
|
testing.expectEqual('function', typeof div.onerror);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="js_listener_invoke">
|
||||||
|
{
|
||||||
|
// Test that JS-set listeners can be invoked directly
|
||||||
|
const div = document.createElement('div');
|
||||||
|
window.jsInvokeResult = 0;
|
||||||
|
|
||||||
|
div.onclick = () => { window.jsInvokeResult = 100; };
|
||||||
|
div.onclick();
|
||||||
|
testing.expectEqual(100, window.jsInvokeResult);
|
||||||
|
|
||||||
|
div.onload = () => { window.jsInvokeResult = 200; };
|
||||||
|
div.onload();
|
||||||
|
testing.expectEqual(200, window.jsInvokeResult);
|
||||||
|
|
||||||
|
div.onfocus = () => { window.jsInvokeResult = 300; };
|
||||||
|
div.onfocus();
|
||||||
|
testing.expectEqual(300, window.jsInvokeResult);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="js_listener_invoke_with_return">
|
||||||
|
{
|
||||||
|
// Test that JS-set listeners return values when invoked
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
div.onclick = () => { return 'click-result'; };
|
||||||
|
testing.expectEqual('click-result', div.onclick());
|
||||||
|
|
||||||
|
div.onload = () => { return 42; };
|
||||||
|
testing.expectEqual(42, div.onload());
|
||||||
|
|
||||||
|
div.onfocus = () => { return { key: 'value' }; };
|
||||||
|
testing.expectEqual('value', div.onfocus().key);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="js_listener_invoke_with_args">
|
||||||
|
{
|
||||||
|
// Test that JS-set listeners can receive arguments when invoked
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
div.onclick = (a, b) => { return a + b; };
|
||||||
|
testing.expectEqual(15, div.onclick(10, 5));
|
||||||
|
|
||||||
|
div.onload = (msg) => { return 'Hello, ' + msg; };
|
||||||
|
testing.expectEqual('Hello, World', div.onload('World'));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="js_setter_override">
|
||||||
|
{
|
||||||
|
// Test that setting a new listener overrides the old one
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
const first = () => { return 1; };
|
||||||
|
const second = () => { return 2; };
|
||||||
|
|
||||||
|
div.onclick = first;
|
||||||
|
testing.expectEqual('function', typeof div.onclick);
|
||||||
|
testing.expectEqual(1, div.onclick());
|
||||||
|
|
||||||
|
div.onclick = second;
|
||||||
|
testing.expectEqual('function', typeof div.onclick);
|
||||||
|
testing.expectEqual(2, div.onclick());
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="different_event_types_independent">
|
||||||
|
{
|
||||||
|
// Test that different event types are stored independently
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
const clickFn = () => {};
|
||||||
|
const focusFn = () => {};
|
||||||
|
const blurFn = () => {};
|
||||||
|
|
||||||
|
div.onclick = clickFn;
|
||||||
|
testing.expectEqual('function', typeof div.onclick);
|
||||||
|
testing.expectEqual(null, div.onfocus);
|
||||||
|
testing.expectEqual(null, div.onblur);
|
||||||
|
|
||||||
|
div.onfocus = focusFn;
|
||||||
|
testing.expectEqual('function', typeof div.onclick);
|
||||||
|
testing.expectEqual('function', typeof div.onfocus);
|
||||||
|
testing.expectEqual(null, div.onblur);
|
||||||
|
|
||||||
|
div.onblur = blurFn;
|
||||||
|
testing.expectEqual('function', typeof div.onclick);
|
||||||
|
testing.expectEqual('function', typeof div.onfocus);
|
||||||
|
testing.expectEqual('function', typeof div.onblur);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="keyboard_event_listeners">
|
||||||
|
{
|
||||||
|
// Test keyboard event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onkeydown);
|
||||||
|
testing.expectEqual(null, div.onkeyup);
|
||||||
|
testing.expectEqual(null, div.onkeypress);
|
||||||
|
|
||||||
|
div.onkeydown = () => {};
|
||||||
|
div.onkeyup = () => {};
|
||||||
|
div.onkeypress = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onkeydown);
|
||||||
|
testing.expectEqual('function', typeof div.onkeyup);
|
||||||
|
testing.expectEqual('function', typeof div.onkeypress);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="mouse_event_listeners">
|
||||||
|
{
|
||||||
|
// Test mouse event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onmousedown);
|
||||||
|
testing.expectEqual(null, div.onmouseup);
|
||||||
|
testing.expectEqual(null, div.onmousemove);
|
||||||
|
testing.expectEqual(null, div.onmouseover);
|
||||||
|
testing.expectEqual(null, div.onmouseout);
|
||||||
|
testing.expectEqual(null, div.ondblclick);
|
||||||
|
|
||||||
|
div.onmousedown = () => {};
|
||||||
|
div.onmouseup = () => {};
|
||||||
|
div.onmousemove = () => {};
|
||||||
|
div.onmouseover = () => {};
|
||||||
|
div.onmouseout = () => {};
|
||||||
|
div.ondblclick = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onmousedown);
|
||||||
|
testing.expectEqual('function', typeof div.onmouseup);
|
||||||
|
testing.expectEqual('function', typeof div.onmousemove);
|
||||||
|
testing.expectEqual('function', typeof div.onmouseover);
|
||||||
|
testing.expectEqual('function', typeof div.onmouseout);
|
||||||
|
testing.expectEqual('function', typeof div.ondblclick);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="pointer_event_listeners">
|
||||||
|
{
|
||||||
|
// Test pointer event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onpointerdown);
|
||||||
|
testing.expectEqual(null, div.onpointerup);
|
||||||
|
testing.expectEqual(null, div.onpointermove);
|
||||||
|
testing.expectEqual(null, div.onpointerover);
|
||||||
|
testing.expectEqual(null, div.onpointerout);
|
||||||
|
testing.expectEqual(null, div.onpointerenter);
|
||||||
|
testing.expectEqual(null, div.onpointerleave);
|
||||||
|
testing.expectEqual(null, div.onpointercancel);
|
||||||
|
|
||||||
|
div.onpointerdown = () => {};
|
||||||
|
div.onpointerup = () => {};
|
||||||
|
div.onpointermove = () => {};
|
||||||
|
div.onpointerover = () => {};
|
||||||
|
div.onpointerout = () => {};
|
||||||
|
div.onpointerenter = () => {};
|
||||||
|
div.onpointerleave = () => {};
|
||||||
|
div.onpointercancel = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onpointerdown);
|
||||||
|
testing.expectEqual('function', typeof div.onpointerup);
|
||||||
|
testing.expectEqual('function', typeof div.onpointermove);
|
||||||
|
testing.expectEqual('function', typeof div.onpointerover);
|
||||||
|
testing.expectEqual('function', typeof div.onpointerout);
|
||||||
|
testing.expectEqual('function', typeof div.onpointerenter);
|
||||||
|
testing.expectEqual('function', typeof div.onpointerleave);
|
||||||
|
testing.expectEqual('function', typeof div.onpointercancel);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="form_event_listeners">
|
||||||
|
{
|
||||||
|
// Test form event listener getters/setters
|
||||||
|
const form = document.createElement('form');
|
||||||
|
|
||||||
|
testing.expectEqual(null, form.onsubmit);
|
||||||
|
testing.expectEqual(null, form.onreset);
|
||||||
|
testing.expectEqual(null, form.onchange);
|
||||||
|
testing.expectEqual(null, form.oninput);
|
||||||
|
testing.expectEqual(null, form.oninvalid);
|
||||||
|
|
||||||
|
form.onsubmit = () => {};
|
||||||
|
form.onreset = () => {};
|
||||||
|
form.onchange = () => {};
|
||||||
|
form.oninput = () => {};
|
||||||
|
form.oninvalid = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof form.onsubmit);
|
||||||
|
testing.expectEqual('function', typeof form.onreset);
|
||||||
|
testing.expectEqual('function', typeof form.onchange);
|
||||||
|
testing.expectEqual('function', typeof form.oninput);
|
||||||
|
testing.expectEqual('function', typeof form.oninvalid);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="drag_event_listeners">
|
||||||
|
{
|
||||||
|
// Test drag event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.ondrag);
|
||||||
|
testing.expectEqual(null, div.ondragstart);
|
||||||
|
testing.expectEqual(null, div.ondragend);
|
||||||
|
testing.expectEqual(null, div.ondragenter);
|
||||||
|
testing.expectEqual(null, div.ondragleave);
|
||||||
|
testing.expectEqual(null, div.ondragover);
|
||||||
|
testing.expectEqual(null, div.ondrop);
|
||||||
|
|
||||||
|
div.ondrag = () => {};
|
||||||
|
div.ondragstart = () => {};
|
||||||
|
div.ondragend = () => {};
|
||||||
|
div.ondragenter = () => {};
|
||||||
|
div.ondragleave = () => {};
|
||||||
|
div.ondragover = () => {};
|
||||||
|
div.ondrop = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.ondrag);
|
||||||
|
testing.expectEqual('function', typeof div.ondragstart);
|
||||||
|
testing.expectEqual('function', typeof div.ondragend);
|
||||||
|
testing.expectEqual('function', typeof div.ondragenter);
|
||||||
|
testing.expectEqual('function', typeof div.ondragleave);
|
||||||
|
testing.expectEqual('function', typeof div.ondragover);
|
||||||
|
testing.expectEqual('function', typeof div.ondrop);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="clipboard_event_listeners">
|
||||||
|
{
|
||||||
|
// Test clipboard event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.oncopy);
|
||||||
|
testing.expectEqual(null, div.oncut);
|
||||||
|
testing.expectEqual(null, div.onpaste);
|
||||||
|
|
||||||
|
div.oncopy = () => {};
|
||||||
|
div.oncut = () => {};
|
||||||
|
div.onpaste = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.oncopy);
|
||||||
|
testing.expectEqual('function', typeof div.oncut);
|
||||||
|
testing.expectEqual('function', typeof div.onpaste);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="scroll_event_listeners">
|
||||||
|
{
|
||||||
|
// Test scroll event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onscroll);
|
||||||
|
testing.expectEqual(null, div.onscrollend);
|
||||||
|
testing.expectEqual(null, div.onresize);
|
||||||
|
|
||||||
|
div.onscroll = () => {};
|
||||||
|
div.onscrollend = () => {};
|
||||||
|
div.onresize = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onscroll);
|
||||||
|
testing.expectEqual('function', typeof div.onscrollend);
|
||||||
|
testing.expectEqual('function', typeof div.onresize);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="animation_event_listeners">
|
||||||
|
{
|
||||||
|
// Test animation event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onanimationstart);
|
||||||
|
testing.expectEqual(null, div.onanimationend);
|
||||||
|
testing.expectEqual(null, div.onanimationiteration);
|
||||||
|
testing.expectEqual(null, div.onanimationcancel);
|
||||||
|
|
||||||
|
div.onanimationstart = () => {};
|
||||||
|
div.onanimationend = () => {};
|
||||||
|
div.onanimationiteration = () => {};
|
||||||
|
div.onanimationcancel = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onanimationstart);
|
||||||
|
testing.expectEqual('function', typeof div.onanimationend);
|
||||||
|
testing.expectEqual('function', typeof div.onanimationiteration);
|
||||||
|
testing.expectEqual('function', typeof div.onanimationcancel);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="transition_event_listeners">
|
||||||
|
{
|
||||||
|
// Test transition event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.ontransitionstart);
|
||||||
|
testing.expectEqual(null, div.ontransitionend);
|
||||||
|
testing.expectEqual(null, div.ontransitionrun);
|
||||||
|
testing.expectEqual(null, div.ontransitioncancel);
|
||||||
|
|
||||||
|
div.ontransitionstart = () => {};
|
||||||
|
div.ontransitionend = () => {};
|
||||||
|
div.ontransitionrun = () => {};
|
||||||
|
div.ontransitioncancel = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.ontransitionstart);
|
||||||
|
testing.expectEqual('function', typeof div.ontransitionend);
|
||||||
|
testing.expectEqual('function', typeof div.ontransitionrun);
|
||||||
|
testing.expectEqual('function', typeof div.ontransitioncancel);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="misc_event_listeners">
|
||||||
|
{
|
||||||
|
// Test miscellaneous event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onwheel);
|
||||||
|
testing.expectEqual(null, div.ontoggle);
|
||||||
|
testing.expectEqual(null, div.oncontextmenu);
|
||||||
|
testing.expectEqual(null, div.onselect);
|
||||||
|
testing.expectEqual(null, div.onabort);
|
||||||
|
testing.expectEqual(null, div.oncancel);
|
||||||
|
testing.expectEqual(null, div.onclose);
|
||||||
|
|
||||||
|
div.onwheel = () => {};
|
||||||
|
div.ontoggle = () => {};
|
||||||
|
div.oncontextmenu = () => {};
|
||||||
|
div.onselect = () => {};
|
||||||
|
div.onabort = () => {};
|
||||||
|
div.oncancel = () => {};
|
||||||
|
div.onclose = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onwheel);
|
||||||
|
testing.expectEqual('function', typeof div.ontoggle);
|
||||||
|
testing.expectEqual('function', typeof div.oncontextmenu);
|
||||||
|
testing.expectEqual('function', typeof div.onselect);
|
||||||
|
testing.expectEqual('function', typeof div.onabort);
|
||||||
|
testing.expectEqual('function', typeof div.oncancel);
|
||||||
|
testing.expectEqual('function', typeof div.onclose);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="media_event_listeners">
|
||||||
|
{
|
||||||
|
// Test media event listener getters/setters
|
||||||
|
const div = document.createElement('div');
|
||||||
|
|
||||||
|
testing.expectEqual(null, div.onplay);
|
||||||
|
testing.expectEqual(null, div.onpause);
|
||||||
|
testing.expectEqual(null, div.onplaying);
|
||||||
|
testing.expectEqual(null, div.onended);
|
||||||
|
testing.expectEqual(null, div.onvolumechange);
|
||||||
|
testing.expectEqual(null, div.onwaiting);
|
||||||
|
testing.expectEqual(null, div.onseeking);
|
||||||
|
testing.expectEqual(null, div.onseeked);
|
||||||
|
testing.expectEqual(null, div.ontimeupdate);
|
||||||
|
testing.expectEqual(null, div.onloadstart);
|
||||||
|
testing.expectEqual(null, div.onprogress);
|
||||||
|
testing.expectEqual(null, div.onstalled);
|
||||||
|
testing.expectEqual(null, div.onsuspend);
|
||||||
|
testing.expectEqual(null, div.oncanplay);
|
||||||
|
testing.expectEqual(null, div.oncanplaythrough);
|
||||||
|
testing.expectEqual(null, div.ondurationchange);
|
||||||
|
testing.expectEqual(null, div.onemptied);
|
||||||
|
testing.expectEqual(null, div.onloadeddata);
|
||||||
|
testing.expectEqual(null, div.onloadedmetadata);
|
||||||
|
testing.expectEqual(null, div.onratechange);
|
||||||
|
|
||||||
|
div.onplay = () => {};
|
||||||
|
div.onpause = () => {};
|
||||||
|
div.onplaying = () => {};
|
||||||
|
div.onended = () => {};
|
||||||
|
div.onvolumechange = () => {};
|
||||||
|
div.onwaiting = () => {};
|
||||||
|
div.onseeking = () => {};
|
||||||
|
div.onseeked = () => {};
|
||||||
|
div.ontimeupdate = () => {};
|
||||||
|
div.onloadstart = () => {};
|
||||||
|
div.onprogress = () => {};
|
||||||
|
div.onstalled = () => {};
|
||||||
|
div.onsuspend = () => {};
|
||||||
|
div.oncanplay = () => {};
|
||||||
|
div.oncanplaythrough = () => {};
|
||||||
|
div.ondurationchange = () => {};
|
||||||
|
div.onemptied = () => {};
|
||||||
|
div.onloadeddata = () => {};
|
||||||
|
div.onloadedmetadata = () => {};
|
||||||
|
div.onratechange = () => {};
|
||||||
|
|
||||||
|
testing.expectEqual('function', typeof div.onplay);
|
||||||
|
testing.expectEqual('function', typeof div.onpause);
|
||||||
|
testing.expectEqual('function', typeof div.onplaying);
|
||||||
|
testing.expectEqual('function', typeof div.onended);
|
||||||
|
testing.expectEqual('function', typeof div.onvolumechange);
|
||||||
|
testing.expectEqual('function', typeof div.onwaiting);
|
||||||
|
testing.expectEqual('function', typeof div.onseeking);
|
||||||
|
testing.expectEqual('function', typeof div.onseeked);
|
||||||
|
testing.expectEqual('function', typeof div.ontimeupdate);
|
||||||
|
testing.expectEqual('function', typeof div.onloadstart);
|
||||||
|
testing.expectEqual('function', typeof div.onprogress);
|
||||||
|
testing.expectEqual('function', typeof div.onstalled);
|
||||||
|
testing.expectEqual('function', typeof div.onsuspend);
|
||||||
|
testing.expectEqual('function', typeof div.oncanplay);
|
||||||
|
testing.expectEqual('function', typeof div.oncanplaythrough);
|
||||||
|
testing.expectEqual('function', typeof div.ondurationchange);
|
||||||
|
testing.expectEqual('function', typeof div.onemptied);
|
||||||
|
testing.expectEqual('function', typeof div.onloadeddata);
|
||||||
|
testing.expectEqual('function', typeof div.onloadedmetadata);
|
||||||
|
testing.expectEqual('function', typeof div.onratechange);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id="defaultChecked">
|
<!-- <script id="defaultChecked">
|
||||||
testing.expectEqual(true, $('#check1').defaultChecked)
|
testing.expectEqual(true, $('#check1').defaultChecked)
|
||||||
testing.expectEqual(false, $('#check2').defaultChecked)
|
testing.expectEqual(false, $('#check2').defaultChecked)
|
||||||
testing.expectEqual(true, $('#radio1').defaultChecked)
|
testing.expectEqual(true, $('#radio1').defaultChecked)
|
||||||
@@ -455,4 +455,4 @@
|
|||||||
input_checked.defaultChecked = true;
|
input_checked.defaultChecked = true;
|
||||||
testing.expectEqual(false, input_checked.checked);
|
testing.expectEqual(false, input_checked.checked);
|
||||||
}
|
}
|
||||||
</script>
|
</script> -->
|
||||||
|
|||||||
12
src/browser/tests/element/html/script/script.html
Normal file
12
src/browser/tests/element/html/script/script.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../../../testing.js"></script>
|
||||||
|
|
||||||
|
<script id="script">
|
||||||
|
{
|
||||||
|
let s = document.createElement('script');
|
||||||
|
testing.expectEqual('', s.src);
|
||||||
|
|
||||||
|
s.src = '/over.9000.js';
|
||||||
|
testing.expectEqual('http://127.0.0.1:9582/over.9000.js', s.src);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
165
src/browser/tests/event/pointer.html
Normal file
165
src/browser/tests/event/pointer.html
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
<script id=default>
|
||||||
|
{
|
||||||
|
let event = new PointerEvent('pointerdown');
|
||||||
|
testing.expectEqual('pointerdown', event.type);
|
||||||
|
testing.expectEqual(true, event instanceof PointerEvent);
|
||||||
|
testing.expectEqual(true, event instanceof MouseEvent);
|
||||||
|
testing.expectEqual(true, event instanceof UIEvent);
|
||||||
|
testing.expectEqual(true, event instanceof Event);
|
||||||
|
testing.expectEqual(0, event.pointerId);
|
||||||
|
testing.expectEqual('', event.pointerType);
|
||||||
|
testing.expectEqual(1.0, event.width);
|
||||||
|
testing.expectEqual(1.0, event.height);
|
||||||
|
testing.expectEqual(0.0, event.pressure);
|
||||||
|
testing.expectEqual(0.0, event.tangentialPressure);
|
||||||
|
testing.expectEqual(0, event.tiltX);
|
||||||
|
testing.expectEqual(0, event.tiltY);
|
||||||
|
testing.expectEqual(0, event.twist);
|
||||||
|
testing.expectEqual(false, event.isPrimary);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=parameters>
|
||||||
|
{
|
||||||
|
let new_event = new PointerEvent('pointerdown', {
|
||||||
|
pointerId: 42,
|
||||||
|
pointerType: 'pen',
|
||||||
|
width: 10.5,
|
||||||
|
height: 20.5,
|
||||||
|
pressure: 0.75,
|
||||||
|
tangentialPressure: -0.25,
|
||||||
|
tiltX: 30,
|
||||||
|
tiltY: 45,
|
||||||
|
twist: 90,
|
||||||
|
isPrimary: true,
|
||||||
|
clientX: 100,
|
||||||
|
clientY: 200,
|
||||||
|
screenX: 300,
|
||||||
|
screenY: 400
|
||||||
|
});
|
||||||
|
testing.expectEqual(42, new_event.pointerId);
|
||||||
|
testing.expectEqual('pen', new_event.pointerType);
|
||||||
|
testing.expectEqual(10.5, new_event.width);
|
||||||
|
testing.expectEqual(20.5, new_event.height);
|
||||||
|
testing.expectEqual(0.75, new_event.pressure);
|
||||||
|
testing.expectEqual(-0.25, new_event.tangentialPressure);
|
||||||
|
testing.expectEqual(30, new_event.tiltX);
|
||||||
|
testing.expectEqual(45, new_event.tiltY);
|
||||||
|
testing.expectEqual(90, new_event.twist);
|
||||||
|
testing.expectEqual(true, new_event.isPrimary);
|
||||||
|
testing.expectEqual(100, new_event.clientX);
|
||||||
|
testing.expectEqual(200, new_event.clientY);
|
||||||
|
testing.expectEqual(300, new_event.screenX);
|
||||||
|
testing.expectEqual(400, new_event.screenY);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=mousePointerType>
|
||||||
|
{
|
||||||
|
let mouse_event = new PointerEvent('pointerdown', { pointerType: 'mouse' });
|
||||||
|
testing.expectEqual('mouse', mouse_event.pointerType);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=touchPointerType>
|
||||||
|
{
|
||||||
|
let touch_event = new PointerEvent('pointerdown', { pointerType: 'touch', pointerId: 1, pressure: 0.5 });
|
||||||
|
testing.expectEqual('touch', touch_event.pointerType);
|
||||||
|
testing.expectEqual(1, touch_event.pointerId);
|
||||||
|
testing.expectEqual(0.5, touch_event.pressure);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=listener>
|
||||||
|
{
|
||||||
|
let pe = new PointerEvent('pointerdown', { pointerId: 123 });
|
||||||
|
testing.expectEqual(true, pe instanceof PointerEvent);
|
||||||
|
testing.expectEqual(true, pe instanceof MouseEvent);
|
||||||
|
testing.expectEqual(true, pe instanceof Event);
|
||||||
|
|
||||||
|
var evt = null;
|
||||||
|
document.addEventListener('pointerdown', function (e) {
|
||||||
|
evt = e;
|
||||||
|
});
|
||||||
|
document.dispatchEvent(pe);
|
||||||
|
testing.expectEqual('pointerdown', evt.type);
|
||||||
|
testing.expectEqual(true, evt instanceof PointerEvent);
|
||||||
|
testing.expectEqual(123, evt.pointerId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=isTrusted>
|
||||||
|
{
|
||||||
|
let pointerEvent = new PointerEvent('pointerup');
|
||||||
|
testing.expectEqual(false, pointerEvent.isTrusted);
|
||||||
|
|
||||||
|
let pointerIsTrusted = null;
|
||||||
|
document.addEventListener('pointertest', (e) => {
|
||||||
|
pointerIsTrusted = e.isTrusted;
|
||||||
|
testing.expectEqual(true, e instanceof PointerEvent);
|
||||||
|
});
|
||||||
|
document.dispatchEvent(new PointerEvent('pointertest'));
|
||||||
|
testing.expectEqual(false, pointerIsTrusted);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=eventTypes>
|
||||||
|
{
|
||||||
|
let down = new PointerEvent('pointerdown');
|
||||||
|
testing.expectEqual('pointerdown', down.type);
|
||||||
|
|
||||||
|
let up = new PointerEvent('pointerup');
|
||||||
|
testing.expectEqual('pointerup', up.type);
|
||||||
|
|
||||||
|
let move = new PointerEvent('pointermove');
|
||||||
|
testing.expectEqual('pointermove', move.type);
|
||||||
|
|
||||||
|
let enter = new PointerEvent('pointerenter');
|
||||||
|
testing.expectEqual('pointerenter', enter.type);
|
||||||
|
|
||||||
|
let leave = new PointerEvent('pointerleave');
|
||||||
|
testing.expectEqual('pointerleave', leave.type);
|
||||||
|
|
||||||
|
let over = new PointerEvent('pointerover');
|
||||||
|
testing.expectEqual('pointerover', over.type);
|
||||||
|
|
||||||
|
let out = new PointerEvent('pointerout');
|
||||||
|
testing.expectEqual('pointerout', out.type);
|
||||||
|
|
||||||
|
let cancel = new PointerEvent('pointercancel');
|
||||||
|
testing.expectEqual('pointercancel', cancel.type);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=inheritedMouseProperties>
|
||||||
|
{
|
||||||
|
let pe = new PointerEvent('pointerdown', {
|
||||||
|
button: 2,
|
||||||
|
buttons: 4,
|
||||||
|
altKey: true,
|
||||||
|
ctrlKey: true,
|
||||||
|
shiftKey: true,
|
||||||
|
metaKey: true
|
||||||
|
});
|
||||||
|
testing.expectEqual(2, pe.button);
|
||||||
|
testing.expectEqual(true, pe.altKey);
|
||||||
|
testing.expectEqual(true, pe.ctrlKey);
|
||||||
|
testing.expectEqual(true, pe.shiftKey);
|
||||||
|
testing.expectEqual(true, pe.metaKey);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=inheritedUIEventProperties>
|
||||||
|
{
|
||||||
|
let pe = new PointerEvent('pointerdown', {
|
||||||
|
detail: 5,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true
|
||||||
|
});
|
||||||
|
testing.expectEqual(5, pe.detail);
|
||||||
|
testing.expectEqual(true, pe.bubbles);
|
||||||
|
testing.expectEqual(true, pe.cancelable);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
testing.eventually(() => {
|
testing.eventually(() => {
|
||||||
testing.expectEqual(true, popstateEventFired);
|
testing.expectEqual(true, popstateEventFired);
|
||||||
testing.expectEqual(state, popstateEventState);
|
testing.expectEqual({testInProgress: true }, popstateEventState);
|
||||||
})
|
})
|
||||||
|
|
||||||
history.back();
|
history.back();
|
||||||
|
|||||||
@@ -1,6 +1 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="testing.js"></script>
|
|
||||||
|
|
||||||
<script id=history-after-nav>
|
|
||||||
testing.expectEqual(true, history.state && history.state.testInProgress);
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -58,3 +58,6 @@
|
|||||||
testing.expectEqual(true, e.toString().includes("FailedToLoad"), {script_id: 'import-404'});
|
testing.expectEqual(true, e.toString().includes("FailedToLoad"), {script_id: 'import-404'});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- this used to crash -->
|
||||||
|
<script type=module src=modules/self_async.js></script>
|
||||||
|
|||||||
1
src/browser/tests/page/modules/self_async.js
Normal file
1
src/browser/tests/page/modules/self_async.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
const c = await import('./self_async.js');
|
||||||
543
src/browser/tests/selection.html
Normal file
543
src/browser/tests/selection.html
Normal file
@@ -0,0 +1,543 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<script src="./testing.js"></script>
|
||||||
|
|
||||||
|
<div id="test-content">
|
||||||
|
<p id="p1">The quick brown fox</p>
|
||||||
|
<p id="p2">jumps over the lazy dog</p>
|
||||||
|
<div id="nested">
|
||||||
|
<span id="s1">Hello</span>
|
||||||
|
<span id="s2">World</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id=basic>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
testing.expectEqual(0, sel.rangeCount);
|
||||||
|
testing.expectEqual("None", sel.type);
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(null, sel.anchorNode);
|
||||||
|
testing.expectEqual(null, sel.focusNode);
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
testing.expectEqual(0, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=collapse>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
// Collapse to a position
|
||||||
|
sel.collapse(textNode, 4);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Caret", sel.type);
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(textNode, sel.focusNode);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(4, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
|
||||||
|
// Collapse to null removes all ranges
|
||||||
|
sel.collapse(null);
|
||||||
|
testing.expectEqual(0, sel.rangeCount);
|
||||||
|
testing.expectEqual("None", sel.type);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=setPosition>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
const textNode = p2.firstChild;
|
||||||
|
|
||||||
|
// setPosition is an alias for collapse
|
||||||
|
sel.setPosition(textNode, 10);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Caret", sel.type);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
|
||||||
|
// Test default offset
|
||||||
|
sel.setPosition(textNode);
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
|
||||||
|
// Test null
|
||||||
|
sel.setPosition(null);
|
||||||
|
testing.expectEqual(0, sel.rangeCount);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=addRange>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const range1 = document.createRange();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
range1.selectNodeContents(p1);
|
||||||
|
|
||||||
|
sel.addRange(range1);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
|
||||||
|
// Adding same range again should do nothing
|
||||||
|
sel.addRange(range1);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
|
||||||
|
// Adding different range
|
||||||
|
const range2 = document.createRange();
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
range2.selectNodeContents(p2);
|
||||||
|
|
||||||
|
sel.addRange(range2);
|
||||||
|
|
||||||
|
// Firefox does support multiple ranges so it will be 2 here instead of 1.
|
||||||
|
// Chrome and Safari don't so we don't either.
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=getRangeAt>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
range.selectNodeContents(p1);
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
const retrieved = sel.getRangeAt(0);
|
||||||
|
testing.expectEqual(range, retrieved);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=removeRange>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const range1 = document.createRange();
|
||||||
|
const range2 = document.createRange();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
|
||||||
|
range1.selectNodeContents(p1);
|
||||||
|
range2.selectNodeContents(p2);
|
||||||
|
|
||||||
|
sel.addRange(range1);
|
||||||
|
sel.addRange(range2);
|
||||||
|
|
||||||
|
// Firefox does support multiple ranges so it will be 2 here instead of 1.
|
||||||
|
// Chrome and Safari don't so we don't either.
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
|
||||||
|
// Chrome doesn't throw an error here even though the spec defines it:
|
||||||
|
// https://w3c.github.io/selection-api/#dom-selection-removerange
|
||||||
|
testing.expectError('NotFoundError', () => { sel.removeRange(range2); });
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(range1, sel.getRangeAt(0));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=removeAllRanges>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const range1 = document.createRange();
|
||||||
|
const range2 = document.createRange();
|
||||||
|
|
||||||
|
range1.selectNodeContents(document.getElementById("p1"));
|
||||||
|
range2.selectNodeContents(document.getElementById("p2"));
|
||||||
|
|
||||||
|
sel.addRange(range1);
|
||||||
|
sel.addRange(range2);
|
||||||
|
|
||||||
|
// Firefox does support multiple ranges so it will be 2 here instead of 1.
|
||||||
|
// Chrome and Safari don't so we don't either.
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
testing.expectEqual(0, sel.rangeCount);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=empty>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(document.getElementById("p1"));
|
||||||
|
|
||||||
|
sel.addRange(range);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
|
||||||
|
// empty() is an alias for removeAllRanges()
|
||||||
|
sel.empty();
|
||||||
|
testing.expectEqual(0, sel.rangeCount);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=collapseToStart>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStart(textNode, 4);
|
||||||
|
range.setEnd(textNode, 15);
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(15, sel.focusOffset);
|
||||||
|
|
||||||
|
sel.collapseToStart();
|
||||||
|
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(4, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=collapseToEnd>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStart(textNode, 4);
|
||||||
|
range.setEnd(textNode, 15);
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
|
||||||
|
sel.collapseToEnd();
|
||||||
|
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(15, sel.anchorOffset);
|
||||||
|
testing.expectEqual(15, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=extend>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
// Start with collapsed selection
|
||||||
|
sel.collapse(textNode, 10);
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
|
||||||
|
// Extend forward
|
||||||
|
sel.extend(textNode, 15);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual(15, sel.focusOffset);
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
|
||||||
|
// Extend backward from anchor
|
||||||
|
sel.extend(textNode, 5);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual(5, sel.focusOffset);
|
||||||
|
testing.expectEqual("backward", sel.direction);
|
||||||
|
|
||||||
|
// Extend to same position as anchor
|
||||||
|
sel.extend(textNode, 10);
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual(10, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=direction>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
// Forward selection
|
||||||
|
sel.collapse(textNode, 5);
|
||||||
|
sel.extend(textNode, 10);
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
testing.expectEqual(5, sel.anchorOffset);
|
||||||
|
testing.expectEqual(10, sel.focusOffset);
|
||||||
|
|
||||||
|
// Backward selection
|
||||||
|
sel.collapse(textNode, 10);
|
||||||
|
sel.extend(textNode, 5);
|
||||||
|
testing.expectEqual("backward", sel.direction);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual(5, sel.focusOffset);
|
||||||
|
|
||||||
|
// None (collapsed)
|
||||||
|
sel.collapse(textNode, 7);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=containsNode>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const nested = document.getElementById("nested");
|
||||||
|
const s1 = document.getElementById("s1");
|
||||||
|
const s2 = document.getElementById("s2");
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(nested);
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
// Partial containment
|
||||||
|
testing.expectEqual(true, sel.containsNode(s1, true));
|
||||||
|
testing.expectEqual(true, sel.containsNode(s2, true));
|
||||||
|
testing.expectEqual(true, sel.containsNode(nested, true));
|
||||||
|
|
||||||
|
// Node outside selection
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
testing.expectEqual(false, sel.containsNode(p1, false));
|
||||||
|
testing.expectEqual(false, sel.containsNode(p1, true));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script id=deleteFromDocument>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
const originalText = textNode.textContent;
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStart(textNode, 4);
|
||||||
|
range.setEnd(textNode, 15);
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
sel.deleteFromDocument();
|
||||||
|
|
||||||
|
// Text should be deleted
|
||||||
|
const expectedText = originalText.slice(0, 4) + originalText.slice(15);
|
||||||
|
testing.expectEqual(expectedText, textNode.textContent);
|
||||||
|
|
||||||
|
// Selection should be collapsed at deletion point
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
|
||||||
|
// Restore original text for other tests
|
||||||
|
textNode.textContent = originalText;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=typeProperty>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
// None type
|
||||||
|
sel.removeAllRanges();
|
||||||
|
testing.expectEqual("None", sel.type);
|
||||||
|
|
||||||
|
// Caret type (collapsed)
|
||||||
|
sel.collapse(textNode, 5);
|
||||||
|
testing.expectEqual("Caret", sel.type);
|
||||||
|
|
||||||
|
// Range type (not collapsed)
|
||||||
|
sel.extend(textNode, 10);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=selectAllChildren>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const nested = document.getElementById("nested");
|
||||||
|
const s1 = document.getElementById("s1");
|
||||||
|
const s2 = document.getElementById("s2");
|
||||||
|
|
||||||
|
// Select all children of nested div
|
||||||
|
sel.selectAllChildren(nested);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
|
||||||
|
// Anchor and focus should be on the parent node
|
||||||
|
testing.expectEqual(nested, sel.anchorNode);
|
||||||
|
testing.expectEqual(nested, sel.focusNode);
|
||||||
|
|
||||||
|
// Should start at offset 0 (before first child)
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
|
||||||
|
const childrenCount = nested.childNodes.length;
|
||||||
|
|
||||||
|
// Should end at offset equal to number of children (after last child)
|
||||||
|
testing.expectEqual(childrenCount, sel.focusOffset);
|
||||||
|
|
||||||
|
// Direction should be forward
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
|
||||||
|
// Should not fully contain the parent itself
|
||||||
|
testing.expectEqual(false, sel.containsNode(nested, false));
|
||||||
|
|
||||||
|
// But should partially contain the parent
|
||||||
|
testing.expectEqual(true, sel.containsNode(nested, true));
|
||||||
|
|
||||||
|
// Verify the range
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
testing.expectEqual(nested, range.startContainer);
|
||||||
|
testing.expectEqual(nested, range.endContainer);
|
||||||
|
testing.expectEqual(0, range.startOffset);
|
||||||
|
testing.expectEqual(childrenCount, range.endOffset);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=selectAllChildrenEmpty>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
// Create an empty element
|
||||||
|
const empty = document.createElement("div");
|
||||||
|
document.body.appendChild(empty);
|
||||||
|
|
||||||
|
// Select all children of empty element
|
||||||
|
sel.selectAllChildren(empty);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Caret", sel.type); // Collapsed because no children
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(empty, sel.anchorNode);
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
testing.expectEqual(0, sel.focusOffset);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(empty);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=selectAllChildrenReplacesSelection>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
// Start with an existing selection
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
sel.selectAllChildren(p1);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(p1, sel.anchorNode);
|
||||||
|
|
||||||
|
// selectAllChildren should replace the existing selection
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
sel.selectAllChildren(p2);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(p2, sel.anchorNode);
|
||||||
|
testing.expectEqual(p2, sel.focusNode);
|
||||||
|
|
||||||
|
// Verify old selection is gone
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
testing.expectEqual(p2, range.startContainer);
|
||||||
|
testing.expectEqual(false, p1 == range.startContainer);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=setBaseAndExtent>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
// Forward selection (anchor before focus)
|
||||||
|
sel.setBaseAndExtent(textNode, 4, textNode, 15);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(textNode, sel.focusNode);
|
||||||
|
testing.expectEqual(15, sel.focusOffset);
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
|
||||||
|
// Backward selection (anchor after focus)
|
||||||
|
sel.setBaseAndExtent(textNode, 15, textNode, 4);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(15, sel.anchorOffset);
|
||||||
|
testing.expectEqual(textNode, sel.focusNode);
|
||||||
|
testing.expectEqual(4, sel.focusOffset);
|
||||||
|
testing.expectEqual("backward", sel.direction);
|
||||||
|
|
||||||
|
// Collapsed selection (anchor equals focus)
|
||||||
|
sel.setBaseAndExtent(textNode, 10, textNode, 10);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Caret", sel.type);
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual(10, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
|
||||||
|
// Across different nodes
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
const textNode2 = p2.firstChild;
|
||||||
|
|
||||||
|
sel.setBaseAndExtent(textNode, 4, textNode2, 5);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(textNode2, sel.focusNode);
|
||||||
|
testing.expectEqual(5, sel.focusOffset);
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
|
||||||
|
// Should replace existing selection
|
||||||
|
sel.setBaseAndExtent(textNode, 0, textNode, 3);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
testing.expectEqual(3, sel.focusOffset);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
211
src/browser/tests/window/onerror.html
Normal file
211
src/browser/tests/window/onerror.html
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<script id=onerrorBasicCallback>
|
||||||
|
{
|
||||||
|
let callbackCalled = false;
|
||||||
|
let receivedArgs = null;
|
||||||
|
|
||||||
|
window.onerror = function(message, source, lineno, colno, error) {
|
||||||
|
callbackCalled = true;
|
||||||
|
receivedArgs = { message, source, lineno, colno, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
const err = new Error('Test error');
|
||||||
|
window.reportError(err);
|
||||||
|
|
||||||
|
testing.expectEqual(true, callbackCalled);
|
||||||
|
testing.expectEqual(true, receivedArgs.message.includes('Test error'));
|
||||||
|
testing.expectEqual(err, receivedArgs.error);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorFiveArguments>
|
||||||
|
{
|
||||||
|
let argCount = 0;
|
||||||
|
|
||||||
|
window.onerror = function() {
|
||||||
|
argCount = arguments.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.reportError(new Error('Five args test'));
|
||||||
|
|
||||||
|
// Per WHATWG spec, onerror receives exactly 5 arguments
|
||||||
|
testing.expectEqual(5, argCount);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorReturnTrueCancelsEvent>
|
||||||
|
{
|
||||||
|
let listenerCalled = false;
|
||||||
|
|
||||||
|
window.onerror = function() {
|
||||||
|
return true; // Should cancel the event
|
||||||
|
};
|
||||||
|
|
||||||
|
const listener = function() {
|
||||||
|
listenerCalled = true;
|
||||||
|
};
|
||||||
|
window.addEventListener('error', listener);
|
||||||
|
|
||||||
|
window.reportError(new Error('Should be cancelled'));
|
||||||
|
|
||||||
|
// The event listener should still be called (onerror returning true
|
||||||
|
// only prevents default, not propagation)
|
||||||
|
testing.expectEqual(true, listenerCalled);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
window.removeEventListener('error', listener);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorAndEventListenerBothCalled>
|
||||||
|
{
|
||||||
|
let onerrorCalled = false;
|
||||||
|
let listenerCalled = false;
|
||||||
|
|
||||||
|
window.onerror = function() {
|
||||||
|
onerrorCalled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listener = function() {
|
||||||
|
listenerCalled = true;
|
||||||
|
};
|
||||||
|
window.addEventListener('error', listener);
|
||||||
|
|
||||||
|
window.reportError(new Error('Both should fire'));
|
||||||
|
|
||||||
|
testing.expectEqual(true, onerrorCalled);
|
||||||
|
testing.expectEqual(true, listenerCalled);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
window.removeEventListener('error', listener);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorCalledBeforeEventListener>
|
||||||
|
{
|
||||||
|
let callOrder = [];
|
||||||
|
|
||||||
|
window.onerror = function() {
|
||||||
|
callOrder.push('onerror');
|
||||||
|
};
|
||||||
|
|
||||||
|
const listener = function() {
|
||||||
|
callOrder.push('listener');
|
||||||
|
};
|
||||||
|
window.addEventListener('error', listener);
|
||||||
|
|
||||||
|
window.reportError(new Error('Order test'));
|
||||||
|
|
||||||
|
// onerror should be called before addEventListener handlers
|
||||||
|
testing.expectEqual('onerror', callOrder[0]);
|
||||||
|
testing.expectEqual('listener', callOrder[1]);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
window.removeEventListener('error', listener);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorGetterSetter>
|
||||||
|
{
|
||||||
|
const handler = function() {};
|
||||||
|
|
||||||
|
testing.expectEqual(null, window.onerror);
|
||||||
|
|
||||||
|
window.onerror = handler;
|
||||||
|
testing.expectEqual(handler, window.onerror);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
testing.expectEqual(null, window.onerror);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorWithNonFunction>
|
||||||
|
{
|
||||||
|
// Setting onerror to a non-function should not throw
|
||||||
|
// but should not be stored as the handler
|
||||||
|
window.onerror = "not a function";
|
||||||
|
testing.expectEqual(null, window.onerror);
|
||||||
|
|
||||||
|
window.onerror = {};
|
||||||
|
testing.expectEqual(null, window.onerror);
|
||||||
|
|
||||||
|
window.onerror = 123;
|
||||||
|
testing.expectEqual(null, window.onerror);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorArgumentTypes>
|
||||||
|
{
|
||||||
|
let receivedTypes = null;
|
||||||
|
|
||||||
|
window.onerror = function(message, source, lineno, colno, error) {
|
||||||
|
receivedTypes = {
|
||||||
|
message: typeof message,
|
||||||
|
source: typeof source,
|
||||||
|
lineno: typeof lineno,
|
||||||
|
colno: typeof colno,
|
||||||
|
error: typeof error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.reportError(new Error('Type check'));
|
||||||
|
|
||||||
|
testing.expectEqual('string', receivedTypes.message);
|
||||||
|
testing.expectEqual('string', receivedTypes.source);
|
||||||
|
testing.expectEqual('number', receivedTypes.lineno);
|
||||||
|
testing.expectEqual('number', receivedTypes.colno);
|
||||||
|
testing.expectEqual('object', receivedTypes.error);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorReturnFalseDoesNotCancel>
|
||||||
|
{
|
||||||
|
let eventDefaultPrevented = false;
|
||||||
|
|
||||||
|
window.onerror = function() {
|
||||||
|
return false; // Should NOT cancel the event
|
||||||
|
};
|
||||||
|
|
||||||
|
const listener = function(e) {
|
||||||
|
eventDefaultPrevented = e.defaultPrevented;
|
||||||
|
};
|
||||||
|
window.addEventListener('error', listener);
|
||||||
|
|
||||||
|
window.reportError(new Error('Return false test'));
|
||||||
|
|
||||||
|
testing.expectEqual(false, eventDefaultPrevented);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
window.removeEventListener('error', listener);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=onerrorReturnTruePreventsDefault>
|
||||||
|
{
|
||||||
|
let eventDefaultPrevented = false;
|
||||||
|
|
||||||
|
window.onerror = function() {
|
||||||
|
return true; // Should cancel (prevent default)
|
||||||
|
};
|
||||||
|
|
||||||
|
const listener = function(e) {
|
||||||
|
eventDefaultPrevented = e.defaultPrevented;
|
||||||
|
};
|
||||||
|
window.addEventListener('error', listener);
|
||||||
|
|
||||||
|
window.reportError(new Error('Return true test'));
|
||||||
|
|
||||||
|
testing.expectEqual(true, eventDefaultPrevented);
|
||||||
|
|
||||||
|
window.onerror = null;
|
||||||
|
window.removeEventListener('error', listener);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -38,7 +38,7 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
||||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
|||||||
return self._proto;
|
return self._proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page: *Page) !void {
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,11 +77,10 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
|||||||
|
|
||||||
// Dispatch abort event
|
// Dispatch abort event
|
||||||
const event = try Event.initTrusted("abort", .{}, page);
|
const event = try Event.initTrusted("abort", .{}, page);
|
||||||
const func = if (self._on_abort) |*g| g.local() else null;
|
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchWithFunction(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
func,
|
local.toLocal(self._on_abort),
|
||||||
.{ .context = "abort signal" },
|
.{ .context = "abort signal" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,7 +88,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
|||||||
// Static method to create an already-aborted signal
|
// Static method to create an already-aborted signal
|
||||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||||
const signal = try init(page);
|
const signal = try init(page);
|
||||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
||||||
return signal;
|
return signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +111,13 @@ const ThrowIfAborted = union(enum) {
|
|||||||
undefined: void,
|
undefined: void,
|
||||||
};
|
};
|
||||||
pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted {
|
pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted {
|
||||||
|
const local = page.js.local.?;
|
||||||
|
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
const exception = switch (self._reason) {
|
const exception = switch (self._reason) {
|
||||||
.string => |str| page.js.throw(str),
|
.string => |str| local.throw(str),
|
||||||
.js_val => |js_val| page.js.throw(try js_val.local().toString(.{ .allocator = page.call_arena })),
|
.js_val => |js_val| local.throw(try local.toLocal(js_val).toString(.{ .allocator = page.call_arena })),
|
||||||
.undefined => page.js.throw("AbortError"),
|
.undefined => local.throw("AbortError"),
|
||||||
};
|
};
|
||||||
return .{ .exception = exception };
|
return .{ .exception = exception };
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,11 @@ const TimeoutCallback = struct {
|
|||||||
|
|
||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
||||||
self.signal.abort(.{ .string = "TimeoutError" }, self.page) catch |err| {
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
self.signal.abort(.{ .string = "TimeoutError" }, &ls.local, self.page) catch |err| {
|
||||||
log.warn(.app, "abort signal timeout", .{ .err = err });
|
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||||
};
|
};
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ fn writeBlobParts(
|
|||||||
/// Returns a Promise that resolves with the contents of the blob
|
/// Returns a Promise that resolves with the contents of the blob
|
||||||
/// as binary data contained in an ArrayBuffer.
|
/// as binary data contained in an ArrayBuffer.
|
||||||
pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
|
pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
|
||||||
return page.js.resolvePromise(js.ArrayBuffer{ .values = self._slice });
|
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = self._slice });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReadableStream = @import("streams/ReadableStream.zig");
|
const ReadableStream = @import("streams/ReadableStream.zig");
|
||||||
@@ -219,7 +219,7 @@ pub fn stream(self: *const Blob, page: *Page) !*ReadableStream {
|
|||||||
/// Returns a Promise that resolves with a string containing
|
/// Returns a Promise that resolves with a string containing
|
||||||
/// the contents of the blob, interpreted as UTF-8.
|
/// the contents of the blob, interpreted as UTF-8.
|
||||||
pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
||||||
return page.js.resolvePromise(self._slice);
|
return page.js.local.?.resolvePromise(self._slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extension to Blob; works on Firefox and Safari.
|
/// Extension to Blob; works on Firefox and Safari.
|
||||||
@@ -227,7 +227,7 @@ pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
|||||||
/// Returns a Promise that resolves with a Uint8Array containing
|
/// Returns a Promise that resolves with a Uint8Array containing
|
||||||
/// the contents of the blob as an array of bytes.
|
/// the contents of the blob as an array of bytes.
|
||||||
pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
|
pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
|
||||||
return page.js.resolvePromise(js.TypedArray(u8){ .values = self._slice });
|
return page.js.local.?.resolvePromise(js.TypedArray(u8){ .values = self._slice });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new Blob object which contains data
|
/// Returns a new Blob object which contains data
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub const init: Console = .{};
|
|||||||
|
|
||||||
pub fn trace(_: *const Console, values: []js.Value, page: *Page) !void {
|
pub fn trace(_: *const Console, values: []js.Value, page: *Page) !void {
|
||||||
logger.debug(.js, "console.trace", .{
|
logger.debug(.js, "console.trace", .{
|
||||||
.stack = page.js.stackTrace() catch "???",
|
.stack = page.js.local.?.stackTrace() catch "???",
|
||||||
.args = ValueWriter{ .page = page, .values = values },
|
.args = ValueWriter{ .page = page, .values = values },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ const ValueWriter = struct {
|
|||||||
try writer.print("\n arg({d}): {f}", .{ i, value });
|
try writer.print("\n arg({d}): {f}", .{ i, value });
|
||||||
}
|
}
|
||||||
if (self.include_stack) {
|
if (self.include_stack) {
|
||||||
try writer.print("\n stack: {s}", .{self.page.js.stackTrace() catch |err| @errorName(err) orelse "???"});
|
try writer.print("\n stack: {s}", .{self.page.js.local.?.stackTrace() catch |err| @errorName(err) orelse "???"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const String = @import("../../string.zig").String;
|
||||||
|
|
||||||
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");
|
||||||
@@ -25,13 +27,16 @@ const CustomElementDefinition = @This();
|
|||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
constructor: js.Function.Global,
|
constructor: js.Function.Global,
|
||||||
|
|
||||||
|
// TODO: Make this a Map<String>
|
||||||
observed_attributes: std.StringHashMapUnmanaged(void) = .{},
|
observed_attributes: std.StringHashMapUnmanaged(void) = .{},
|
||||||
|
|
||||||
// For customized built-in elements, this is the element tag they extend (e.g., .button)
|
// For customized built-in elements, this is the element tag they extend (e.g., .button)
|
||||||
// For autonomous custom elements, this is null
|
// For autonomous custom elements, this is null
|
||||||
extends: ?Element.Tag = null,
|
extends: ?Element.Tag = null,
|
||||||
|
|
||||||
pub fn isAttributeObserved(self: *const CustomElementDefinition, name: []const u8) bool {
|
pub fn isAttributeObserved(self: *const CustomElementDefinition, name: String) bool {
|
||||||
return self.observed_attributes.contains(name);
|
return self.observed_attributes.contains(name.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isAutonomous(self: *const CustomElementDefinition) bool {
|
pub fn isAutonomous(self: *const CustomElementDefinition) bool {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self._when_defined.fetchRemove(name)) |entry| {
|
if (self._when_defined.fetchRemove(name)) |entry| {
|
||||||
entry.value.local().resolve("whenDefined", constructor);
|
page.js.toLocal(entry.value).resolve("whenDefined", constructor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,22 +120,23 @@ pub fn upgrade(self: *CustomElementRegistry, root: *Node, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) !js.Promise {
|
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) !js.Promise {
|
||||||
|
const local = page.js.local.?;
|
||||||
if (self._definitions.get(name)) |definition| {
|
if (self._definitions.get(name)) |definition| {
|
||||||
return page.js.resolvePromise(definition.constructor);
|
return local.resolvePromise(definition.constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gop = try self._when_defined.getOrPut(page.arena, name);
|
const gop = try self._when_defined.getOrPut(page.arena, name);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
return gop.value_ptr.local().promise();
|
return local.toLocal(gop.value_ptr.*).promise();
|
||||||
}
|
}
|
||||||
errdefer _ = self._when_defined.remove(name);
|
errdefer _ = self._when_defined.remove(name);
|
||||||
const owned_name = try page.dupeString(name);
|
const owned_name = try page.dupeString(name);
|
||||||
|
|
||||||
const resolver = try page.js.createPromiseResolver().persist();
|
const resolver = local.createPromiseResolver();
|
||||||
gop.key_ptr.* = owned_name;
|
gop.key_ptr.* = owned_name;
|
||||||
gop.value_ptr.* = resolver;
|
gop.value_ptr.* = try resolver.persist();
|
||||||
|
|
||||||
return resolver.local().promise();
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void {
|
fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void {
|
||||||
@@ -174,8 +175,12 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
|
|||||||
page._upgrading_element = node;
|
page._upgrading_element = node;
|
||||||
defer page._upgrading_element = prev_upgrading;
|
defer page._upgrading_element = prev_upgrading;
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
var caught: js.TryCatch.Caught = undefined;
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
_ = definition.constructor.local().newInstance(&caught) catch |err| {
|
_ = ls.toLocal(definition.constructor).newInstance(&caught) catch |err| {
|
||||||
log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err, .caught = caught });
|
log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err, .caught = caught });
|
||||||
return error.CustomElementUpgradeFailed;
|
return error.CustomElementUpgradeFailed;
|
||||||
};
|
};
|
||||||
@@ -183,9 +188,9 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
|
|||||||
// Invoke attributeChangedCallback for existing observed attributes
|
// Invoke attributeChangedCallback for existing observed attributes
|
||||||
var attr_it = custom.asElement().attributeIterator();
|
var attr_it = custom.asElement().attributeIterator();
|
||||||
while (attr_it.next()) |attr| {
|
while (attr_it.next()) |attr| {
|
||||||
const name = attr._name.str();
|
const name = attr._name;
|
||||||
if (definition.isAttributeObserved(name)) {
|
if (definition.isAttributeObserved(name)) {
|
||||||
custom.invokeAttributeChangedCallback(name, null, attr._value.str(), page);
|
custom.invokeAttributeChangedCallback(name, null, attr._value, page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,14 +63,14 @@ pub fn getFilter(self: *const DOMNodeIterator) ?FilterOpts {
|
|||||||
return self._filter._original_filter;
|
return self._filter._original_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||||
var node = self._reference_node;
|
var node = self._reference_node;
|
||||||
var before_node = self._pointer_before_reference_node;
|
var before_node = self._pointer_before_reference_node;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (before_node) {
|
if (before_node) {
|
||||||
before_node = false;
|
before_node = false;
|
||||||
const result = try self.filterNode(node);
|
const result = try self.filterNode(node, page);
|
||||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._reference_node = node;
|
self._reference_node = node;
|
||||||
self._pointer_before_reference_node = false;
|
self._pointer_before_reference_node = false;
|
||||||
@@ -84,7 +84,7 @@ pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
|||||||
}
|
}
|
||||||
node = next.?;
|
node = next.?;
|
||||||
|
|
||||||
const result = try self.filterNode(node);
|
const result = try self.filterNode(node, page);
|
||||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._reference_node = node;
|
self._reference_node = node;
|
||||||
self._pointer_before_reference_node = false;
|
self._pointer_before_reference_node = false;
|
||||||
@@ -94,13 +94,13 @@ pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previousNode(self: *DOMNodeIterator) !?*Node {
|
pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||||
var node = self._reference_node;
|
var node = self._reference_node;
|
||||||
var before_node = self._pointer_before_reference_node;
|
var before_node = self._pointer_before_reference_node;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!before_node) {
|
if (!before_node) {
|
||||||
const result = try self.filterNode(node);
|
const result = try self.filterNode(node, page);
|
||||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._reference_node = node;
|
self._reference_node = node;
|
||||||
self._pointer_before_reference_node = true;
|
self._pointer_before_reference_node = true;
|
||||||
@@ -119,7 +119,7 @@ pub fn previousNode(self: *DOMNodeIterator) !?*Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filterNode(self: *const DOMNodeIterator, node: *Node) !i32 {
|
fn filterNode(self: *const DOMNodeIterator, node: *Node, page: *Page) !i32 {
|
||||||
// First check whatToShow
|
// First check whatToShow
|
||||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||||
return NodeFilter.FILTER_SKIP;
|
return NodeFilter.FILTER_SKIP;
|
||||||
@@ -128,7 +128,7 @@ fn filterNode(self: *const DOMNodeIterator, node: *Node) !i32 {
|
|||||||
// Then check the filter callback
|
// Then check the filter callback
|
||||||
// For NodeIterator, REJECT and SKIP are equivalent - both skip the node
|
// For NodeIterator, REJECT and SKIP are equivalent - both skip the node
|
||||||
// but continue with its descendants
|
// but continue with its descendants
|
||||||
const result = try self._filter.acceptNode(node);
|
const result = try self._filter.acceptNode(node, page.js.local.?);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ pub fn parseFromString(
|
|||||||
@"image/svg+xml",
|
@"image/svg+xml",
|
||||||
}, mime_type) orelse return error.NotSupported;
|
}, mime_type) orelse return error.NotSupported;
|
||||||
|
|
||||||
|
const arena = try page.getArena(.{ .debug = "DOMParser.parseFromString" });
|
||||||
|
defer page.releaseArena(arena);
|
||||||
|
|
||||||
return switch (target_mime) {
|
return switch (target_mime) {
|
||||||
.@"text/html" => {
|
.@"text/html" => {
|
||||||
// Create a new HTMLDocument
|
// Create a new HTMLDocument
|
||||||
@@ -61,7 +64,7 @@ pub fn parseFromString(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse HTML into the document
|
// Parse HTML into the document
|
||||||
var parser = Parser.init(page.arena, doc.asNode(), page);
|
var parser = Parser.init(arena, doc.asNode(), page);
|
||||||
parser.parse(normalized);
|
parser.parse(normalized);
|
||||||
|
|
||||||
if (parser.err) |pe| {
|
if (parser.err) |pe| {
|
||||||
@@ -78,7 +81,7 @@ pub fn parseFromString(
|
|||||||
|
|
||||||
// Parse XML into XMLDocument.
|
// Parse XML into XMLDocument.
|
||||||
const doc_node = doc.asNode();
|
const doc_node = doc.asNode();
|
||||||
var parser = Parser.init(page.arena, doc_node, page);
|
var parser = Parser.init(arena, doc_node, page);
|
||||||
parser.parseXML(html);
|
parser.parseXML(html);
|
||||||
|
|
||||||
if (parser.err) |pe| {
|
if (parser.err) |pe| {
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ pub fn setCurrentNode(self: *DOMTreeWalker, node: *Node) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Navigation methods
|
// Navigation methods
|
||||||
pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
pub fn parentNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current._parent;
|
var node = self._current._parent;
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (n == self._root._parent) {
|
if (n == self._root._parent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -77,11 +77,11 @@ pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
pub fn firstChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current.firstChild();
|
var node = self._current.firstChild();
|
||||||
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
const filter_result = try self.acceptNode(n);
|
const filter_result = try self.acceptNode(n, page);
|
||||||
|
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
@@ -117,11 +117,11 @@ pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
pub fn lastChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current.lastChild();
|
var node = self._current.lastChild();
|
||||||
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
const filter_result = try self.acceptNode(n);
|
const filter_result = try self.acceptNode(n, page);
|
||||||
|
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
@@ -157,10 +157,10 @@ pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previousSibling(self: *DOMTreeWalker) !?*Node {
|
pub fn previousSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self.previousSiblingOrNull(self._current);
|
var node = self.previousSiblingOrNull(self._current);
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -169,10 +169,10 @@ pub fn previousSibling(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextSibling(self: *DOMTreeWalker) !?*Node {
|
pub fn nextSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self.nextSiblingOrNull(self._current);
|
var node = self.nextSiblingOrNull(self._current);
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ pub fn nextSibling(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current;
|
var node = self._current;
|
||||||
while (node != self._root) {
|
while (node != self._root) {
|
||||||
var sibling = self.previousSiblingOrNull(node);
|
var sibling = self.previousSiblingOrNull(node);
|
||||||
@@ -189,7 +189,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
node = sib;
|
node = sib;
|
||||||
|
|
||||||
// Check if this sibling is rejected before descending into it
|
// Check if this sibling is rejected before descending into it
|
||||||
const sib_result = try self.acceptNode(node);
|
const sib_result = try self.acceptNode(node, page);
|
||||||
if (sib_result == NodeFilter.FILTER_REJECT) {
|
if (sib_result == NodeFilter.FILTER_REJECT) {
|
||||||
// Skip this sibling and its descendants entirely
|
// Skip this sibling and its descendants entirely
|
||||||
sibling = self.previousSiblingOrNull(node);
|
sibling = self.previousSiblingOrNull(node);
|
||||||
@@ -204,7 +204,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
while (child) |c| {
|
while (child) |c| {
|
||||||
if (!self.isInSubtree(c)) break;
|
if (!self.isInSubtree(c)) break;
|
||||||
|
|
||||||
const filter_result = try self.acceptNode(c);
|
const filter_result = try self.acceptNode(c, page);
|
||||||
if (filter_result == NodeFilter.FILTER_REJECT) {
|
if (filter_result == NodeFilter.FILTER_REJECT) {
|
||||||
// Skip this child and try its previous sibling
|
// Skip this child and try its previous sibling
|
||||||
child = self.previousSiblingOrNull(c);
|
child = self.previousSiblingOrNull(c);
|
||||||
@@ -220,7 +220,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
node = child.?;
|
node = child.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(node, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parent = node._parent orelse return null;
|
const parent = node._parent orelse return null;
|
||||||
if (try self.acceptNode(parent) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(parent, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = parent;
|
self._current = parent;
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
@@ -241,14 +241,14 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
pub fn nextNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current;
|
var node = self._current;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Try children first (depth-first)
|
// Try children first (depth-first)
|
||||||
if (node.firstChild()) |child| {
|
if (node.firstChild()) |child| {
|
||||||
node = child;
|
node = child;
|
||||||
const filter_result = try self.acceptNode(node);
|
const filter_result = try self.acceptNode(node, page);
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
@@ -271,7 +271,7 @@ pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
|
|
||||||
if (node.nextSibling()) |sibling| {
|
if (node.nextSibling()) |sibling| {
|
||||||
node = sibling;
|
node = sibling;
|
||||||
const filter_result = try self.acceptNode(node);
|
const filter_result = try self.acceptNode(node, page);
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
@@ -293,7 +293,7 @@ pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
fn acceptNode(self: *const DOMTreeWalker, node: *Node) !i32 {
|
fn acceptNode(self: *const DOMTreeWalker, node: *Node, page: *Page) !i32 {
|
||||||
// First check whatToShow
|
// First check whatToShow
|
||||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||||
return NodeFilter.FILTER_SKIP;
|
return NodeFilter.FILTER_SKIP;
|
||||||
@@ -303,7 +303,7 @@ fn acceptNode(self: *const DOMTreeWalker, node: *Node) !i32 {
|
|||||||
// For TreeWalker, REJECT means reject node and its descendants
|
// For TreeWalker, REJECT means reject node and its descendants
|
||||||
// SKIP means skip node but check its descendants
|
// SKIP means skip node but check its descendants
|
||||||
// ACCEPT means accept the node
|
// ACCEPT means accept the node
|
||||||
return try self._filter.acceptNode(node);
|
return try self._filter.acceptNode(node, page.js.local.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isInSubtree(self: *const DOMTreeWalker, node: *Node) bool {
|
fn isInSubtree(self: *const DOMTreeWalker, node: *Node) bool {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const DOMTreeWalker = @import("DOMTreeWalker.zig");
|
|||||||
const DOMNodeIterator = @import("DOMNodeIterator.zig");
|
const DOMNodeIterator = @import("DOMNodeIterator.zig");
|
||||||
const DOMImplementation = @import("DOMImplementation.zig");
|
const DOMImplementation = @import("DOMImplementation.zig");
|
||||||
const StyleSheetList = @import("css/StyleSheetList.zig");
|
const StyleSheetList = @import("css/StyleSheetList.zig");
|
||||||
|
const Selection = @import("Selection.zig");
|
||||||
|
|
||||||
pub const XMLDocument = @import("XMLDocument.zig");
|
pub const XMLDocument = @import("XMLDocument.zig");
|
||||||
pub const HTMLDocument = @import("HTMLDocument.zig");
|
pub const HTMLDocument = @import("HTMLDocument.zig");
|
||||||
@@ -55,6 +56,7 @@ _style_sheets: ?*StyleSheetList = null,
|
|||||||
_write_insertion_point: ?*Node = null,
|
_write_insertion_point: ?*Node = null,
|
||||||
_script_created_parser: ?Parser.Streaming = null,
|
_script_created_parser: ?Parser.Streaming = null,
|
||||||
_adopted_style_sheets: ?js.Object.Global = null,
|
_adopted_style_sheets: ?js.Object.Global = null,
|
||||||
|
_selection: Selection = .init,
|
||||||
|
|
||||||
pub const Type = union(enum) {
|
pub const Type = union(enum) {
|
||||||
generic,
|
generic,
|
||||||
@@ -142,7 +144,7 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement
|
|||||||
|
|
||||||
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(comptime .wrap("is"), .wrap(is_value), page);
|
||||||
try Element.Html.Custom.checkAndAttachBuiltIn(element, page);
|
try Element.Html.Custom.checkAndAttachBuiltIn(element, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,26 +162,26 @@ pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8
|
|||||||
return node.as(Element);
|
return node.as(Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createAttribute(_: *const Document, name: []const u8, page: *Page) !?*Element.Attribute {
|
pub fn createAttribute(_: *const Document, name: String.Global, page: *Page) !?*Element.Attribute {
|
||||||
try Element.Attribute.validateAttributeName(name);
|
try Element.Attribute.validateAttributeName(name.str);
|
||||||
return page._factory.node(Element.Attribute{
|
return page._factory.node(Element.Attribute{
|
||||||
._proto = undefined,
|
._proto = undefined,
|
||||||
._name = try page.dupeString(name),
|
._name = name.str,
|
||||||
._value = "",
|
._value = String.empty,
|
||||||
._element = null,
|
._element = null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: []const u8, page: *Page) !?*Element.Attribute {
|
pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: String.Global, page: *Page) !?*Element.Attribute {
|
||||||
if (std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml") == false) {
|
if (std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml") == false) {
|
||||||
log.warn(.not_implemented, "document.createAttributeNS", .{ .namespace = namespace });
|
log.warn(.not_implemented, "document.createAttributeNS", .{ .namespace = namespace });
|
||||||
}
|
}
|
||||||
|
|
||||||
try Element.Attribute.validateAttributeName(name);
|
try Element.Attribute.validateAttributeName(name.str);
|
||||||
return page._factory.node(Element.Attribute{
|
return page._factory.node(Element.Attribute{
|
||||||
._proto = undefined,
|
._proto = undefined,
|
||||||
._name = try page.dupeString(name),
|
._name = name.str,
|
||||||
._value = "",
|
._value = String.empty,
|
||||||
._element = null,
|
._element = null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -197,7 +199,7 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
|
|||||||
if (self._removed_ids.remove(id)) {
|
if (self._removed_ids.remove(id)) {
|
||||||
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| {
|
||||||
const element_id = el.getAttributeSafe("id") orelse continue;
|
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
|
||||||
if (std.mem.eql(u8, element_id, id)) {
|
if (std.mem.eql(u8, element_id, id)) {
|
||||||
// we ignore this error to keep getElementById easy to call
|
// we ignore this error to keep getElementById easy to call
|
||||||
// if it really failed, then we're out of memory and nothing's
|
// if it really failed, then we're out of memory and nothing's
|
||||||
@@ -276,12 +278,16 @@ pub fn getDocumentElement(self: *Document) ?*Element {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelector(self: *Document, input: []const u8, page: *Page) !?*Element {
|
pub fn getSelection(self: *Document) *Selection {
|
||||||
return Selector.querySelector(self.asNode(), input, page);
|
return &self._selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelectorAll(self: *Document, input: []const u8, page: *Page) !*Selector.List {
|
pub fn querySelector(self: *Document, input: String, page: *Page) !?*Element {
|
||||||
return Selector.querySelectorAll(self.asNode(), input, page);
|
return Selector.querySelector(self.asNode(), input.str(), page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn querySelectorAll(self: *Document, input: String, page: *Page) !*Selector.List {
|
||||||
|
return Selector.querySelectorAll(self.asNode(), input.str(), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getImplementation(_: *const Document) DOMImplementation {
|
pub fn getImplementation(_: *const Document) DOMImplementation {
|
||||||
@@ -642,7 +648,10 @@ pub fn write(self: *Document, text: []const []const u8, page: *Page) !void {
|
|||||||
page._parse_mode = .document_write;
|
page._parse_mode = .document_write;
|
||||||
defer page._parse_mode = previous_parse_mode;
|
defer page._parse_mode = previous_parse_mode;
|
||||||
|
|
||||||
var parser = Parser.init(page.call_arena, fragment_node, page);
|
const arena = try page.getArena(.{ .debug = "Document.write" });
|
||||||
|
defer page.releaseArena(arena);
|
||||||
|
|
||||||
|
var parser = Parser.init(arena, fragment_node, page);
|
||||||
parser.parseFragment(html);
|
parser.parseFragment(html);
|
||||||
|
|
||||||
// Extract children from wrapper HTML element (html5ever wraps fragments)
|
// Extract children from wrapper HTML element (html5ever wraps fragments)
|
||||||
@@ -655,7 +664,7 @@ pub fn write(self: *Document, text: []const []const u8, page: *Page) !void {
|
|||||||
|
|
||||||
var it = if (first.is(Element.Html.Html) == null) fragment_node.childrenIterator() else first.childrenIterator();
|
var it = if (first.is(Element.Html.Html) == null) fragment_node.childrenIterator() else first.childrenIterator();
|
||||||
while (it.next()) |child| {
|
while (it.next()) |child| {
|
||||||
try children_to_insert.append(page.call_arena, child);
|
try children_to_insert.append(arena, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children_to_insert.items.len == 0) {
|
if (children_to_insert.items.len == 0) {
|
||||||
@@ -770,7 +779,7 @@ pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object.Global {
|
|||||||
if (self._adopted_style_sheets) |ass| {
|
if (self._adopted_style_sheets) |ass| {
|
||||||
return ass;
|
return ass;
|
||||||
}
|
}
|
||||||
const js_arr = page.js.newArray(0);
|
const js_arr = page.js.local.?.newArray(0);
|
||||||
const js_obj = js_arr.toObject();
|
const js_obj = js_arr.toObject();
|
||||||
self._adopted_style_sheets = try js_obj.persist();
|
self._adopted_style_sheets = try js_obj.persist();
|
||||||
return self._adopted_style_sheets.?;
|
return self._adopted_style_sheets.?;
|
||||||
@@ -962,6 +971,7 @@ pub const JsApi = struct {
|
|||||||
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, .{});
|
||||||
|
pub const getSelection = bridge.function(Document.getSelection, .{});
|
||||||
pub const getElementsByClassName = bridge.function(Document.getElementsByClassName, .{});
|
pub const getElementsByClassName = bridge.function(Document.getElementsByClassName, .{});
|
||||||
pub const getElementsByName = bridge.function(Document.getElementsByName, .{});
|
pub const getElementsByName = bridge.function(Document.getElementsByName, .{});
|
||||||
pub const adoptNode = bridge.function(Document.adoptNode, .{ .dom_exception = true });
|
pub const adoptNode = bridge.function(Document.adoptNode, .{ .dom_exception = true });
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ pub fn getElementById(self: *DocumentFragment, id: []const u8) ?*Element {
|
|||||||
|
|
||||||
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| {
|
||||||
if (el.getAttributeSafe("id")) |element_id| {
|
if (el.getAttributeSafe(comptime .wrap("id"))) |element_id| {
|
||||||
if (std.mem.eql(u8, element_id, id)) {
|
if (std.mem.eql(u8, element_id, id)) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,129 @@ pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTok
|
|||||||
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);
|
||||||
|
|
||||||
|
/// Better to discriminate it since not directly a pointer int.
|
||||||
|
///
|
||||||
|
/// See `calcAttrListenerKey` to obtain one.
|
||||||
|
const AttrListenerKey = u64;
|
||||||
|
/// Use `getAttrListenerKey` to create a key.
|
||||||
|
pub const AttrListenerLookup = std.AutoHashMapUnmanaged(AttrListenerKey, js.Function.Global);
|
||||||
|
|
||||||
|
/// Enum of known event listeners; increasing the size of it (u7)
|
||||||
|
/// can cause `AttrListenerKey` to behave incorrectly.
|
||||||
|
pub const KnownListener = enum(u7) {
|
||||||
|
abort,
|
||||||
|
animationcancel,
|
||||||
|
animationend,
|
||||||
|
animationiteration,
|
||||||
|
animationstart,
|
||||||
|
auxclick,
|
||||||
|
beforeinput,
|
||||||
|
beforematch,
|
||||||
|
beforetoggle,
|
||||||
|
blur,
|
||||||
|
cancel,
|
||||||
|
canplay,
|
||||||
|
canplaythrough,
|
||||||
|
change,
|
||||||
|
click,
|
||||||
|
close,
|
||||||
|
command,
|
||||||
|
contentvisibilityautostatechange,
|
||||||
|
contextlost,
|
||||||
|
contextmenu,
|
||||||
|
contextrestored,
|
||||||
|
copy,
|
||||||
|
cuechange,
|
||||||
|
cut,
|
||||||
|
dblclick,
|
||||||
|
drag,
|
||||||
|
dragend,
|
||||||
|
dragenter,
|
||||||
|
dragexit,
|
||||||
|
dragleave,
|
||||||
|
dragover,
|
||||||
|
dragstart,
|
||||||
|
drop,
|
||||||
|
durationchange,
|
||||||
|
emptied,
|
||||||
|
ended,
|
||||||
|
@"error",
|
||||||
|
focus,
|
||||||
|
formdata,
|
||||||
|
fullscreenchange,
|
||||||
|
fullscreenerror,
|
||||||
|
gotpointercapture,
|
||||||
|
input,
|
||||||
|
invalid,
|
||||||
|
keydown,
|
||||||
|
keypress,
|
||||||
|
keyup,
|
||||||
|
load,
|
||||||
|
loadeddata,
|
||||||
|
loadedmetadata,
|
||||||
|
loadstart,
|
||||||
|
lostpointercapture,
|
||||||
|
mousedown,
|
||||||
|
mousemove,
|
||||||
|
mouseout,
|
||||||
|
mouseover,
|
||||||
|
mouseup,
|
||||||
|
paste,
|
||||||
|
pause,
|
||||||
|
play,
|
||||||
|
playing,
|
||||||
|
pointercancel,
|
||||||
|
pointerdown,
|
||||||
|
pointerenter,
|
||||||
|
pointerleave,
|
||||||
|
pointermove,
|
||||||
|
pointerout,
|
||||||
|
pointerover,
|
||||||
|
pointerrawupdate,
|
||||||
|
pointerup,
|
||||||
|
progress,
|
||||||
|
ratechange,
|
||||||
|
reset,
|
||||||
|
resize,
|
||||||
|
scroll,
|
||||||
|
scrollend,
|
||||||
|
securitypolicyviolation,
|
||||||
|
seeked,
|
||||||
|
seeking,
|
||||||
|
select,
|
||||||
|
selectionchange,
|
||||||
|
selectstart,
|
||||||
|
slotchange,
|
||||||
|
stalled,
|
||||||
|
submit,
|
||||||
|
@"suspend",
|
||||||
|
timeupdate,
|
||||||
|
toggle,
|
||||||
|
transitioncancel,
|
||||||
|
transitionend,
|
||||||
|
transitionrun,
|
||||||
|
transitionstart,
|
||||||
|
volumechange,
|
||||||
|
waiting,
|
||||||
|
wheel,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Calculates a lookup key (`AttrListenerKey`) to use with `AttrListenerLookup` for an element.
|
||||||
|
/// NEVER use generated key to retrieve a pointer back. Portability is not guaranteed.
|
||||||
|
pub fn calcAttrListenerKey(self: *Element, event_type: KnownListener) AttrListenerKey {
|
||||||
|
// We can use `Element` for the key too; `EventTarget` is strict about
|
||||||
|
// its size and alignment, though.
|
||||||
|
const target = self.asEventTarget();
|
||||||
|
// Check if we have 3 bits available from alignment of 8.
|
||||||
|
lp.assert(@alignOf(@TypeOf(target)) == 8, "createLookupKey: incorrect alignment", .{
|
||||||
|
.event_target_alignment = @alignOf(@TypeOf(target)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ptr = @intFromPtr(target) >> 3;
|
||||||
|
lp.assert(ptr < (1 << 57), "createLookupKey: pointer overflow", .{ .ptr = ptr });
|
||||||
|
return ptr | (@as(u64, @intFromEnum(event_type)) << 57);
|
||||||
|
}
|
||||||
|
|
||||||
pub const Namespace = enum(u8) {
|
pub const Namespace = enum(u8) {
|
||||||
html,
|
html,
|
||||||
svg,
|
svg,
|
||||||
@@ -427,35 +550,35 @@ pub fn setInnerHTML(self: *Element, html: []const u8, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getId(self: *const Element) []const u8 {
|
pub fn getId(self: *const Element) []const u8 {
|
||||||
return self.getAttributeSafe("id") orelse "";
|
return self.getAttributeSafe(comptime .wrap("id")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setId(self: *Element, value: []const u8, page: *Page) !void {
|
pub fn setId(self: *Element, value: []const u8, page: *Page) !void {
|
||||||
return self.setAttributeSafe("id", value, page);
|
return self.setAttributeSafe(comptime .wrap("id"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSlot(self: *const Element) []const u8 {
|
pub fn getSlot(self: *const Element) []const u8 {
|
||||||
return self.getAttributeSafe("slot") orelse "";
|
return self.getAttributeSafe(comptime .wrap("slot")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void {
|
pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void {
|
||||||
return self.setAttributeSafe("slot", value, page);
|
return self.setAttributeSafe(comptime .wrap("slot"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDir(self: *const Element) []const u8 {
|
pub fn getDir(self: *const Element) []const u8 {
|
||||||
return self.getAttributeSafe("dir") orelse "";
|
return self.getAttributeSafe(comptime .wrap("dir")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setDir(self: *Element, value: []const u8, page: *Page) !void {
|
pub fn setDir(self: *Element, value: []const u8, page: *Page) !void {
|
||||||
return self.setAttributeSafe("dir", value, page);
|
return self.setAttributeSafe(comptime .wrap("dir"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getClassName(self: *const Element) []const u8 {
|
pub fn getClassName(self: *const Element) []const u8 {
|
||||||
return self.getAttributeSafe("class") orelse "";
|
return self.getAttributeSafe(comptime .wrap("class")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setClassName(self: *Element, value: []const u8, page: *Page) !void {
|
pub fn setClassName(self: *Element, value: []const u8, page: *Page) !void {
|
||||||
return self.setAttributeSafe("class", value, page);
|
return self.setAttributeSafe(comptime .wrap("class"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeIterator(self: *Element) Attribute.InnerIterator {
|
pub fn attributeIterator(self: *Element) Attribute.InnerIterator {
|
||||||
@@ -463,7 +586,7 @@ pub fn attributeIterator(self: *Element) Attribute.InnerIterator {
|
|||||||
return attributes.iterator();
|
return attributes.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAttribute(self: *const Element, name: []const u8, page: *Page) !?[]const u8 {
|
pub fn getAttribute(self: *const Element, name: String, page: *Page) !?String {
|
||||||
const attributes = self._attributes orelse return null;
|
const attributes = self._attributes orelse return null;
|
||||||
return attributes.get(name, page);
|
return attributes.get(name, page);
|
||||||
}
|
}
|
||||||
@@ -472,9 +595,9 @@ pub fn getAttribute(self: *const Element, name: []const u8, page: *Page) !?[]con
|
|||||||
pub fn getAttributeNS(
|
pub fn getAttributeNS(
|
||||||
self: *const Element,
|
self: *const Element,
|
||||||
maybe_namespace: ?[]const u8,
|
maybe_namespace: ?[]const u8,
|
||||||
local_name: []const u8,
|
local_name: String,
|
||||||
page: *Page,
|
page: *Page,
|
||||||
) !?[]const u8 {
|
) !?String {
|
||||||
if (maybe_namespace) |namespace| {
|
if (maybe_namespace) |namespace| {
|
||||||
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
|
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
|
||||||
log.warn(.not_implemented, "Element.getAttributeNS", .{ .namespace = namespace });
|
log.warn(.not_implemented, "Element.getAttributeNS", .{ .namespace = namespace });
|
||||||
@@ -484,18 +607,18 @@ pub fn getAttributeNS(
|
|||||||
return self.getAttribute(local_name, page);
|
return self.getAttribute(local_name, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAttributeSafe(self: *const Element, name: []const u8) ?[]const u8 {
|
pub fn getAttributeSafe(self: *const Element, name: String) ?[]const u8 {
|
||||||
const attributes = self._attributes orelse return null;
|
const attributes = self._attributes orelse return null;
|
||||||
return attributes.getSafe(name);
|
return attributes.getSafe(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasAttribute(self: *const Element, name: []const u8, page: *Page) !bool {
|
pub fn hasAttribute(self: *const Element, name: String, page: *Page) !bool {
|
||||||
const attributes = self._attributes orelse return false;
|
const attributes = self._attributes orelse return false;
|
||||||
const value = try attributes.get(name, page);
|
const value = try attributes.get(name, page);
|
||||||
return value != null;
|
return value != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasAttributeSafe(self: *const Element, name: []const u8) bool {
|
pub fn hasAttributeSafe(self: *const Element, name: String) bool {
|
||||||
const attributes = self._attributes orelse return false;
|
const attributes = self._attributes orelse return false;
|
||||||
return attributes.hasSafe(name);
|
return attributes.hasSafe(name);
|
||||||
}
|
}
|
||||||
@@ -505,12 +628,12 @@ pub fn hasAttributes(self: *const Element) bool {
|
|||||||
return attributes.isEmpty() == false;
|
return attributes.isEmpty() == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAttributeNode(self: *Element, name: []const u8, page: *Page) !?*Attribute {
|
pub fn getAttributeNode(self: *Element, name: String, page: *Page) !?*Attribute {
|
||||||
const attributes = self._attributes orelse return null;
|
const attributes = self._attributes orelse return null;
|
||||||
return attributes.getAttribute(name, self, page);
|
return attributes.getAttribute(name, self, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAttribute(self: *Element, name: []const u8, value: []const u8, page: *Page) !void {
|
pub fn setAttribute(self: *Element, name: String, value: String, page: *Page) !void {
|
||||||
try Attribute.validateAttributeName(name);
|
try Attribute.validateAttributeName(name);
|
||||||
const attributes = try self.getOrCreateAttributeList(page);
|
const attributes = try self.getOrCreateAttributeList(page);
|
||||||
_ = try attributes.put(name, value, self, page);
|
_ = try attributes.put(name, value, self, page);
|
||||||
@@ -533,10 +656,10 @@ pub fn setAttributeNS(
|
|||||||
qualified_name[idx + 1 ..]
|
qualified_name[idx + 1 ..]
|
||||||
else
|
else
|
||||||
qualified_name;
|
qualified_name;
|
||||||
return self.setAttribute(local_name, value, page);
|
return self.setAttribute(.wrap(local_name), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAttributeSafe(self: *Element, name: []const u8, value: []const u8, page: *Page) !void {
|
pub fn setAttributeSafe(self: *Element, name: String, value: String, page: *Page) !void {
|
||||||
const attributes = try self.getOrCreateAttributeList(page);
|
const attributes = try self.getOrCreateAttributeList(page);
|
||||||
_ = try attributes.putSafe(name, value, self, page);
|
_ = try attributes.putSafe(name, value, self, page);
|
||||||
}
|
}
|
||||||
@@ -607,19 +730,19 @@ pub fn setAttributeNode(self: *Element, attr: *Attribute, page: *Page) !?*Attrib
|
|||||||
return attributes.putAttribute(attr, self, page);
|
return attributes.putAttribute(attr, self, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeAttribute(self: *Element, name: []const u8, page: *Page) !void {
|
pub fn removeAttribute(self: *Element, name: String, page: *Page) !void {
|
||||||
const attributes = self._attributes orelse return;
|
const attributes = self._attributes orelse return;
|
||||||
return attributes.delete(name, self, page);
|
return attributes.delete(name, self, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggleAttribute(self: *Element, name: []const u8, force: ?bool, page: *Page) !bool {
|
pub fn toggleAttribute(self: *Element, name: String, force: ?bool, page: *Page) !bool {
|
||||||
try Attribute.validateAttributeName(name);
|
try Attribute.validateAttributeName(name);
|
||||||
const has = try self.hasAttribute(name, page);
|
const has = try self.hasAttribute(name, page);
|
||||||
|
|
||||||
const should_add = force orelse !has;
|
const should_add = force orelse !has;
|
||||||
|
|
||||||
if (should_add and !has) {
|
if (should_add and !has) {
|
||||||
try self.setAttribute(name, "", page);
|
try self.setAttribute(name, String.empty, page);
|
||||||
return true;
|
return true;
|
||||||
} else if (!should_add and has) {
|
} else if (!should_add and has) {
|
||||||
try self.removeAttribute(name, page);
|
try self.removeAttribute(name, page);
|
||||||
@@ -666,7 +789,7 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
|||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
|
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
|
||||||
._element = self,
|
._element = self,
|
||||||
._attribute_name = "class",
|
._attribute_name = comptime .wrap("class"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return gop.value_ptr.*;
|
return gop.value_ptr.*;
|
||||||
@@ -677,7 +800,7 @@ pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
|||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
|
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
|
||||||
._element = self,
|
._element = self,
|
||||||
._attribute_name = "rel",
|
._attribute_name = comptime .wrap("rel"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return gop.value_ptr.*;
|
return gop.value_ptr.*;
|
||||||
@@ -919,10 +1042,10 @@ fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, heigh
|
|||||||
if (width == 5.0) width = 1920.0;
|
if (width == 5.0) width = 1920.0;
|
||||||
if (height == 5.0) height = 100_000_000.0;
|
if (height == 5.0) height = 100_000_000.0;
|
||||||
} else if (tag == .img or tag == .iframe) {
|
} else if (tag == .img or tag == .iframe) {
|
||||||
if (self.getAttributeSafe("width")) |w| {
|
if (self.getAttributeSafe(comptime .wrap("width"))) |w| {
|
||||||
width = std.fmt.parseFloat(f64, w) catch width;
|
width = std.fmt.parseFloat(f64, w) catch width;
|
||||||
}
|
}
|
||||||
if (self.getAttributeSafe("height")) |h| {
|
if (self.getAttributeSafe(comptime .wrap("height"))) |h| {
|
||||||
height = std.fmt.parseFloat(f64, h) catch height;
|
height = std.fmt.parseFloat(f64, h) catch height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ pub const JsApi = struct {
|
|||||||
pub const applets = bridge.accessor(HTMLDocument.getApplets, null, .{});
|
pub const applets = bridge.accessor(HTMLDocument.getApplets, null, .{});
|
||||||
pub const plugins = bridge.accessor(HTMLDocument.getEmbeds, null, .{});
|
pub const plugins = bridge.accessor(HTMLDocument.getEmbeds, null, .{});
|
||||||
pub const currentScript = bridge.accessor(HTMLDocument.getCurrentScript, null, .{});
|
pub const currentScript = bridge.accessor(HTMLDocument.getCurrentScript, null, .{});
|
||||||
pub const location = bridge.accessor(HTMLDocument.getLocation, HTMLDocument.setLocation, .{ .cache = "location" });
|
pub const location = bridge.accessor(HTMLDocument.getLocation, HTMLDocument.setLocation, .{});
|
||||||
pub const all = bridge.accessor(HTMLDocument.getAll, null, .{});
|
pub const all = bridge.accessor(HTMLDocument.getAll, null, .{});
|
||||||
pub const cookie = bridge.accessor(HTMLDocument.getCookie, HTMLDocument.setCookie, .{});
|
pub const cookie = bridge.accessor(HTMLDocument.getCookie, HTMLDocument.setCookie, .{});
|
||||||
pub const doctype = bridge.accessor(HTMLDocument.getDocType, null, .{});
|
pub const doctype = bridge.accessor(HTMLDocument.getDocType, null, .{});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub fn getLength(_: *const History, page: *Page) u32 {
|
|||||||
|
|
||||||
pub fn getState(_: *const History, page: *Page) !?js.Value {
|
pub fn getState(_: *const History, page: *Page) !?js.Value {
|
||||||
if (page._session.navigation.getCurrentEntry()._state.value) |state| {
|
if (page._session.navigation.getCurrentEntry()._state.value) |state| {
|
||||||
const value = try page.js.parseJSON(state);
|
const value = try page.js.local.?.parseJSON(state);
|
||||||
return value;
|
return value;
|
||||||
} else return null;
|
} else return null;
|
||||||
}
|
}
|
||||||
@@ -81,11 +81,10 @@ fn goInner(delta: i32, page: *Page) !void {
|
|||||||
if (try page.isSameOrigin(url)) {
|
if (try page.isSameOrigin(url)) {
|
||||||
const event = try PopStateEvent.initTrusted("popstate", .{ .state = entry._state.value }, page);
|
const event = try PopStateEvent.initTrusted("popstate", .{ .state = entry._state.value }, page);
|
||||||
|
|
||||||
const func = if (page.window._on_popstate) |*g| g.local() else null;
|
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchWithFunction(
|
||||||
page.window.asEventTarget(),
|
page.window.asEventTarget(),
|
||||||
event.asEvent(),
|
event.asEvent(),
|
||||||
func,
|
page.js.toLocal(page.window._on_popstate),
|
||||||
.{ .context = "Pop State" },
|
.{ .context = "Pop State" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,7 +246,12 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
|
|||||||
|
|
||||||
const entries = try self.takeRecords(page);
|
const entries = try self.takeRecords(page);
|
||||||
var caught: js.TryCatch.Caught = undefined;
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
self._callback.local().tryCall(void, .{ entries, self }, &caught) catch |err| {
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
ls.toLocal(self._callback).tryCall(void, .{ entries, self }, &caught) catch |err| {
|
||||||
log.err(.page, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
|
log.err(.page, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ const PostMessageCallback = struct {
|
|||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||||
defer self.deinit();
|
defer self.deinit();
|
||||||
|
const page = self.page;
|
||||||
|
|
||||||
if (self.port._closed) {
|
if (self.port._closed) {
|
||||||
return null;
|
return null;
|
||||||
@@ -125,16 +126,19 @@ const PostMessageCallback = struct {
|
|||||||
.data = self.message,
|
.data = self.message,
|
||||||
.origin = "",
|
.origin = "",
|
||||||
.source = null,
|
.source = null,
|
||||||
}, self.page) catch |err| {
|
}, page) catch |err| {
|
||||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const func = if (self.port._on_message) |*g| g.local() else null;
|
var ls: js.Local.Scope = undefined;
|
||||||
self.page._event_manager.dispatchWithFunction(
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
page._event_manager.dispatchWithFunction(
|
||||||
self.port.asEventTarget(),
|
self.port.asEventTarget(),
|
||||||
event.asEvent(),
|
event.asEvent(),
|
||||||
func,
|
ls.toLocal(self.port._on_message),
|
||||||
.{ .context = "MessagePort message" },
|
.{ .context = "MessagePort message" },
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const String = @import("../../string.zig").String;
|
||||||
|
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
@@ -109,8 +111,8 @@ pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord {
|
|||||||
pub fn notifyAttributeChange(
|
pub fn notifyAttributeChange(
|
||||||
self: *MutationObserver,
|
self: *MutationObserver,
|
||||||
target: *Element,
|
target: *Element,
|
||||||
attribute_name: []const u8,
|
attribute_name: String,
|
||||||
old_value: ?[]const u8,
|
old_value: ?String,
|
||||||
page: *Page,
|
page: *Page,
|
||||||
) !void {
|
) !void {
|
||||||
const target_node = target.asNode();
|
const target_node = target.asNode();
|
||||||
@@ -129,7 +131,7 @@ pub fn notifyAttributeChange(
|
|||||||
}
|
}
|
||||||
if (obs.options.attributeFilter) |filter| {
|
if (obs.options.attributeFilter) |filter| {
|
||||||
for (filter) |name| {
|
for (filter) |name| {
|
||||||
if (std.mem.eql(u8, name, attribute_name)) {
|
if (attribute_name.eqlSlice(name)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -140,9 +142,9 @@ pub fn notifyAttributeChange(
|
|||||||
const record = try page._factory.create(MutationRecord{
|
const record = try page._factory.create(MutationRecord{
|
||||||
._type = .attributes,
|
._type = .attributes,
|
||||||
._target = target_node,
|
._target = target_node,
|
||||||
._attribute_name = try page.arena.dupe(u8, attribute_name),
|
._attribute_name = try page.arena.dupe(u8, attribute_name.str()),
|
||||||
._old_value = if (obs.options.attributeOldValue and old_value != null)
|
._old_value = if (obs.options.attributeOldValue and old_value != null)
|
||||||
try page.arena.dupe(u8, old_value.?)
|
try page.arena.dupe(u8, old_value.?.str())
|
||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
._added_nodes = &.{},
|
._added_nodes = &.{},
|
||||||
@@ -248,8 +250,12 @@ pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
|
|||||||
// Take a copy of the records and clear the list before calling callback
|
// Take a copy of the records and clear the list before calling callback
|
||||||
// This ensures mutations triggered during the callback go into a fresh list
|
// This ensures mutations triggered during the callback go into a fresh list
|
||||||
const records = try self.takeRecords(page);
|
const records = try self.takeRecords(page);
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
var caught: js.TryCatch.Caught = undefined;
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
self._callback.local().tryCall(void, .{ records, self }, &caught) catch |err| {
|
ls.toLocal(self._callback).tryCall(void, .{ records, self }, &caught) catch |err| {
|
||||||
log.err(.page, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
|
log.err(.page, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const String = @import("../../string.zig").String;
|
||||||
|
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
const reflect = @import("../reflect.zig");
|
const reflect = @import("../reflect.zig");
|
||||||
@@ -268,7 +269,7 @@ pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!vo
|
|||||||
.document => {},
|
.document => {},
|
||||||
.document_type => {},
|
.document_type => {},
|
||||||
.document_fragment => {},
|
.document_fragment => {},
|
||||||
.attribute => |attr| try writer.writeAll(attr._value),
|
.attribute => |attr| try writer.writeAll(attr._value.str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +298,7 @@ pub fn setTextContent(self: *Node, data: []const u8, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
return frag.replaceChildren(&.{.{ .text = data }}, page);
|
return frag.replaceChildren(&.{.{ .text = data }}, page);
|
||||||
},
|
},
|
||||||
.attribute => |attr| return attr.setValue(data, page),
|
.attribute => |attr| return attr.setValue(.wrap(data), page),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +314,7 @@ pub fn getNodeName(self: *const Node, buf: []u8) []const u8 {
|
|||||||
.document => "#document",
|
.document => "#document",
|
||||||
.document_type => |dt| dt.getName(),
|
.document_type => |dt| dt.getName(),
|
||||||
.document_fragment => "#document-fragment",
|
.document_fragment => "#document-fragment",
|
||||||
.attribute => |attr| attr._name,
|
.attribute => |attr| attr._name.str(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +560,7 @@ pub fn replaceChild(self: *Node, new_child: *Node, old_child: *Node, page: *Page
|
|||||||
pub fn getNodeValue(self: *const Node) ?[]const u8 {
|
pub fn getNodeValue(self: *const Node) ?[]const u8 {
|
||||||
return switch (self._type) {
|
return switch (self._type) {
|
||||||
.cdata => |c| c.getData(),
|
.cdata => |c| c.getData(),
|
||||||
.attribute => |attr| attr._value,
|
.attribute => |attr| attr._value.str(),
|
||||||
.element => null,
|
.element => null,
|
||||||
.document => null,
|
.document => null,
|
||||||
.document_type => null,
|
.document_type => null,
|
||||||
@@ -567,9 +568,9 @@ pub fn getNodeValue(self: *const Node) ?[]const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setNodeValue(self: *const Node, value: ?[]const u8, page: *Page) !void {
|
pub fn setNodeValue(self: *const Node, value: ?String, page: *Page) !void {
|
||||||
switch (self._type) {
|
switch (self._type) {
|
||||||
.cdata => |c| try c.setData(value, page),
|
.cdata => |c| try c.setData(if (value) |v| v.str() else null, page),
|
||||||
.attribute => |attr| try attr.setValue(value, page),
|
.attribute => |attr| try attr.setValue(value, page),
|
||||||
.element => {},
|
.element => {},
|
||||||
.document => {},
|
.document => {},
|
||||||
@@ -910,7 +911,7 @@ pub const JsApi = struct {
|
|||||||
return buf.written();
|
return buf.written();
|
||||||
},
|
},
|
||||||
.cdata => |cdata| return cdata.getData(),
|
.cdata => |cdata| return cdata.getData(),
|
||||||
.attribute => |attr| return attr._value,
|
.attribute => |attr| return attr._value.str(),
|
||||||
.document => return null,
|
.document => return null,
|
||||||
.document_type => return null,
|
.document_type => return null,
|
||||||
.document_fragment => return null,
|
.document_fragment => return null,
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ pub const SHOW_DOCUMENT_TYPE: u32 = 0x200;
|
|||||||
pub const SHOW_DOCUMENT_FRAGMENT: u32 = 0x400;
|
pub const SHOW_DOCUMENT_FRAGMENT: u32 = 0x400;
|
||||||
pub const SHOW_NOTATION: u32 = 0x800;
|
pub const SHOW_NOTATION: u32 = 0x800;
|
||||||
|
|
||||||
pub fn acceptNode(self: *const NodeFilter, node: *Node) !i32 {
|
pub fn acceptNode(self: *const NodeFilter, node: *Node, local: *const js.Local) !i32 {
|
||||||
const func = self._func orelse return FILTER_ACCEPT;
|
const func = self._func orelse return FILTER_ACCEPT;
|
||||||
return func.local().call(i32, .{node});
|
return local.toLocal(func).call(i32, .{node});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shouldShow(node: *const Node, what_to_show: u32) bool {
|
pub fn shouldShow(node: *const Node, what_to_show: u32) bool {
|
||||||
|
|||||||
@@ -362,10 +362,10 @@ pub const Mark = struct {
|
|||||||
|
|
||||||
pub const Measure = struct {
|
pub const Measure = struct {
|
||||||
_proto: *Entry,
|
_proto: *Entry,
|
||||||
_detail: ?js.Object.Global,
|
_detail: ?js.Value.Global,
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
detail: ?js.Object = null,
|
detail: ?js.Value = null,
|
||||||
start: ?TimestampOrMark,
|
start: ?TimestampOrMark,
|
||||||
end: ?TimestampOrMark,
|
end: ?TimestampOrMark,
|
||||||
duration: ?f64 = null,
|
duration: ?f64 = null,
|
||||||
@@ -378,7 +378,7 @@ pub const Measure = struct {
|
|||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
maybe_detail: ?js.Object,
|
maybe_detail: ?js.Value,
|
||||||
start_timestamp: f64,
|
start_timestamp: f64,
|
||||||
end_timestamp: f64,
|
end_timestamp: f64,
|
||||||
maybe_duration: ?f64,
|
maybe_duration: ?f64,
|
||||||
@@ -405,7 +405,7 @@ pub const Measure = struct {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDetail(self: *const Measure) ?js.Object.Global {
|
pub fn getDetail(self: *const Measure) ?js.Value.Global {
|
||||||
return self._detail;
|
return self._detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,13 @@ pub inline fn hasRecords(self: *const PerformanceObserver) bool {
|
|||||||
/// Runs the PerformanceObserver's callback with records; emptying it out.
|
/// Runs the PerformanceObserver's callback with records; emptying it out.
|
||||||
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
|
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
|
||||||
const records = try self.takeRecords(page);
|
const records = try self.takeRecords(page);
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
var caught: js.TryCatch.Caught = undefined;
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
self._callback.local().tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
|
ls.toLocal(self._callback).tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
|
||||||
log.err(.page, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
|
log.err(.page, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
426
src/browser/webapi/Selection.zig
Normal file
426
src/browser/webapi/Selection.zig
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
|
||||||
|
const js = @import("../js/js.zig");
|
||||||
|
const Page = @import("../Page.zig");
|
||||||
|
const Range = @import("Range.zig");
|
||||||
|
const AbstractRange = @import("AbstractRange.zig");
|
||||||
|
const Node = @import("Node.zig");
|
||||||
|
|
||||||
|
/// https://w3c.github.io/selection-api/
|
||||||
|
const Selection = @This();
|
||||||
|
|
||||||
|
pub const SelectionDirection = enum { backward, forward, none };
|
||||||
|
|
||||||
|
_range: ?*Range = null,
|
||||||
|
_direction: SelectionDirection = .none,
|
||||||
|
|
||||||
|
pub const init: Selection = .{};
|
||||||
|
|
||||||
|
fn isInTree(self: *const Selection) bool {
|
||||||
|
if (self._range == null) return false;
|
||||||
|
const anchor_node = self.getAnchorNode() orelse return false;
|
||||||
|
const focus_node = self.getFocusNode() orelse return false;
|
||||||
|
return anchor_node.isConnected() and focus_node.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getAnchorNode(self: *const Selection) ?*Node {
|
||||||
|
const range = self._range orelse return null;
|
||||||
|
|
||||||
|
const node = switch (self._direction) {
|
||||||
|
.backward => range.asAbstractRange().getEndContainer(),
|
||||||
|
.forward, .none => range.asAbstractRange().getStartContainer(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return if (node.isConnected()) node else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getAnchorOffset(self: *const Selection) u32 {
|
||||||
|
const range = self._range orelse return 0;
|
||||||
|
|
||||||
|
const anchor_node = self.getAnchorNode() orelse return 0;
|
||||||
|
if (!anchor_node.isConnected()) return 0;
|
||||||
|
|
||||||
|
return switch (self._direction) {
|
||||||
|
.backward => range.asAbstractRange().getEndOffset(),
|
||||||
|
.forward, .none => range.asAbstractRange().getStartOffset(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDirection(self: *const Selection) []const u8 {
|
||||||
|
return @tagName(self._direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFocusNode(self: *const Selection) ?*Node {
|
||||||
|
const range = self._range orelse return null;
|
||||||
|
|
||||||
|
const node = switch (self._direction) {
|
||||||
|
.backward => range.asAbstractRange().getStartContainer(),
|
||||||
|
.forward, .none => range.asAbstractRange().getEndContainer(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return if (node.isConnected()) node else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFocusOffset(self: *const Selection) u32 {
|
||||||
|
const range = self._range orelse return 0;
|
||||||
|
const focus_node = self.getFocusNode() orelse return 0;
|
||||||
|
if (!focus_node.isConnected()) return 0;
|
||||||
|
|
||||||
|
return switch (self._direction) {
|
||||||
|
.backward => range.asAbstractRange().getStartOffset(),
|
||||||
|
.forward, .none => range.asAbstractRange().getEndOffset(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getIsCollapsed(self: *const Selection) bool {
|
||||||
|
const range = self._range orelse return true;
|
||||||
|
return range.asAbstractRange().getCollapsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRangeCount(self: *const Selection) u32 {
|
||||||
|
if (self._range == null) return 0;
|
||||||
|
if (!self.isInTree()) return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getType(self: *const Selection) []const u8 {
|
||||||
|
if (self._range == null) return "None";
|
||||||
|
if (!self.isInTree()) return "None";
|
||||||
|
if (self.getIsCollapsed()) return "Caret";
|
||||||
|
return "Range";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addRange(self: *Selection, range: *Range) !void {
|
||||||
|
if (self._range != null) return;
|
||||||
|
self._range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeRange(self: *Selection, range: *Range) !void {
|
||||||
|
if (self._range == range) {
|
||||||
|
self._range = null;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return error.NotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeAllRanges(self: *Selection) void {
|
||||||
|
self._range = null;
|
||||||
|
self._direction = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
||||||
|
const range = self._range orelse return;
|
||||||
|
|
||||||
|
const abstract = range.asAbstractRange();
|
||||||
|
const last_node = abstract.getEndContainer();
|
||||||
|
const last_offset = abstract.getEndOffset();
|
||||||
|
|
||||||
|
const new_range = try Range.init(page);
|
||||||
|
try new_range.setStart(last_node, last_offset);
|
||||||
|
try new_range.setEnd(last_node, last_offset);
|
||||||
|
|
||||||
|
self._range = new_range;
|
||||||
|
self._direction = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
||||||
|
const range = self._range orelse return;
|
||||||
|
|
||||||
|
const abstract = range.asAbstractRange();
|
||||||
|
const first_node = abstract.getStartContainer();
|
||||||
|
const first_offset = abstract.getStartOffset();
|
||||||
|
|
||||||
|
const new_range = try Range.init(page);
|
||||||
|
try new_range.setStart(first_node, first_offset);
|
||||||
|
try new_range.setEnd(first_node, first_offset);
|
||||||
|
|
||||||
|
self._range = new_range;
|
||||||
|
self._direction = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn containsNode(self: *const Selection, node: *Node, partial: bool) !bool {
|
||||||
|
const range = self._range orelse return false;
|
||||||
|
|
||||||
|
if (partial) {
|
||||||
|
if (range.intersectsNode(node)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const abstract = range.asAbstractRange();
|
||||||
|
if (abstract.getStartContainer() == node or abstract.getEndContainer() == node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = node.parentNode() orelse return false;
|
||||||
|
const offset = parent.getChildIndex(node) orelse return false;
|
||||||
|
const start_cmp = range.comparePoint(parent, offset) catch return false;
|
||||||
|
const end_cmp = range.comparePoint(parent, offset + 1) catch return false;
|
||||||
|
|
||||||
|
if (start_cmp <= 0 and end_cmp >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteFromDocument(self: *Selection, page: *Page) !void {
|
||||||
|
const range = self._range orelse return;
|
||||||
|
|
||||||
|
try range.deleteContents(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
||||||
|
const range = self._range orelse return error.InvalidState;
|
||||||
|
const offset = _offset orelse 0;
|
||||||
|
|
||||||
|
if (offset > node.getLength()) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const old_anchor = switch (self._direction) {
|
||||||
|
.backward => range.asAbstractRange().getEndContainer(),
|
||||||
|
.forward, .none => range.asAbstractRange().getStartContainer(),
|
||||||
|
};
|
||||||
|
const old_anchor_offset = switch (self._direction) {
|
||||||
|
.backward => range.asAbstractRange().getEndOffset(),
|
||||||
|
.forward, .none => range.asAbstractRange().getStartOffset(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const new_range = try Range.init(page);
|
||||||
|
|
||||||
|
const cmp = AbstractRange.compareBoundaryPoints(node, offset, old_anchor, old_anchor_offset);
|
||||||
|
switch (cmp) {
|
||||||
|
.before => {
|
||||||
|
try new_range.setStart(node, offset);
|
||||||
|
try new_range.setEnd(old_anchor, old_anchor_offset);
|
||||||
|
self._direction = .backward;
|
||||||
|
},
|
||||||
|
.after => {
|
||||||
|
try new_range.setStart(old_anchor, old_anchor_offset);
|
||||||
|
try new_range.setEnd(node, offset);
|
||||||
|
self._direction = .forward;
|
||||||
|
},
|
||||||
|
.equal => {
|
||||||
|
try new_range.setStart(old_anchor, old_anchor_offset);
|
||||||
|
try new_range.setEnd(old_anchor, old_anchor_offset);
|
||||||
|
self._direction = .none;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self._range = new_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRangeAt(self: *Selection, index: u32) !*Range {
|
||||||
|
if (index != 0) return error.IndexSizeError;
|
||||||
|
if (!self.isInTree()) return error.IndexSizeError;
|
||||||
|
const range = self._range orelse return error.IndexSizeError;
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModifyAlter = enum {
|
||||||
|
move,
|
||||||
|
extend,
|
||||||
|
|
||||||
|
pub fn fromString(str: []const u8) ?ModifyAlter {
|
||||||
|
return std.meta.stringToEnum(ModifyAlter, str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModifyDirection = enum {
|
||||||
|
forward,
|
||||||
|
backward,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
|
||||||
|
pub fn fromString(str: []const u8) ?ModifyDirection {
|
||||||
|
return std.meta.stringToEnum(ModifyDirection, str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModifyGranularity = enum {
|
||||||
|
character,
|
||||||
|
word,
|
||||||
|
line,
|
||||||
|
paragraph,
|
||||||
|
lineboundary,
|
||||||
|
// Firefox doesn't implement:
|
||||||
|
// - sentence
|
||||||
|
// - paragraph
|
||||||
|
// - sentenceboundary
|
||||||
|
// - paragraphboundary
|
||||||
|
// - documentboundary
|
||||||
|
// so we won't either for now.
|
||||||
|
|
||||||
|
pub fn fromString(str: []const u8) ?ModifyGranularity {
|
||||||
|
return std.meta.stringToEnum(ModifyGranularity, str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn modify(
|
||||||
|
self: *Selection,
|
||||||
|
alter_str: []const u8,
|
||||||
|
direction_str: []const u8,
|
||||||
|
granularity_str: []const u8,
|
||||||
|
) !void {
|
||||||
|
const alter = ModifyAlter.fromString(alter_str) orelse return error.InvalidParams;
|
||||||
|
const direction = ModifyDirection.fromString(direction_str) orelse return error.InvalidParams;
|
||||||
|
const granularity = ModifyGranularity.fromString(granularity_str) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
_ = self._range orelse return;
|
||||||
|
|
||||||
|
log.warn(.not_implemented, "Selection.modify", .{
|
||||||
|
.alter = alter,
|
||||||
|
.direction = direction,
|
||||||
|
.granularity = granularity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selectAllChildren(self: *Selection, parent: *Node, page: *Page) !void {
|
||||||
|
if (parent._type == .document_type) return error.InvalidNodeTypeError;
|
||||||
|
|
||||||
|
const range = try Range.init(page);
|
||||||
|
try range.setStart(parent, 0);
|
||||||
|
|
||||||
|
const child_count = parent.getLength();
|
||||||
|
try range.setEnd(parent, @intCast(child_count));
|
||||||
|
|
||||||
|
self._range = range;
|
||||||
|
self._direction = .forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setBaseAndExtent(
|
||||||
|
self: *Selection,
|
||||||
|
anchor_node: *Node,
|
||||||
|
anchor_offset: u32,
|
||||||
|
focus_node: *Node,
|
||||||
|
focus_offset: u32,
|
||||||
|
page: *Page,
|
||||||
|
) !void {
|
||||||
|
if (anchor_offset > anchor_node.getLength()) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focus_offset > focus_node.getLength()) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmp = AbstractRange.compareBoundaryPoints(
|
||||||
|
anchor_node,
|
||||||
|
anchor_offset,
|
||||||
|
focus_node,
|
||||||
|
focus_offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
const range = try Range.init(page);
|
||||||
|
|
||||||
|
switch (cmp) {
|
||||||
|
.before => {
|
||||||
|
try range.setStart(anchor_node, anchor_offset);
|
||||||
|
try range.setEnd(focus_node, focus_offset);
|
||||||
|
self._direction = .forward;
|
||||||
|
},
|
||||||
|
.after => {
|
||||||
|
try range.setStart(focus_node, focus_offset);
|
||||||
|
try range.setEnd(anchor_node, anchor_offset);
|
||||||
|
self._direction = .backward;
|
||||||
|
},
|
||||||
|
.equal => {
|
||||||
|
try range.setStart(anchor_node, anchor_offset);
|
||||||
|
try range.setEnd(anchor_node, anchor_offset);
|
||||||
|
self._direction = .none;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self._range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !void {
|
||||||
|
const node = _node orelse {
|
||||||
|
self.removeAllRanges();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node._type == .document_type) return error.InvalidNodeType;
|
||||||
|
|
||||||
|
const offset = _offset orelse 0;
|
||||||
|
if (offset > node.getLength()) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = try Range.init(page);
|
||||||
|
try range.setStart(node, offset);
|
||||||
|
try range.setEnd(node, offset);
|
||||||
|
|
||||||
|
self._range = range;
|
||||||
|
self._direction = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
|
||||||
|
const range = self._range orelse return "";
|
||||||
|
return try range.toString(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const JsApi = struct {
|
||||||
|
pub const bridge = js.Bridge(Selection);
|
||||||
|
|
||||||
|
pub const Meta = struct {
|
||||||
|
pub const name = "Selection";
|
||||||
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{});
|
||||||
|
pub const anchorOffset = bridge.accessor(Selection.getAnchorOffset, null, .{});
|
||||||
|
pub const direction = bridge.accessor(Selection.getDirection, null, .{});
|
||||||
|
pub const focusNode = bridge.accessor(Selection.getFocusNode, null, .{});
|
||||||
|
pub const focusOffset = bridge.accessor(Selection.getFocusOffset, null, .{});
|
||||||
|
pub const isCollapsed = bridge.accessor(Selection.getIsCollapsed, null, .{});
|
||||||
|
pub const rangeCount = bridge.accessor(Selection.getRangeCount, null, .{});
|
||||||
|
pub const @"type" = bridge.accessor(Selection.getType, null, .{});
|
||||||
|
|
||||||
|
pub const addRange = bridge.function(Selection.addRange, .{});
|
||||||
|
pub const collapse = bridge.function(Selection.collapse, .{ .dom_exception = true });
|
||||||
|
pub const collapseToEnd = bridge.function(Selection.collapseToEnd, .{});
|
||||||
|
pub const collapseToStart = bridge.function(Selection.collapseToStart, .{});
|
||||||
|
pub const containsNode = bridge.function(Selection.containsNode, .{});
|
||||||
|
pub const deleteFromDocument = bridge.function(Selection.deleteFromDocument, .{});
|
||||||
|
pub const empty = bridge.function(Selection.removeAllRanges, .{});
|
||||||
|
pub const extend = bridge.function(Selection.extend, .{ .dom_exception = true });
|
||||||
|
// unimplemented: getComposedRanges
|
||||||
|
pub const getRangeAt = bridge.function(Selection.getRangeAt, .{ .dom_exception = true });
|
||||||
|
pub const modify = bridge.function(Selection.modify, .{});
|
||||||
|
pub const removeAllRanges = bridge.function(Selection.removeAllRanges, .{});
|
||||||
|
pub const removeRange = bridge.function(Selection.removeRange, .{ .dom_exception = true });
|
||||||
|
pub const selectAllChildren = bridge.function(Selection.selectAllChildren, .{});
|
||||||
|
pub const setBaseAndExtent = bridge.function(Selection.setBaseAndExtent, .{ .dom_exception = true });
|
||||||
|
pub const setPosition = bridge.function(Selection.collapse, .{});
|
||||||
|
pub const toString = bridge.function(Selection.toString, .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "WebApi: Selection" {
|
||||||
|
try testing.htmlRunner("selection.html", .{});
|
||||||
|
}
|
||||||
@@ -84,7 +84,7 @@ pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element
|
|||||||
// Do a tree walk to find another element with this ID
|
// Do a tree walk to find another element with this ID
|
||||||
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| {
|
||||||
const element_id = el.getAttributeSafe("id") orelse continue;
|
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
|
||||||
if (std.mem.eql(u8, element_id, id)) {
|
if (std.mem.eql(u8, element_id, id)) {
|
||||||
// we ignore this error to keep getElementById easy to call
|
// we ignore this error to keep getElementById easy to call
|
||||||
// if it really failed, then we're out of memory and nothing's
|
// if it really failed, then we're out of memory and nothing's
|
||||||
|
|||||||
@@ -96,10 +96,10 @@ pub fn generateKey(
|
|||||||
page: *Page,
|
page: *Page,
|
||||||
) !js.Promise {
|
) !js.Promise {
|
||||||
const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| {
|
const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| {
|
||||||
return page.js.rejectPromise(@errorName(err));
|
return page.js.local.?.rejectPromise(@errorName(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
return page.js.resolvePromise(key_or_pair);
|
return page.js.local.?.resolvePromise(key_or_pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exports a key: that is, it takes as input a CryptoKey object and gives you
|
/// Exports a key: that is, it takes as input a CryptoKey object and gives you
|
||||||
@@ -115,7 +115,7 @@ pub fn exportKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, format, "raw")) {
|
if (std.mem.eql(u8, format, "raw")) {
|
||||||
return page.js.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
|
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
|
||||||
@@ -125,7 +125,7 @@ pub fn exportKey(
|
|||||||
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
||||||
}
|
}
|
||||||
|
|
||||||
return page.js.rejectPromise(@errorName(error.NotSupported));
|
return page.js.local.?.rejectPromise(@errorName(error.NotSupported));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive a secret key from a master key.
|
/// Derive a secret key from a master key.
|
||||||
@@ -140,14 +140,14 @@ pub fn deriveBits(
|
|||||||
.ecdh_or_x25519 => |p| {
|
.ecdh_or_x25519 => |p| {
|
||||||
const name = p.name;
|
const name = p.name;
|
||||||
if (std.mem.eql(u8, name, "X25519")) {
|
if (std.mem.eql(u8, name, "X25519")) {
|
||||||
return page.js.resolvePromise(base_key.deriveBitsX25519(p.public, length, page));
|
return page.js.local.?.resolvePromise(base_key.deriveBitsX25519(p.public, length, page));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, name, "ECDH")) {
|
if (std.mem.eql(u8, name, "ECDH")) {
|
||||||
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
|
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return page.js.rejectPromise(@errorName(error.NotSupported));
|
return page.js.local.?.rejectPromise(@errorName(error.NotSupported));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -184,19 +184,19 @@ pub fn sign(
|
|||||||
.hmac => {
|
.hmac => {
|
||||||
// Verify algorithm.
|
// Verify algorithm.
|
||||||
if (!algorithm.isHMAC()) {
|
if (!algorithm.isHMAC()) {
|
||||||
return page.js.rejectPromise(@errorName(error.InvalidAccessError));
|
return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call sign for HMAC.
|
// Call sign for HMAC.
|
||||||
const result = key.signHMAC(data, page) catch |err| {
|
const result = key.signHMAC(data, page) catch |err| {
|
||||||
return page.js.rejectPromise(@errorName(err));
|
return page.js.local.?.rejectPromise(@errorName(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
return page.js.resolvePromise(result);
|
return page.js.local.?.resolvePromise(result);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
||||||
return page.js.rejectPromise(@errorName(error.InvalidAccessError));
|
return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -454,10 +454,10 @@ pub const CryptoKey = struct {
|
|||||||
if (signed != null) {
|
if (signed != null) {
|
||||||
// CRYPTO_memcmp compare in constant time so prohibits time-based attacks.
|
// CRYPTO_memcmp compare in constant time so prohibits time-based attacks.
|
||||||
const res = crypto.CRYPTO_memcmp(signed, @ptrCast(signature.ptr), signature.len);
|
const res = crypto.CRYPTO_memcmp(signed, @ptrCast(signature.ptr), signature.len);
|
||||||
return page.js.resolvePromise(res == 0);
|
return page.js.local.?.resolvePromise(res == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return page.js.resolvePromise(false);
|
return page.js.local.?.resolvePromise(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// X25519.
|
// X25519.
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const storage = @import("storage/storage.zig");
|
|||||||
const Element = @import("Element.zig");
|
const Element = @import("Element.zig");
|
||||||
const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
|
const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
|
||||||
const CustomElementRegistry = @import("CustomElementRegistry.zig");
|
const CustomElementRegistry = @import("CustomElementRegistry.zig");
|
||||||
|
const Selection = @import("Selection.zig");
|
||||||
|
|
||||||
const Window = @This();
|
const Window = @This();
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ _storage_bucket: *storage.Bucket,
|
|||||||
_on_load: ?js.Function.Global = null,
|
_on_load: ?js.Function.Global = null,
|
||||||
_on_pageshow: ?js.Function.Global = null,
|
_on_pageshow: ?js.Function.Global = null,
|
||||||
_on_popstate: ?js.Function.Global = null,
|
_on_popstate: ?js.Function.Global = null,
|
||||||
_on_error: ?js.Function.Global = null, // TODO: invoke on error?
|
_on_error: ?js.Function.Global = null,
|
||||||
_on_unhandled_rejection: ?js.Function.Global = null, // TODO: invoke on error
|
_on_unhandled_rejection: ?js.Function.Global = null, // TODO: invoke on error
|
||||||
_location: *Location,
|
_location: *Location,
|
||||||
_timer_id: u30 = 0,
|
_timer_id: u30 = 0,
|
||||||
@@ -129,6 +130,10 @@ pub fn getLocation(self: *const Window) *Location {
|
|||||||
return self._location;
|
return self._location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getSelection(self: *const Window) *Selection {
|
||||||
|
return &self._document._selection;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setLocation(_: *const Window, url: [:0]const u8, page: *Page) !void {
|
pub fn setLocation(_: *const Window, url: [:0]const u8, page: *Page) !void {
|
||||||
return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script);
|
return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script);
|
||||||
}
|
}
|
||||||
@@ -189,7 +194,7 @@ pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, pag
|
|||||||
return Fetch.init(input, options, page);
|
return Fetch.init(input, options, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setTimeout(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value.Global, page: *Page) !u32 {
|
pub fn setTimeout(self: *Window, cb: js.Function.Temp, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
|
||||||
return self.scheduleCallback(cb, delay_ms orelse 0, .{
|
return self.scheduleCallback(cb, delay_ms orelse 0, .{
|
||||||
.repeat = false,
|
.repeat = false,
|
||||||
.params = params,
|
.params = params,
|
||||||
@@ -198,7 +203,7 @@ pub fn setTimeout(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params:
|
|||||||
}, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setInterval(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params: []js.Value.Global, page: *Page) !u32 {
|
pub fn setInterval(self: *Window, cb: js.Function.Temp, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
|
||||||
return self.scheduleCallback(cb, delay_ms orelse 0, .{
|
return self.scheduleCallback(cb, delay_ms orelse 0, .{
|
||||||
.repeat = true,
|
.repeat = true,
|
||||||
.params = params,
|
.params = params,
|
||||||
@@ -207,7 +212,7 @@ pub fn setInterval(self: *Window, cb: js.Function.Global, delay_ms: ?u32, params
|
|||||||
}, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setImmediate(self: *Window, cb: js.Function.Global, params: []js.Value.Global, page: *Page) !u32 {
|
pub fn setImmediate(self: *Window, cb: js.Function.Temp, params: []js.Value.Temp, page: *Page) !u32 {
|
||||||
return self.scheduleCallback(cb, 0, .{
|
return self.scheduleCallback(cb, 0, .{
|
||||||
.repeat = false,
|
.repeat = false,
|
||||||
.params = params,
|
.params = params,
|
||||||
@@ -216,7 +221,7 @@ pub fn setImmediate(self: *Window, cb: js.Function.Global, params: []js.Value.Gl
|
|||||||
}, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requestAnimationFrame(self: *Window, cb: js.Function.Global, page: *Page) !u32 {
|
pub fn requestAnimationFrame(self: *Window, cb: js.Function.Temp, page: *Page) !u32 {
|
||||||
return self.scheduleCallback(cb, 5, .{
|
return self.scheduleCallback(cb, 5, .{
|
||||||
.repeat = false,
|
.repeat = false,
|
||||||
.params = &.{},
|
.params = &.{},
|
||||||
@@ -253,7 +258,7 @@ pub fn cancelAnimationFrame(self: *Window, id: u32) void {
|
|||||||
const RequestIdleCallbackOpts = struct {
|
const RequestIdleCallbackOpts = struct {
|
||||||
timeout: ?u32 = null,
|
timeout: ?u32 = null,
|
||||||
};
|
};
|
||||||
pub fn requestIdleCallback(self: *Window, cb: js.Function.Global, opts_: ?RequestIdleCallbackOpts, page: *Page) !u32 {
|
pub fn requestIdleCallback(self: *Window, cb: js.Function.Temp, opts_: ?RequestIdleCallbackOpts, page: *Page) !u32 {
|
||||||
const opts = opts_ orelse RequestIdleCallbackOpts{};
|
const opts = opts_ orelse RequestIdleCallbackOpts{};
|
||||||
return self.scheduleCallback(cb, opts.timeout orelse 50, .{
|
return self.scheduleCallback(cb, opts.timeout orelse 50, .{
|
||||||
.mode = .idle,
|
.mode = .idle,
|
||||||
@@ -269,15 +274,41 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void {
|
|||||||
sc.removed = true;
|
sc.removed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reportError(self: *Window, err: js.Value.Global, page: *Page) !void {
|
pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
|
||||||
const error_event = try ErrorEvent.initTrusted("error", .{
|
const error_event = try ErrorEvent.initTrusted("error", .{
|
||||||
.@"error" = err,
|
.@"error" = try err.persist(),
|
||||||
.message = err.local().toString(.{}) catch "Unknown error",
|
.message = err.toString(.{}) catch "Unknown error",
|
||||||
.bubbles = false,
|
.bubbles = false,
|
||||||
.cancelable = true,
|
.cancelable = true,
|
||||||
}, page);
|
}, page);
|
||||||
|
|
||||||
const event = error_event.asEvent();
|
const event = error_event.asEvent();
|
||||||
|
|
||||||
|
// Invoke window.onerror callback if set (per WHATWG spec, this is called
|
||||||
|
// with 5 arguments: message, source, lineno, colno, error)
|
||||||
|
// If it returns true, the event is cancelled.
|
||||||
|
if (self._on_error) |on_error| {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
const local_func = ls.toLocal(on_error);
|
||||||
|
const result = local_func.call(js.Value, .{
|
||||||
|
error_event._message,
|
||||||
|
error_event._filename,
|
||||||
|
error_event._line_number,
|
||||||
|
error_event._column_number,
|
||||||
|
err,
|
||||||
|
}) catch null;
|
||||||
|
|
||||||
|
// Per spec: returning true from onerror cancels the event
|
||||||
|
if (result) |r| {
|
||||||
|
if (r.isTrue()) {
|
||||||
|
event._prevent_default = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try page._event_manager.dispatch(self.asEventTarget(), event);
|
try page._event_manager.dispatch(self.asEventTarget(), event);
|
||||||
|
|
||||||
if (comptime builtin.is_test == false) {
|
if (comptime builtin.is_test == false) {
|
||||||
@@ -465,13 +496,13 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
|||||||
|
|
||||||
const ScheduleOpts = struct {
|
const ScheduleOpts = struct {
|
||||||
repeat: bool,
|
repeat: bool,
|
||||||
params: []js.Value.Global,
|
params: []js.Value.Temp,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
low_priority: bool = false,
|
low_priority: bool = false,
|
||||||
animation_frame: bool = false,
|
animation_frame: bool = false,
|
||||||
mode: ScheduleCallback.Mode = .normal,
|
mode: ScheduleCallback.Mode = .normal,
|
||||||
};
|
};
|
||||||
fn scheduleCallback(self: *Window, cb: js.Function.Global, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
|
fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
|
||||||
if (self._timers.count() > 512) {
|
if (self._timers.count() > 512) {
|
||||||
// these are active
|
// these are active
|
||||||
return error.TooManyTimeout;
|
return error.TooManyTimeout;
|
||||||
@@ -481,9 +512,9 @@ fn scheduleCallback(self: *Window, cb: js.Function.Global, delay_ms: u32, opts:
|
|||||||
self._timer_id = timer_id;
|
self._timer_id = timer_id;
|
||||||
|
|
||||||
const params = opts.params;
|
const params = opts.params;
|
||||||
var persisted_params: []js.Value.Global = &.{};
|
var persisted_params: []js.Value.Temp = &.{};
|
||||||
if (params.len > 0) {
|
if (params.len > 0) {
|
||||||
persisted_params = try page.arena.dupe(js.Value.Global, params);
|
persisted_params = try page.arena.dupe(js.Value.Temp, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gop = try self._timers.getOrPut(page.arena, timer_id);
|
const gop = try self._timers.getOrPut(page.arena, timer_id);
|
||||||
@@ -523,11 +554,11 @@ const ScheduleCallback = struct {
|
|||||||
// delay, in ms, to repeat. When null, will be removed after the first time
|
// delay, in ms, to repeat. When null, will be removed after the first time
|
||||||
repeat_ms: ?u32,
|
repeat_ms: ?u32,
|
||||||
|
|
||||||
cb: js.Function.Global,
|
cb: js.Function.Temp,
|
||||||
|
|
||||||
page: *Page,
|
page: *Page,
|
||||||
|
|
||||||
params: []const js.Value.Global,
|
params: []const js.Value.Temp,
|
||||||
|
|
||||||
removed: bool = false,
|
removed: bool = false,
|
||||||
|
|
||||||
@@ -540,6 +571,10 @@ const ScheduleCallback = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn deinit(self: *ScheduleCallback) void {
|
fn deinit(self: *ScheduleCallback) void {
|
||||||
|
self.page.js.release(self.cb);
|
||||||
|
for (self.params) |param| {
|
||||||
|
self.page.js.release(param);
|
||||||
|
}
|
||||||
self.page._factory.destroy(self);
|
self.page._factory.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,32 +587,34 @@ const ScheduleCallback = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
.idle => {
|
.idle => {
|
||||||
const IdleDeadline = @import("IdleDeadline.zig");
|
const IdleDeadline = @import("IdleDeadline.zig");
|
||||||
self.cb.local().call(void, .{IdleDeadline{}}) catch |err| {
|
ls.toLocal(self.cb).call(void, .{IdleDeadline{}}) catch |err| {
|
||||||
log.warn(.js, "window.idleCallback", .{ .name = self.name, .err = err });
|
log.warn(.js, "window.idleCallback", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.animation_frame => {
|
.animation_frame => {
|
||||||
self.cb.local().call(void, .{page.window._performance.now()}) catch |err| {
|
ls.toLocal(self.cb).call(void, .{page.window._performance.now()}) catch |err| {
|
||||||
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.normal => {
|
.normal => {
|
||||||
self.cb.local().call(void, self.params) catch |err| {
|
ls.toLocal(self.cb).call(void, self.params) catch |err| {
|
||||||
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
|
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
ls.local.runMicrotasks();
|
||||||
if (self.repeat_ms) |ms| {
|
if (self.repeat_ms) |ms| {
|
||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
defer self.deinit();
|
defer self.deinit();
|
||||||
|
|
||||||
_ = page.window._timers.remove(self.timer_id);
|
_ = page.window._timers.remove(self.timer_id);
|
||||||
page.js.runMicrotasks();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -647,7 +684,7 @@ pub const JsApi = struct {
|
|||||||
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" });
|
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" });
|
||||||
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" });
|
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" });
|
||||||
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = "document" });
|
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = "document" });
|
||||||
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{ .cache = "location" });
|
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{});
|
||||||
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
||||||
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
||||||
pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" });
|
pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" });
|
||||||
@@ -656,7 +693,7 @@ pub const JsApi = struct {
|
|||||||
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
pub const onload = bridge.accessor(Window.getOnLoad, Window.setOnLoad, .{});
|
||||||
pub const onpageshow = bridge.accessor(Window.getOnPageShow, Window.setOnPageShow, .{});
|
pub const onpageshow = bridge.accessor(Window.getOnPageShow, Window.setOnPageShow, .{});
|
||||||
pub const onpopstate = bridge.accessor(Window.getOnPopState, Window.setOnPopState, .{});
|
pub const onpopstate = bridge.accessor(Window.getOnPopState, Window.setOnPopState, .{});
|
||||||
pub const onerror = bridge.accessor(Window.getOnError, Window.getOnError, .{});
|
pub const onerror = bridge.accessor(Window.getOnError, Window.setOnError, .{});
|
||||||
pub const onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{});
|
pub const onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{});
|
||||||
pub const fetch = bridge.function(Window.fetch, .{});
|
pub const fetch = bridge.function(Window.fetch, .{});
|
||||||
pub const queueMicrotask = bridge.function(Window.queueMicrotask, .{});
|
pub const queueMicrotask = bridge.function(Window.queueMicrotask, .{});
|
||||||
@@ -676,6 +713,7 @@ pub const JsApi = struct {
|
|||||||
pub const atob = bridge.function(Window.atob, .{});
|
pub const atob = bridge.function(Window.atob, .{});
|
||||||
pub const reportError = bridge.function(Window.reportError, .{});
|
pub const reportError = bridge.function(Window.reportError, .{});
|
||||||
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
|
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
|
||||||
|
pub const getSelection = bridge.function(Window.getSelection, .{});
|
||||||
pub const isSecureContext = bridge.accessor(Window.getIsSecureContext, null, .{});
|
pub const isSecureContext = bridge.accessor(Window.getIsSecureContext, null, .{});
|
||||||
pub const frames = bridge.accessor(Window.getWindow, null, .{ .cache = "frames" });
|
pub const frames = bridge.accessor(Window.getWindow, null, .{ .cache = "frames" });
|
||||||
pub const index = bridge.indexed(Window.getFrame, .{ .null_as_undefined = true });
|
pub const index = bridge.indexed(Window.getFrame, .{ .null_as_undefined = true });
|
||||||
|
|||||||
@@ -46,20 +46,22 @@ pub fn getPending(_: *const Animation) bool {
|
|||||||
|
|
||||||
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
|
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
|
||||||
if (self._finished_resolver == null) {
|
if (self._finished_resolver == null) {
|
||||||
const resolver = try page.js.createPromiseResolver().persist();
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
resolver.local().resolve("Animation.getFinished", self);
|
resolver.resolve("Animation.getFinished", self);
|
||||||
self._finished_resolver = resolver;
|
self._finished_resolver = try resolver.persist();
|
||||||
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
return self._finished_resolver.?.local().promise();
|
return page.js.toLocal(self._finished_resolver).?.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
|
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
|
||||||
// never resolved, because we're always "finished"
|
// never resolved, because we're always "finished"
|
||||||
if (self._ready_resolver == null) {
|
if (self._ready_resolver == null) {
|
||||||
const resolver = try page.js.createPromiseResolver().persist();
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
self._ready_resolver = resolver;
|
self._ready_resolver = try resolver.persist();
|
||||||
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
return self._ready_resolver.?.local().promise();
|
return page.js.toLocal(self._ready_resolver).?.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getEffect(self: *const Animation) ?js.Object.Global {
|
pub fn getEffect(self: *const Animation) ?js.Object.Global {
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const log = @import("../../../log.zig");
|
const log = @import("../../../log.zig");
|
||||||
const js = @import("../../js/js.zig");
|
const String = @import("../../../string.zig").String;
|
||||||
|
|
||||||
|
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");
|
||||||
const GenericIterator = @import("iterator.zig").Entry;
|
const GenericIterator = @import("iterator.zig").Entry;
|
||||||
@@ -31,7 +32,7 @@ pub const DOMTokenList = @This();
|
|||||||
// is that lists tend to be very short (often just 1 item).
|
// is that lists tend to be very short (often just 1 item).
|
||||||
|
|
||||||
_element: *Element,
|
_element: *Element,
|
||||||
_attribute_name: []const u8,
|
_attribute_name: String,
|
||||||
|
|
||||||
pub const KeyIterator = GenericIterator(Iterator, "0");
|
pub const KeyIterator = GenericIterator(Iterator, "0");
|
||||||
pub const ValueIterator = GenericIterator(Iterator, "1");
|
pub const ValueIterator = GenericIterator(Iterator, "1");
|
||||||
@@ -159,7 +160,7 @@ pub fn getValue(self: *const DOMTokenList) []const u8 {
|
|||||||
return self._element.getAttributeSafe(self._attribute_name) orelse "";
|
return self._element.getAttributeSafe(self._attribute_name) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setValue(self: *DOMTokenList, value: []const u8, page: *Page) !void {
|
pub fn setValue(self: *DOMTokenList, value: String, page: *Page) !void {
|
||||||
try self._element.setAttribute(self._attribute_name, value, page);
|
try self._element.setAttribute(self._attribute_name, value, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +227,7 @@ fn validateToken(token: []const u8) !void {
|
|||||||
|
|
||||||
fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void {
|
fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void {
|
||||||
const joined = try std.mem.join(page.call_arena, " ", tokens.keys());
|
const joined = try std.mem.join(page.call_arena, " ", tokens.keys());
|
||||||
try self._element.setAttribute(self._attribute_name, joined, page);
|
try self._element.setAttribute(self._attribute_name, .wrap(joined), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Iterator = struct {
|
const Iterator = struct {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ pub fn getByName(self: *HTMLAllCollection, name: []const u8, page: *Page) ?*Elem
|
|||||||
|
|
||||||
while (tw.next()) |node| {
|
while (tw.next()) |node| {
|
||||||
if (node.is(Element)) |el| {
|
if (node.is(Element)) |el| {
|
||||||
if (el.getAttributeSafe("name")) |attr_name| {
|
if (el.getAttributeSafe(comptime .wrap("name"))) |attr_name| {
|
||||||
if (std.mem.eql(u8, attr_name, name)) {
|
if (std.mem.eql(u8, attr_name, name)) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Pag
|
|||||||
var it = try self.iterator();
|
var it = try self.iterator();
|
||||||
while (it.next()) |element| {
|
while (it.next()) |element| {
|
||||||
const is_match = blk: {
|
const is_match = blk: {
|
||||||
if (element.getAttributeSafe("id")) |id| {
|
if (element.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||||
if (std.mem.eql(u8, id, name)) {
|
if (std.mem.eql(u8, id, name)) {
|
||||||
break :blk true;
|
break :blk true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (element.getAttributeSafe("name")) |elem_name| {
|
if (element.getAttributeSafe(comptime .wrap("name"))) |elem_name| {
|
||||||
if (std.mem.eql(u8, elem_name, name)) {
|
if (std.mem.eql(u8, elem_name, name)) {
|
||||||
break :blk true;
|
break :blk true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ pub fn getValue(self: *RadioNodeList) ![]const u8 {
|
|||||||
if (!input.getChecked()) {
|
if (!input.getChecked()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return element.getAttributeSafe("value") orelse "on";
|
return element.getAttributeSafe(comptime .wrap("value")) orelse "on";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const input_value = element.getAttributeSafe("value");
|
const input_value = element.getAttributeSafe(comptime .wrap("value"));
|
||||||
const matches_value = blk: {
|
const matches_value = blk: {
|
||||||
if (std.mem.eql(u8, value, "on")) {
|
if (std.mem.eql(u8, value, "on")) {
|
||||||
break :blk input_value == null or (input_value != null and std.mem.eql(u8, input_value.?, "on"));
|
break :blk input_value == null or (input_value != null and std.mem.eql(u8, input_value.?, "on"));
|
||||||
@@ -99,12 +99,12 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn matches(self: *const RadioNodeList, element: *Element) bool {
|
fn matches(self: *const RadioNodeList, element: *Element) bool {
|
||||||
if (element.getAttributeSafe("id")) |id| {
|
if (element.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||||
if (std.mem.eql(u8, id, self._name)) {
|
if (std.mem.eql(u8, id, self._name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (element.getAttributeSafe("name")) |elem_name| {
|
if (element.getAttributeSafe(comptime .wrap("name"))) |elem_name| {
|
||||||
if (std.mem.eql(u8, elem_name, self._name)) {
|
if (std.mem.eql(u8, elem_name, self._name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
// (like length or getAtIndex)
|
// (like length or getAtIndex)
|
||||||
var tw = self._tw.clone();
|
var tw = self._tw.clone();
|
||||||
while (self.nextTw(&tw)) |element| {
|
while (self.nextTw(&tw)) |element| {
|
||||||
const element_name = element.getAttributeSafe("name") orelse continue;
|
const element_name = element.getAttributeSafe(comptime .wrap("name")) orelse continue;
|
||||||
if (std.mem.eql(u8, element_name, name)) {
|
if (std.mem.eql(u8, element_name, name)) {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const el = node.is(Element) orelse return false;
|
const el = node.is(Element) orelse return false;
|
||||||
const class_attr = el.getAttributeSafe("class") orelse return false;
|
const class_attr = el.getAttributeSafe(comptime .wrap("class")) orelse return false;
|
||||||
for (self._filter) |class_name| {
|
for (self._filter) |class_name| {
|
||||||
if (!Selector.classAttributeContains(class_attr, class_name)) {
|
if (!Selector.classAttributeContains(class_attr, class_name)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -238,7 +238,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
},
|
},
|
||||||
.name => {
|
.name => {
|
||||||
const el = node.is(Element) orelse return false;
|
const el = node.is(Element) orelse return false;
|
||||||
const name_attr = el.getAttributeSafe("name") orelse return false;
|
const name_attr = el.getAttributeSafe(comptime .wrap("name")) orelse return false;
|
||||||
return std.mem.eql(u8, name_attr, self._filter);
|
return std.mem.eql(u8, name_attr, self._filter);
|
||||||
},
|
},
|
||||||
.all_elements => return node._type == .element,
|
.all_elements => return node._type == .element,
|
||||||
@@ -258,14 +258,14 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
const el = node.is(Element) orelse return false;
|
const el = node.is(Element) orelse return false;
|
||||||
const Anchor = Element.Html.Anchor;
|
const Anchor = Element.Html.Anchor;
|
||||||
if (el.is(Anchor) == null) return false;
|
if (el.is(Anchor) == null) return false;
|
||||||
return el.hasAttributeSafe("href");
|
return el.hasAttributeSafe(comptime .wrap("href"));
|
||||||
},
|
},
|
||||||
.anchors => {
|
.anchors => {
|
||||||
// Anchors are <a> elements with name attribute
|
// Anchors are <a> elements with name attribute
|
||||||
const el = node.is(Element) orelse return false;
|
const el = node.is(Element) orelse return false;
|
||||||
const Anchor = Element.Html.Anchor;
|
const Anchor = Element.Html.Anchor;
|
||||||
if (el.is(Anchor) == null) return false;
|
if (el.is(Anchor) == null) return false;
|
||||||
return el.hasAttributeSafe("name");
|
return el.hasAttributeSafe(comptime .wrap("name"));
|
||||||
},
|
},
|
||||||
.form => {
|
.form => {
|
||||||
const el = node.is(Element) orelse return false;
|
const el = node.is(Element) orelse return false;
|
||||||
@@ -273,8 +273,8 @@ pub fn NodeLive(comptime mode: Mode) type {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.getAttributeSafe("form")) |form_attr| {
|
if (el.getAttributeSafe(comptime .wrap("form"))) |form_attr| {
|
||||||
const form_id = self._filter.asElement().getAttributeSafe("id") orelse return false;
|
const form_id = self._filter.asElement().getAttributeSafe(comptime .wrap("id")) orelse return false;
|
||||||
return std.mem.eql(u8, form_attr, form_id);
|
return std.mem.eql(u8, form_attr, form_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ pub fn replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise
|
|||||||
_ = self;
|
_ = self;
|
||||||
_ = text;
|
_ = text;
|
||||||
// TODO: clear self.css_rules
|
// TODO: clear self.css_rules
|
||||||
return page.js.resolvePromise({});
|
return page.js.local.?.resolvePromise({});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replaceSync(self: *CSSStyleSheet, text: []const u8) !void {
|
pub fn replaceSync(self: *CSSStyleSheet, text: []const u8) !void {
|
||||||
|
|||||||
@@ -39,33 +39,33 @@ pub fn registerTypes() []const type {
|
|||||||
pub const Attribute = @This();
|
pub const Attribute = @This();
|
||||||
|
|
||||||
_proto: *Node,
|
_proto: *Node,
|
||||||
_name: []const u8,
|
_name: String,
|
||||||
_value: []const u8,
|
_value: String,
|
||||||
_element: ?*Element,
|
_element: ?*Element,
|
||||||
|
|
||||||
pub fn format(self: *const Attribute, writer: *std.Io.Writer) !void {
|
pub fn format(self: *const Attribute, writer: *std.Io.Writer) !void {
|
||||||
return formatAttribute(self._name, self._value, writer);
|
return formatAttribute(self._name, self._value, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Attribute) []const u8 {
|
pub fn getName(self: *const Attribute) String {
|
||||||
return self._name;
|
return self._name;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getValue(self: *const Attribute) []const u8 {
|
pub fn getValue(self: *const Attribute) String {
|
||||||
return self._value;
|
return self._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setValue(self: *Attribute, data_: ?[]const u8, page: *Page) !void {
|
pub fn setValue(self: *Attribute, data_: ?String, page: *Page) !void {
|
||||||
const data = data_ orelse "";
|
const data = data_ orelse String.empty;
|
||||||
const el = self._element orelse {
|
const el = self._element orelse {
|
||||||
self._value = try page.dupeString(data);
|
self._value = try data.dupe(page.arena);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// this takes ownership of the data
|
// this takes ownership of the data
|
||||||
try el.setAttribute(self._name, data, page);
|
try el.setAttribute(self._name, data, page);
|
||||||
|
|
||||||
// not the most efficient, but we don't expect this to be called often
|
// not the most efficient, but we don't expect this to be called often
|
||||||
self._value = (try el.getAttribute(self._name, page)) orelse "";
|
self._value = (try el.getAttribute(self._name, page)) orelse String.empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getNamespaceURI(_: *const Attribute) ?[]const u8 {
|
pub fn getNamespaceURI(_: *const Attribute) ?[]const u8 {
|
||||||
@@ -77,7 +77,7 @@ pub fn getOwnerElement(self: *const Attribute) ?*Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn isEqualNode(self: *const Attribute, other: *const Attribute) bool {
|
pub fn isEqualNode(self: *const Attribute, other: *const Attribute) bool {
|
||||||
return std.mem.eql(u8, self.getName(), other.getName()) and std.mem.eql(u8, self.getValue(), other.getValue());
|
return self.getName().eql(other.getName()) and self.getValue().eql(other.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone(self: *const Attribute, page: *Page) !*Attribute {
|
pub fn clone(self: *const Attribute, page: *Page) !*Attribute {
|
||||||
@@ -139,9 +139,9 @@ pub const List = struct {
|
|||||||
return self._list.first == null;
|
return self._list.first == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: *const List, name: []const u8, page: *Page) !?[]const u8 {
|
pub fn get(self: *const List, name: String, page: *Page) !?String {
|
||||||
const entry = (try self.getEntry(name, page)) orelse return null;
|
const entry = (try self.getEntry(name, page)) orelse return null;
|
||||||
return entry._value.str();
|
return entry._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn length(self: *const List) usize {
|
pub inline fn length(self: *const List) usize {
|
||||||
@@ -170,17 +170,17 @@ pub const List = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// meant for internal usage, where the name is known to be properly cased
|
// meant for internal usage, where the name is known to be properly cased
|
||||||
pub fn getSafe(self: *const List, name: []const u8) ?[]const u8 {
|
pub fn getSafe(self: *const List, name: String) ?[]const u8 {
|
||||||
const entry = self.getEntryWithNormalizedName(name) orelse return null;
|
const entry = self.getEntryWithNormalizedName(name) orelse return null;
|
||||||
return entry._value.str();
|
return entry._value.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// meant for internal usage, where the name is known to be properly cased
|
// meant for internal usage, where the name is known to be properly cased
|
||||||
pub fn hasSafe(self: *const List, name: []const u8) bool {
|
pub fn hasSafe(self: *const List, name: String) bool {
|
||||||
return self.getEntryWithNormalizedName(name) != null;
|
return self.getEntryWithNormalizedName(name) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAttribute(self: *const List, name: []const u8, element: ?*Element, page: *Page) !?*Attribute {
|
pub fn getAttribute(self: *const List, name: String, element: ?*Element, page: *Page) !?*Attribute {
|
||||||
const entry = (try self.getEntry(name, page)) orelse return null;
|
const entry = (try self.getEntry(name, page)) orelse return null;
|
||||||
const gop = try page._attribute_lookup.getOrPut(page.arena, @intFromPtr(entry));
|
const gop = try page._attribute_lookup.getOrPut(page.arena, @intFromPtr(entry));
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
@@ -191,33 +191,33 @@ pub const List = struct {
|
|||||||
return attribute;
|
return attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry {
|
pub fn put(self: *List, name: String, value: String, element: *Element, page: *Page) !*Entry {
|
||||||
const result = try self.getEntryAndNormalizedName(name, page);
|
const result = try self.getEntryAndNormalizedName(name, page);
|
||||||
return self._put(result, value, element, page);
|
return self._put(result, value, element, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn putSafe(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry {
|
pub fn putSafe(self: *List, name: String, value: String, element: *Element, page: *Page) !*Entry {
|
||||||
const entry = self.getEntryWithNormalizedName(name);
|
const entry = self.getEntryWithNormalizedName(name);
|
||||||
return self._put(.{ .entry = entry, .normalized = name }, value, element, page);
|
return self._put(.{ .entry = entry, .normalized = name }, value, element, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _put(self: *List, result: NormalizeAndEntry, value: []const u8, element: *Element, page: *Page) !*Entry {
|
fn _put(self: *List, result: NormalizeAndEntry, value: String, element: *Element, page: *Page) !*Entry {
|
||||||
const is_id = shouldAddToIdMap(result.normalized, element);
|
const is_id = shouldAddToIdMap(result.normalized, element);
|
||||||
|
|
||||||
var entry: *Entry = undefined;
|
var entry: *Entry = undefined;
|
||||||
var old_value: ?[]const u8 = null;
|
var old_value: ?String = null;
|
||||||
if (result.entry) |e| {
|
if (result.entry) |e| {
|
||||||
old_value = try page.call_arena.dupe(u8, e._value.str());
|
old_value = try e._value.dupe(page.call_arena);
|
||||||
if (is_id) {
|
if (is_id) {
|
||||||
page.removeElementId(element, e._value.str());
|
page.removeElementId(element, e._value.str());
|
||||||
}
|
}
|
||||||
e._value = try String.init(page.arena, value, .{});
|
e._value = try value.dupe(page.arena);
|
||||||
entry = e;
|
entry = e;
|
||||||
} else {
|
} else {
|
||||||
entry = try page._factory.create(Entry{
|
entry = try page._factory.create(Entry{
|
||||||
._node = .{},
|
._node = .{},
|
||||||
._name = try String.init(page.arena, result.normalized, .{}),
|
._name = try result.normalized.dupe(page.arena),
|
||||||
._value = try String.init(page.arena, value, .{}),
|
._value = try value.dupe(page.arena),
|
||||||
});
|
});
|
||||||
self._list.append(&entry._node);
|
self._list.append(&entry._node);
|
||||||
self._len += 1;
|
self._len += 1;
|
||||||
@@ -230,7 +230,7 @@ pub const List = struct {
|
|||||||
try page.addElementId(parent, element, entry._value.str());
|
try page.addElementId(parent, element, entry._value.str());
|
||||||
}
|
}
|
||||||
page.domChanged();
|
page.domChanged();
|
||||||
page.attributeChange(element, result.normalized, entry._value.str(), old_value);
|
page.attributeChange(element, result.normalized, entry._value, old_value);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ pub const List = struct {
|
|||||||
|
|
||||||
// called form our parser, names already lower-cased
|
// called form our parser, names already lower-cased
|
||||||
pub fn putNew(self: *List, name: []const u8, value: []const u8, page: *Page) !void {
|
pub fn putNew(self: *List, name: []const u8, value: []const u8, page: *Page) !void {
|
||||||
if (try self.getEntry(name, page) != null) {
|
if (try self.getEntry(.wrap(name), page) != null) {
|
||||||
// When parsing, if there are dupicate names, it isn't valid, and
|
// When parsing, if there are dupicate names, it isn't valid, and
|
||||||
// the first is kept
|
// the first is kept
|
||||||
return;
|
return;
|
||||||
@@ -281,12 +281,12 @@ pub const List = struct {
|
|||||||
self._len += 1;
|
self._len += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self: *List, name: []const u8, element: *Element, page: *Page) !void {
|
pub fn delete(self: *List, name: String, element: *Element, page: *Page) !void {
|
||||||
const result = try self.getEntryAndNormalizedName(name, page);
|
const result = try self.getEntryAndNormalizedName(name, page);
|
||||||
const entry = result.entry orelse return;
|
const entry = result.entry orelse return;
|
||||||
|
|
||||||
const is_id = shouldAddToIdMap(result.normalized, element);
|
const is_id = shouldAddToIdMap(result.normalized, element);
|
||||||
const old_value = entry._value.str();
|
const old_value = entry._value;
|
||||||
|
|
||||||
if (is_id) {
|
if (is_id) {
|
||||||
page.removeElementId(element, entry._value.str());
|
page.removeElementId(element, entry._value.str());
|
||||||
@@ -314,7 +314,7 @@ pub const List = struct {
|
|||||||
return .{ ._node = self._list.first };
|
return .{ ._node = self._list.first };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getEntry(self: *const List, name: []const u8, page: *Page) !?*Entry {
|
fn getEntry(self: *const List, name: String, page: *Page) !?*Entry {
|
||||||
const result = try self.getEntryAndNormalizedName(name, page);
|
const result = try self.getEntryAndNormalizedName(name, page);
|
||||||
return result.entry;
|
return result.entry;
|
||||||
}
|
}
|
||||||
@@ -322,10 +322,10 @@ pub const List = struct {
|
|||||||
// Dangerous, the returned normalized name is only valid until someone
|
// Dangerous, the returned normalized name is only valid until someone
|
||||||
// else uses pages.buf.
|
// else uses pages.buf.
|
||||||
const NormalizeAndEntry = struct {
|
const NormalizeAndEntry = struct {
|
||||||
normalized: []const u8,
|
|
||||||
entry: ?*Entry,
|
entry: ?*Entry,
|
||||||
|
normalized: String,
|
||||||
};
|
};
|
||||||
fn getEntryAndNormalizedName(self: *const List, name: []const u8, page: *Page) !NormalizeAndEntry {
|
fn getEntryAndNormalizedName(self: *const List, name: String, page: *Page) !NormalizeAndEntry {
|
||||||
const normalized =
|
const normalized =
|
||||||
if (self.normalize) try normalizeNameForLookup(name, page) else name;
|
if (self.normalize) try normalizeNameForLookup(name, page) else name;
|
||||||
|
|
||||||
@@ -335,11 +335,11 @@ pub const List = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getEntryWithNormalizedName(self: *const List, name: []const u8) ?*Entry {
|
fn getEntryWithNormalizedName(self: *const List, name: String) ?*Entry {
|
||||||
var node = self._list.first;
|
var node = self._list.first;
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
var e = Entry.fromNode(n);
|
var e = Entry.fromNode(n);
|
||||||
if (e._name.eqlSlice(name)) {
|
if (e._name.eql(name)) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
node = n.next;
|
node = n.next;
|
||||||
@@ -363,7 +363,7 @@ pub const List = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: *const Entry, writer: *std.Io.Writer) !void {
|
pub fn format(self: *const Entry, writer: *std.Io.Writer) !void {
|
||||||
return formatAttribute(self._name.str(), self._value.str(), writer);
|
return formatAttribute(self._name, self._value, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toAttribute(self: *const Entry, element: ?*Element, page: *Page) !*Attribute {
|
pub fn toAttribute(self: *const Entry, element: ?*Element, page: *Page) !*Attribute {
|
||||||
@@ -373,15 +373,15 @@ pub const List = struct {
|
|||||||
// Cannot directly reference self._name.str() and self._value.str()
|
// Cannot directly reference self._name.str() and self._value.str()
|
||||||
// This attribute can outlive the list entry (the node can be
|
// This attribute can outlive the list entry (the node can be
|
||||||
// removed from the element's attribute, but still exist in the DOM)
|
// removed from the element's attribute, but still exist in the DOM)
|
||||||
._name = try page.dupeString(self._name.str()),
|
._name = try self._name.dupe(page.arena),
|
||||||
._value = try page.dupeString(self._value.str()),
|
._value = try self._value.dupe(page.arena),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
fn shouldAddToIdMap(normalized_name: []const u8, element: *Element) bool {
|
fn shouldAddToIdMap(normalized_name: String, element: *Element) bool {
|
||||||
if (!std.mem.eql(u8, normalized_name, "id")) {
|
if (!normalized_name.eql(comptime .wrap("id"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,17 +394,19 @@ fn shouldAddToIdMap(normalized_name: []const u8, element: *Element) bool {
|
|||||||
return node.isConnected();
|
return node.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validateAttributeName(name: []const u8) !void {
|
pub fn validateAttributeName(name: String) !void {
|
||||||
if (name.len == 0) {
|
const name_str = name.str();
|
||||||
|
|
||||||
|
if (name_str.len == 0) {
|
||||||
return error.InvalidCharacterError;
|
return error.InvalidCharacterError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const first = name[0];
|
const first = name_str[0];
|
||||||
if ((first >= '0' and first <= '9') or first == '-' or first == '.') {
|
if ((first >= '0' and first <= '9') or first == '-' or first == '.') {
|
||||||
return error.InvalidCharacterError;
|
return error.InvalidCharacterError;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (name) |c| {
|
for (name_str) |c| {
|
||||||
if (c == 0 or c == '/' or c == '=' or c == '>' or std.ascii.isWhitespace(c)) {
|
if (c == 0 or c == '/' or c == '=' or c == '>' or std.ascii.isWhitespace(c)) {
|
||||||
return error.InvalidCharacterError;
|
return error.InvalidCharacterError;
|
||||||
}
|
}
|
||||||
@@ -420,14 +422,16 @@ pub fn validateAttributeName(name: []const u8) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normalizeNameForLookup(name: []const u8, page: *Page) ![]const u8 {
|
pub fn normalizeNameForLookup(name: String, page: *Page) !String {
|
||||||
if (!needsLowerCasing(name)) {
|
if (!needsLowerCasing(name.str())) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
if (name.len < page.buf.len) {
|
const normalized = if (name.len < page.buf.len)
|
||||||
return std.ascii.lowerString(&page.buf, name);
|
std.ascii.lowerString(&page.buf, name.str())
|
||||||
}
|
else
|
||||||
return std.ascii.allocLowerString(page.call_arena, name);
|
try std.ascii.allocLowerString(page.call_arena, name.str());
|
||||||
|
|
||||||
|
return .wrap(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn needsLowerCasing(name: []const u8) bool {
|
fn needsLowerCasing(name: []const u8) bool {
|
||||||
@@ -481,7 +485,7 @@ pub const NamedNodeMap = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getByName(self: *const NamedNodeMap, name: []const u8, page: *Page) !?*Attribute {
|
pub fn getByName(self: *const NamedNodeMap, name: String, page: *Page) !?*Attribute {
|
||||||
return self._list.getAttribute(name, self._element, page);
|
return self._list.getAttribute(name, self._element, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,7 +494,7 @@ pub const NamedNodeMap = struct {
|
|||||||
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 {
|
pub fn removeByName(self: *const NamedNodeMap, name: String, page: *Page) !?*Attribute {
|
||||||
// this 2-step process (get then delete) isn't efficient. But we don't
|
// 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.
|
// expect this to be called often, and this lets us keep delete straightforward.
|
||||||
const attr = (try self.getByName(name, page)) orelse return null;
|
const attr = (try self.getByName(name, page)) orelse return null;
|
||||||
@@ -556,11 +560,13 @@ pub const InnerIterator = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn formatAttribute(name: []const u8, value: []const u8, writer: *std.Io.Writer) !void {
|
fn formatAttribute(name: String, value_: String, writer: *std.Io.Writer) !void {
|
||||||
try writer.writeAll(name);
|
try writer.writeAll(name.str());
|
||||||
|
|
||||||
// Boolean attributes with empty values are serialized without a value
|
// Boolean attributes with empty values are serialized without a value
|
||||||
if (value.len == 0 and boolean_attributes_lookup.has(name)) {
|
|
||||||
|
const value = value_.str();
|
||||||
|
if (value.len == 0 and boolean_attributes_lookup.has(name.str())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const js = @import("../../js/js.zig");
|
|||||||
|
|
||||||
const Element = @import("../Element.zig");
|
const Element = @import("../Element.zig");
|
||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
|
const String = @import("../../../string.zig").String;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
@@ -28,28 +29,60 @@ const DOMStringMap = @This();
|
|||||||
|
|
||||||
_element: *Element,
|
_element: *Element,
|
||||||
|
|
||||||
fn getProperty(self: *DOMStringMap, name: []const u8, page: *Page) !?[]const u8 {
|
fn getProperty(self: *DOMStringMap, name: String, page: *Page) !?String {
|
||||||
const attr_name = try camelToKebab(page.call_arena, name);
|
const attr_name = try camelToKebab(page.call_arena, name);
|
||||||
return try self._element.getAttribute(attr_name, page);
|
return try self._element.getAttribute(attr_name, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setProperty(self: *DOMStringMap, name: []const u8, value: []const u8, page: *Page) !void {
|
fn setProperty(self: *DOMStringMap, name: String, value: String, page: *Page) !void {
|
||||||
const attr_name = try camelToKebab(page.call_arena, name);
|
const attr_name = try camelToKebab(page.call_arena, name);
|
||||||
return self._element.setAttributeSafe(attr_name, value, page);
|
return self._element.setAttributeSafe(attr_name, value, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deleteProperty(self: *DOMStringMap, name: []const u8, page: *Page) !void {
|
fn deleteProperty(self: *DOMStringMap, name: String, page: *Page) !void {
|
||||||
const attr_name = try camelToKebab(page.call_arena, name);
|
const attr_name = try camelToKebab(page.call_arena, name);
|
||||||
try self._element.removeAttribute(attr_name, page);
|
try self._element.removeAttribute(attr_name, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fooBar -> foo-bar
|
// fooBar -> data-foo-bar (with SSO optimization for short strings)
|
||||||
fn camelToKebab(arena: Allocator, camel: []const u8) ![]const u8 {
|
fn camelToKebab(arena: Allocator, camel: String) !String {
|
||||||
|
const camel_str = camel.str();
|
||||||
|
|
||||||
|
// Calculate output length
|
||||||
|
var output_len: usize = 5; // "data-"
|
||||||
|
for (camel_str, 0..) |c, i| {
|
||||||
|
output_len += 1;
|
||||||
|
if (std.ascii.isUpper(c) and i > 0) output_len += 1; // extra char for '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output_len <= 12) {
|
||||||
|
// SSO path - no allocation!
|
||||||
|
var content: [12]u8 = @splat(0);
|
||||||
|
@memcpy(content[0..5], "data-");
|
||||||
|
var idx: usize = 5;
|
||||||
|
|
||||||
|
for (camel_str, 0..) |c, i| {
|
||||||
|
if (std.ascii.isUpper(c)) {
|
||||||
|
if (i > 0) {
|
||||||
|
content[idx] = '-';
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
content[idx] = std.ascii.toLower(c);
|
||||||
|
} else {
|
||||||
|
content[idx] = c;
|
||||||
|
}
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .len = @intCast(output_len), .payload = .{ .content = content } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: allocate for longer strings
|
||||||
var result: std.ArrayList(u8) = .empty;
|
var result: std.ArrayList(u8) = .empty;
|
||||||
try result.ensureTotalCapacity(arena, 5 + camel.len * 2);
|
try result.ensureTotalCapacity(arena, output_len);
|
||||||
result.appendSliceAssumeCapacity("data-");
|
result.appendSliceAssumeCapacity("data-");
|
||||||
|
|
||||||
for (camel, 0..) |c, i| {
|
for (camel_str, 0..) |c, i| {
|
||||||
if (std.ascii.isUpper(c)) {
|
if (std.ascii.isUpper(c)) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
result.appendAssumeCapacity('-');
|
result.appendAssumeCapacity('-');
|
||||||
@@ -60,7 +93,7 @@ fn camelToKebab(arena: Allocator, camel: []const u8) ![]const u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.items;
|
return try String.init(arena, result.items, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
// data-foo-bar -> fooBar
|
// data-foo-bar -> fooBar
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ fn _getInnerText(self: *HtmlElement, writer: *std.Io.Writer, state: *innerTextSt
|
|||||||
.document => {},
|
.document => {},
|
||||||
.document_type => {},
|
.document_type => {},
|
||||||
.document_fragment => {},
|
.document_fragment => {},
|
||||||
.attribute => |attr| try writer.writeAll(attr._value),
|
.attribute => |attr| try writer.writeAll(attr._value.str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,8 +281,11 @@ pub fn insertAdjacentHTML(
|
|||||||
});
|
});
|
||||||
const doc_node = doc.asNode();
|
const doc_node = doc.asNode();
|
||||||
|
|
||||||
|
const arena = try page.getArena(.{ .debug = "HTML.insertAdjacentHTML" });
|
||||||
|
defer page.releaseArena(arena);
|
||||||
|
|
||||||
const Parser = @import("../../parser/Parser.zig");
|
const Parser = @import("../../parser/Parser.zig");
|
||||||
var parser = Parser.init(page.call_arena, doc_node, page);
|
var parser = Parser.init(arena, doc_node, page);
|
||||||
parser.parse(html);
|
parser.parse(html);
|
||||||
|
|
||||||
// Check if there's parsing error.
|
// Check if there's parsing error.
|
||||||
@@ -290,22 +293,23 @@ pub fn insertAdjacentHTML(
|
|||||||
return error.Invalid;
|
return error.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always get it wrapped like so:
|
// The parser wraps content in a document structure:
|
||||||
// <html><head></head><body>{ ... }</body></html>
|
// - Typical: <html><head>...</head><body>...</body></html>
|
||||||
// None of the following can be null.
|
// - Head-only: <html><head><meta></head></html> (no body)
|
||||||
const maybe_html_node = doc_node.firstChild();
|
// - Empty/comments: May have no <html> element at all
|
||||||
lp.assert(maybe_html_node != null, "Html.insertAdjacentHTML null html", .{});
|
const html_node = doc_node.firstChild() orelse return;
|
||||||
const html_node = maybe_html_node orelse return;
|
|
||||||
|
|
||||||
const maybe_body_node = html_node.lastChild();
|
|
||||||
lp.assert(maybe_body_node != null, "Html.insertAdjacentHTML null bodys", .{});
|
|
||||||
const body = maybe_body_node orelse return;
|
|
||||||
|
|
||||||
const target_node, const prev_node = try self.asElement().asNode().findAdjacentNodes(position);
|
const target_node, const prev_node = try self.asElement().asNode().findAdjacentNodes(position);
|
||||||
|
|
||||||
var iter = body.childrenIterator();
|
// Iterate through all children of <html> (typically <head> and/or <body>)
|
||||||
while (iter.next()) |child_node| {
|
// and insert their children (not the containers themselves) into the target.
|
||||||
_ = try target_node.insertBefore(child_node, prev_node, page);
|
// This handles both body content AND head-only elements like <meta>, <title>, etc.
|
||||||
|
var html_children = html_node.childrenIterator();
|
||||||
|
while (html_children.next()) |container| {
|
||||||
|
var iter = container.childrenIterator();
|
||||||
|
while (iter.next()) |child_node| {
|
||||||
|
_ = try target_node.insertBefore(child_node, prev_node, page);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +333,766 @@ pub fn click(self: *HtmlElement, page: *Page) !void {
|
|||||||
try page._event_manager.dispatch(self.asEventTarget(), event.asEvent());
|
try page._event_manager.dispatch(self.asEventTarget(), event.asEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setOnAbort(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .abort, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnAbort(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .abort);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnAnimationCancel(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .animationcancel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnAnimationCancel(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .animationcancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnAnimationEnd(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .animationend, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnAnimationEnd(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .animationend);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnAnimationIteration(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .animationiteration, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnAnimationIteration(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .animationiteration);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnAnimationStart(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .animationstart, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnAnimationStart(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .animationstart);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnAuxClick(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .auxclick, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnAuxClick(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .auxclick);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnBeforeInput(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .beforeinput, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnBeforeInput(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .beforeinput);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnBeforeMatch(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .beforematch, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnBeforeMatch(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .beforematch);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnBeforeToggle(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .beforetoggle, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnBeforeToggle(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .beforetoggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnBlur(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .blur, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnBlur(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCancel(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .cancel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCancel(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCanPlay(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .canplay, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCanPlay(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .canplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCanPlayThrough(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .canplaythrough, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCanPlayThrough(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .canplaythrough);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .change, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .change);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnClick(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .click, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnClick(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .click);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnClose(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .close, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnClose(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .close);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCommand(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .command, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCommand(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .command);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnContentVisibilityAutoStateChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .contentvisibilityautostatechange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnContentVisibilityAutoStateChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .contentvisibilityautostatechange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnContextLost(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .contextlost, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnContextLost(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .contextlost);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnContextMenu(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .contextmenu, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnContextMenu(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .contextmenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnContextRestored(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .contextrestored, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnContextRestored(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .contextrestored);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCopy(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .copy, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCopy(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCueChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .cuechange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCueChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .cuechange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnCut(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .cut, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnCut(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .cut);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDblClick(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dblclick, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDblClick(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dblclick);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDrag(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .drag, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDrag(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .drag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDragEnd(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dragend, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDragEnd(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dragend);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDragEnter(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dragenter, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDragEnter(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dragenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDragExit(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dragexit, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDragExit(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dragexit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDragLeave(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dragleave, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDragLeave(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dragleave);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDragOver(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dragover, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDragOver(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dragover);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDragStart(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .dragstart, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDragStart(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .dragstart);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDrop(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .drop, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDrop(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnDurationChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .durationchange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnDurationChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .durationchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnEmptied(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .emptied, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnEmptied(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .emptied);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnEnded(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .ended, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnEnded(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .ended);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnError(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .@"error", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnError(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .@"error");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnFocus(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .focus, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnFocus(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnFormData(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .formdata, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnFormData(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .formdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnFullscreenChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .fullscreenchange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnFullscreenChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .fullscreenchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnFullscreenError(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .fullscreenerror, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnFullscreenError(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .fullscreenerror);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnGotPointerCapture(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .gotpointercapture, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnGotPointerCapture(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .gotpointercapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnInput(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .input, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnInput(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .input);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnInvalid(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .invalid, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnInvalid(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnKeyDown(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .keydown, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnKeyDown(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .keydown);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnKeyPress(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .keypress, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnKeyPress(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .keypress);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnKeyUp(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .keyup, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnKeyUp(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .keyup);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnLoad(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .load, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnLoad(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .load);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnLoadedData(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .loadeddata, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnLoadedData(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .loadeddata);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnLoadedMetadata(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .loadedmetadata, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnLoadedMetadata(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .loadedmetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnLoadStart(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .loadstart, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnLoadStart(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .loadstart);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnLostPointerCapture(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .lostpointercapture, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnLostPointerCapture(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .lostpointercapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnMouseDown(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .mousedown, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnMouseDown(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .mousedown);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnMouseMove(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .mousemove, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnMouseMove(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .mousemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnMouseOut(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .mouseout, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnMouseOut(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .mouseout);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnMouseOver(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .mouseover, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnMouseOver(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .mouseover);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnMouseUp(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .mouseup, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnMouseUp(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .mouseup);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPaste(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .paste, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPaste(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .paste);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPause(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pause, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPause(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pause);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPlay(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .play, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPlay(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .play);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPlaying(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .playing, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPlaying(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerCancel(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointercancel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerCancel(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointercancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerDown(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerdown, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerDown(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerEnter(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerenter, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerEnter(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerLeave(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerleave, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerLeave(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerleave);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerMove(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointermove, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerMove(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointermove);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerOut(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerout, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerOut(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerout);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerOver(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerover, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerOver(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerover);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerRawUpdate(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerrawupdate, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerRawUpdate(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerrawupdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnPointerUp(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .pointerup, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnPointerUp(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .pointerup);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnProgress(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .progress, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnProgress(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnRateChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .ratechange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnRateChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .ratechange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnReset(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .reset, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnReset(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnResize(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .resize, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnResize(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .resize);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnScroll(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .scroll, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnScroll(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnScrollEnd(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .scrollend, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnScrollEnd(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .scrollend);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSecurityPolicyViolation(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .securitypolicyviolation, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSecurityPolicyViolation(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .securitypolicyviolation);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSeeked(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .seeked, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSeeked(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .seeked);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSeeking(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .seeking, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSeeking(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .seeking);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSelect(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .select, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSelect(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .select);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSelectionChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .selectionchange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSelectionChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .selectionchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSelectStart(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .selectstart, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSelectStart(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .selectstart);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSlotChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .slotchange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSlotChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .slotchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnStalled(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .stalled, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnStalled(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .stalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSubmit(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .submit, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSubmit(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .submit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSuspend(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .@"suspend", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnSuspend(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .@"suspend");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnTimeUpdate(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .timeupdate, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnTimeUpdate(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .timeupdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnToggle(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .toggle, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnToggle(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnTransitionCancel(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .transitioncancel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnTransitionCancel(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .transitioncancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnTransitionEnd(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .transitionend, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnTransitionEnd(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .transitionend);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnTransitionRun(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .transitionrun, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnTransitionRun(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .transitionrun);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnTransitionStart(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .transitionstart, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnTransitionStart(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .transitionstart);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnVolumeChange(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .volumechange, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnVolumeChange(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .volumechange);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnWaiting(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .waiting, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnWaiting(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .waiting);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnWheel(self: *HtmlElement, callback: js.Function.Global, page: *Page) !void {
|
||||||
|
return page.setAttrListener(self.asElement(), .wheel, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOnWheel(self: *HtmlElement, page: *Page) ?js.Function.Global {
|
||||||
|
return page.getAttrListener(self.asElement(), .wheel);
|
||||||
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
pub const bridge = js.Bridge(HtmlElement);
|
pub const bridge = js.Bridge(HtmlElement);
|
||||||
|
|
||||||
@@ -348,6 +1112,102 @@ pub const JsApi = struct {
|
|||||||
}
|
}
|
||||||
pub const insertAdjacentHTML = bridge.function(HtmlElement.insertAdjacentHTML, .{ .dom_exception = true });
|
pub const insertAdjacentHTML = bridge.function(HtmlElement.insertAdjacentHTML, .{ .dom_exception = true });
|
||||||
pub const click = bridge.function(HtmlElement.click, .{});
|
pub const click = bridge.function(HtmlElement.click, .{});
|
||||||
|
|
||||||
|
pub const onabort = bridge.accessor(HtmlElement.getOnAbort, HtmlElement.setOnAbort, .{});
|
||||||
|
pub const onanimationcancel = bridge.accessor(HtmlElement.getOnAnimationCancel, HtmlElement.setOnAnimationCancel, .{});
|
||||||
|
pub const onanimationend = bridge.accessor(HtmlElement.getOnAnimationEnd, HtmlElement.setOnAnimationEnd, .{});
|
||||||
|
pub const onanimationiteration = bridge.accessor(HtmlElement.getOnAnimationIteration, HtmlElement.setOnAnimationIteration, .{});
|
||||||
|
pub const onanimationstart = bridge.accessor(HtmlElement.getOnAnimationStart, HtmlElement.setOnAnimationStart, .{});
|
||||||
|
pub const onauxclick = bridge.accessor(HtmlElement.getOnAuxClick, HtmlElement.setOnAuxClick, .{});
|
||||||
|
pub const onbeforeinput = bridge.accessor(HtmlElement.getOnBeforeInput, HtmlElement.setOnBeforeInput, .{});
|
||||||
|
pub const onbeforematch = bridge.accessor(HtmlElement.getOnBeforeMatch, HtmlElement.setOnBeforeMatch, .{});
|
||||||
|
pub const onbeforetoggle = bridge.accessor(HtmlElement.getOnBeforeToggle, HtmlElement.setOnBeforeToggle, .{});
|
||||||
|
pub const onblur = bridge.accessor(HtmlElement.getOnBlur, HtmlElement.setOnBlur, .{});
|
||||||
|
pub const oncancel = bridge.accessor(HtmlElement.getOnCancel, HtmlElement.setOnCancel, .{});
|
||||||
|
pub const oncanplay = bridge.accessor(HtmlElement.getOnCanPlay, HtmlElement.setOnCanPlay, .{});
|
||||||
|
pub const oncanplaythrough = bridge.accessor(HtmlElement.getOnCanPlayThrough, HtmlElement.setOnCanPlayThrough, .{});
|
||||||
|
pub const onchange = bridge.accessor(HtmlElement.getOnChange, HtmlElement.setOnChange, .{});
|
||||||
|
pub const onclick = bridge.accessor(HtmlElement.getOnClick, HtmlElement.setOnClick, .{});
|
||||||
|
pub const onclose = bridge.accessor(HtmlElement.getOnClose, HtmlElement.setOnClose, .{});
|
||||||
|
pub const oncommand = bridge.accessor(HtmlElement.getOnCommand, HtmlElement.setOnCommand, .{});
|
||||||
|
pub const oncontentvisibilityautostatechange = bridge.accessor(HtmlElement.getOnContentVisibilityAutoStateChange, HtmlElement.setOnContentVisibilityAutoStateChange, .{});
|
||||||
|
pub const oncontextlost = bridge.accessor(HtmlElement.getOnContextLost, HtmlElement.setOnContextLost, .{});
|
||||||
|
pub const oncontextmenu = bridge.accessor(HtmlElement.getOnContextMenu, HtmlElement.setOnContextMenu, .{});
|
||||||
|
pub const oncontextrestored = bridge.accessor(HtmlElement.getOnContextRestored, HtmlElement.setOnContextRestored, .{});
|
||||||
|
pub const oncopy = bridge.accessor(HtmlElement.getOnCopy, HtmlElement.setOnCopy, .{});
|
||||||
|
pub const oncuechange = bridge.accessor(HtmlElement.getOnCueChange, HtmlElement.setOnCueChange, .{});
|
||||||
|
pub const oncut = bridge.accessor(HtmlElement.getOnCut, HtmlElement.setOnCut, .{});
|
||||||
|
pub const ondblclick = bridge.accessor(HtmlElement.getOnDblClick, HtmlElement.setOnDblClick, .{});
|
||||||
|
pub const ondrag = bridge.accessor(HtmlElement.getOnDrag, HtmlElement.setOnDrag, .{});
|
||||||
|
pub const ondragend = bridge.accessor(HtmlElement.getOnDragEnd, HtmlElement.setOnDragEnd, .{});
|
||||||
|
pub const ondragenter = bridge.accessor(HtmlElement.getOnDragEnter, HtmlElement.setOnDragEnter, .{});
|
||||||
|
pub const ondragexit = bridge.accessor(HtmlElement.getOnDragExit, HtmlElement.setOnDragExit, .{});
|
||||||
|
pub const ondragleave = bridge.accessor(HtmlElement.getOnDragLeave, HtmlElement.setOnDragLeave, .{});
|
||||||
|
pub const ondragover = bridge.accessor(HtmlElement.getOnDragOver, HtmlElement.setOnDragOver, .{});
|
||||||
|
pub const ondragstart = bridge.accessor(HtmlElement.getOnDragStart, HtmlElement.setOnDragStart, .{});
|
||||||
|
pub const ondrop = bridge.accessor(HtmlElement.getOnDrop, HtmlElement.setOnDrop, .{});
|
||||||
|
pub const ondurationchange = bridge.accessor(HtmlElement.getOnDurationChange, HtmlElement.setOnDurationChange, .{});
|
||||||
|
pub const onemptied = bridge.accessor(HtmlElement.getOnEmptied, HtmlElement.setOnEmptied, .{});
|
||||||
|
pub const onended = bridge.accessor(HtmlElement.getOnEnded, HtmlElement.setOnEnded, .{});
|
||||||
|
pub const onerror = bridge.accessor(HtmlElement.getOnError, HtmlElement.setOnError, .{});
|
||||||
|
pub const onfocus = bridge.accessor(HtmlElement.getOnFocus, HtmlElement.setOnFocus, .{});
|
||||||
|
pub const onformdata = bridge.accessor(HtmlElement.getOnFormData, HtmlElement.setOnFormData, .{});
|
||||||
|
pub const onfullscreenchange = bridge.accessor(HtmlElement.getOnFullscreenChange, HtmlElement.setOnFullscreenChange, .{});
|
||||||
|
pub const onfullscreenerror = bridge.accessor(HtmlElement.getOnFullscreenError, HtmlElement.setOnFullscreenError, .{});
|
||||||
|
pub const ongotpointercapture = bridge.accessor(HtmlElement.getOnGotPointerCapture, HtmlElement.setOnGotPointerCapture, .{});
|
||||||
|
pub const oninput = bridge.accessor(HtmlElement.getOnInput, HtmlElement.setOnInput, .{});
|
||||||
|
pub const oninvalid = bridge.accessor(HtmlElement.getOnInvalid, HtmlElement.setOnInvalid, .{});
|
||||||
|
pub const onkeydown = bridge.accessor(HtmlElement.getOnKeyDown, HtmlElement.setOnKeyDown, .{});
|
||||||
|
pub const onkeypress = bridge.accessor(HtmlElement.getOnKeyPress, HtmlElement.setOnKeyPress, .{});
|
||||||
|
pub const onkeyup = bridge.accessor(HtmlElement.getOnKeyUp, HtmlElement.setOnKeyUp, .{});
|
||||||
|
pub const onload = bridge.accessor(HtmlElement.getOnLoad, HtmlElement.setOnLoad, .{});
|
||||||
|
pub const onloadeddata = bridge.accessor(HtmlElement.getOnLoadedData, HtmlElement.setOnLoadedData, .{});
|
||||||
|
pub const onloadedmetadata = bridge.accessor(HtmlElement.getOnLoadedMetadata, HtmlElement.setOnLoadedMetadata, .{});
|
||||||
|
pub const onloadstart = bridge.accessor(HtmlElement.getOnLoadStart, HtmlElement.setOnLoadStart, .{});
|
||||||
|
pub const onlostpointercapture = bridge.accessor(HtmlElement.getOnLostPointerCapture, HtmlElement.setOnLostPointerCapture, .{});
|
||||||
|
pub const onmousedown = bridge.accessor(HtmlElement.getOnMouseDown, HtmlElement.setOnMouseDown, .{});
|
||||||
|
pub const onmousemove = bridge.accessor(HtmlElement.getOnMouseMove, HtmlElement.setOnMouseMove, .{});
|
||||||
|
pub const onmouseout = bridge.accessor(HtmlElement.getOnMouseOut, HtmlElement.setOnMouseOut, .{});
|
||||||
|
pub const onmouseover = bridge.accessor(HtmlElement.getOnMouseOver, HtmlElement.setOnMouseOver, .{});
|
||||||
|
pub const onmouseup = bridge.accessor(HtmlElement.getOnMouseUp, HtmlElement.setOnMouseUp, .{});
|
||||||
|
pub const onpaste = bridge.accessor(HtmlElement.getOnPaste, HtmlElement.setOnPaste, .{});
|
||||||
|
pub const onpause = bridge.accessor(HtmlElement.getOnPause, HtmlElement.setOnPause, .{});
|
||||||
|
pub const onplay = bridge.accessor(HtmlElement.getOnPlay, HtmlElement.setOnPlay, .{});
|
||||||
|
pub const onplaying = bridge.accessor(HtmlElement.getOnPlaying, HtmlElement.setOnPlaying, .{});
|
||||||
|
pub const onpointercancel = bridge.accessor(HtmlElement.getOnPointerCancel, HtmlElement.setOnPointerCancel, .{});
|
||||||
|
pub const onpointerdown = bridge.accessor(HtmlElement.getOnPointerDown, HtmlElement.setOnPointerDown, .{});
|
||||||
|
pub const onpointerenter = bridge.accessor(HtmlElement.getOnPointerEnter, HtmlElement.setOnPointerEnter, .{});
|
||||||
|
pub const onpointerleave = bridge.accessor(HtmlElement.getOnPointerLeave, HtmlElement.setOnPointerLeave, .{});
|
||||||
|
pub const onpointermove = bridge.accessor(HtmlElement.getOnPointerMove, HtmlElement.setOnPointerMove, .{});
|
||||||
|
pub const onpointerout = bridge.accessor(HtmlElement.getOnPointerOut, HtmlElement.setOnPointerOut, .{});
|
||||||
|
pub const onpointerover = bridge.accessor(HtmlElement.getOnPointerOver, HtmlElement.setOnPointerOver, .{});
|
||||||
|
pub const onpointerrawupdate = bridge.accessor(HtmlElement.getOnPointerRawUpdate, HtmlElement.setOnPointerRawUpdate, .{});
|
||||||
|
pub const onpointerup = bridge.accessor(HtmlElement.getOnPointerUp, HtmlElement.setOnPointerUp, .{});
|
||||||
|
pub const onprogress = bridge.accessor(HtmlElement.getOnProgress, HtmlElement.setOnProgress, .{});
|
||||||
|
pub const onratechange = bridge.accessor(HtmlElement.getOnRateChange, HtmlElement.setOnRateChange, .{});
|
||||||
|
pub const onreset = bridge.accessor(HtmlElement.getOnReset, HtmlElement.setOnReset, .{});
|
||||||
|
pub const onresize = bridge.accessor(HtmlElement.getOnResize, HtmlElement.setOnResize, .{});
|
||||||
|
pub const onscroll = bridge.accessor(HtmlElement.getOnScroll, HtmlElement.setOnScroll, .{});
|
||||||
|
pub const onscrollend = bridge.accessor(HtmlElement.getOnScrollEnd, HtmlElement.setOnScrollEnd, .{});
|
||||||
|
pub const onsecuritypolicyviolation = bridge.accessor(HtmlElement.getOnSecurityPolicyViolation, HtmlElement.setOnSecurityPolicyViolation, .{});
|
||||||
|
pub const onseeked = bridge.accessor(HtmlElement.getOnSeeked, HtmlElement.setOnSeeked, .{});
|
||||||
|
pub const onseeking = bridge.accessor(HtmlElement.getOnSeeking, HtmlElement.setOnSeeking, .{});
|
||||||
|
pub const onselect = bridge.accessor(HtmlElement.getOnSelect, HtmlElement.setOnSelect, .{});
|
||||||
|
pub const onselectionchange = bridge.accessor(HtmlElement.getOnSelectionChange, HtmlElement.setOnSelectionChange, .{});
|
||||||
|
pub const onselectstart = bridge.accessor(HtmlElement.getOnSelectStart, HtmlElement.setOnSelectStart, .{});
|
||||||
|
pub const onslotchange = bridge.accessor(HtmlElement.getOnSlotChange, HtmlElement.setOnSlotChange, .{});
|
||||||
|
pub const onstalled = bridge.accessor(HtmlElement.getOnStalled, HtmlElement.setOnStalled, .{});
|
||||||
|
pub const onsubmit = bridge.accessor(HtmlElement.getOnSubmit, HtmlElement.setOnSubmit, .{});
|
||||||
|
pub const onsuspend = bridge.accessor(HtmlElement.getOnSuspend, HtmlElement.setOnSuspend, .{});
|
||||||
|
pub const ontimeupdate = bridge.accessor(HtmlElement.getOnTimeUpdate, HtmlElement.setOnTimeUpdate, .{});
|
||||||
|
pub const ontoggle = bridge.accessor(HtmlElement.getOnToggle, HtmlElement.setOnToggle, .{});
|
||||||
|
pub const ontransitioncancel = bridge.accessor(HtmlElement.getOnTransitionCancel, HtmlElement.setOnTransitionCancel, .{});
|
||||||
|
pub const ontransitionend = bridge.accessor(HtmlElement.getOnTransitionEnd, HtmlElement.setOnTransitionEnd, .{});
|
||||||
|
pub const ontransitionrun = bridge.accessor(HtmlElement.getOnTransitionRun, HtmlElement.setOnTransitionRun, .{});
|
||||||
|
pub const ontransitionstart = bridge.accessor(HtmlElement.getOnTransitionStart, HtmlElement.setOnTransitionStart, .{});
|
||||||
|
pub const onvolumechange = bridge.accessor(HtmlElement.getOnVolumeChange, HtmlElement.setOnVolumeChange, .{});
|
||||||
|
pub const onwaiting = bridge.accessor(HtmlElement.getOnWaiting, HtmlElement.setOnWaiting, .{});
|
||||||
|
pub const onwheel = bridge.accessor(HtmlElement.getOnWheel, HtmlElement.setOnWheel, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Build = struct {
|
pub const Build = struct {
|
||||||
@@ -378,3 +1238,8 @@ pub const Build = struct {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../../testing.zig");
|
||||||
|
test "WebApi: HTML.event_listeners" {
|
||||||
|
try testing.htmlRunner("element/html/event_listeners.html", .{});
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub fn asNode(self: *Anchor) *Node {
|
|||||||
|
|
||||||
pub fn getHref(self: *Anchor, page: *Page) ![]const u8 {
|
pub fn getHref(self: *Anchor, page: *Page) ![]const u8 {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
const href = element.getAttributeSafe("href") orelse return "";
|
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return "";
|
||||||
if (href.len == 0) {
|
if (href.len == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -48,15 +48,15 @@ pub fn getHref(self: *Anchor, page: *Page) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setHref(self: *Anchor, value: []const u8, page: *Page) !void {
|
pub fn setHref(self: *Anchor, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("href", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("href"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTarget(self: *Anchor) []const u8 {
|
pub fn getTarget(self: *Anchor) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("target") orelse "";
|
return self.asElement().getAttributeSafe(comptime .wrap("target")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setTarget(self: *Anchor, value: []const u8, page: *Page) !void {
|
pub fn setTarget(self: *Anchor, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("target", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("target"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOrigin(self: *Anchor, page: *Page) ![]const u8 {
|
pub fn getOrigin(self: *Anchor, page: *Page) ![]const u8 {
|
||||||
@@ -167,19 +167,19 @@ pub fn setProtocol(self: *Anchor, value: []const u8, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getType(self: *Anchor) []const u8 {
|
pub fn getType(self: *Anchor) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("type") orelse "";
|
return self.asElement().getAttributeSafe(comptime .wrap("type")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void {
|
pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("type", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Anchor) []const u8 {
|
pub fn getName(self: *const Anchor) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Anchor, value: []const u8, page: *Page) !void {
|
pub fn setName(self: *Anchor, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getText(self: *Anchor, page: *Page) ![:0]const u8 {
|
pub fn getText(self: *Anchor, page: *Page) ![:0]const u8 {
|
||||||
@@ -191,7 +191,7 @@ pub fn setText(self: *Anchor, value: []const u8, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 {
|
fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 {
|
||||||
const href = self.asElement().getAttributeSafe("href") orelse return null;
|
const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return null;
|
||||||
if (href.len == 0) {
|
if (href.len == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const String = @import("../../../../string.zig").String;
|
||||||
|
|
||||||
const js = @import("../../../js/js.zig");
|
const js = @import("../../../js/js.zig");
|
||||||
const Page = @import("../../../Page.zig");
|
const Page = @import("../../../Page.zig");
|
||||||
|
|
||||||
@@ -27,16 +29,16 @@ const Audio = @This();
|
|||||||
|
|
||||||
_proto: *Media,
|
_proto: *Media,
|
||||||
|
|
||||||
pub fn constructor(maybe_url: ?[]const u8, page: *Page) !*Media {
|
pub fn constructor(maybe_url: ?String, page: *Page) !*Media {
|
||||||
const node = try page.createElementNS(.html, "audio", null);
|
const node = try page.createElementNS(.html, "audio", null);
|
||||||
const el = node.as(Element);
|
const el = node.as(Element);
|
||||||
|
|
||||||
const list = try el.getOrCreateAttributeList(page);
|
const list = try el.getOrCreateAttributeList(page);
|
||||||
// Always set to "auto" initially.
|
// Always set to "auto" initially.
|
||||||
_ = try list.putSafe("preload", "auto", el, page);
|
_ = try list.putSafe(comptime .wrap("preload"), comptime .wrap("auto"), el, page);
|
||||||
// Set URL if provided.
|
// Set URL if provided.
|
||||||
if (maybe_url) |url| {
|
if (maybe_url) |url| {
|
||||||
_ = try list.putSafe("src", url, el, page);
|
_ = try list.putSafe(comptime .wrap("src"), url, el, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
return node.as(Media);
|
return node.as(Media);
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ pub const JsApi = struct {
|
|||||||
pub const Build = struct {
|
pub const Build = struct {
|
||||||
pub fn complete(node: *Node, page: *Page) !void {
|
pub fn complete(node: *Node, page: *Page) !void {
|
||||||
const el = node.as(Element);
|
const el = node.as(Element);
|
||||||
const on_load = el.getAttributeSafe("onload") orelse return;
|
const on_load = el.getAttributeSafe(comptime .wrap("onload")) orelse return;
|
||||||
if (page.js.stringToFunction(on_load)) |func| {
|
if (page.js.stringToPersistedFunction(on_load)) |func| {
|
||||||
page.window._on_load = try func.persist();
|
page.window._on_load = func;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.err(.js, "body.onload", .{ .err = err, .str = on_load });
|
log.err(.js, "body.onload", .{ .err = err, .str = on_load });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,50 +39,50 @@ pub fn asNode(self: *Button) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDisabled(self: *const Button) bool {
|
pub fn getDisabled(self: *const Button) bool {
|
||||||
return self.asConstElement().getAttributeSafe("disabled") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void {
|
pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
try self.asElement().setAttributeSafe("disabled", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("disabled", page);
|
try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Button) []const u8 {
|
pub fn getName(self: *const Button) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Button, name: []const u8, page: *Page) !void {
|
pub fn setName(self: *Button, name: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", name, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getType(self: *const Button) []const u8 {
|
pub fn getType(self: *const Button) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("type") orelse "submit";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "submit";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setType(self: *Button, typ: []const u8, page: *Page) !void {
|
pub fn setType(self: *Button, typ: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("type", typ, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(typ), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getValue(self: *const Button) []const u8 {
|
pub fn getValue(self: *const Button) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("value") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("value")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setValue(self: *Button, value: []const u8, page: *Page) !void {
|
pub fn setValue(self: *Button, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("value", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRequired(self: *const Button) bool {
|
pub fn getRequired(self: *const Button) bool {
|
||||||
return self.asConstElement().getAttributeSafe("required") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRequired(self: *Button, required: bool, page: *Page) !void {
|
pub fn setRequired(self: *Button, required: bool, page: *Page) !void {
|
||||||
if (required) {
|
if (required) {
|
||||||
try self.asElement().setAttributeSafe("required", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("required", page);
|
try self.asElement().removeAttribute(comptime .wrap("required"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ pub fn getForm(self: *Button, page: *Page) ?*Form {
|
|||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
// 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(comptime .wrap("form"))) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id, page)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,23 +40,23 @@ pub fn asNode(self: *Canvas) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getWidth(self: *const Canvas) u32 {
|
pub fn getWidth(self: *const Canvas) u32 {
|
||||||
const attr = self.asConstElement().getAttributeSafe("width") orelse return 300;
|
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 300;
|
||||||
return std.fmt.parseUnsigned(u32, attr, 10) catch 300;
|
return std.fmt.parseUnsigned(u32, attr, 10) catch 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setWidth(self: *Canvas, value: u32, page: *Page) !void {
|
pub fn setWidth(self: *Canvas, value: u32, page: *Page) !void {
|
||||||
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
||||||
try self.asElement().setAttributeSafe("width", str, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("width"), .wrap(str), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getHeight(self: *const Canvas) u32 {
|
pub fn getHeight(self: *const Canvas) u32 {
|
||||||
const attr = self.asConstElement().getAttributeSafe("height") orelse return 150;
|
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 150;
|
||||||
return std.fmt.parseUnsigned(u32, attr, 10) catch 150;
|
return std.fmt.parseUnsigned(u32, attr, 10) catch 150;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setHeight(self: *Canvas, value: u32, page: *Page) !void {
|
pub fn setHeight(self: *Canvas, value: u32, page: *Page) !void {
|
||||||
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
||||||
try self.asElement().setAttributeSafe("height", str, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("height"), .wrap(str), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Since there's no base class rendering contextes inherit from,
|
/// Since there's no base class rendering contextes inherit from,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ pub fn invokeDisconnectedCallback(self: *Custom, page: *Page) void {
|
|||||||
self.invokeCallback("disconnectedCallback", .{}, page);
|
self.invokeCallback("disconnectedCallback", .{}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invokeAttributeChangedCallback(self: *Custom, name: []const u8, old_value: ?[]const u8, new_value: ?[]const u8, page: *Page) void {
|
pub fn invokeAttributeChangedCallback(self: *Custom, name: String, old_value: ?String, new_value: ?String, page: *Page) void {
|
||||||
const definition = self._definition orelse return;
|
const definition = self._definition orelse return;
|
||||||
if (!definition.isAttributeObserved(name)) {
|
if (!definition.isAttributeObserved(name)) {
|
||||||
return;
|
return;
|
||||||
@@ -144,7 +144,7 @@ pub fn invokeDisconnectedCallbackOnElement(element: *Element, page: *Page) void
|
|||||||
invokeCallbackOnElement(element, definition, "disconnectedCallback", .{}, page);
|
invokeCallbackOnElement(element, definition, "disconnectedCallback", .{}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invokeAttributeChangedCallbackOnElement(element: *Element, name: []const u8, old_value: ?[]const u8, new_value: ?[]const u8, page: *Page) void {
|
pub fn invokeAttributeChangedCallbackOnElement(element: *Element, name: String, old_value: ?String, new_value: ?String, page: *Page) void {
|
||||||
// Autonomous custom element
|
// Autonomous custom element
|
||||||
if (element.is(Custom)) |custom| {
|
if (element.is(Custom)) |custom| {
|
||||||
custom.invokeAttributeChangedCallback(name, old_value, new_value, page);
|
custom.invokeAttributeChangedCallback(name, old_value, new_value, page);
|
||||||
@@ -160,10 +160,12 @@ pub fn invokeAttributeChangedCallbackOnElement(element: *Element, name: []const
|
|||||||
fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefinition, comptime callback_name: [:0]const u8, args: anytype, page: *Page) void {
|
fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefinition, comptime callback_name: [:0]const u8, args: anytype, page: *Page) void {
|
||||||
_ = definition;
|
_ = definition;
|
||||||
|
|
||||||
const ctx = page.js;
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
// Get the JS element object
|
// Get the JS element object
|
||||||
const js_val = ctx.zigValueToJs(element, .{}) catch return;
|
const js_val = ls.local.zigValueToJs(element, .{}) catch return;
|
||||||
const js_element = js_val.toObject();
|
const js_element = js_val.toObject();
|
||||||
|
|
||||||
// Call the callback method if it exists
|
// Call the callback method if it exists
|
||||||
@@ -172,7 +174,7 @@ fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefiniti
|
|||||||
|
|
||||||
// Check if element has "is" attribute and attach customized built-in definition
|
// Check if element has "is" attribute and attach customized built-in definition
|
||||||
pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
|
pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
|
||||||
const is_value = element.getAttributeSafe("is") orelse return;
|
const is_value = element.getAttributeSafe(comptime .wrap("is")) orelse return;
|
||||||
|
|
||||||
const custom_elements = page.window.getCustomElements();
|
const custom_elements = page.window.getCustomElements();
|
||||||
const definition = custom_elements._definitions.get(is_value) orelse return;
|
const definition = custom_elements._definitions.get(is_value) orelse return;
|
||||||
@@ -195,8 +197,26 @@ pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
|
|||||||
page._upgrading_element = node;
|
page._upgrading_element = node;
|
||||||
defer page._upgrading_element = prev_upgrading;
|
defer page._upgrading_element = prev_upgrading;
|
||||||
|
|
||||||
|
// PERFORMANCE OPTIMIZATION: This pattern is discouraged in general code.
|
||||||
|
// Used here because: (1) multiple early returns before needing Local,
|
||||||
|
// (2) called from both V8 callbacks (Local exists) and parser (no Local).
|
||||||
|
// Prefer either: requiring *const js.Local parameter, OR always creating
|
||||||
|
// Local.Scope upfront.
|
||||||
|
var ls: ?js.Local.Scope = null;
|
||||||
|
var local = blk: {
|
||||||
|
if (page.js.local) |l| {
|
||||||
|
break :blk l;
|
||||||
|
}
|
||||||
|
ls = undefined;
|
||||||
|
page.js.localScope(&ls.?);
|
||||||
|
break :blk &ls.?.local;
|
||||||
|
};
|
||||||
|
defer if (ls) |*_ls| {
|
||||||
|
_ls.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
var caught: js.TryCatch.Caught = undefined;
|
var caught: js.TryCatch.Caught = undefined;
|
||||||
_ = definition.constructor.local().newInstance(&caught) catch |err| {
|
_ = local.toLocal(definition.constructor).newInstance(&caught) catch |err| {
|
||||||
log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err, .caught = caught });
|
log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err, .caught = caught });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -207,9 +227,11 @@ fn invokeCallback(self: *Custom, comptime callback_name: [:0]const u8, args: any
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = page.js;
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
const js_val = ctx.zigValueToJs(self, .{}) catch return;
|
const js_val = ls.local.zigValueToJs(self, .{}) catch return;
|
||||||
const js_element = js_val.toObject();
|
const js_element = js_val.toObject();
|
||||||
|
|
||||||
js_element.callMethod(void, callback_name, args) catch return;
|
js_element.callMethod(void, callback_name, args) catch return;
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ pub fn asNode(self: *Data) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getValue(self: *Data) []const u8 {
|
pub fn getValue(self: *Data) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("value") orelse "";
|
return self.asElement().getAttributeSafe(comptime .wrap("value")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setValue(self: *Data, value: []const u8, page: *Page) !void {
|
pub fn setValue(self: *Data, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("value", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -20,23 +20,23 @@ pub fn asNode(self: *Dialog) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOpen(self: *const Dialog) bool {
|
pub fn getOpen(self: *const Dialog) bool {
|
||||||
return self.asConstElement().getAttributeSafe("open") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("open")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void {
|
pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void {
|
||||||
if (open) {
|
if (open) {
|
||||||
try self.asElement().setAttributeSafe("open", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("open"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("open", page);
|
try self.asElement().removeAttribute(comptime .wrap("open"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getReturnValue(self: *const Dialog) []const u8 {
|
pub fn getReturnValue(self: *const Dialog) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("returnvalue") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("returnvalue")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setReturnValue(self: *Dialog, value: []const u8, page: *Page) !void {
|
pub fn setReturnValue(self: *Dialog, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("returnvalue", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("returnvalue"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -43,15 +43,15 @@ pub fn asNode(self: *Form) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Form) []const u8 {
|
pub fn getName(self: *const Form) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Form, name: []const u8, page: *Page) !void {
|
pub fn setName(self: *Form, name: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", name, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getMethod(self: *const Form) []const u8 {
|
pub fn getMethod(self: *const Form) []const u8 {
|
||||||
const method = self.asConstElement().getAttributeSafe("method") orelse return "get";
|
const method = self.asConstElement().getAttributeSafe(comptime .wrap("method")) orelse return "get";
|
||||||
|
|
||||||
if (std.ascii.eqlIgnoreCase(method, "post")) {
|
if (std.ascii.eqlIgnoreCase(method, "post")) {
|
||||||
return "post";
|
return "post";
|
||||||
@@ -64,11 +64,11 @@ pub fn getMethod(self: *const Form) []const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setMethod(self: *Form, method: []const u8, page: *Page) !void {
|
pub fn setMethod(self: *Form, method: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("method", method, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("method"), .wrap(method), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getElements(self: *Form, page: *Page) !*collections.HTMLFormControlsCollection {
|
pub fn getElements(self: *Form, page: *Page) !*collections.HTMLFormControlsCollection {
|
||||||
const form_id = self.asElement().getAttributeSafe("id");
|
const form_id = self.asElement().getAttributeSafe(comptime .wrap("id"));
|
||||||
const root = if (form_id != null)
|
const root = if (form_id != null)
|
||||||
self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls
|
self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image {
|
|||||||
|
|
||||||
if (w_) |w| blk: {
|
if (w_) |w| blk: {
|
||||||
const w_string = std.fmt.bufPrint(&page.buf, "{d}", .{w}) catch break :blk;
|
const w_string = std.fmt.bufPrint(&page.buf, "{d}", .{w}) catch break :blk;
|
||||||
try el.setAttributeSafe("width", w_string, page);
|
try el.setAttributeSafe(comptime .wrap("width"), .wrap(w_string), page);
|
||||||
}
|
}
|
||||||
if (h_) |h| blk: {
|
if (h_) |h| blk: {
|
||||||
const h_string = std.fmt.bufPrint(&page.buf, "{d}", .{h}) catch break :blk;
|
const h_string = std.fmt.bufPrint(&page.buf, "{d}", .{h}) catch break :blk;
|
||||||
try el.setAttributeSafe("height", h_string, page);
|
try el.setAttributeSafe(comptime .wrap("height"), .wrap(h_string), page);
|
||||||
}
|
}
|
||||||
return el.as(Image);
|
return el.as(Image);
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ pub fn asNode(self: *Image) *Node {
|
|||||||
|
|
||||||
pub fn getSrc(self: *const Image, page: *Page) ![]const u8 {
|
pub fn getSrc(self: *const Image, page: *Page) ![]const u8 {
|
||||||
const element = self.asConstElement();
|
const element = self.asConstElement();
|
||||||
const src = element.getAttributeSafe("src") orelse return "";
|
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
|
||||||
if (src.len == 0) {
|
if (src.len == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -46,54 +46,54 @@ pub fn getSrc(self: *const Image, page: *Page) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void {
|
pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("src", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAlt(self: *const Image) []const u8 {
|
pub fn getAlt(self: *const Image) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("alt") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("alt")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void {
|
pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("alt", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("alt"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getWidth(self: *const Image) u32 {
|
pub fn getWidth(self: *const Image) u32 {
|
||||||
const attr = self.asConstElement().getAttributeSafe("width") orelse return 0;
|
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 0;
|
||||||
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
|
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setWidth(self: *Image, value: u32, page: *Page) !void {
|
pub fn setWidth(self: *Image, value: u32, page: *Page) !void {
|
||||||
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
||||||
try self.asElement().setAttributeSafe("width", str, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("width"), .wrap(str), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getHeight(self: *const Image) u32 {
|
pub fn getHeight(self: *const Image) u32 {
|
||||||
const attr = self.asConstElement().getAttributeSafe("height") orelse return 0;
|
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 0;
|
||||||
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
|
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setHeight(self: *Image, value: u32, page: *Page) !void {
|
pub fn setHeight(self: *Image, value: u32, page: *Page) !void {
|
||||||
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
|
||||||
try self.asElement().setAttributeSafe("height", str, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("height"), .wrap(str), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCrossOrigin(self: *const Image) ?[]const u8 {
|
pub fn getCrossOrigin(self: *const Image) ?[]const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("crossorigin");
|
return self.asConstElement().getAttributeSafe(comptime .wrap("crossorigin"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void {
|
pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void {
|
||||||
if (value) |v| {
|
if (value) |v| {
|
||||||
return self.asElement().setAttributeSafe("crossorigin", v, page);
|
return self.asElement().setAttributeSafe(comptime .wrap("crossorigin"), .wrap(v), page);
|
||||||
}
|
}
|
||||||
return self.asElement().removeAttribute("crossorigin", page);
|
return self.asElement().removeAttribute(comptime .wrap("crossorigin"), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getLoading(self: *const Image) []const u8 {
|
pub fn getLoading(self: *const Image) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("loading") orelse "eager";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("loading")) orelse "eager";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void {
|
pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("loading", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("loading"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const String = @import("../../../../string.zig").String;
|
||||||
|
|
||||||
const js = @import("../../../js/js.zig");
|
const js = @import("../../../js/js.zig");
|
||||||
const Page = @import("../../../Page.zig");
|
const Page = @import("../../../Page.zig");
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ const Node = @import("../../Node.zig");
|
|||||||
const Element = @import("../../Element.zig");
|
const Element = @import("../../Element.zig");
|
||||||
const HtmlElement = @import("../Html.zig");
|
const HtmlElement = @import("../Html.zig");
|
||||||
const Form = @import("Form.zig");
|
const Form = @import("Form.zig");
|
||||||
|
const Selection = @import("../../Selection.zig");
|
||||||
|
|
||||||
const Input = @This();
|
const Input = @This();
|
||||||
|
|
||||||
@@ -74,9 +77,12 @@ _value: ?[]const u8 = null,
|
|||||||
_checked: bool = false,
|
_checked: bool = false,
|
||||||
_checked_dirty: bool = false,
|
_checked_dirty: bool = false,
|
||||||
_input_type: Type = .text,
|
_input_type: Type = .text,
|
||||||
_selected: bool = false,
|
|
||||||
_indeterminate: bool = false,
|
_indeterminate: bool = false,
|
||||||
|
|
||||||
|
_selection_start: u32 = 0,
|
||||||
|
_selection_end: u32 = 0,
|
||||||
|
_selection_direction: Selection.SelectionDirection = .none,
|
||||||
|
|
||||||
pub fn asElement(self: *Input) *Element {
|
pub fn asElement(self: *Input) *Element {
|
||||||
return self._proto._proto;
|
return self._proto._proto;
|
||||||
}
|
}
|
||||||
@@ -94,7 +100,7 @@ pub fn getType(self: *const Input) []const u8 {
|
|||||||
pub fn setType(self: *Input, typ: []const u8, page: *Page) !void {
|
pub fn setType(self: *Input, typ: []const u8, page: *Page) !void {
|
||||||
// Setting the type property should update the attribute, which will trigger attributeChange
|
// Setting the type property should update the attribute, which will trigger attributeChange
|
||||||
const type_enum = Type.fromString(typ);
|
const type_enum = Type.fromString(typ);
|
||||||
try self.asElement().setAttributeSafe("type", type_enum.toString(), page);
|
try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(type_enum.toString()), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getValue(self: *const Input) []const u8 {
|
pub fn getValue(self: *const Input) []const u8 {
|
||||||
@@ -112,7 +118,7 @@ pub fn getDefaultValue(self: *const Input) []const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setDefaultValue(self: *Input, value: []const u8, page: *Page) !void {
|
pub fn setDefaultValue(self: *Input, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("value", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getChecked(self: *const Input) bool {
|
pub fn getChecked(self: *const Input) bool {
|
||||||
@@ -143,52 +149,52 @@ pub fn getDefaultChecked(self: *const Input) bool {
|
|||||||
|
|
||||||
pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void {
|
pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
try self.asElement().setAttributeSafe("checked", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("checked"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("checked", page);
|
try self.asElement().removeAttribute(comptime .wrap("checked"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDisabled(self: *const Input) bool {
|
pub fn getDisabled(self: *const Input) bool {
|
||||||
// TODO: Also check for disabled fieldset ancestors
|
// TODO: Also check for disabled fieldset ancestors
|
||||||
// (but not if we're inside a <legend> of that fieldset)
|
// (but not if we're inside a <legend> of that fieldset)
|
||||||
return self.asConstElement().getAttributeSafe("disabled") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void {
|
pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
try self.asElement().setAttributeSafe("disabled", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("disabled", page);
|
try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Input) []const u8 {
|
pub fn getName(self: *const Input) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Input, name: []const u8, page: *Page) !void {
|
pub fn setName(self: *Input, name: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", name, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAccept(self: *const Input) []const u8 {
|
pub fn getAccept(self: *const Input) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("accept") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("accept")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void {
|
pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("accept", accept, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("accept"), .wrap(accept), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAlt(self: *const Input) []const u8 {
|
pub fn getAlt(self: *const Input) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("alt") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("alt")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void {
|
pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("alt", alt, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("alt"), .wrap(alt), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getMaxLength(self: *const Input) i32 {
|
pub fn getMaxLength(self: *const Input) i32 {
|
||||||
const attr = self.asConstElement().getAttributeSafe("maxlength") orelse return -1;
|
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("maxlength")) orelse return -1;
|
||||||
return std.fmt.parseInt(i32, attr, 10) catch -1;
|
return std.fmt.parseInt(i32, attr, 10) catch -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,11 +204,11 @@ pub fn setMaxLength(self: *Input, max_length: i32, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
var buf: [32]u8 = undefined;
|
var buf: [32]u8 = undefined;
|
||||||
const value = std.fmt.bufPrint(&buf, "{d}", .{max_length}) catch unreachable;
|
const value = std.fmt.bufPrint(&buf, "{d}", .{max_length}) catch unreachable;
|
||||||
try self.asElement().setAttributeSafe("maxlength", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("maxlength"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSize(self: *const Input) i32 {
|
pub fn getSize(self: *const Input) i32 {
|
||||||
const attr = self.asConstElement().getAttributeSafe("size") orelse return 20;
|
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("size")) orelse return 20;
|
||||||
const parsed = std.fmt.parseInt(i32, attr, 10) catch return 20;
|
const parsed = std.fmt.parseInt(i32, attr, 10) catch return 20;
|
||||||
return if (parsed == 0) 20 else parsed;
|
return if (parsed == 0) 20 else parsed;
|
||||||
}
|
}
|
||||||
@@ -212,58 +218,170 @@ pub fn setSize(self: *Input, size: i32, page: *Page) !void {
|
|||||||
return error.ZeroNotAllowed;
|
return error.ZeroNotAllowed;
|
||||||
}
|
}
|
||||||
if (size < 0) {
|
if (size < 0) {
|
||||||
return self.asElement().setAttributeSafe("size", "20", page);
|
return self.asElement().setAttributeSafe(comptime .wrap("size"), .wrap("20"), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf: [32]u8 = undefined;
|
var buf: [32]u8 = undefined;
|
||||||
const value = std.fmt.bufPrint(&buf, "{d}", .{size}) catch unreachable;
|
const value = std.fmt.bufPrint(&buf, "{d}", .{size}) catch unreachable;
|
||||||
try self.asElement().setAttributeSafe("size", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("size"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSrc(self: *const Input, page: *Page) ![]const u8 {
|
pub fn getSrc(self: *const Input, page: *Page) ![]const u8 {
|
||||||
const src = self.asConstElement().getAttributeSafe("src") orelse return "";
|
const src = self.asConstElement().getAttributeSafe(comptime .wrap("src")) orelse return "";
|
||||||
// If attribute is explicitly set (even if empty), resolve it against the base URL
|
// If attribute is explicitly set (even if empty), resolve it against the base URL
|
||||||
return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{});
|
return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSrc(self: *Input, src: []const u8, page: *Page) !void {
|
pub fn setSrc(self: *Input, src: []const u8, page: *Page) !void {
|
||||||
const trimmed = std.mem.trim(u8, src, &std.ascii.whitespace);
|
const trimmed = std.mem.trim(u8, src, &std.ascii.whitespace);
|
||||||
try self.asElement().setAttributeSafe("src", trimmed, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(trimmed), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getReadonly(self: *const Input) bool {
|
pub fn getReadonly(self: *const Input) bool {
|
||||||
return self.asConstElement().getAttributeSafe("readonly") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("readonly")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void {
|
pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void {
|
||||||
if (readonly) {
|
if (readonly) {
|
||||||
try self.asElement().setAttributeSafe("readonly", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("readonly"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("readonly", page);
|
try self.asElement().removeAttribute(comptime .wrap("readonly"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRequired(self: *const Input) bool {
|
pub fn getRequired(self: *const Input) bool {
|
||||||
return self.asConstElement().getAttributeSafe("required") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRequired(self: *Input, required: bool, page: *Page) !void {
|
pub fn setRequired(self: *Input, required: bool, page: *Page) !void {
|
||||||
if (required) {
|
if (required) {
|
||||||
try self.asElement().setAttributeSafe("required", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("required", page);
|
try self.asElement().removeAttribute(comptime .wrap("required"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(self: *Input) void {
|
pub fn select(self: *Input) !void {
|
||||||
self._selected = true;
|
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
||||||
|
try self.setSelectionRange(0, len, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selectionAvailable(self: *const Input) bool {
|
||||||
|
switch (self._input_type) {
|
||||||
|
.text, .search, .url, .tel, .password => return true,
|
||||||
|
else => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HowSelected = union(enum) { partial: struct { u32, u32 }, full, none };
|
||||||
|
|
||||||
|
fn howSelected(self: *const Input) HowSelected {
|
||||||
|
if (!self.selectionAvailable()) return .none;
|
||||||
|
const value = self._value orelse return .none;
|
||||||
|
|
||||||
|
if (self._selection_start == self._selection_end) return .none;
|
||||||
|
if (self._selection_start == 0 and self._selection_end == value.len) return .full;
|
||||||
|
return .{ .partial = .{ self._selection_start, self._selection_end } };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn innerInsert(self: *Input, str: []const u8, page: *Page) !void {
|
||||||
|
const arena = page.arena;
|
||||||
|
|
||||||
|
switch (self.howSelected()) {
|
||||||
|
.full => {
|
||||||
|
// if the input is fully selected, replace the content.
|
||||||
|
const new_value = try arena.dupe(u8, str);
|
||||||
|
try self.setValue(new_value, page);
|
||||||
|
self._selection_start = @intCast(new_value.len);
|
||||||
|
self._selection_end = @intCast(new_value.len);
|
||||||
|
self._selection_direction = .none;
|
||||||
|
},
|
||||||
|
.partial => |range| {
|
||||||
|
// if the input is partially selected, replace the selected content.
|
||||||
|
const current_value = self.getValue();
|
||||||
|
const before = current_value[0..range[0]];
|
||||||
|
const remaining = current_value[range[1]..];
|
||||||
|
|
||||||
|
const new_value = try std.mem.concat(
|
||||||
|
arena,
|
||||||
|
u8,
|
||||||
|
&.{ before, str, remaining },
|
||||||
|
);
|
||||||
|
try self.setValue(new_value, page);
|
||||||
|
|
||||||
|
const new_pos = range[0] + str.len;
|
||||||
|
self._selection_start = @intCast(new_pos);
|
||||||
|
self._selection_end = @intCast(new_pos);
|
||||||
|
self._selection_direction = .none;
|
||||||
|
},
|
||||||
|
.none => {
|
||||||
|
// if the input is not selected, just insert at cursor.
|
||||||
|
const current_value = self.getValue();
|
||||||
|
const new_value = try std.mem.concat(arena, u8, &.{ current_value, str });
|
||||||
|
try self.setValue(new_value, page);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSelectionDirection(self: *const Input) []const u8 {
|
||||||
|
return @tagName(self._selection_direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSelectionStart(self: *const Input) !?u32 {
|
||||||
|
if (!self.selectionAvailable()) return null;
|
||||||
|
return self._selection_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSelectionStart(self: *Input, value: u32) !void {
|
||||||
|
if (!self.selectionAvailable()) return error.InvalidStateError;
|
||||||
|
self._selection_start = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSelectionEnd(self: *const Input) !?u32 {
|
||||||
|
if (!self.selectionAvailable()) return null;
|
||||||
|
return self._selection_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSelectionEnd(self: *Input, value: u32) !void {
|
||||||
|
if (!self.selectionAvailable()) return error.InvalidStateError;
|
||||||
|
self._selection_end = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSelectionRange(self: *Input, selection_start: u32, selection_end: u32, selection_dir: ?[]const u8) !void {
|
||||||
|
if (!self.selectionAvailable()) return error.InvalidStateError;
|
||||||
|
|
||||||
|
const direction = blk: {
|
||||||
|
if (selection_dir) |sd| {
|
||||||
|
break :blk std.meta.stringToEnum(Selection.SelectionDirection, sd) orelse .none;
|
||||||
|
} else break :blk .none;
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = self._value orelse {
|
||||||
|
self._selection_start = 0;
|
||||||
|
self._selection_end = 0;
|
||||||
|
self._selection_direction = .none;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const len_u32: u32 = @intCast(value.len);
|
||||||
|
var start: u32 = if (selection_start > len_u32) len_u32 else selection_start;
|
||||||
|
const end: u32 = if (selection_end > len_u32) len_u32 else selection_end;
|
||||||
|
|
||||||
|
// If end is less than start, both are equal to end.
|
||||||
|
if (end < start) {
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._selection_direction = direction;
|
||||||
|
self._selection_start = start;
|
||||||
|
self._selection_end = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getForm(self: *Input, page: *Page) ?*Form {
|
pub fn getForm(self: *Input, page: *Page) ?*Form {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
// 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(comptime .wrap("form"))) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id, page)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
@@ -286,7 +404,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form {
|
|||||||
fn uncheckRadioGroup(self: *Input, page: *Page) !void {
|
fn uncheckRadioGroup(self: *Input, page: *Page) !void {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
const name = element.getAttributeSafe("name") orelse return;
|
const name = element.getAttributeSafe(comptime .wrap("name")) orelse return;
|
||||||
if (name.len == 0) {
|
if (name.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -304,7 +422,7 @@ fn uncheckRadioGroup(self: *Input, page: *Page) !void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const other_name = other_element.getAttributeSafe("name") orelse continue;
|
const other_name = other_element.getAttributeSafe(comptime .wrap("name")) orelse continue;
|
||||||
if (!std.mem.eql(u8, name, other_name)) {
|
if (!std.mem.eql(u8, name, other_name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -352,6 +470,11 @@ pub const JsApi = struct {
|
|||||||
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 indeterminate = bridge.accessor(Input.getIndeterminate, Input.setIndeterminate, .{});
|
||||||
pub const select = bridge.function(Input.select, .{});
|
pub const select = bridge.function(Input.select, .{});
|
||||||
|
|
||||||
|
pub const selectionStart = bridge.accessor(Input.getSelectionStart, Input.setSelectionStart, .{});
|
||||||
|
pub const selectionEnd = bridge.accessor(Input.getSelectionEnd, Input.setSelectionEnd, .{});
|
||||||
|
pub const selectionDirection = bridge.accessor(Input.getSelectionDirection, null, .{});
|
||||||
|
pub const setSelectionRange = bridge.function(Input.setSelectionRange, .{ .dom_exception = true });
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Build = struct {
|
pub const Build = struct {
|
||||||
@@ -360,14 +483,14 @@ pub const Build = struct {
|
|||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
// Store initial values from attributes
|
// Store initial values from attributes
|
||||||
self._default_value = element.getAttributeSafe("value");
|
self._default_value = element.getAttributeSafe(comptime .wrap("value"));
|
||||||
self._default_checked = element.getAttributeSafe("checked") != null;
|
self._default_checked = element.getAttributeSafe(comptime .wrap("checked")) != null;
|
||||||
|
|
||||||
// Current state starts equal to default
|
// Current state starts equal to default
|
||||||
self._value = self._default_value;
|
self._value = self._default_value;
|
||||||
self._checked = self._default_checked;
|
self._checked = self._default_checked;
|
||||||
|
|
||||||
self._input_type = if (element.getAttributeSafe("type")) |type_attr|
|
self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr|
|
||||||
Type.fromString(type_attr)
|
Type.fromString(type_attr)
|
||||||
else
|
else
|
||||||
.text;
|
.text;
|
||||||
@@ -378,12 +501,12 @@ pub const Build = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, page: *Page) !void {
|
pub fn attributeChange(element: *Element, name: String, value: String, page: *Page) !void {
|
||||||
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return;
|
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return;
|
||||||
const self = element.as(Input);
|
const self = element.as(Input);
|
||||||
switch (attribute) {
|
switch (attribute) {
|
||||||
.type => self._input_type = Type.fromString(value),
|
.type => self._input_type = Type.fromString(value.str()),
|
||||||
.value => self._default_value = value,
|
.value => self._default_value = try page.arena.dupe(u8, value.str()),
|
||||||
.checked => {
|
.checked => {
|
||||||
self._default_checked = true;
|
self._default_checked = true;
|
||||||
// Only update checked state if it hasn't been manually modified
|
// Only update checked state if it hasn't been manually modified
|
||||||
@@ -398,8 +521,8 @@ pub const Build = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeRemove(element: *Element, name: []const u8, _: *Page) !void {
|
pub fn attributeRemove(element: *Element, name: String, _: *Page) !void {
|
||||||
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return;
|
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return;
|
||||||
const self = element.as(Input);
|
const self = element.as(Input);
|
||||||
switch (attribute) {
|
switch (attribute) {
|
||||||
.type => self._input_type = .text,
|
.type => self._input_type = .text,
|
||||||
@@ -422,7 +545,9 @@ pub const Build = struct {
|
|||||||
clone._value = source._value;
|
clone._value = source._value;
|
||||||
clone._checked = source._checked;
|
clone._checked = source._checked;
|
||||||
clone._checked_dirty = source._checked_dirty;
|
clone._checked_dirty = source._checked_dirty;
|
||||||
clone._selected = source._selected;
|
clone._selection_direction = source._selection_direction;
|
||||||
|
clone._selection_start = source._selection_start;
|
||||||
|
clone._selection_end = source._selection_end;
|
||||||
clone._indeterminate = source._indeterminate;
|
clone._indeterminate = source._indeterminate;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub fn asNode(self: *Link) *Node {
|
|||||||
|
|
||||||
pub fn getHref(self: *Link, page: *Page) ![]const u8 {
|
pub fn getHref(self: *Link, page: *Page) ![]const u8 {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
const href = element.getAttributeSafe("href") orelse return "";
|
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return "";
|
||||||
if (href.len == 0) {
|
if (href.len == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -46,15 +46,15 @@ pub fn getHref(self: *Link, page: *Page) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setHref(self: *Link, value: []const u8, page: *Page) !void {
|
pub fn setHref(self: *Link, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("href", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("href"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRel(self: *Link) []const u8 {
|
pub fn getRel(self: *Link) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("rel") orelse return "";
|
return self.asElement().getAttributeSafe(comptime .wrap("rel")) orelse return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRel(self: *Link, value: []const u8, page: *Page) !void {
|
pub fn setRel(self: *Link, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("rel", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("rel"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ pub fn setCurrentTime(self: *Media, value: f64) void {
|
|||||||
|
|
||||||
pub fn getSrc(self: *const Media, page: *Page) ![]const u8 {
|
pub fn getSrc(self: *const Media, page: *Page) ![]const u8 {
|
||||||
const element = self.asConstElement();
|
const element = self.asConstElement();
|
||||||
const src = element.getAttributeSafe("src") orelse return "";
|
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
|
||||||
if (src.len == 0) {
|
if (src.len == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -228,51 +228,51 @@ pub fn getSrc(self: *const Media, page: *Page) ![]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSrc(self: *Media, value: []const u8, page: *Page) !void {
|
pub fn setSrc(self: *Media, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("src", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getAutoplay(self: *const Media) bool {
|
pub fn getAutoplay(self: *const Media) bool {
|
||||||
return self.asConstElement().getAttributeSafe("autoplay") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("autoplay")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void {
|
pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void {
|
||||||
if (value) {
|
if (value) {
|
||||||
try self.asElement().setAttributeSafe("autoplay", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("autoplay"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("autoplay", page);
|
try self.asElement().removeAttribute(comptime .wrap("autoplay"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getControls(self: *const Media) bool {
|
pub fn getControls(self: *const Media) bool {
|
||||||
return self.asConstElement().getAttributeSafe("controls") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("controls")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setControls(self: *Media, value: bool, page: *Page) !void {
|
pub fn setControls(self: *Media, value: bool, page: *Page) !void {
|
||||||
if (value) {
|
if (value) {
|
||||||
try self.asElement().setAttributeSafe("controls", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("controls"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("controls", page);
|
try self.asElement().removeAttribute(comptime .wrap("controls"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getLoop(self: *const Media) bool {
|
pub fn getLoop(self: *const Media) bool {
|
||||||
return self.asConstElement().getAttributeSafe("loop") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("loop")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setLoop(self: *Media, value: bool, page: *Page) !void {
|
pub fn setLoop(self: *Media, value: bool, page: *Page) !void {
|
||||||
if (value) {
|
if (value) {
|
||||||
try self.asElement().setAttributeSafe("loop", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("loop"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("loop", page);
|
try self.asElement().removeAttribute(comptime .wrap("loop"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPreload(self: *const Media) []const u8 {
|
pub fn getPreload(self: *const Media) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("preload") orelse "auto";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("preload")) orelse "auto";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void {
|
pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("preload", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("preload"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -37,35 +37,35 @@ pub fn asNode(self: *Meta) *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *Meta) []const u8 {
|
pub fn getName(self: *Meta) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("name") orelse return "";
|
return self.asElement().getAttributeSafe(comptime .wrap("name")) orelse return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Meta, value: []const u8, page: *Page) !void {
|
pub fn setName(self: *Meta, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getHttpEquiv(self: *Meta) []const u8 {
|
pub fn getHttpEquiv(self: *Meta) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("http-equiv") orelse return "";
|
return self.asElement().getAttributeSafe(comptime .wrap("http-equiv")) orelse return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void {
|
pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("http-equiv", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("http-equiv"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getContent(self: *Meta) []const u8 {
|
pub fn getContent(self: *Meta) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("content") orelse return "";
|
return self.asElement().getAttributeSafe(comptime .wrap("content")) orelse return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void {
|
pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("content", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("content"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getMedia(self: *Meta) []const u8 {
|
pub fn getMedia(self: *Meta) []const u8 {
|
||||||
return self.asElement().getAttributeSafe("media") orelse return "";
|
return self.asElement().getAttributeSafe(comptime .wrap("media")) orelse return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void {
|
pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("media", value, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("media"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const String = @import("../../../../string.zig").String;
|
||||||
|
|
||||||
const js = @import("../../../js/js.zig");
|
const js = @import("../../../js/js.zig");
|
||||||
const Page = @import("../../../Page.zig");
|
const Page = @import("../../../Page.zig");
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ pub fn getValue(self: *Option, page: *Page) []const u8 {
|
|||||||
|
|
||||||
pub fn setValue(self: *Option, value: []const u8, page: *Page) !void {
|
pub fn setValue(self: *Option, value: []const u8, page: *Page) !void {
|
||||||
const owned = try page.dupeString(value);
|
const owned = try page.dupeString(value);
|
||||||
try self.asElement().setAttributeSafe("value", owned, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(owned), page);
|
||||||
self._value = owned;
|
self._value = owned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,18 +89,18 @@ pub fn getDisabled(self: *const Option) bool {
|
|||||||
pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void {
|
pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void {
|
||||||
self._disabled = disabled;
|
self._disabled = disabled;
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
try self.asElement().setAttributeSafe("disabled", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("disabled", page);
|
try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Option) []const u8 {
|
pub fn getName(self: *const Option) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Option, name: []const u8, page: *Page) !void {
|
pub fn setName(self: *Option, name: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", name, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
@@ -124,21 +126,21 @@ pub const Build = struct {
|
|||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
// Check for value attribute
|
// Check for value attribute
|
||||||
self._value = element.getAttributeSafe("value");
|
self._value = element.getAttributeSafe(comptime .wrap("value"));
|
||||||
|
|
||||||
// Check for selected attribute
|
// Check for selected attribute
|
||||||
self._default_selected = element.getAttributeSafe("selected") != null;
|
self._default_selected = element.getAttributeSafe(comptime .wrap("selected")) != null;
|
||||||
self._selected = self._default_selected;
|
self._selected = self._default_selected;
|
||||||
|
|
||||||
// Check for disabled attribute
|
// Check for disabled attribute
|
||||||
self._disabled = element.getAttributeSafe("disabled") != null;
|
self._disabled = element.getAttributeSafe(comptime .wrap("disabled")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void {
|
pub fn attributeChange(element: *Element, name: String, value: String, _: *Page) !void {
|
||||||
const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return;
|
const attribute = std.meta.stringToEnum(enum { value, selected }, name.str()) orelse return;
|
||||||
const self = element.as(Option);
|
const self = element.as(Option);
|
||||||
switch (attribute) {
|
switch (attribute) {
|
||||||
.value => self._value = value,
|
.value => self._value = value.str(),
|
||||||
.selected => {
|
.selected => {
|
||||||
self._default_selected = true;
|
self._default_selected = true;
|
||||||
self._selected = true;
|
self._selected = true;
|
||||||
@@ -146,8 +148,8 @@ pub const Build = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attributeRemove(element: *Element, name: []const u8, _: *Page) !void {
|
pub fn attributeRemove(element: *Element, name: String, _: *Page) !void {
|
||||||
const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return;
|
const attribute = std.meta.stringToEnum(enum { value, selected }, name.str()) orelse return;
|
||||||
const self = element.as(Option);
|
const self = element.as(Option);
|
||||||
switch (attribute) {
|
switch (attribute) {
|
||||||
.value => self._value = null,
|
.value => self._value = null,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Page = @import("../../../Page.zig");
|
|||||||
const Node = @import("../../Node.zig");
|
const Node = @import("../../Node.zig");
|
||||||
const Element = @import("../../Element.zig");
|
const Element = @import("../../Element.zig");
|
||||||
const HtmlElement = @import("../Html.zig");
|
const HtmlElement = @import("../Html.zig");
|
||||||
|
const URL = @import("../../URL.zig");
|
||||||
|
|
||||||
const Script = @This();
|
const Script = @This();
|
||||||
|
|
||||||
@@ -45,33 +46,34 @@ pub fn asNode(self: *Script) *Node {
|
|||||||
return self.asElement().asNode();
|
return self.asElement().asNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSrc(self: *const Script) []const u8 {
|
pub fn getSrc(self: *const Script, page: *Page) ![]const u8 {
|
||||||
return self._src;
|
if (self._src.len == 0) return "";
|
||||||
|
return try URL.resolve(page.call_arena, page.base(), self._src, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSrc(self: *Script, src: []const u8, page: *Page) !void {
|
pub fn setSrc(self: *Script, src: []const u8, page: *Page) !void {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
try element.setAttributeSafe("src", src, page);
|
try element.setAttributeSafe(comptime .wrap("src"), .wrap(src), page);
|
||||||
self._src = element.getAttributeSafe("src") orelse unreachable;
|
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse unreachable;
|
||||||
if (element.asNode().isConnected()) {
|
if (element.asNode().isConnected()) {
|
||||||
try page.scriptAddedCallback(false, self);
|
try page.scriptAddedCallback(false, self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getType(self: *const Script) []const u8 {
|
pub fn getType(self: *const Script) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("type") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setType(self: *Script, value: []const u8, page: *Page) !void {
|
pub fn setType(self: *Script, value: []const u8, page: *Page) !void {
|
||||||
return self.asElement().setAttributeSafe("type", value, page);
|
return self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getNonce(self: *const Script) []const u8 {
|
pub fn getNonce(self: *const Script) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("nonce") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("nonce")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void {
|
pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void {
|
||||||
return self.asElement().setAttributeSafe("nonce", value, page);
|
return self.asElement().setAttributeSafe(comptime .wrap("nonce"), .wrap(value), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOnLoad(self: *const Script) ?js.Function.Global {
|
pub fn getOnLoad(self: *const Script) ?js.Function.Global {
|
||||||
@@ -91,7 +93,7 @@ pub fn setOnError(self: *Script, cb: ?js.Function.Global) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getNoModule(self: *const Script) bool {
|
pub fn getNoModule(self: *const Script) bool {
|
||||||
return self.asConstElement().getAttributeSafe("nomodule") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("nomodule")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setInnerText(self: *Script, text: []const u8, page: *Page) !void {
|
pub fn setInnerText(self: *Script, text: []const u8, page: *Page) !void {
|
||||||
@@ -125,19 +127,19 @@ pub const Build = struct {
|
|||||||
pub fn complete(node: *Node, page: *Page) !void {
|
pub fn complete(node: *Node, page: *Page) !void {
|
||||||
const self = node.as(Script);
|
const self = node.as(Script);
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
self._src = element.getAttributeSafe("src") orelse "";
|
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse "";
|
||||||
|
|
||||||
if (element.getAttributeSafe("onload")) |on_load| {
|
if (element.getAttributeSafe(comptime .wrap("onload"))) |on_load| {
|
||||||
if (page.js.stringToFunction(on_load)) |func| {
|
if (page.js.stringToPersistedFunction(on_load)) |func| {
|
||||||
self._on_load = try func.persist();
|
self._on_load = func;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
|
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.getAttributeSafe("onerror")) |on_error| {
|
if (element.getAttributeSafe(comptime .wrap("onerror"))) |on_error| {
|
||||||
if (page.js.stringToFunction(on_error)) |func| {
|
if (page.js.stringToPersistedFunction(on_error)) |func| {
|
||||||
self._on_error = try func.persist();
|
self._on_error = func;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
|
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,39 +121,39 @@ pub fn setSelectedIndex(self: *Select, index: i32) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getMultiple(self: *const Select) bool {
|
pub fn getMultiple(self: *const Select) bool {
|
||||||
return self.asConstElement().getAttributeSafe("multiple") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("multiple")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void {
|
pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void {
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
try self.asElement().setAttributeSafe("multiple", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("multiple"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("multiple", page);
|
try self.asElement().removeAttribute(comptime .wrap("multiple"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDisabled(self: *const Select) bool {
|
pub fn getDisabled(self: *const Select) bool {
|
||||||
return self.asConstElement().getAttributeSafe("disabled") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void {
|
pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
try self.asElement().setAttributeSafe("disabled", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("disabled", page);
|
try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *const Select) []const u8 {
|
pub fn getName(self: *const Select) []const u8 {
|
||||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setName(self: *Select, name: []const u8, page: *Page) !void {
|
pub fn setName(self: *Select, name: []const u8, page: *Page) !void {
|
||||||
try self.asElement().setAttributeSafe("name", name, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSize(self: *const Select) u32 {
|
pub fn getSize(self: *const Select) u32 {
|
||||||
const s = self.asConstElement().getAttributeSafe("size") orelse return 0;
|
const s = self.asConstElement().getAttributeSafe(comptime .wrap("size")) orelse return 0;
|
||||||
|
|
||||||
const trimmed = std.mem.trimLeft(u8, s, &std.ascii.whitespace);
|
const trimmed = std.mem.trimLeft(u8, s, &std.ascii.whitespace);
|
||||||
|
|
||||||
@@ -172,18 +172,18 @@ pub fn getSize(self: *const Select) u32 {
|
|||||||
|
|
||||||
pub fn setSize(self: *Select, size: u32, page: *Page) !void {
|
pub fn setSize(self: *Select, size: u32, page: *Page) !void {
|
||||||
const size_string = try std.fmt.allocPrint(page.call_arena, "{d}", .{size});
|
const size_string = try std.fmt.allocPrint(page.call_arena, "{d}", .{size});
|
||||||
try self.asElement().setAttributeSafe("size", size_string, page);
|
try self.asElement().setAttributeSafe(comptime .wrap("size"), .wrap(size_string), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRequired(self: *const Select) bool {
|
pub fn getRequired(self: *const Select) bool {
|
||||||
return self.asConstElement().getAttributeSafe("required") != null;
|
return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRequired(self: *Select, required: bool, page: *Page) !void {
|
pub fn setRequired(self: *Select, required: bool, page: *Page) !void {
|
||||||
if (required) {
|
if (required) {
|
||||||
try self.asElement().setAttributeSafe("required", "", page);
|
try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page);
|
||||||
} else {
|
} else {
|
||||||
try self.asElement().removeAttribute("required", page);
|
try self.asElement().removeAttribute(comptime .wrap("required"), page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ pub fn getForm(self: *Select, page: *Page) ?*Form {
|
|||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
// 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(comptime .wrap("form"))) |form_id| {
|
||||||
if (page.document.getElementById(form_id, page)) |form_element| {
|
if (page.document.getElementById(form_id, page)) |form_element| {
|
||||||
return form_element.is(Form);
|
return form_element.is(Form);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user