Merge pull request #664 from lightpanda-io/treewalker
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled

Add TreeWalker
This commit is contained in:
Karl Seguin
2025-05-26 11:06:56 +08:00
committed by GitHub
15 changed files with 436 additions and 77 deletions

View File

@@ -30,6 +30,9 @@ const css = @import("css.zig");
const Element = @import("element.zig").Element;
const ElementUnion = @import("element.zig").Union;
const TreeWalker = @import("tree_walker.zig").TreeWalker;
const Env = @import("../env.zig").Env;
const DOMImplementation = @import("implementation.zig").DOMImplementation;
@@ -238,6 +241,10 @@ pub const Document = struct {
pub fn _replaceChildren(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
return Node.replaceChildren(parser.documentToNode(self), nodes);
}
pub fn _createTreeWalker(_: *parser.Document, root: *parser.Node, what_to_show: ?u32, filter: ?TreeWalker.TreeWalkerOpts) !TreeWalker {
return try TreeWalker.init(root, what_to_show, filter);
}
};
const testing = @import("../../testing.zig");

View File

@@ -26,6 +26,8 @@ const Node = @import("node.zig");
const MutationObserver = @import("mutation_observer.zig");
const IntersectionObserver = @import("intersection_observer.zig");
const DOMParser = @import("dom_parser.zig").DOMParser;
const TreeWalker = @import("tree_walker.zig").TreeWalker;
const NodeFilter = @import("node_filter.zig").NodeFilter;
pub const Interfaces = .{
DOMException,
@@ -39,4 +41,6 @@ pub const Interfaces = .{
MutationObserver.Interfaces,
IntersectionObserver.Interfaces,
DOMParser,
TreeWalker,
NodeFilter,
};

View File

@@ -57,7 +57,7 @@ pub const EventTarget = struct {
pub fn _addEventListener(
self: *parser.EventTarget,
typ: []const u8,
cbk: Env.Callback,
cbk: Env.Function,
opts_: ?AddEventListenerOpts,
state: *SessionState,
) !void {
@@ -104,7 +104,7 @@ pub const EventTarget = struct {
pub fn _removeEventListener(
self: *parser.EventTarget,
typ: []const u8,
cbk: Env.Callback,
cbk: Env.Function,
capture: ?bool,
// TODO: hanle EventListenerOptions
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114

View File

@@ -40,7 +40,7 @@ const log = std.log.scoped(.events);
// The returned Entries are phony, they always indicate full intersection.
// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
pub const IntersectionObserver = struct {
callback: Env.Callback,
callback: Env.Function,
options: IntersectionObserverOptions,
state: *SessionState,
@@ -48,7 +48,7 @@ pub const IntersectionObserver = struct {
// new IntersectionObserver(callback)
// new IntersectionObserver(callback, options) [not supported yet]
pub fn constructor(callback: Env.Callback, options_: ?IntersectionObserverOptions, state: *SessionState) !IntersectionObserver {
pub fn constructor(callback: Env.Function, options_: ?IntersectionObserverOptions, state: *SessionState) !IntersectionObserver {
var options = IntersectionObserverOptions{
.root = parser.documentToNode(parser.documentHTMLToDocument(state.window.document)),
.rootMargin = "0px 0px 0px 0px",
@@ -85,8 +85,8 @@ pub const IntersectionObserver = struct {
.options = &self.options,
});
var result: Env.Callback.Result = undefined;
self.callback.tryCall(.{self.observed_entries.items}, &result) catch {
var result: Env.Function.Result = undefined;
self.callback.tryCall(void, .{self.observed_entries.items}, &result) catch {
log.err("intersection observer callback error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

View File

@@ -36,14 +36,14 @@ const log = std.log.scoped(.events);
// WEB IDL https://dom.spec.whatwg.org/#interface-mutationobserver
pub const MutationObserver = struct {
cbk: Env.Callback,
cbk: Env.Function,
arena: Allocator,
// List of records which were observed. When the scopeEnds, we need to
// execute our callback with it.
observed: std.ArrayListUnmanaged(*MutationRecord),
pub fn constructor(cbk: Env.Callback, state: *SessionState) !MutationObserver {
pub fn constructor(cbk: Env.Function, state: *SessionState) !MutationObserver {
return .{
.cbk = cbk,
.observed = .{},
@@ -113,8 +113,8 @@ pub const MutationObserver = struct {
for (record) |r| {
const records = [_]MutationRecord{r.*};
var result: Env.Callback.Result = undefined;
self.cbk.tryCall(.{records}, &result) catch {
var result: Env.Function.Result = undefined;
self.cbk.tryCall(void, .{records}, &result) catch {
log.err("mutation observer callback error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

View File

@@ -0,0 +1,52 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
pub const NodeFilter = struct {
pub const _FILTER_ACCEPT: u16 = 1;
pub const _FILTER_REJECT: u16 = 2;
pub const _FILTER_SKIP: u16 = 3;
pub const _SHOW_ALL: u32 = std.math.maxInt(u32);
pub const _SHOW_ELEMENT: u32 = 0b1;
pub const _SHOW_ATTRIBUTE: u32 = 0b10;
pub const _SHOW_TEXT: u32 = 0b100;
pub const _SHOW_CDATA_SECTION: u32 = 0b1000;
pub const _SHOW_ENTITY_REFERENCE: u32 = 0b10000;
pub const _SHOW_ENTITY: u32 = 0b100000;
pub const _SHOW_PROCESSING_INSTRUCTION: u32 = 0b1000000;
pub const _SHOW_COMMENT: u32 = 0b10000000;
pub const _SHOW_DOCUMENT: u32 = 0b100000000;
pub const _SHOW_DOCUMENT_TYPE: u32 = 0b1000000000;
pub const _SHOW_DOCUMENT_FRAGMENT: u32 = 0b10000000000;
pub const _SHOW_NOTATION: u32 = 0b100000000000;
};
const testing = @import("../../testing.zig");
test "Browser.DOM.NodeFilter" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit();
try runner.testCases(&.{
.{ "NodeFilter.FILTER_ACCEPT", "1" },
.{ "NodeFilter.FILTER_REJECT", "2" },
.{ "NodeFilter.FILTER_SKIP", "3" },
.{ "NodeFilter.SHOW_ALL", "4294967295" },
.{ "NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT", "129" },
}, .{});
}

View File

@@ -21,7 +21,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const JsThis = @import("../env.zig").JsThis;
const Callback = @import("../env.zig").Callback;
const Function = @import("../env.zig").Function;
const NodeUnion = @import("node.zig").Union;
const Node = @import("node.zig").Node;
@@ -141,11 +141,11 @@ pub const NodeList = struct {
// };
// }
pub fn _forEach(self: *NodeList, cbk: Callback) !void { // TODO handle thisArg
pub fn _forEach(self: *NodeList, cbk: Function) !void { // TODO handle thisArg
for (self.nodes.items, 0..) |n, i| {
const ii: u32 = @intCast(i);
var result: Callback.Result = undefined;
cbk.tryCall(.{ n, ii, self }, &result) catch {
var result: Function.Result = undefined;
cbk.tryCall(void, .{ n, ii, self }, &result) catch {
log.err("callback error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

View File

@@ -21,7 +21,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const iterator = @import("../iterator/iterator.zig");
const Callback = @import("../env.zig").Callback;
const Function = @import("../env.zig").Function;
const JsObject = @import("../env.zig").JsObject;
const DOMException = @import("exceptions.zig").DOMException;
@@ -138,11 +138,11 @@ pub const DOMTokenList = struct {
}
// TODO handle thisArg
pub fn _forEach(self: *parser.TokenList, cbk: Callback, this_arg: JsObject) !void {
pub fn _forEach(self: *parser.TokenList, cbk: Function, this_arg: JsObject) !void {
var entries = _entries(self);
while (try entries._next()) |entry| {
var result: Callback.Result = undefined;
cbk.tryCallWithThis(this_arg, .{ entry.@"1", entry.@"0", self }, &result) catch {
var result: Function.Result = undefined;
cbk.tryCallWithThis(void, this_arg, .{ entry.@"1", entry.@"0", self }, &result) catch {
log.err("callback error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

View File

@@ -0,0 +1,293 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const parser = @import("../netsurf.zig");
const NodeFilter = @import("node_filter.zig").NodeFilter;
const Env = @import("../env.zig").Env;
const SessionState = @import("../env.zig").SessionState;
// https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
pub const TreeWalker = struct {
root: *parser.Node,
current_node: *parser.Node,
what_to_show: u32,
filter: ?Env.Function,
pub const TreeWalkerOpts = union(enum) {
function: Env.Function,
object: struct { acceptNode: Env.Function },
};
pub fn init(node: *parser.Node, what_to_show: ?u32, filter: ?TreeWalkerOpts) !TreeWalker {
var filter_func: ?Env.Function = null;
if (filter) |f| {
filter_func = switch (f) {
.function => |func| func,
.object => |o| o.acceptNode,
};
}
return .{
.root = node,
.current_node = node,
.what_to_show = what_to_show orelse NodeFilter._SHOW_ALL,
.filter = filter_func,
};
}
const VerifyResult = enum { accept, skip, reject };
fn verify(self: *const TreeWalker, node: *parser.Node) !VerifyResult {
const node_type = try parser.nodeType(node);
const what_to_show = self.what_to_show;
// Verify that we can show this node type.
if (!switch (node_type) {
.attribute => what_to_show & NodeFilter._SHOW_ATTRIBUTE != 0,
.cdata_section => what_to_show & NodeFilter._SHOW_CDATA_SECTION != 0,
.comment => what_to_show & NodeFilter._SHOW_COMMENT != 0,
.document => what_to_show & NodeFilter._SHOW_DOCUMENT != 0,
.document_fragment => what_to_show & NodeFilter._SHOW_DOCUMENT_FRAGMENT != 0,
.document_type => what_to_show & NodeFilter._SHOW_DOCUMENT_TYPE != 0,
.element => what_to_show & NodeFilter._SHOW_ELEMENT != 0,
.entity => what_to_show & NodeFilter._SHOW_ENTITY != 0,
.entity_reference => what_to_show & NodeFilter._SHOW_ENTITY_REFERENCE != 0,
.notation => what_to_show & NodeFilter._SHOW_NOTATION != 0,
.processing_instruction => what_to_show & NodeFilter._SHOW_PROCESSING_INSTRUCTION != 0,
.text => what_to_show & NodeFilter._SHOW_TEXT != 0,
}) return .reject;
// Verify that we aren't filtering it out.
if (self.filter) |f| {
const filter = try f.call(u32, .{node});
return switch (filter) {
NodeFilter._FILTER_ACCEPT => .accept,
NodeFilter._FILTER_REJECT => .reject,
NodeFilter._FILTER_SKIP => .skip,
else => .reject,
};
} else return .accept;
}
pub fn get_root(self: *TreeWalker) *parser.Node {
return self.root;
}
pub fn get_currentNode(self: *TreeWalker) *parser.Node {
return self.current_node;
}
pub fn get_whatToShow(self: *TreeWalker) u32 {
return self.what_to_show;
}
pub fn get_filter(self: *TreeWalker) ?Env.Function {
return self.filter;
}
pub fn set_currentNode(self: *TreeWalker, node: *parser.Node) !void {
self.current_node = node;
}
fn firstChild(self: *const TreeWalker, node: *parser.Node) !?*parser.Node {
const children = try parser.nodeGetChildNodes(node);
const child_count = try parser.nodeListLength(children);
for (0..child_count) |i| {
const index: u32 = @intCast(i);
const child = (try parser.nodeListItem(children, index)) orelse return null;
switch (try self.verify(child)) {
.accept => return child,
.reject => continue,
.skip => if (try self.firstChild(child)) |gchild| return gchild,
}
}
return null;
}
fn lastChild(self: *const TreeWalker, node: *parser.Node) !?*parser.Node {
const children = try parser.nodeGetChildNodes(node);
const child_count = try parser.nodeListLength(children);
var index: u32 = child_count;
while (index > 0) {
index -= 1;
const child = (try parser.nodeListItem(children, index)) orelse return null;
switch (try self.verify(child)) {
.accept => return child,
.reject => continue,
.skip => if (try self.lastChild(child)) |gchild| return gchild,
}
}
return null;
}
fn nextSibling(self: *const TreeWalker, node: *parser.Node) !?*parser.Node {
var current = node;
while (true) {
current = (try parser.nodeNextSibling(current)) orelse return null;
switch (try self.verify(current)) {
.accept => return current,
.skip, .reject => continue,
}
}
return null;
}
fn previousSibling(self: *const TreeWalker, node: *parser.Node) !?*parser.Node {
var current = node;
while (true) {
current = (try parser.nodePreviousSibling(current)) orelse return null;
switch (try self.verify(current)) {
.accept => return current,
.skip, .reject => continue,
}
}
return null;
}
fn parentNode(self: *const TreeWalker, node: *parser.Node) !?*parser.Node {
if (self.root == node) return null;
var current = node;
while (true) {
if (current == self.root) return null;
current = (try parser.nodeParentNode(current)) orelse return null;
switch (try self.verify(current)) {
.accept => return current,
.reject, .skip => continue,
}
}
}
pub fn _firstChild(self: *TreeWalker) !?*parser.Node {
if (try self.firstChild(self.current_node)) |child| {
self.current_node = child;
return child;
}
return null;
}
pub fn _lastChild(self: *TreeWalker) !?*parser.Node {
if (try self.lastChild(self.current_node)) |child| {
self.current_node = child;
return child;
}
return null;
}
pub fn _nextNode(self: *TreeWalker) !?*parser.Node {
if (try self.firstChild(self.current_node)) |child| {
self.current_node = child;
return child;
}
var current = self.current_node;
while (current != self.root) {
if (try self.nextSibling(current)) |sibling| {
self.current_node = sibling;
return sibling;
}
current = (try parser.nodeParentNode(current)) orelse break;
}
return null;
}
pub fn _nextSibling(self: *TreeWalker) !?*parser.Node {
if (try self.nextSibling(self.current_node)) |sibling| {
self.current_node = sibling;
return sibling;
}
return null;
}
pub fn _parentNode(self: *TreeWalker) !?*parser.Node {
if (try self.parentNode(self.current_node)) |parent| {
self.current_node = parent;
return parent;
}
return null;
}
pub fn _previousNode(self: *TreeWalker) !?*parser.Node {
var current = self.current_node;
while (try parser.nodePreviousSibling(current)) |previous| {
current = previous;
switch (try self.verify(current)) {
.accept => {
// Get last child if it has one.
if (try self.lastChild(current)) |child| {
self.current_node = child;
return child;
}
// Otherwise, this node is our previous one.
self.current_node = current;
return current;
},
.reject => continue,
.skip => {
// Get last child if it has one.
if (try self.lastChild(current)) |child| {
self.current_node = child;
return child;
}
},
}
}
if (current != self.root) {
if (try self.parentNode(current)) |parent| {
self.current_node = parent;
return parent;
}
}
return null;
}
pub fn _previousSibling(self: *TreeWalker) !?*parser.Node {
if (try self.previousSibling(self.current_node)) |sibling| {
self.current_node = sibling;
return sibling;
}
return null;
}
};

View File

@@ -43,7 +43,7 @@ const WebApis = struct {
pub const JsThis = Env.JsThis;
pub const JsObject = Env.JsObject;
pub const Callback = Env.Callback;
pub const Function = Env.Function;
pub const Env = js.Env(*SessionState, WebApis);
const Window = @import("html/window.zig").Window;

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const parser = @import("../netsurf.zig");
const Callback = @import("../env.zig").Callback;
const Function = @import("../env.zig").Function;
const generate = @import("../../runtime/generate.zig");
const DOMException = @import("../dom/exceptions.zig").DOMException;
@@ -140,10 +140,10 @@ pub const Event = struct {
};
pub const EventHandler = struct {
callback: Callback,
callback: Function,
node: parser.EventNode,
pub fn init(allocator: Allocator, callback: Callback) !*EventHandler {
pub fn init(allocator: Allocator, callback: Function) !*EventHandler {
const eh = try allocator.create(EventHandler);
eh.* = .{
.callback = callback,
@@ -162,8 +162,8 @@ pub const EventHandler = struct {
};
const self: *EventHandler = @fieldParentPtr("node", node);
var result: Callback.Result = undefined;
self.callback.tryCall(.{ievent}, &result) catch {
var result: Function.Result = undefined;
self.callback.tryCall(void, .{ievent}, &result) catch {
log.err("event handler error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const parser = @import("../netsurf.zig");
const Callback = @import("../env.zig").Callback;
const Function = @import("../env.zig").Function;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
// https://drafts.csswg.org/cssom-view/#the-mediaquerylist-interface
@@ -39,7 +39,7 @@ pub const MediaQueryList = struct {
return self.media;
}
pub fn _addListener(_: *const MediaQueryList, _: Callback) void {}
pub fn _addListener(_: *const MediaQueryList, _: Function) void {}
pub fn _removeListener(_: *const MediaQueryList, _: Callback) void {}
pub fn _removeListener(_: *const MediaQueryList, _: Function) void {}
};

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const parser = @import("../netsurf.zig");
const Callback = @import("../env.zig").Callback;
const Function = @import("../env.zig").Function;
const SessionState = @import("../env.zig").SessionState;
const Loop = @import("../../runtime/loop.zig").Loop;
@@ -159,12 +159,12 @@ pub const Window = struct {
// Returns the request ID, that uniquely identifies the entry in the callback list.
pub fn _requestAnimationFrame(
self: *Window,
callback: Callback,
callback: Function,
) !u32 {
// We immediately execute the callback, but this may not be correct TBD.
// Since: When multiple callbacks queued by requestAnimationFrame() begin to fire in a single frame, each receives the same timestamp even though time has passed during the computation of every previous callback's workload.
var result: Callback.Result = undefined;
callback.tryCall(.{self.performance._now()}, &result) catch {
var result: Function.Result = undefined;
callback.tryCall(void, .{self.performance._now()}, &result) catch {
log.err("Window.requestAnimationFrame(): {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};
@@ -178,12 +178,12 @@ pub const Window = struct {
}
// TODO handle callback arguments.
pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, state: *SessionState) !u32 {
return self.createTimeout(cbk, delay, state, false);
}
// TODO handle callback arguments.
pub fn _setInterval(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, state: *SessionState) !u32 {
return self.createTimeout(cbk, delay, state, true);
}
@@ -204,7 +204,7 @@ pub const Window = struct {
};
}
fn createTimeout(self: *Window, cbk: Callback, delay_: ?u32, state: *SessionState, comptime repeat: bool) !u32 {
fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, state: *SessionState, comptime repeat: bool) !u32 {
if (self.timers.count() > 512) {
return error.TooManyTimeout;
}
@@ -255,7 +255,7 @@ const TimerCallback = struct {
timer_id: u31,
// The JavaScript callback to execute
cbk: Callback,
cbk: Function,
// This is the internal data that the event loop tracks. We'll get this
// back in run and, from it, can get our TimerCallback instance
@@ -269,8 +269,8 @@ const TimerCallback = struct {
fn run(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
const self: *TimerCallback = @fieldParentPtr("node", node);
var result: Callback.Result = undefined;
self.cbk.tryCall(.{}, &result) catch {
var result: Function.Result = undefined;
self.cbk.tryCall(void, .{}, &result) catch {
log.err("timeout callback error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const Env = @import("../env.zig").Env;
const Callback = Env.Callback;
const Function = Env.Function;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
@@ -35,18 +35,18 @@ pub const XMLHttpRequestEventTarget = struct {
// Extend libdom event target for pure zig struct.
base: parser.EventTargetTBase = parser.EventTargetTBase{},
onloadstart_cbk: ?Callback = null,
onprogress_cbk: ?Callback = null,
onabort_cbk: ?Callback = null,
onload_cbk: ?Callback = null,
ontimeout_cbk: ?Callback = null,
onloadend_cbk: ?Callback = null,
onloadstart_cbk: ?Function = null,
onprogress_cbk: ?Function = null,
onabort_cbk: ?Function = null,
onload_cbk: ?Function = null,
ontimeout_cbk: ?Function = null,
onloadend_cbk: ?Function = null,
fn register(
self: *XMLHttpRequestEventTarget,
alloc: std.mem.Allocator,
typ: []const u8,
cbk: Callback,
cbk: Function,
) !void {
const target = @as(*parser.EventTarget, @ptrCast(self));
const eh = try EventHandler.init(alloc, try cbk.withThis(target));
@@ -69,51 +69,51 @@ pub const XMLHttpRequestEventTarget = struct {
try parser.eventTargetRemoveEventListener(et, typ, lst.?, false);
}
pub fn get_onloadstart(self: *XMLHttpRequestEventTarget) ?Callback {
pub fn get_onloadstart(self: *XMLHttpRequestEventTarget) ?Function {
return self.onloadstart_cbk;
}
pub fn get_onprogress(self: *XMLHttpRequestEventTarget) ?Callback {
pub fn get_onprogress(self: *XMLHttpRequestEventTarget) ?Function {
return self.onprogress_cbk;
}
pub fn get_onabort(self: *XMLHttpRequestEventTarget) ?Callback {
pub fn get_onabort(self: *XMLHttpRequestEventTarget) ?Function {
return self.onabort_cbk;
}
pub fn get_onload(self: *XMLHttpRequestEventTarget) ?Callback {
pub fn get_onload(self: *XMLHttpRequestEventTarget) ?Function {
return self.onload_cbk;
}
pub fn get_ontimeout(self: *XMLHttpRequestEventTarget) ?Callback {
pub fn get_ontimeout(self: *XMLHttpRequestEventTarget) ?Function {
return self.ontimeout_cbk;
}
pub fn get_onloadend(self: *XMLHttpRequestEventTarget) ?Callback {
pub fn get_onloadend(self: *XMLHttpRequestEventTarget) ?Function {
return self.onloadend_cbk;
}
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
if (self.onloadstart_cbk) |cbk| try self.unregister("loadstart", cbk.id);
try self.register(state.arena, "loadstart", handler);
self.onloadstart_cbk = handler;
}
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
if (self.onprogress_cbk) |cbk| try self.unregister("progress", cbk.id);
try self.register(state.arena, "progress", handler);
self.onprogress_cbk = handler;
}
pub fn set_onabort(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
pub fn set_onabort(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
if (self.onabort_cbk) |cbk| try self.unregister("abort", cbk.id);
try self.register(state.arena, "abort", handler);
self.onabort_cbk = handler;
}
pub fn set_onload(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
pub fn set_onload(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
if (self.onload_cbk) |cbk| try self.unregister("load", cbk.id);
try self.register(state.arena, "load", handler);
self.onload_cbk = handler;
}
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
if (self.ontimeout_cbk) |cbk| try self.unregister("timeout", cbk.id);
try self.register(state.arena, "timeout", handler);
self.ontimeout_cbk = handler;
}
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, handler: Callback, state: *SessionState) !void {
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
if (self.onloadend_cbk) |cbk| try self.unregister("loadend", cbk.id);
try self.register(state.arena, "loadend", handler);
self.onloadend_cbk = handler;

View File

@@ -912,7 +912,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// compatible with. A compatible field has higher precedence
// than a coercible, but still isn't a perfect match.
var compatible_index: ?usize = null;
inline for (u.fields, 0..) |field, i| {
switch (try self.probeJsValueToZig(named_function, field.type, js_value)) {
.value => |v| return @unionInit(T, field.name, v),
@@ -949,9 +948,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// Extracted so that it can be used in both jsValueToZig and in
// probeJsValueToZig. Avoids having to duplicate this logic when probing.
fn jsValueToStruct(self: *Scope, comptime named_function: NamedFunction, comptime T: type, js_value: v8.Value) !?T {
if (@hasDecl(T, "_CALLBACK_ID_KLUDGE")) {
if (@hasDecl(T, "_FUNCTION_ID_KLUDGE")) {
if (!js_value.isFunction()) {
return error.InvalidArgument;
return null;
}
const func = v8.Persistent(v8.Function).init(self.isolate, js_value.castTo(v8.Function));
@@ -1220,25 +1219,25 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}
};
pub const Callback = struct {
pub const Function = struct {
id: usize,
scope: *Scope,
this: ?v8.Object = null,
func: PersistentFunction,
// We use this when mapping a JS value to a Zig object. We can't
// Say we have a Zig function that takes a Callback, we can't just
// check param.type == Callback, because Callback is a generic.
// Say we have a Zig function that takes a Function, we can't just
// check param.type == Function, because Function is a generic.
// So, as a quick hack, we can determine if the Zig type is a
// callback by checking @hasDecl(T, "_CALLBACK_ID_KLUDGE")
const _CALLBACK_ID_KLUDGE = true;
// callback by checking @hasDecl(T, "_FUNCTION_ID_KLUDGE")
const _FUNCTION_ID_KLUDGE = true;
pub const Result = struct {
stack: ?[]const u8,
exception: []const u8,
};
pub fn withThis(self: *const Callback, value: anytype) !Callback {
pub fn withThis(self: *const Function, value: anytype) !Function {
return .{
.id = self.id,
.func = self.func,
@@ -1247,20 +1246,20 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
};
}
pub fn call(self: *const Callback, args: anytype) !void {
return self.callWithThis(self.getThis(), args);
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
return self.callWithThis(T, self.getThis(), args);
}
pub fn tryCall(self: *const Callback, args: anytype, result: *Result) !void {
return self.tryCallWithThis(self.getThis(), args, result);
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, result: *Result) !T {
return self.tryCallWithThis(T, self.getThis(), args, result);
}
pub fn tryCallWithThis(self: *const Callback, this: anytype, args: anytype, result: *Result) !void {
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !void {
var try_catch: TryCatch = undefined;
try_catch.init(self.scope);
defer try_catch.deinit();
self.callWithThis(this, args) catch |err| {
return self.callWithThis(T, this, args) catch |err| {
if (try_catch.hasCaught()) {
const allocator = self.scope.call_arena;
result.stack = try_catch.stack(allocator) catch null;
@@ -1273,7 +1272,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
};
}
pub fn callWithThis(self: *const Callback, this: anytype, args: anytype) !void {
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
const scope = self.scope;
const js_this = try scope.valueToExistingObject(this);
@@ -1289,14 +1288,18 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
if (result == null) {
return error.JSExecCallback;
}
if (@typeInfo(T) == .void) return {};
const named_function = comptime NamedFunction.init(T, "callResult");
return scope.jsValueToZig(named_function, T, result.?);
}
fn getThis(self: *const Callback) v8.Object {
fn getThis(self: *const Function) v8.Object {
return self.this orelse self.scope.context.getGlobal();
}
// debug/helper to print the source of the JS callback
pub fn printFunc(self: Callback) !void {
pub fn printFunc(self: Function) !void {
const scope = self.scope;
const value = self.func.castToFunction().toValue();
const src = try valueToString(scope.call_arena, value, scope.isolate, scope.context);
@@ -1455,7 +1458,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// Env.JsObject. Want a TypedArray? Env.TypedArray.
pub fn TypedArray(comptime T: type) type {
return struct {
// See Callback._CALLBACK_ID_KLUDGE
// See Function._FUNCTION_ID_KLUDGE
const _TYPED_ARRAY_ID_KLUDGE = true;
values: []const T,
@@ -1989,7 +1992,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return js_obj.toValue();
}
if (T == Callback) {
if (T == Function) {
// we're returnig a callback
return value.func.toValue();
}