Skip to content

Commit 27d1f79

Browse files
authored
Merge pull request #583 from lightpanda-io/share-state-and-global-with-the-isolated
Share underlying DOM global Window with the isolated World
2 parents 2910f4f + 83ef21e commit 27d1f79

File tree

6 files changed

+83
-66
lines changed

6 files changed

+83
-66
lines changed

src/browser/browser.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ pub const Page = struct {
272272
.cookie_jar = &session.cookie_jar,
273273
.http_client = browser.http_client,
274274
},
275-
.scope = try session.executor.startScope(&self.window, &self.state, self),
275+
.scope = try session.executor.startScope(&self.window, &self.state, self, true),
276276
};
277277

278278
// load polyfills

src/cdp/cdp.zig

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const Env = @import("../browser/env.zig").Env;
2525
const asUint = @import("../str/parser.zig").asUint;
2626
const Browser = @import("../browser/browser.zig").Browser;
2727
const Session = @import("../browser/browser.zig").Session;
28+
const Page = @import("../browser/browser.zig").Page;
2829
const Inspector = @import("../browser/env.zig").Env.Inspector;
2930
const Incrementing = @import("../id.zig").Incrementing;
3031
const Notification = @import("../notification.zig").Notification;
@@ -359,7 +360,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
359360
self.node_search_list.reset();
360361
}
361362

