mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-30 15:41:48 +00:00 
			
		
		
		
	Add CustomEvent api
This commit is contained in:
		
							
								
								
									
										81
									
								
								src/browser/events/custom_event.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/browser/events/custom_event.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | // 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 Event = @import("event.zig").Event; | ||||||
|  | const JsObject = @import("../env.zig").JsObject; | ||||||
|  |  | ||||||
|  | // https://dom.spec.whatwg.org/#interface-customevent | ||||||
|  | pub const CustomEvent = struct { | ||||||
|  |     pub const prototype = *Event; | ||||||
|  |  | ||||||
|  |     proto: parser.Event, | ||||||
|  |     detail: ?JsObject, | ||||||
|  |  | ||||||
|  |     const CustomEventInit = struct { | ||||||
|  |         bubbles: bool = false, | ||||||
|  |         cancelable: bool = false, | ||||||
|  |         composed: bool = false, | ||||||
|  |         detail: ?JsObject = null, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     pub fn constructor(event_type: []const u8, opts_: ?CustomEventInit) !CustomEvent { | ||||||
|  |         const opts = opts_ orelse CustomEventInit{}; | ||||||
|  |  | ||||||
|  |         const event = try parser.eventCreate(); | ||||||
|  |         defer parser.eventDestroy(event); | ||||||
|  |         try parser.eventInit(event, event_type, .{ | ||||||
|  |             .bubbles = opts.bubbles, | ||||||
|  |             .cancelable = opts.cancelable, | ||||||
|  |             .composed = opts.composed, | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return .{ | ||||||
|  |             .proto = event.*, | ||||||
|  |             .detail = if (opts.detail) |d| try d.persist() else null, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn get_detail(self: *CustomEvent) ?JsObject { | ||||||
|  |         return self.detail; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const testing = @import("../../testing.zig"); | ||||||
|  | test "Browser.CustomEvent" { | ||||||
|  |     var runner = try testing.jsRunner(testing.tracking_allocator, .{}); | ||||||
|  |     defer runner.deinit(); | ||||||
|  |  | ||||||
|  |     try runner.testCases(&.{ | ||||||
|  |         .{ "let capture = null", "undefined"}, | ||||||
|  |         .{ "const el = document.createElement('div');", "undefined"}, | ||||||
|  |         .{ "el.addEventListener('c1', (e) => { capture = 'c1-' + new String(e.detail)})", "undefined"}, | ||||||
|  |         .{ "el.addEventListener('c2', (e) => { capture = 'c2-' + new String(e.detail.over)})", "undefined"}, | ||||||
|  |  | ||||||
|  |         .{ "el.dispatchEvent(new CustomEvent('c1'));", "true"}, | ||||||
|  |         .{ "capture", "c1-null"}, | ||||||
|  |  | ||||||
|  |         .{ "el.dispatchEvent(new CustomEvent('c1', {detail: '123'}));", "true"}, | ||||||
|  |         .{ "capture", "c1-123"}, | ||||||
|  |  | ||||||
|  |         .{ "el.dispatchEvent(new CustomEvent('c2', {detail: {over: 9000}}));", "true"}, | ||||||
|  |         .{ "capture", "c2-9000"}, | ||||||
|  |     }, .{}); | ||||||
|  | } | ||||||
| @@ -27,6 +27,7 @@ const DOMException = @import("../dom/exceptions.zig").DOMException; | |||||||
| const EventTarget = @import("../dom/event_target.zig").EventTarget; | const EventTarget = @import("../dom/event_target.zig").EventTarget; | ||||||
| const EventTargetUnion = @import("../dom/event_target.zig").Union; | const EventTargetUnion = @import("../dom/event_target.zig").Union; | ||||||
|  |  | ||||||
|  | const CustomEvent = @import("custom_event.zig").CustomEvent; | ||||||
| const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent; | const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent; | ||||||
|  |  | ||||||
| const log = std.log.scoped(.events); | const log = std.log.scoped(.events); | ||||||
| @@ -34,6 +35,7 @@ const log = std.log.scoped(.events); | |||||||
| // Event interfaces | // Event interfaces | ||||||
| pub const Interfaces = .{ | pub const Interfaces = .{ | ||||||
|     Event, |     Event, | ||||||
|  |     CustomEvent, | ||||||
|     ProgressEvent, |     ProgressEvent, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -56,13 +58,14 @@ pub const Event = struct { | |||||||
|     pub fn toInterface(evt: *parser.Event) !Union { |     pub fn toInterface(evt: *parser.Event) !Union { | ||||||
|         return switch (try parser.eventGetInternalType(evt)) { |         return switch (try parser.eventGetInternalType(evt)) { | ||||||
|             .event => .{ .Event = evt }, |             .event => .{ .Event = evt }, | ||||||
|  |             .custom_event => .{ .CustomEvent = @as(*CustomEvent, @ptrCast(evt)).* }, | ||||||
|             .progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* }, |             .progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* }, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn constructor(eventType: []const u8, opts: ?EventInit) !*parser.Event { |     pub fn constructor(event_type: []const u8, opts: ?EventInit) !*parser.Event { | ||||||
|         const event = try parser.eventCreate(); |         const event = try parser.eventCreate(); | ||||||
|         try parser.eventInit(event, eventType, opts orelse EventInit{}); |         try parser.eventInit(event, event_type, opts orelse EventInit{}); | ||||||
|         return event; |         return event; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -519,6 +519,7 @@ pub fn eventSetInternalType(evt: *Event, internal_type: EventType) !void { | |||||||
| pub const EventType = enum(u8) { | pub const EventType = enum(u8) { | ||||||
|     event = 0, |     event = 0, | ||||||
|     progress_event = 1, |     progress_event = 1, | ||||||
|  |     custom_event = 2, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub const MutationEvent = c.dom_mutation_event; | pub const MutationEvent = c.dom_mutation_event; | ||||||
|   | |||||||
| @@ -516,8 +516,17 @@ pub fn Env(comptime S: type, comptime types: anytype) type { | |||||||
|             // every PeristentObjet we've created during the lifetime of the scope. |             // every PeristentObjet we've created during the lifetime of the scope. | ||||||
|             // More importantly, it serves as an identity map - for a given Zig |             // More importantly, it serves as an identity map - for a given Zig | ||||||
|             // instance, we map it to the same PersistentObject. |             // instance, we map it to the same PersistentObject. | ||||||
|  |             // The key is the @intFromPtr of the Zig value | ||||||
|             identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{}, |             identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{}, | ||||||
|  |  | ||||||
|  |             // Similar to the identity map, but used much less frequently. Some | ||||||
|  |             // web APIs have to manage opaque values. Ideally, they use an | ||||||
|  |             // JsObject, but the JsObject has no lifetime guarantee beyond the | ||||||
|  |             // current call. They can call .persist() on their JsObject to get | ||||||
|  |             // a `*PersistentObject()`. We need to track these to free them. | ||||||
|  |             // The key is the @intFromPtr of the v8.Object.handle. | ||||||
|  |             js_object_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{}, | ||||||
|  |  | ||||||
|             // When we need to load a resource (i.e. an external script), we call |             // When we need to load a resource (i.e. an external script), we call | ||||||
|             // this function to get the source. This is always a reference to the |             // this function to get the source. This is always a reference to the | ||||||
|             // Page's fetchModuleSource, but we use a function pointer |             // Page's fetchModuleSource, but we use a function pointer | ||||||
| @@ -535,10 +544,20 @@ pub fn Env(comptime S: type, comptime types: anytype) type { | |||||||
|             // no init, started with executor.startScope() |             // no init, started with executor.startScope() | ||||||
|  |  | ||||||
|             fn deinit(self: *Scope) void { |             fn deinit(self: *Scope) void { | ||||||
|                 var it = self.identity_map.valueIterator(); |                 { | ||||||
|                 while (it.next()) |p| { |                     var it = self.identity_map.valueIterator(); | ||||||
|                     p.deinit(); |                     while (it.next()) |p| { | ||||||
|  |                         p.deinit(); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 { | ||||||
|  |                     var it = self.js_object_map.valueIterator(); | ||||||
|  |                     while (it.next()) |p| { | ||||||
|  |                         p.deinit(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 for (self.callbacks.items) |*cb| { |                 for (self.callbacks.items) |*cb| { | ||||||
|                     cb.deinit(); |                     cb.deinit(); | ||||||
|                 } |                 } | ||||||
| @@ -871,13 +890,13 @@ pub fn Env(comptime S: type, comptime types: anytype) type { | |||||||
|             scope: *Scope, |             scope: *Scope, | ||||||
|             js_obj: v8.Object, |             js_obj: v8.Object, | ||||||
|  |  | ||||||
|             // If a Zig struct wants the Object parameter, it'll declare a |             // If a Zig struct wants the JsObject parameter, it'll declare a | ||||||
|             // function like: |             // function like: | ||||||
|             //    fn _length(self: *const NodeList, js_obj: Env.Object) usize |             //    fn _length(self: *const NodeList, js_obj: Env.JsObject) usize | ||||||
|             // |             // | ||||||
|             // When we're trying to call this function, we can't just do |             // When we're trying to call this function, we can't just do | ||||||
|             //    if (params[i].type.? == Object) |             //    if (params[i].type.? == JsObject) | ||||||
|             // Because there is _no_ object, there's only an Env.Object, where |             // Because there is _no_ JsObject, there's only an Env.JsObject, where | ||||||
|             // Env is a generic. |             // Env is a generic. | ||||||
|             // We could probably figure out a way to do this, but simply checking |             // We could probably figure out a way to do this, but simply checking | ||||||
|             // for this declaration is _a lot_ easier. |             // for this declaration is _a lot_ easier. | ||||||
| @@ -915,6 +934,22 @@ pub fn Env(comptime S: type, comptime types: anytype) type { | |||||||
|             pub fn format(self: JsObject, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { |             pub fn format(self: JsObject, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { | ||||||
|                 return writer.writeAll(try self.toString()); |                 return writer.writeAll(try self.toString()); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             pub fn persist(self: JsObject) !JsObject { | ||||||
|  |                 var scope = self.scope; | ||||||
|  |                 const js_obj = self.js_obj; | ||||||
|  |                 const handle = js_obj.handle; | ||||||
|  |  | ||||||
|  |                 const gop = try scope.js_object_map.getOrPut(scope.scope_arena, @intFromPtr(handle)); | ||||||
|  |                 if (gop.found_existing == false) { | ||||||
|  |                     gop.value_ptr.* = PersistentObject.init(scope.isolate, js_obj); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return .{ | ||||||
|  |                     .scope = scope, | ||||||
|  |                     .js_obj = gop.value_ptr.castToObject(), | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         // This only exists so that we know whether a function wants the opaque |         // This only exists so that we know whether a function wants the opaque | ||||||
| @@ -1448,6 +1483,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type { | |||||||
|                         return value.func.toValue(); |                         return value.func.toValue(); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  |                     if (T == JsObject) { | ||||||
|  |                         // we're returning a v8.Object | ||||||
|  |                         return value.js_obj.toValue(); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     if (s.is_tuple) { |                     if (s.is_tuple) { | ||||||
|                         // return the tuple struct as an array |                         // return the tuple struct as an array | ||||||
|                         var js_arr = v8.Array.init(isolate, @intCast(s.fields.len)); |                         var js_arr = v8.Array.init(isolate, @intCast(s.fields.len)); | ||||||
| @@ -1495,7 +1535,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { | |||||||
|                 .error_union => return zigValueToJs(templates, isolate, context, value catch |err| return err), |                 .error_union => return zigValueToJs(templates, isolate, context, value catch |err| return err), | ||||||
|                 else => {}, |                 else => {}, | ||||||
|             } |             } | ||||||
|             @compileLog(@typeInfo(T)); |  | ||||||
|             @compileError("A function returns an unsupported type: " ++ @typeName(T)); |             @compileError("A function returns an unsupported type: " ++ @typeName(T)); | ||||||
|         } |         } | ||||||
|         // Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque |         // Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Karl Seguin
					Karl Seguin