@@ -67,8 +67,18 @@ client: *Http.Client,
6767allocator : Allocator ,
6868buffer_pool : BufferPool ,
6969script_pool : std .heap .MemoryPool (PendingScript ),
70+ sync_module_pool : std .heap .MemoryPool (SyncModule ),
7071async_module_pool : std .heap .MemoryPool (AsyncModule ),
7172
73+ // We can download multiple sync modules in parallel, but we want to process
74+ // then in order. We can't use an OrderList, like the other script types,
75+ // because the order we load them might not be the order we want to process
76+ // them in (I'm not sure this is true, but as far as I can tell, v8 doesn't
77+ // make any guarantees about the list of sub-module dependencies it gives us
78+ // So this is more like a cache. When a SyncModule is complete, it's put here
79+ // and can be requested as needed.
80+ sync_modules : std .StringHashMapUnmanaged (* SyncModule ),
81+
7282const OrderList = std .DoublyLinkedList ;
7383
7484pub fn init (browser : * Browser , page : * Page ) ScriptManager {
@@ -80,24 +90,42 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager {
8090 .scripts = .{},
8191 .deferreds = .{},
8292 .asyncs_ready = .{},
93+ .sync_modules = .empty ,
8394 .is_evaluating = false ,
8495 .allocator = allocator ,
8596 .client = browser .http_client ,
8697 .static_scripts_done = false ,
8798 .buffer_pool = BufferPool .init (allocator , 5 ),
8899 .script_pool = std .heap .MemoryPool (PendingScript ).init (allocator ),
100+ .sync_module_pool = std .heap .MemoryPool (SyncModule ).init (allocator ),
89101 .async_module_pool = std .heap .MemoryPool (AsyncModule ).init (allocator ),
90102 };
91103}
92104
93105pub fn deinit (self : * ScriptManager ) void {
94106 self .reset ();
107+ var it = self .sync_modules .valueIterator ();
108+ while (it .next ()) | value_ptr | {
109+ value_ptr .* .buffer .deinit (self .allocator );
110+ self .sync_module_pool .destroy (value_ptr .* );
111+ }
112+
95113 self .buffer_pool .deinit ();
96114 self .script_pool .deinit ();
115+ self .sync_module_pool .deinit ();
97116 self .async_module_pool .deinit ();
117+
118+ self .sync_modules .deinit (self .allocator );
98119}
99120
100121pub fn reset (self : * ScriptManager ) void {
122+ var it = self .sync_modules .valueIterator ();
123+ while (it .next ()) | value_ptr | {
124+ value_ptr .* .buffer .deinit (self .allocator );
125+ self .sync_module_pool .destroy (value_ptr .* );
126+ }
127+ self .sync_modules .clearRetainingCapacity ();
128+
101129 self .clearList (& self .asyncs );
102130 self .clearList (& self .scripts );
103131 self .clearList (& self .deferreds );
@@ -260,49 +288,70 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
260288// Unlike external modules which can only ever be executed after releasing an
261289// http handle, these are executed without there necessarily being a free handle.
262290// Thus, Http/Client.zig maintains a dedicated handle for these calls.
263- pub fn blockingGet (self : * ScriptManager , url : [:0 ]const u8 ) ! GetResult {
264- std .debug .assert (self .is_blocking == false );
265-
266- self .is_blocking = true ;
267- defer {
268- self .is_blocking = false ;
269-
270- // we blocked evaluation while loading this script, there could be
271- // scripts ready to process.
272- self .evaluate ();
291+ pub fn getModule (self : * ScriptManager , url : [:0 ]const u8 ) ! void {
292+ const gop = try self .sync_modules .getOrPut (self .allocator , url );
293+ if (gop .found_existing ) {
294+ // already requested
295+ return ;
273296 }
297+ errdefer _ = self .sync_modules .remove (url );
274298
275- var blocking = Blocking {
276- .allocator = self .allocator ,
277- .buffer_pool = & self .buffer_pool ,
278- };
299+ const sync = try self .sync_module_pool .create ();
300+ errdefer self .sync_module_pool .destroy (sync );
301+
302+ sync .* = .{ .manager = self };
303+ gop .value_ptr .* = sync ;
279304
280305 var headers = try self .client .newHeaders ();
281306 try self .page .requestCookie (.{}).headersForRequest (self .page .arena , url , & headers );
282307
283- var client = self .client ;
284- try client .blockingRequest (.{
308+ try self .client .request (.{
285309 .url = url ,
310+ .ctx = sync ,
286311 .method = .GET ,
287312 .headers = headers ,
288313 .cookie_jar = self .page .cookie_jar ,
289- .ctx = & blocking ,
290314 .resource_type = .script ,
291- .start_callback = if (log .enabled (.http , .debug )) Blocking .startCallback else null ,
292- .header_callback = Blocking .headerCallback ,
293- .data_callback = Blocking .dataCallback ,
294- .done_callback = Blocking .doneCallback ,
295- .error_callback = Blocking .errorCallback ,
315+ .start_callback = if (log .enabled (.http , .debug )) SyncModule .startCallback else null ,
316+ .header_callback = SyncModule .headerCallback ,
317+ .data_callback = SyncModule .dataCallback ,
318+ .done_callback = SyncModule .doneCallback ,
319+ .error_callback = SyncModule .errorCallback ,
296320 });
321+ }
322+
323+ pub fn waitForModule (self : * ScriptManager , url : [:0 ]const u8 ) ! GetResult {
324+ std .debug .assert (self .is_blocking == false );
325+ self .is_blocking = true ;
326+ defer self .is_blocking = false ;
327+
328+ // Normally it's dangerous to hold on to map pointers. But here, the map
329+ // can't change. It's possible that by calling `tick`, other entries within
330+ // the map will have their value change, but the map itself is immutable
331+ // during this tick.
332+ const entry = self .sync_modules .getEntry (url ) orelse {
333+ return error .UnknownModule ;
334+ };
335+ const sync = entry .value_ptr .* ;
297336
298- // rely on http's timeout settings to avoid an endless/long loop.
337+ var client = self . client ;
299338 while (true ) {
300- _ = try client .tick (200 );
301- switch (blocking .state ) {
302- .running = > {},
303- .done = > | result | return result ,
339+ switch (sync .state ) {
340+ .loading = > {},
341+ .done = > {
342+ // Our caller has its own higher level cache (caching the
343+ // actual compiled module). There's no reason for us to keep this
344+ defer self .sync_module_pool .destroy (sync );
345+ defer self .sync_modules .removeByPtr (entry .key_ptr );
346+ return .{
347+ .buffer = sync .buffer ,
348+ .buffer_pool = & self .buffer_pool ,
349+ };
350+ },
304351 .err = > | err | return err ,
305352 }
353+ // rely on http's timeout settings to avoid an endless/long loop.
354+ _ = try client .tick (200 );
306355 }
307356}
308357
@@ -333,7 +382,6 @@ pub fn getAsyncModule(self: *ScriptManager, url: [:0]const u8, cb: AsyncModule.C
333382 .error_callback = AsyncModule .errorCallback ,
334383 });
335384}
336-
337385pub fn staticScriptsDone (self : * ScriptManager ) void {
338386 std .debug .assert (self .static_scripts_done == false );
339387 self .static_scripts_done = true ;
@@ -783,16 +831,15 @@ const BufferPool = struct {
783831 }
784832};
785833
786- const Blocking = struct {
787- allocator : Allocator ,
788- buffer_pool : * BufferPool ,
789- state : State = .{ .running = {} },
834+ const SyncModule = struct {
835+ manager : * ScriptManager ,
790836 buffer : std .ArrayListUnmanaged (u8 ) = .{},
837+ state : State = .loading ,
791838
792839 const State = union (enum ) {
793- running : void ,
840+ done ,
841+ loading ,
794842 err : anyerror ,
795- done : GetResult ,
796843 };
797844
798845 fn startCallback (transfer : * Http.Transfer ) ! void {
@@ -808,12 +855,13 @@ const Blocking = struct {
808855 .content_type = header .contentType (),
809856 });
810857
858+ var self : * SyncModule = @ptrCast (@alignCast (transfer .ctx ));
811859 if (header .status != 200 ) {
860+ self .finished (.{ .err = error .InvalidStatusCode });
812861 return error .InvalidStatusCode ;
813862 }
814863
815- var self : * Blocking = @ptrCast (@alignCast (transfer .ctx ));
816- self .buffer = self .buffer_pool .get ();
864+ self .buffer = self .manager .buffer_pool .get ();
817865 }
818866
819867 fn dataCallback (transfer : * Http.Transfer , data : []const u8 ) ! void {
@@ -823,8 +871,8 @@ const Blocking = struct {
823871 // .blocking = true,
824872 // });
825873
826- var self : * Blocking = @ptrCast (@alignCast (transfer .ctx ));
827- self .buffer .appendSlice (self .allocator , data ) catch | err | {
874+ var self : * SyncModule = @ptrCast (@alignCast (transfer .ctx ));
875+ self .buffer .appendSlice (self .manager . allocator , data ) catch | err | {
828876 log .err (.http , "SM.dataCallback" , .{
829877 .err = err ,
830878 .len = data .len ,
@@ -836,19 +884,17 @@ const Blocking = struct {
836884 }
837885
838886 fn doneCallback (ctx : * anyopaque ) ! void {
839- var self : * Blocking = @ptrCast (@alignCast (ctx ));
840- self .state = .{ .done = .{
841- .buffer = self .buffer ,
842- .buffer_pool = self .buffer_pool ,
843- } };
887+ var self : * SyncModule = @ptrCast (@alignCast (ctx ));
888+ self .finished (.done );
844889 }
845890
846891 fn errorCallback (ctx : * anyopaque , err : anyerror ) void {
847- var self : * Blocking = @ptrCast (@alignCast (ctx ));
848- self .state = .{ .err = err };
849- if (self .buffer .items .len > 0 ) {
850- self .buffer_pool .release (self .buffer );
851- }
892+ var self : * SyncModule = @ptrCast (@alignCast (ctx ));
893+ self .finished (.{ .err = err });
894+ }
895+
896+ fn finished (self : * SyncModule , state : State ) void {
897+ self .state = state ;
852898 }
853899};
854900
0 commit comments