mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Merge pull request #872 from lightpanda-io/dynamic-import
This commit is contained in:
@@ -1018,12 +1018,16 @@ const Script = struct {
|
|||||||
.cacheable = cacheable,
|
.cacheable = cacheable,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = switch (self.kind) {
|
const failed = blk: {
|
||||||
.javascript => page.main_context.eval(body, src),
|
switch (self.kind) {
|
||||||
.module => page.main_context.module(body, src, cacheable),
|
.javascript => _ = page.main_context.eval(body, src) catch break :blk true,
|
||||||
|
// We don't care about waiting for the evaluation here.
|
||||||
|
.module => _ = page.main_context.module(body, src, cacheable) catch break :blk true,
|
||||||
|
}
|
||||||
|
break :blk false;
|
||||||
};
|
};
|
||||||
|
|
||||||
result catch {
|
if (failed) {
|
||||||
if (page.delayed_navigation) {
|
if (page.delayed_navigation) {
|
||||||
return error.Terminated;
|
return error.Terminated;
|
||||||
}
|
}
|
||||||
@@ -1038,7 +1042,8 @@ const Script = struct {
|
|||||||
|
|
||||||
try self.executeCallback("onerror", page);
|
try self.executeCallback("onerror", page);
|
||||||
return error.JsErr;
|
return error.JsErr;
|
||||||
};
|
}
|
||||||
|
|
||||||
try self.executeCallback("onload", page);
|
try self.executeCallback("onload", page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
var isolate = v8.Isolate.init(params);
|
var isolate = v8.Isolate.init(params);
|
||||||
errdefer isolate.deinit();
|
errdefer isolate.deinit();
|
||||||
|
|
||||||
|
// This is the callback that runs whenever a module is dynamically imported.
|
||||||
|
isolate.setHostImportModuleDynamicallyCallback(JsContext.dynamicModuleCallback);
|
||||||
|
|
||||||
isolate.enter();
|
isolate.enter();
|
||||||
errdefer isolate.exit();
|
errdefer isolate.exit();
|
||||||
|
|
||||||
@@ -759,17 +762,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compile and eval a JS module
|
// compile and eval a JS module
|
||||||
// It doesn't wait for callbacks execution
|
// It returns null if the module is already compiled and in the cache.
|
||||||
pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !void {
|
// It returns a v8.Promise if the module must be evaluated.
|
||||||
if (!cacheable) {
|
pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !?v8.Promise {
|
||||||
return self.moduleNoCache(src, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const arena = self.context_arena;
|
const arena = self.context_arena;
|
||||||
|
|
||||||
const gop = try self.module_cache.getOrPut(arena, url);
|
if (cacheable and self.module_cache.contains(url)) {
|
||||||
if (gop.found_existing) {
|
return null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
errdefer _ = self.module_cache.remove(url);
|
errdefer _ = self.module_cache.remove(url);
|
||||||
|
|
||||||
@@ -779,8 +778,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
|
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
|
||||||
errdefer _ = self.module_identifier.remove(m.getIdentityHash());
|
errdefer _ = self.module_identifier.remove(m.getIdentityHash());
|
||||||
|
|
||||||
gop.key_ptr.* = owned_url;
|
if (cacheable) {
|
||||||
gop.value_ptr.* = PersistentModule.init(self.isolate, m);
|
try self.module_cache.putNoClobber(
|
||||||
|
arena,
|
||||||
|
owned_url,
|
||||||
|
PersistentModule.init(self.isolate, m),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// resolveModuleCallback loads module's dependencies.
|
// resolveModuleCallback loads module's dependencies.
|
||||||
const v8_context = self.v8_context;
|
const v8_context = self.v8_context;
|
||||||
@@ -788,21 +792,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
return error.ModuleInstantiationError;
|
return error.ModuleInstantiationError;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = try m.evaluate(v8_context);
|
const evaluated = try m.evaluate(v8_context);
|
||||||
}
|
// https://v8.github.io/api/head/classv8_1_1Module.html#a1f1758265a4082595757c3251bb40e0f
|
||||||
|
// Must be a promise that gets returned here.
|
||||||
fn moduleNoCache(self: *JsContext, src: []const u8, url: []const u8) !void {
|
std.debug.assert(evaluated.isPromise());
|
||||||
const m = try compileModule(self.isolate, src, url);
|
const promise = v8.Promise{ .handle = evaluated.handle };
|
||||||
|
return promise;
|
||||||
const arena = self.context_arena;
|
|
||||||
const owned_url = try arena.dupe(u8, url);
|
|
||||||
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
|
|
||||||
|
|
||||||
const v8_context = self.v8_context;
|
|
||||||
if (try m.instantiate(v8_context, resolveModuleCallback) == false) {
|
|
||||||
return error.ModuleInstantiationError;
|
|
||||||
}
|
|
||||||
_ = try m.evaluate(v8_context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap a v8.Exception
|
// Wrap a v8.Exception
|
||||||
@@ -1514,6 +1509,160 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
|||||||
type_index = prototype_index;
|
type_index = prototype_index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dynamicModuleCallback(
|
||||||
|
v8_ctx: ?*const v8.c.Context,
|
||||||
|
host_defined_options: ?*const v8.c.Data,
|
||||||
|
resource_name: ?*const v8.c.Value,
|
||||||
|
v8_specifier: ?*const v8.c.String,
|
||||||
|
import_attrs: ?*const v8.c.FixedArray,
|
||||||
|
) callconv(.c) ?*v8.c.Promise {
|
||||||
|
_ = host_defined_options;
|
||||||
|
_ = import_attrs;
|
||||||
|
const ctx: v8.Context = .{ .handle = v8_ctx.? };
|
||||||
|
const context: *JsContext = @ptrFromInt(ctx.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||||
|
const iso = context.isolate;
|
||||||
|
const resolver = v8.PromiseResolver.init(context.v8_context);
|
||||||
|
|
||||||
|
const specifier: v8.String = .{ .handle = v8_specifier.? };
|
||||||
|
const specifier_str = jsStringToZig(context.context_arena, specifier, iso) catch {
|
||||||
|
const error_msg = v8.String.initUtf8(iso, "Failed to parse module specifier");
|
||||||
|
_ = resolver.reject(ctx, error_msg.toValue());
|
||||||
|
return @constCast(resolver.getPromise().handle);
|
||||||
|
};
|
||||||
|
const resource: v8.String = .{ .handle = resource_name.? };
|
||||||
|
const resource_str = jsStringToZig(context.context_arena, resource, iso) catch {
|
||||||
|
const error_msg = v8.String.initUtf8(iso, "Failed to parse module resource");
|
||||||
|
_ = resolver.reject(ctx, error_msg.toValue());
|
||||||
|
return @constCast(resolver.getPromise().handle);
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalized_specifier = @import("../url.zig").stitch(
|
||||||
|
context.context_arena,
|
||||||
|
specifier_str,
|
||||||
|
resource_str,
|
||||||
|
.{ .alloc = .if_needed },
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
log.debug(.js, "dynamic import", .{
|
||||||
|
.specifier = specifier_str,
|
||||||
|
.resource = resource_str,
|
||||||
|
.normalized_specifier = normalized_specifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
_dynamicModuleCallback(context, normalized_specifier, &resolver) catch |err| {
|
||||||
|
log.err(.js, "dynamic module callback", .{
|
||||||
|
.err = err,
|
||||||
|
});
|
||||||
|
// Must be rejected at this point
|
||||||
|
// otherwise, we will just wait on a pending promise.
|
||||||
|
std.debug.assert(resolver.getPromise().getState() == .kRejected);
|
||||||
|
};
|
||||||
|
return @constCast(resolver.getPromise().handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _dynamicModuleCallback(
|
||||||
|
self: *JsContext,
|
||||||
|
specifier: []const u8,
|
||||||
|
resolver: *const v8.PromiseResolver,
|
||||||
|
) !void {
|
||||||
|
const iso = self.isolate;
|
||||||
|
const ctx = self.v8_context;
|
||||||
|
|
||||||
|
const module_loader = self.module_loader;
|
||||||
|
const source = module_loader.func(module_loader.ptr, specifier) catch {
|
||||||
|
const error_msg = v8.String.initUtf8(iso, "Failed to load module");
|
||||||
|
_ = resolver.reject(ctx, error_msg.toValue());
|
||||||
|
return;
|
||||||
|
} orelse {
|
||||||
|
const error_msg = v8.String.initUtf8(iso, "Module source not available");
|
||||||
|
_ = resolver.reject(ctx, error_msg.toValue());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var try_catch: TryCatch = undefined;
|
||||||
|
try_catch.init(self);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
const maybe_promise = self.module(source, specifier, true) catch {
|
||||||
|
log.err(.js, "module compilation failed", .{
|
||||||
|
.specifier = specifier,
|
||||||
|
.exception = try_catch.exception(self.call_arena) catch "unknown error",
|
||||||
|
.stack = try_catch.stack(self.call_arena) catch null,
|
||||||
|
.line = try_catch.sourceLineNumber() orelse 0,
|
||||||
|
});
|
||||||
|
const error_msg = if (try_catch.hasCaught()) blk: {
|
||||||
|
const exception_str = try_catch.exception(self.call_arena) catch "Evaluation error";
|
||||||
|
break :blk v8.String.initUtf8(iso, exception_str orelse "Evaluation error");
|
||||||
|
} else v8.String.initUtf8(iso, "Module evaluation failed");
|
||||||
|
_ = resolver.reject(ctx, error_msg.toValue());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const new_module = self.module_cache.get(specifier).?.castToModule();
|
||||||
|
|
||||||
|
if (maybe_promise) |promise| {
|
||||||
|
// This means we must wait for the evaluation.
|
||||||
|
const EvaluationData = struct {
|
||||||
|
specifier: []const u8,
|
||||||
|
module: v8.Persistent(v8.Module),
|
||||||
|
resolver: v8.Persistent(v8.PromiseResolver),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ev_data = try self.context_arena.create(EvaluationData);
|
||||||
|
ev_data.* = .{
|
||||||
|
.specifier = specifier,
|
||||||
|
.module = v8.Persistent(v8.Module).init(iso, new_module),
|
||||||
|
.resolver = v8.Persistent(v8.PromiseResolver).init(iso, resolver.*),
|
||||||
|
};
|
||||||
|
const external = v8.External.init(iso, @ptrCast(ev_data));
|
||||||
|
|
||||||
|
const then_callback = v8.Function.initWithData(ctx, struct {
|
||||||
|
pub fn callback(info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const cb_info = v8.FunctionCallbackInfo{ .handle = info.? };
|
||||||
|
const cb_isolate = cb_info.getIsolate();
|
||||||
|
const cb_context = cb_isolate.getCurrentContext();
|
||||||
|
const data: *EvaluationData = @ptrCast(@alignCast(cb_info.getExternalValue()));
|
||||||
|
const cb_module = data.module.castToModule();
|
||||||
|
const cb_resolver = data.resolver.castToPromiseResolver();
|
||||||
|
|
||||||
|
const namespace = cb_module.getModuleNamespace();
|
||||||
|
log.info(.js, "dynamic import complete", .{ .specifier = data.specifier });
|
||||||
|
_ = cb_resolver.resolve(cb_context, namespace);
|
||||||
|
}
|
||||||
|
}.callback, external);
|
||||||
|
|
||||||
|
const catch_callback = v8.Function.initWithData(ctx, struct {
|
||||||
|
pub fn callback(info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const cb_info = v8.FunctionCallbackInfo{ .handle = info.? };
|
||||||
|
const cb_context = cb_info.getIsolate().getCurrentContext();
|
||||||
|
const data: *EvaluationData = @ptrCast(@alignCast(cb_info.getExternalValue()));
|
||||||
|
const cb_resolver = data.resolver.castToPromiseResolver();
|
||||||
|
|
||||||
|
log.err(.js, "dynamic import failed", .{ .specifier = data.specifier });
|
||||||
|
_ = cb_resolver.reject(cb_context, cb_info.getData());
|
||||||
|
}
|
||||||
|
}.callback, external);
|
||||||
|
|
||||||
|
_ = promise.thenAndCatch(ctx, then_callback, catch_callback) catch {
|
||||||
|
log.err(.js, "module evaluation is promise", .{
|
||||||
|
.specifier = specifier,
|
||||||
|
.line = try_catch.sourceLineNumber() orelse 0,
|
||||||
|
});
|
||||||
|
const error_msg = v8.String.initUtf8(iso, "Evaluation is a promise");
|
||||||
|
_ = resolver.reject(ctx, error_msg.toValue());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// This means it is already present in the cache.
|
||||||
|
const namespace = new_module.getModuleNamespace();
|
||||||
|
log.info(.js, "dynamic import complete", .{
|
||||||
|
.module = new_module,
|
||||||
|
.namespace = namespace,
|
||||||
|
});
|
||||||
|
_ = resolver.resolve(ctx, namespace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Function = struct {
|
pub const Function = struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user