mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-29 15:13:28 +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, | ||||
|         }); | ||||
|  | ||||
|         const result = switch (self.kind) { | ||||
|             .javascript => page.main_context.eval(body, src), | ||||
|             .module => page.main_context.module(body, src, cacheable), | ||||
|         const failed = blk: { | ||||
|             switch (self.kind) { | ||||
|                 .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) { | ||||
|                 return error.Terminated; | ||||
|             } | ||||
| @@ -1038,7 +1042,8 @@ const Script = struct { | ||||
|  | ||||
|             try self.executeCallback("onerror", page); | ||||
|             return error.JsErr; | ||||
|         }; | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|             errdefer isolate.deinit(); | ||||
|  | ||||
|             // This is the callback that runs whenever a module is dynamically imported. | ||||
|             isolate.setHostImportModuleDynamicallyCallback(JsContext.dynamicModuleCallback); | ||||
|  | ||||
|             isolate.enter(); | ||||
|             errdefer isolate.exit(); | ||||
|  | ||||
| @@ -759,17 +762,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | ||||
|             } | ||||
|  | ||||
|             // compile and eval a JS module | ||||
|             // It doesn't wait for callbacks execution | ||||
|             pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !void { | ||||
|                 if (!cacheable) { | ||||
|                     return self.moduleNoCache(src, url); | ||||
|                 } | ||||
|  | ||||
|             // It returns null if the module is already compiled and in the cache. | ||||
|             // It returns a v8.Promise if the module must be evaluated. | ||||
|             pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !?v8.Promise { | ||||
|                 const arena = self.context_arena; | ||||
|  | ||||
|                 const gop = try self.module_cache.getOrPut(arena, url); | ||||
|                 if (gop.found_existing) { | ||||
|                     return; | ||||
|                 if (cacheable and self.module_cache.contains(url)) { | ||||
|                     return null; | ||||
|                 } | ||||
|                 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); | ||||
|                 errdefer _ = self.module_identifier.remove(m.getIdentityHash()); | ||||
|  | ||||
|                 gop.key_ptr.* = owned_url; | ||||
|                 gop.value_ptr.* = PersistentModule.init(self.isolate, m); | ||||
|                 if (cacheable) { | ||||
|                     try self.module_cache.putNoClobber( | ||||
|                         arena, | ||||
|                         owned_url, | ||||
|                         PersistentModule.init(self.isolate, m), | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
|                 // resolveModuleCallback loads module's dependencies. | ||||
|                 const v8_context = self.v8_context; | ||||
| @@ -788,21 +792,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | ||||
|                     return error.ModuleInstantiationError; | ||||
|                 } | ||||
|  | ||||
|                 _ = try m.evaluate(v8_context); | ||||
|             } | ||||
|  | ||||
|             fn moduleNoCache(self: *JsContext, src: []const u8, url: []const u8) !void { | ||||
|                 const m = try compileModule(self.isolate, src, url); | ||||
|  | ||||
|                 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); | ||||
|                 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. | ||||
|                 std.debug.assert(evaluated.isPromise()); | ||||
|                 const promise = v8.Promise{ .handle = evaluated.handle }; | ||||
|                 return promise; | ||||
|             } | ||||
|  | ||||
|             // Wrap a v8.Exception | ||||
| @@ -1514,6 +1509,160 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { | ||||
|                     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 { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 muki
					muki