mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
AddEventListener object listener
Instead of taking a callback function, addEventListener can take an object that exposes a `handleEvent` function. When used this way, `this` is automatically bound. I don't think the current behavior is correct when `handleEvent` is defined as a property (getter), but I couldn't figure out how to make it work the way WPT expects, and it hopefully isn't a common usage pattern. Also added option support to removeEventListener.
This commit is contained in:
@@ -57,7 +57,7 @@ pub const EventTarget = struct {
|
||||
pub fn _addEventListener(
|
||||
self: *parser.EventTarget,
|
||||
typ: []const u8,
|
||||
cbk: Env.Function,
|
||||
listener: EventHandler.Listener,
|
||||
opts_: ?AddEventListenerOpts,
|
||||
page: *Page,
|
||||
) !void {
|
||||
@@ -80,6 +80,8 @@ pub const EventTarget = struct {
|
||||
}
|
||||
}
|
||||
|
||||
const cbk = (try listener.callback(self)) orelse return;
|
||||
|
||||
// check if event target has already this listener
|
||||
const lst = try parser.eventTargetHasListener(
|
||||
self,
|
||||
@@ -90,8 +92,7 @@ pub const EventTarget = struct {
|
||||
if (lst != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eh = try EventHandler.init(page.arena, try cbk.withThis(self));
|
||||
const eh = try EventHandler.init(page.arena, cbk);
|
||||
|
||||
try parser.eventTargetAddEventListener(
|
||||
self,
|
||||
@@ -101,19 +102,34 @@ pub const EventTarget = struct {
|
||||
);
|
||||
}
|
||||
|
||||
const RemoveEventListenerOpts = union(enum) {
|
||||
opts: Opts,
|
||||
capture: bool,
|
||||
|
||||
const Opts = struct {
|
||||
capture: ?bool,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn _removeEventListener(
|
||||
self: *parser.EventTarget,
|
||||
typ: []const u8,
|
||||
cbk: Env.Function,
|
||||
capture: ?bool,
|
||||
// TODO: hanle EventListenerOptions
|
||||
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||
opts_: ?RemoveEventListenerOpts,
|
||||
) !void {
|
||||
var capture = false;
|
||||
if (opts_) |opts| {
|
||||
capture = switch (opts) {
|
||||
.capture => |c| c,
|
||||
.opts => |o| o.capture orelse false,
|
||||
};
|
||||
}
|
||||
|
||||
// check if event target has already this listener
|
||||
const lst = try parser.eventTargetHasListener(
|
||||
self,
|
||||
typ,
|
||||
capture orelse false,
|
||||
capture,
|
||||
cbk.id,
|
||||
);
|
||||
if (lst == null) {
|
||||
@@ -125,7 +141,7 @@ pub const EventTarget = struct {
|
||||
self,
|
||||
typ,
|
||||
lst.?,
|
||||
capture orelse false,
|
||||
capture,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -244,4 +260,11 @@ test "Browser.DOM.EventTarget" {
|
||||
.{ "phase", "3" },
|
||||
.{ "cur.getAttribute('id')", "content" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "const obj1 = {calls: 0, handleEvent: function() { this.calls += 1; } };", null },
|
||||
.{ "content.addEventListener('he', obj1);", null },
|
||||
.{ "content.dispatchEvent(new Event('he'));", null },
|
||||
.{ "obj1.calls", "1" },
|
||||
}, .{});
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const log = @import("../../log.zig");
|
||||
const parser = @import("../netsurf.zig");
|
||||
const Function = @import("../env.zig").Function;
|
||||
const generate = @import("../../runtime/generate.zig");
|
||||
|
||||
const DOMException = @import("../dom/exceptions.zig").DOMException;
|
||||
@@ -142,6 +141,24 @@ pub const EventHandler = struct {
|
||||
callback: Function,
|
||||
node: parser.EventNode,
|
||||
|
||||
const Env = @import("../env.zig").Env;
|
||||
const Function = Env.Function;
|
||||
|
||||
pub const Listener = union(enum) {
|
||||
function: Function,
|
||||
object: Env.JsObject,
|
||||
|
||||
pub fn callback(self: Listener, target: *parser.EventTarget) !?Function {
|
||||
return switch (self) {
|
||||
.function => |func| try func.withThis(target),
|
||||
.object => |obj| blk: {
|
||||
const func = (try obj.getFunction("handleEvent")) orelse return null;
|
||||
break :blk try func.withThis(try obj.persist());
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(allocator: Allocator, callback: Function) !*EventHandler {
|
||||
const eh = try allocator.create(EventHandler);
|
||||
eh.* = .{
|
||||
|
||||
@@ -44,16 +44,20 @@ pub const XMLHttpRequestEventTarget = struct {
|
||||
self: *XMLHttpRequestEventTarget,
|
||||
alloc: std.mem.Allocator,
|
||||
typ: []const u8,
|
||||
cbk: Function,
|
||||
) !void {
|
||||
listener: EventHandler.Listener,
|
||||
) !?Function {
|
||||
const target = @as(*parser.EventTarget, @ptrCast(self));
|
||||
const eh = try EventHandler.init(alloc, try cbk.withThis(target));
|
||||
|
||||
const callback = (try listener.callback(target)) orelse return null;
|
||||
const eh = try EventHandler.init(alloc, callback);
|
||||
try parser.eventTargetAddEventListener(
|
||||
target,
|
||||
typ,
|
||||
&eh.node,
|
||||
false,
|
||||
);
|
||||
|
||||
return callback;
|
||||
}
|
||||
fn unregister(self: *XMLHttpRequestEventTarget, typ: []const u8, cbk_id: usize) !void {
|
||||
const et = @as(*parser.EventTarget, @ptrCast(self));
|
||||
@@ -86,34 +90,28 @@ pub const XMLHttpRequestEventTarget = struct {
|
||||
return self.onloadend_cbk;
|
||||
}
|
||||
|
||||
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
|
||||
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
|
||||
if (self.onloadstart_cbk) |cbk| try self.unregister("loadstart", cbk.id);
|
||||
try self.register(page.arena, "loadstart", handler);
|
||||
self.onloadstart_cbk = handler;
|
||||
self.onloadstart_cbk = try self.register(page.arena, "loadstart", listener);
|
||||
}
|
||||
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
|
||||
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
|
||||
if (self.onprogress_cbk) |cbk| try self.unregister("progress", cbk.id);
|
||||
try self.register(page.arena, "progress", handler);
|
||||
self.onprogress_cbk = handler;
|
||||
self.onprogress_cbk = try self.register(page.arena, "progress", listener);
|
||||
}
|
||||
pub fn set_onabort(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
|
||||
pub fn set_onabort(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
|
||||
if (self.onabort_cbk) |cbk| try self.unregister("abort", cbk.id);
|
||||
try self.register(page.arena, "abort", handler);
|
||||
self.onabort_cbk = handler;
|
||||
self.onabort_cbk = try self.register(page.arena, "abort", listener);
|
||||
}
|
||||
pub fn set_onload(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
|
||||
pub fn set_onload(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
|
||||
if (self.onload_cbk) |cbk| try self.unregister("load", cbk.id);
|
||||
try self.register(page.arena, "load", handler);
|
||||
self.onload_cbk = handler;
|
||||
self.onload_cbk = try self.register(page.arena, "load", listener);
|
||||
}
|
||||
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
|
||||
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
|
||||
if (self.ontimeout_cbk) |cbk| try self.unregister("timeout", cbk.id);
|
||||
try self.register(page.arena, "timeout", handler);
|
||||
self.ontimeout_cbk = handler;
|
||||
self.ontimeout_cbk = try self.register(page.arena, "timeout", listener);
|
||||
}
|
||||
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
|
||||
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, listener: EventHandler.Listener, page: *Page) !void {
|
||||
if (self.onloadend_cbk) |cbk| try self.unregister("loadend", cbk.id);
|
||||
try self.register(page.arena, "loadend", handler);
|
||||
self.onloadend_cbk = handler;
|
||||
self.onloadend_cbk = try self.register(page.arena, "loadend", listener);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -582,7 +582,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
|
||||
// Given an anytype, turns it into a v8.Object. The anytype could be:
|
||||
// 1 - A V8.object already
|
||||
// 2 - Our this JsObject wrapper around a V8.Object
|
||||
// 2 - Our JsObject wrapper around a V8.Object
|
||||
// 3 - A zig instance that has previously been given to V8
|
||||
// (i.e., the value has to be known to the executor)
|
||||
fn valueToExistingObject(self: *const Scope, value: anytype) !v8.Object {
|
||||
@@ -951,15 +951,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
if (!js_value.isFunction()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const func = v8.Persistent(v8.Function).init(self.isolate, js_value.castTo(v8.Function));
|
||||
try self.trackCallback(func);
|
||||
|
||||
return .{
|
||||
.func = func,
|
||||
.scope = self,
|
||||
.id = js_value.castTo(v8.Object).getIdentityHash(),
|
||||
};
|
||||
return try self.createFunction(js_value);
|
||||
}
|
||||
|
||||
const js_obj = js_value.castTo(v8.Object);
|
||||
@@ -996,6 +988,20 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
return value;
|
||||
}
|
||||
|
||||
fn createFunction(self: *Scope, js_value: v8.Value) !Function {
|
||||
// caller should have made sure this was a function
|
||||
std.debug.assert(js_value.isFunction());
|
||||
|
||||
const func = v8.Persistent(v8.Function).init(self.isolate, js_value.castTo(v8.Function));
|
||||
try self.trackCallback(func);
|
||||
|
||||
return .{
|
||||
.func = func,
|
||||
.scope = self,
|
||||
.id = js_value.castTo(v8.Object).getIdentityHash(),
|
||||
};
|
||||
}
|
||||
|
||||
// Probing is part of trying to map a JS value to a Zig union. There's
|
||||
// a lot of ambiguity in this process, in part because some JS values
|
||||
// can almost always be coerced. For example, anything can be coerced
|
||||
@@ -1234,11 +1240,16 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
};
|
||||
|
||||
pub fn withThis(self: *const Function, value: anytype) !Function {
|
||||
const this_obj = if (@TypeOf(value) == JsObject)
|
||||
value.js_obj
|
||||
else
|
||||
try self.scope.valueToExistingObject(value);
|
||||
|
||||
return .{
|
||||
.id = self.id,
|
||||
.this = this_obj,
|
||||
.func = self.func,
|
||||
.scope = self.scope,
|
||||
.this = try self.scope.valueToExistingObject(value),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1375,6 +1386,17 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.js_obj = gop.value_ptr.castToObject(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getFunction(self: JsObject, name: []const u8) !?Function {
|
||||
const scope = self.scope;
|
||||
const js_name = v8.String.initUtf8(scope.isolate, name);
|
||||
|
||||
const js_value = try self.js_obj.getValue(scope.context, js_name.toName());
|
||||
if (!js_value.isFunction()) {
|
||||
return null;
|
||||
}
|
||||
return try scope.createFunction(js_value);
|
||||
}
|
||||
};
|
||||
|
||||
// This only exists so that we know whether a function wants the opaque
|
||||
|
||||
Reference in New Issue
Block a user