mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23:28 +00:00
Add MessageChannel
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2023-2025s Lightpanda (Selecy SAS)
|
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||||
//
|
//
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
|||||||
361
src/browser/dom/MessageChannel.zig
Normal file
361
src/browser/dom/MessageChannel.zig
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
// 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 parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
|
const Env = @import("../env.zig").Env;
|
||||||
|
const Page = @import("../page.zig").Page;
|
||||||
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
|
const EventHandler = @import("../events/event.zig").EventHandler;
|
||||||
|
|
||||||
|
const JsObject = Env.JsObject;
|
||||||
|
const Function = Env.Function;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const MAX_QUEUE_SIZE = 10;
|
||||||
|
|
||||||
|
pub const Interfaces = .{ MessageChannel, MessagePort };
|
||||||
|
|
||||||
|
const MessageChannel = @This();
|
||||||
|
|
||||||
|
port1: *MessagePort,
|
||||||
|
port2: *MessagePort,
|
||||||
|
|
||||||
|
pub fn constructor(page: *Page) !MessageChannel {
|
||||||
|
// Why do we allocate this rather than storing directly in the struct?
|
||||||
|
// https://github.com/lightpanda-io/project/discussions/165
|
||||||
|
const port1 = try page.arena.create(MessagePort);
|
||||||
|
const port2 = try page.arena.create(MessagePort);
|
||||||
|
port1.* = .{
|
||||||
|
.pair = port2,
|
||||||
|
};
|
||||||
|
port2.* = .{
|
||||||
|
.pair = port1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.port1 = port1,
|
||||||
|
.port2 = port2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_port1(self: *const MessageChannel) *MessagePort {
|
||||||
|
return self.port1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_port2(self: *const MessageChannel) *MessagePort {
|
||||||
|
return self.port2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const MessagePort = struct {
|
||||||
|
pub const prototype = *EventTarget;
|
||||||
|
|
||||||
|
proto: parser.EventTargetTBase = .{ .internal_target_type = .message_port },
|
||||||
|
|
||||||
|
pair: *MessagePort,
|
||||||
|
closed: bool = false,
|
||||||
|
started: bool = false,
|
||||||
|
onmessage_cbk: ?Function = null,
|
||||||
|
onmessageerror_cbk: ?Function = null,
|
||||||
|
// This is the queue of messages to dispatch to THIS MessagePort when the
|
||||||
|
// MessagePort is started.
|
||||||
|
queue: std.ArrayListUnmanaged(JsObject) = .empty,
|
||||||
|
|
||||||
|
pub const PostMessageOption = union(enum) {
|
||||||
|
transfer: JsObject,
|
||||||
|
options: Opts,
|
||||||
|
|
||||||
|
pub const Opts = struct {
|
||||||
|
transfer: JsObject,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn _postMessage(self: *MessagePort, obj: JsObject, opts_: ?PostMessageOption, page: *Page) !void {
|
||||||
|
if (self.closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts_ != null) {
|
||||||
|
log.warn(.web_api, "not implemented", .{ .feature = "MessagePort postMessage options" });
|
||||||
|
return error.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.pair.dispatchOrQueue(obj, page.arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start impacts the ability to receive a message.
|
||||||
|
// Given pair1 (started) and pair2 (not started), then:
|
||||||
|
// pair2.postMessage('x'); //will be dispatched to pair1.onmessage
|
||||||
|
// pair1.postMessage('x'); // will be queued until pair2 is started
|
||||||
|
pub fn _start(self: *MessagePort) !void {
|
||||||
|
if (self.started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.started = true;
|
||||||
|
for (self.queue.items) |data| {
|
||||||
|
try self.dispatch(data);
|
||||||
|
}
|
||||||
|
// we'll never use this queue again, but it's allocated with an arena
|
||||||
|
// we don't even need to clear it, but it seems a bit safer to do at
|
||||||
|
// least that
|
||||||
|
self.queue.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closing seems to stop both the publishing and receiving of messages,
|
||||||
|
// effectively rendering the channel useless. It cannot be reversed.
|
||||||
|
pub fn _close(self: *MessagePort) void {
|
||||||
|
self.closed = true;
|
||||||
|
self.pair.closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_onmessage(self: *MessagePort) ?Function {
|
||||||
|
return self.onmessage_cbk;
|
||||||
|
}
|
||||||
|
pub fn get_onmessageerror(self: *MessagePort) ?Function {
|
||||||
|
return self.onmessageerror_cbk;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_onmessage(self: *MessagePort, listener: EventHandler.Listener, page: *Page) !void {
|
||||||
|
if (self.onmessage_cbk) |cbk| {
|
||||||
|
try self.unregister("message", cbk.id);
|
||||||
|
}
|
||||||
|
self.onmessage_cbk = try self.register(page.arena, "message", listener);
|
||||||
|
|
||||||
|
// When onmessage is set directly, then it's like start() was called.
|
||||||
|
// If addEventListener('message') is used, the app has to call start()
|
||||||
|
// explicitly.
|
||||||
|
try self._start();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_onmessageerror(self: *MessagePort, listener: EventHandler.Listener, page: *Page) !void {
|
||||||
|
if (self.onmessageerror_cbk) |cbk| {
|
||||||
|
try self.unregister("messageerror", cbk.id);
|
||||||
|
}
|
||||||
|
self.onmessageerror_cbk = try self.register(page.arena, "messageerror", listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// called from our pair. If port1.postMessage("x") is called, then this
|
||||||
|
// will be called on port2.
|
||||||
|
fn dispatchOrQueue(self: *MessagePort, obj: JsObject, arena: Allocator) !void {
|
||||||
|
// our pair should have checked this already
|
||||||
|
std.debug.assert(self.closed == false);
|
||||||
|
|
||||||
|
if (self.started) {
|
||||||
|
return self.dispatch(try obj.persist());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.queue.items.len > MAX_QUEUE_SIZE) {
|
||||||
|
// This isn't part of the spec, but not putting a limit is reckless
|
||||||
|
return error.MessageQueueLimit;
|
||||||
|
}
|
||||||
|
return self.queue.append(arena, try obj.persist());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch(self: *MessagePort, obj: JsObject) !void {
|
||||||
|
// obj is already persisted, don't use `MessageEvent.constructor`, but
|
||||||
|
// go directly to `init`, which assumes persisted objects.
|
||||||
|
var evt = try MessageEvent.init(.{ .data = obj });
|
||||||
|
_ = try parser.eventTargetDispatchEvent(
|
||||||
|
parser.toEventTarget(MessagePort, self),
|
||||||
|
@as(*parser.Event, @ptrCast(&evt)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(
|
||||||
|
self: *MessagePort,
|
||||||
|
alloc: Allocator,
|
||||||
|
typ: []const u8,
|
||||||
|
listener: EventHandler.Listener,
|
||||||
|
) !?Function {
|
||||||
|
const target = @as(*parser.EventTarget, @ptrCast(self));
|
||||||
|
const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable;
|
||||||
|
return eh.callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unregister(self: *MessagePort, typ: []const u8, cbk_id: usize) !void {
|
||||||
|
const et = @as(*parser.EventTarget, @ptrCast(self));
|
||||||
|
const lst = try parser.eventTargetHasListener(et, typ, false, cbk_id);
|
||||||
|
if (lst == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try parser.eventTargetRemoveEventListener(et, typ, lst.?, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MessageEvent = struct {
|
||||||
|
const Event = @import("../events/event.zig").Event;
|
||||||
|
const DOMException = @import("exceptions.zig").DOMException;
|
||||||
|
|
||||||
|
pub const prototype = *Event;
|
||||||
|
pub const Exception = DOMException;
|
||||||
|
pub const union_make_copy = true;
|
||||||
|
|
||||||
|
proto: parser.Event,
|
||||||
|
data: ?JsObject,
|
||||||
|
|
||||||
|
// You would think if port1 sends to port2, the source would be port2
|
||||||
|
// (which is how I read the documentation), but it appears to always be
|
||||||
|
// null. It can always be set explicitly via the constructor;
|
||||||
|
source: ?JsObject,
|
||||||
|
|
||||||
|
origin: []const u8,
|
||||||
|
|
||||||
|
// This is used for Server-Sent events. Appears to always be an empty
|
||||||
|
// string for MessagePort messages.
|
||||||
|
last_event_id: []const u8,
|
||||||
|
|
||||||
|
// This might be related to the "transfer" option of postMessage which
|
||||||
|
// we don't yet support. For "normal" message, it's always an empty array.
|
||||||
|
// Though it could be set explicitly via the constructor
|
||||||
|
ports: []*MessagePort,
|
||||||
|
|
||||||
|
const Options = struct {
|
||||||
|
data: ?JsObject = null,
|
||||||
|
source: ?JsObject = null,
|
||||||
|
origin: []const u8 = "",
|
||||||
|
lastEventId: []const u8 = "",
|
||||||
|
ports: []*MessagePort = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn constructor(opts: Options) !MessageEvent {
|
||||||
|
return init(.{
|
||||||
|
.data = if (opts.data) |obj| try obj.persist() else null,
|
||||||
|
.source = if (opts.source) |obj| try obj.persist() else null,
|
||||||
|
.ports = opts.ports,
|
||||||
|
.origin = opts.origin,
|
||||||
|
.lastEventId = opts.lastEventId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is like "constructor", but it assumes JsObjects have already been
|
||||||
|
// persisted. Necessary because this `new MessageEvent()` can be called
|
||||||
|
// directly from JS OR from a port.postMessage. In the latter case, data
|
||||||
|
// may have already been persisted (as it might need to be queued);
|
||||||
|
fn init(opts: Options) !MessageEvent {
|
||||||
|
const event = try parser.eventCreate();
|
||||||
|
defer parser.eventDestroy(event);
|
||||||
|
try parser.eventInit(event, "message", .{});
|
||||||
|
try parser.eventSetInternalType(event, .message_event);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.proto = event.*,
|
||||||
|
.data = opts.data,
|
||||||
|
.source = opts.source,
|
||||||
|
.ports = opts.ports,
|
||||||
|
.origin = opts.origin,
|
||||||
|
.last_event_id = opts.lastEventId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data(self: *const MessageEvent) !?JsObject {
|
||||||
|
return self.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_origin(self: *const MessageEvent) []const u8 {
|
||||||
|
return self.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_source(self: *const MessageEvent) ?JsObject {
|
||||||
|
return self.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ports(self: *const MessageEvent) []*MessagePort {
|
||||||
|
return self.ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lastEventId(self: *const MessageEvent) []const u8 {
|
||||||
|
return self.last_event_id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "Browser.MessageChannel" {
|
||||||
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{
|
||||||
|
.html = "",
|
||||||
|
});
|
||||||
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "const mc1 = new MessageChannel()", null },
|
||||||
|
.{ "mc1.port1 == mc1.port1", "true" },
|
||||||
|
.{ "mc1.port2 == mc1.port2", "true" },
|
||||||
|
.{ "mc1.port1 != mc1.port2", "true" },
|
||||||
|
.{ "mc1.port1.postMessage('msg1');", "undefined" },
|
||||||
|
.{
|
||||||
|
\\ let message = null;
|
||||||
|
\\ let target = null;
|
||||||
|
\\ let currentTarget = null;
|
||||||
|
\\ mc1.port2.onmessage = (e) => {
|
||||||
|
\\ message = e.data;
|
||||||
|
\\ target = e.target;
|
||||||
|
\\ currentTarget = e.currentTarget;
|
||||||
|
\\ };
|
||||||
|
,
|
||||||
|
null,
|
||||||
|
},
|
||||||
|
// as soon as onmessage is called, queued messages are delivered
|
||||||
|
.{ "message", "msg1" },
|
||||||
|
.{ "target == mc1.port2", "true" },
|
||||||
|
.{ "currentTarget == mc1.port2", "true" },
|
||||||
|
|
||||||
|
.{ "mc1.port1.postMessage('msg2');", "undefined" },
|
||||||
|
.{ "message", "msg2" },
|
||||||
|
.{ "target == mc1.port2", "true" },
|
||||||
|
.{ "currentTarget == mc1.port2", "true" },
|
||||||
|
|
||||||
|
.{ "message = null", null },
|
||||||
|
.{ "mc1.port1.close();", null },
|
||||||
|
.{ "mc1.port1.postMessage('msg3');", "undefined" },
|
||||||
|
.{ "message", "null" },
|
||||||
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "const mc2 = new MessageChannel()", null },
|
||||||
|
.{ "mc2.port2.postMessage('msg1');", "undefined" },
|
||||||
|
.{ "mc2.port1.postMessage('msg2');", "undefined" },
|
||||||
|
.{
|
||||||
|
\\ let message1 = null;
|
||||||
|
\\ mc2.port1.addEventListener('message', (e) => {
|
||||||
|
\\ message1 = e.data;
|
||||||
|
\\ });
|
||||||
|
,
|
||||||
|
null,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
\\ let message2 = null;
|
||||||
|
\\ mc2.port2.addEventListener('message', (e) => {
|
||||||
|
\\ message2 = e.data;
|
||||||
|
\\ });
|
||||||
|
,
|
||||||
|
null,
|
||||||
|
},
|
||||||
|
.{ "message1", "null" },
|
||||||
|
.{ "message2", "null" },
|
||||||
|
.{ "mc2.port2.start()", null },
|
||||||
|
|
||||||
|
.{ "message1", "null" },
|
||||||
|
.{ "message2", "msg2" },
|
||||||
|
.{ "message2 = null", null },
|
||||||
|
|
||||||
|
.{ "mc2.port1.start()", null },
|
||||||
|
.{ "message1", "msg1" },
|
||||||
|
.{ "message2", "null" },
|
||||||
|
}, .{});
|
||||||
|
}
|
||||||
@@ -53,4 +53,5 @@ pub const Interfaces = .{
|
|||||||
PerformanceObserver,
|
PerformanceObserver,
|
||||||
@import("range.zig").Interfaces,
|
@import("range.zig").Interfaces,
|
||||||
@import("Animation.zig"),
|
@import("Animation.zig"),
|
||||||
|
@import("MessageChannel.zig").Interfaces,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub const Union = union(enum) {
|
|||||||
node: nod.Union,
|
node: nod.Union,
|
||||||
xhr: *@import("../xhr/xhr.zig").XMLHttpRequest,
|
xhr: *@import("../xhr/xhr.zig").XMLHttpRequest,
|
||||||
plain: *parser.EventTarget,
|
plain: *parser.EventTarget,
|
||||||
|
message_port: *@import("MessageChannel.zig").MessagePort,
|
||||||
};
|
};
|
||||||
|
|
||||||
// EventTarget implementation
|
// EventTarget implementation
|
||||||
@@ -63,6 +64,9 @@ pub const EventTarget = struct {
|
|||||||
const base: *XMLHttpRequestEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et)));
|
const base: *XMLHttpRequestEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et)));
|
||||||
return .{ .xhr = @fieldParentPtr("proto", base) };
|
return .{ .xhr = @fieldParentPtr("proto", base) };
|
||||||
},
|
},
|
||||||
|
.message_port => {
|
||||||
|
return .{ .message_port = @fieldParentPtr("proto", @as(*parser.EventTargetTBase, @ptrCast(et))) };
|
||||||
|
},
|
||||||
else => return error.MissingEventTargetType,
|
else => return error.MissingEventTargetType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ const CustomEvent = @import("custom_event.zig").CustomEvent;
|
|||||||
const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent;
|
const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent;
|
||||||
const MouseEvent = @import("mouse_event.zig").MouseEvent;
|
const MouseEvent = @import("mouse_event.zig").MouseEvent;
|
||||||
const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
|
const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
|
||||||
|
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
|
||||||
|
|
||||||
// Event interfaces
|
// Event interfaces
|
||||||
pub const Interfaces = .{ Event, CustomEvent, ProgressEvent, MouseEvent, ErrorEvent };
|
pub const Interfaces = .{ Event, CustomEvent, ProgressEvent, MouseEvent, ErrorEvent, MessageEvent };
|
||||||
|
|
||||||
pub const Union = generate.Union(Interfaces);
|
pub const Union = generate.Union(Interfaces);
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ pub const Event = struct {
|
|||||||
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
|
.progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* },
|
||||||
.mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) },
|
.mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) },
|
||||||
.error_event => .{ .ErrorEvent = @as(*ErrorEvent, @ptrCast(evt)).* },
|
.error_event => .{ .ErrorEvent = @as(*ErrorEvent, @ptrCast(evt)).* },
|
||||||
|
.message_event => .{ .MessageEvent = @as(*MessageEvent, @ptrCast(evt)).* },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,6 +528,7 @@ pub const EventType = enum(u8) {
|
|||||||
error_event = 4,
|
error_event = 4,
|
||||||
abort_signal = 5,
|
abort_signal = 5,
|
||||||
xhr_event = 6,
|
xhr_event = 6,
|
||||||
|
message_event = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MutationEvent = c.dom_mutation_event;
|
pub const MutationEvent = c.dom_mutation_event;
|
||||||
@@ -786,6 +787,7 @@ pub const EventTargetTBase = extern struct {
|
|||||||
window = 4,
|
window = 4,
|
||||||
performance = 5,
|
performance = 5,
|
||||||
media_query_list = 6,
|
media_query_list = 6,
|
||||||
|
message_port = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{
|
vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{
|
||||||
|
|||||||
@@ -634,13 +634,15 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
// The key is the @intFromPtr of the Zig value
|
// The key is the @intFromPtr of the Zig value
|
||||||
identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
|
identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
|
||||||
|
|
||||||
// Similar to the identity map, but used much less frequently. Some
|
// Some web APIs have to manage opaque values. Ideally, they use an
|
||||||
// web APIs have to manage opaque values. Ideally, they use an
|
|
||||||
// JsObject, but the JsObject has no lifetime guarantee beyond the
|
// JsObject, but the JsObject has no lifetime guarantee beyond the
|
||||||
// current call. They can call .persist() on their JsObject to get
|
// current call. They can call .persist() on their JsObject to get
|
||||||
// a `*PersistentObject()`. We need to track these to free them.
|
// a `*PersistentObject()`. We need to track these to free them.
|
||||||
// The key is the @intFromPtr of the v8.Object.handle.
|
// This used to be a map and acted like identity_map; the key was
|
||||||
js_object_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
|
// the @intFromPtr(js_obj.handle). But v8 can re-use address. Without
|
||||||
|
// a reliable way to know if an object has already been persisted,
|
||||||
|
// we now simply persist every time persist() is called.
|
||||||
|
js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty,
|
||||||
|
|
||||||
// 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
|
||||||
@@ -690,11 +692,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
for (self.js_object_list.items) |*p| {
|
||||||
var it = self.js_object_map.valueIterator();
|
p.deinit();
|
||||||
while (it.next()) |p| {
|
|
||||||
p.deinit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1883,16 +1882,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
pub fn persist(self: JsObject) !JsObject {
|
pub fn persist(self: JsObject) !JsObject {
|
||||||
var js_context = self.js_context;
|
var js_context = self.js_context;
|
||||||
const js_obj = self.js_obj;
|
const js_obj = self.js_obj;
|
||||||
const handle = js_obj.handle;
|
|
||||||
|
|
||||||
const gop = try js_context.js_object_map.getOrPut(js_context.context_arena, @intFromPtr(handle));
|
const persisted = PersistentObject.init(js_context.isolate, js_obj);
|
||||||
if (gop.found_existing == false) {
|
try js_context.js_object_list.append(js_context.context_arena, persisted);
|
||||||
gop.value_ptr.* = PersistentObject.init(js_context.isolate, js_obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.js_context = js_context,
|
.js_context = js_context,
|
||||||
.js_obj = gop.value_ptr.castToObject(),
|
.js_obj = persisted.castToObject(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user