362-
pub fn createIsolatedWorld(self: *Self) !void {
363+
pub fn createIsolatedWorld(self: *Self, page: *Page) !void {
363364
if (self.isolated_world != null) {
364365
return error.CurrentlyOnly1IsolatedWorldSupported;
365366
}
@@ -369,15 +370,18 @@ pub fn BrowserContext(comptime CDP_T: type) type {
369370

370371
self.isolated_world = .{
371372
.name = "",
372-
.global = .{},
373373
.scope = undefined,
374374
.executor = executor,
375-
.grant_universal_access = false,
375+
.grant_universal_access = true,
376376
};
377377
var world = &self.isolated_world.?;
378378

379-
// TODO: can we do something better than passing `undefined` for the state?
380-
world.scope = try world.executor.startScope(&world.global, undefined, {});
379+
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
380+
// (assuming grantUniveralAccess will be set to True!).
381+
// We just created the world and the page. The page's state lives in the session, but is update on navigation.
382+
// This also means this pointer becomes invalid after removePage untill a new page is created.
383+
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
384+
world.scope = try world.executor.startScope(&page.window, &page.state, {}, false);
381385
}
382386

383387
pub fn nodeWriter(self: *Self, node: *const Node, opts: Node.Writer.Opts) Node.Writer {
@@ -499,7 +503,6 @@ const IsolatedWorld = struct {
499503
scope: *Env.Scope,
500504
executor: Env.Executor,
501505
grant_universal_access: bool,
502-
global: @import("../browser/html/window.zig").Window,
503506

504507
pub fn deinit(self: *IsolatedWorld) void {
505508
self.executor.deinit();

src/cdp/domains/target.zig

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,10 @@ fn createTarget(cmd: anytype) !void {
122122

123123
const target_id = cmd.cdp.target_id_gen.next();
124124

125-
try bc.createIsolatedWorld();
126125
bc.target_id = target_id;
127126

128-
const page = try bc.session.createPage();
129-
130-
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
131-
// (assuming grantUniveralAccess will be set to True!).
132-
// We just created the world and the page. The page's state lives in the session, but is update on navigation.
133-
// This also means this pointer becomes invalid after removePage untill a new page is created.
134-
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
135-
bc.isolated_world.?.scope.state = &page.state;
127+
var page = try bc.session.createPage();
128+
try bc.createIsolatedWorld(page);
136129

137130
{
138131
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});

src/runtime/js.zig

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
328328
// when the handle_scope is freed.
329329
// We also maintain our own "scope_arena" which allows us to have
330330
// all page related memory easily managed.
331-
pub fn startScope(self: *Executor, global: anytype, state: State, module_loader: anytype) !*Scope {
331+
pub fn startScope(self: *Executor, global: anytype, state: State, module_loader: anytype, enter: bool) !*Scope {
332332
std.debug.assert(self.scope == null);
333333

334334
const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) {
@@ -345,61 +345,76 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
345345
const isolate = env.isolate;
346346
const Global = @TypeOf(global.*);
347347

348-
var handle_scope: v8.HandleScope = undefined;
349-
v8.HandleScope.init(&handle_scope, isolate);
350-
errdefer handle_scope.deinit();
348+
var context: v8.Context = blk: {
349+
var handle_scope: v8.HandleScope = undefined;
350+
v8.HandleScope.init(&handle_scope, isolate);
351+
defer handle_scope.deinit();
351352

352-
const js_global = v8.FunctionTemplate.initDefault(isolate);
353-
attachClass(Global, isolate, js_global);
353+
const js_global = v8.FunctionTemplate.initDefault(isolate);
354+
attachClass(Global, isolate, js_global);
354355

355-
const global_template = js_global.getInstanceTemplate();
356-
global_template.setInternalFieldCount(1);
356+
const global_template = js_global.getInstanceTemplate();
357+
global_template.setInternalFieldCount(1);
357358

358-
// All the FunctionTemplates that we created and setup in Env.init
359-
// are now going to get associated with our global instance.
360-
const templates = &self.env.templates;
361-
inline for (Types, 0..) |s, i| {
362-
const Struct = @field(types, s.name);
363-
const class_name = v8.String.initUtf8(isolate, comptime classNameForStruct(Struct));
364-
global_template.set(class_name.toName(), templates[i], v8.PropertyAttribute.None);
365-
}
359+
// All the FunctionTemplates that we created and setup in Env.init
360+
// are now going to get associated with our global instance.
361+
const templates = &self.env.templates;
362+
inline for (Types, 0..) |s, i| {
363+
const Struct = @field(types, s.name);
364+
const class_name = v8.String.initUtf8(isolate, comptime classNameForStruct(Struct));
365+
global_template.set(class_name.toName(), templates[i], v8.PropertyAttribute.None);
366+
}
366367

367-
// The global object (Window) has already been hooked into the v8
368-
// engine when the Env was initialized - like every other type.
369-
// But the V8 global is its own FunctionTemplate instance so even
370-
// though it's also a Window, we need to set the prototype for this
371-
// specific instance of the the Window.
372-
if (@hasDecl(Global, "prototype")) {
373-
const proto_type = Receiver(@typeInfo(Global.prototype).pointer.child);
374-
const proto_name = @typeName(proto_type);
375-
const proto_index = @field(TYPE_LOOKUP, proto_name).index;
376-
js_global.inherit(templates[proto_index]);
377-
}
368+
// The global object (Window) has already been hooked into the v8
369+
// engine when the Env was initialized - like every other type.
370+
// But the V8 global is its own FunctionTemplate instance so even
371+
// though it's also a Window, we need to set the prototype for this
372+
// specific instance of the the Window.
373+
if (@hasDecl(Global, "prototype")) {
374+
const proto_type = Receiver(@typeInfo(Global.prototype).pointer.child);
375+
const proto_name = @typeName(proto_type);
376+
const proto_index = @field(TYPE_LOOKUP, proto_name).index;
377+
js_global.inherit(templates[proto_index]);
378+
}
378379

379-
const context = v8.Context.init(isolate, global_template, null);
380-
context.enter();
381-
errdefer context.exit();
380+
const context_local = v8.Context.init(isolate, global_template, null);
381+
const context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
382+
context.enter();
383+
errdefer if (enter) context.exit();
384+
defer if (!enter) context.exit();
385+
386+
// This shouldn't be necessary, but it is:
387+
// https://groups.google.com/g/v8-users/c/qAQQBmbi--8
388+
// TODO: see if newer V8 engines have a way around this.
389+
inline for (Types, 0..) |s, i| {
390+
const Struct = @field(types, s.name);
391+
392+
if (@hasDecl(Struct, "prototype")) {
393+
const proto_type = Receiver(@typeInfo(Struct.prototype).pointer.child);
394+
const proto_name = @typeName(proto_type);
395+
if (@hasField(TypeLookup, proto_name) == false) {
396+
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
397+
}
382398

383-
// This shouldn't be necessary, but it is:
384-
// https://groups.google.com/g/v8-users/c/qAQQBmbi--8
385-
// TODO: see if newer V8 engines have a way around this.
386-
inline for (Types, 0..) |s, i| {
387-
const Struct = @field(types, s.name);
399+
const proto_index = @field(TYPE_LOOKUP, proto_name).index;
400+
const proto_obj = templates[proto_index].getFunction(context).toObject();
388401

389-
if (@hasDecl(Struct, "prototype")) {
390-
const proto_type = Receiver(@typeInfo(Struct.prototype).pointer.child);
391-
const proto_name = @typeName(proto_type);
392-
if (@hasField(TypeLookup, proto_name) == false) {
393-
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
402+
const self_obj = templates[i].getFunction(context).toObject();
403+
_ = self_obj.setPrototype(context, proto_obj);
394404
}
395-
396-
const proto_index = @field(TYPE_LOOKUP, proto_name).index;
397-
const proto_obj = templates[proto_index].getFunction(context).toObject();
398-
399-
const self_obj = templates[i].getFunction(context).toObject();
400-
_ = self_obj.setPrototype(context, proto_obj);
401405
}
406+
break :blk context;
407+
};
408+
409+
// 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.
410+
// The main Context/Scope that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
411+
// 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
412+
var handle_scope: ?v8.HandleScope = null;
413+
if (enter) {
414+
handle_scope = @as(v8.HandleScope, undefined);
415+
v8.HandleScope.init(&handle_scope.?, isolate);
402416
}
417+
errdefer if (enter) handle_scope.?.deinit();
403418

404419
self.scope = Scope{
405420
.state = state,
@@ -453,8 +468,9 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
453468
pub const Scope = struct {
454469
state: State,
455470
isolate: v8.Isolate,
471+
// This context is a persistent object. The persistent needs to be recovered and reset.
456472
context: v8.Context,
457-
handle_scope: v8.HandleScope,
473+
handle_scope: ?v8.HandleScope,
458474

459475
// references the Env.template array
460476
templates: []v8.FunctionTemplate,
@@ -506,8 +522,12 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
506522
for (self.callbacks.items) |*cb| {
507523
cb.deinit();
508524
}
509-
self.context.exit();
510-
self.handle_scope.deinit();
525+
if (self.handle_scope) |*scope| {
526+
scope.deinit();
527+
self.context.exit();
528+
}
529+
var presistent_context = v8.Persistent(v8.Context).recoverCast(self.context);
530+
presistent_context.deinit();
511531
}
512532

513533
fn trackCallback(self: *Scope, pf: PersistentFunction) !void {

src/runtime/testing.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
4949
if (Global == void) &default_global else global,
5050
state,
5151
{},
52+
true,
5253
);
5354
return self;
5455
}

src/testing.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ pub const JsRunner = struct {
437437
self.executor = try self.env.newExecutor();
438438
errdefer self.executor.deinit();
439439

440-
self.scope = try self.executor.startScope(&self.window, &self.state, {});
440+
self.scope = try self.executor.startScope(&self.window, &self.state, {}, true);
441441
return self;
442442
}
443443

0 commit comments

Comments
 (0)