@@ -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 );
@@ -259,49 +287,70 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
259287// Unlike external modules which can only ever be executed after releasing an
260288// http handle, these are executed without there necessarily being a free handle.
261289// Thus, Http/Client.zig maintains a dedicated handle for these calls.
262- pub fn blockingGet (self : * ScriptManager , url : [:0 ]const u8 ) ! GetResult {
263- std .debug .assert (self .is_blocking == false );
264-
265- self .is_blocking = true ;
266- defer {
267- self .is_blocking = false ;
268-
269- // we blocked evaluation while loading this script, there could be
270- // scripts ready to process.
271- self .evaluate ();
290+ pub fn getModule (self : * ScriptManager , url : [:0 ]const u8 ) ! void {
291+ const gop = try self .sync_modules .getOrPut (self .allocator , url );
292+ if (gop .found_existing ) {
293+ // already requested
294+ return ;
272295 }
296+ errdefer _ = self .sync_modules .remove (url );
273297
274- var blocking = Blocking {
275- .allocator = self .allocator ,
276- .buffer_pool = & self .buffer_pool ,
277- };
298+ const sync = try self .sync_module_pool .create ();
299+ errdefer self .sync_module_pool .destroy (sync );
300+
301+ sync .* = .{ .manager = self };
302+ gop .value_ptr .* = sync ;
278303
279304 var headers = try self .client .newHeaders ();
280305 try self .page .requestCookie (.{}).headersForRequest (self .page .arena , url , & headers );
281306
282- var client = self .client ;
283- try client .blockingRequest (.{
307+ try self .client .request (.{
284308 .url = url ,
309+ .ctx = sync ,
285310 .method = .GET ,
286311 .headers = headers ,
287312 .cookie_jar = self .page .cookie_jar ,
288- .ctx = & blocking ,
289313 .resource_type = .script ,
290- .start_callback = if (log .enabled (.http , .debug )) Blocking .startCallback else null ,
291- .header_callback = Blocking .headerCallback ,
292- .data_callback = Blocking .dataCallback ,
293- .done_callback = Blocking .doneCallback ,
294- .error_callback = Blocking .errorCallback ,
314+ .start_callback = if (log .enabled (.http , .debug )) SyncModule .startCallback else null ,
315+ .header_callback = SyncModule .headerCallback ,
316+ .data_callback = SyncModule .dataCallback ,
317+ .done_callback = SyncModule .doneCallback ,
318+ .error_callback = SyncModule .errorCallback ,
295319 });
320+ }
321+
322+ pub fn waitForModule (self : * ScriptManager , url : [:0 ]const u8 ) ! GetResult {
323+ std .debug .assert (self .is_blocking == false );
324+ self .is_blocking = true ;
325+ defer self .is_blocking = false ;
326+
327+ // Normally it's dangerous to hold on to map pointers. But here, the map
328+ // can't change. It's possible that by calling `tick`, other entries within
329+ // the map will have their value change, but the map itself is immutable
330+ // during this tick.
331+ const entry = self .sync_modules .getEntry (url ) orelse {
332+ return error .UnknownModule ;
333+ };
334+ const sync = entry .value_ptr .* ;
296335
297- // rely on http's timeout settings to avoid an endless/long loop.
336+ var client = self . client ;
298337 while (true ) {
299- _ = try client .tick (200 );
300- switch (blocking .state ) {
301- .running = > {},
302- .done = > | result | return result ,
338+ switch (sync .state ) {
339+ .loading = > {},
340+ .done = > {
341+ // Our caller has its own higher level cache (caching the
342+ // actual compiled module). There's no reason for us to keep this
343+ defer self .sync_module_pool .destroy (sync );
344+ defer self .sync_modules .removeByPtr (entry .key_ptr );
345+ return .{
346+ .buffer = sync .buffer ,
347+ .buffer_pool = & self .buffer_pool ,
348+ };
349+ },
303350 .err = > | err | return err ,
304351 }
352+ // rely on http's timeout settings to avoid an endless/long loop.
353+ _ = try client .tick (200 );
305354 }
306355}
307356
@@ -332,7 +381,6 @@ pub fn getAsyncModule(self: *ScriptManager, url: [:0]const u8, cb: AsyncModule.C
332381 .error_callback = AsyncModule .errorCallback ,
333382 });
334383}
335-
336384pub fn staticScriptsDone (self : * ScriptManager ) void {
337385 std .debug .assert (self .static_scripts_done == false );
338386 self .static_scripts_done = true ;
@@ -782,16 +830,15 @@ const BufferPool = struct {
782830 }
783831};
784832
785- const Blocking = struct {
786- allocator : Allocator ,
787- buffer_pool : * BufferPool ,
788- state : State = .{ .running = {} },
833+ const SyncModule = struct {
834+ manager : * ScriptManager ,
789835 buffer : std .ArrayListUnmanaged (u8 ) = .{},
836+ state : State = .loading ,
790837
791838 const State = union (enum ) {
792- running : void ,
839+ done ,
840+ loading ,
793841 err : anyerror ,
794- done : GetResult ,
795842 };
796843
797844 fn startCallback (transfer : * Http.Transfer ) ! void {
@@ -807,12 +854,13 @@ const Blocking = struct {
807854 .content_type = header .contentType (),
808855 });
809856
857+ var self : * SyncModule = @ptrCast (@alignCast (transfer .ctx ));
810858 if (header .status != 200 ) {
859+ self .finished (.{ .err = error .InvalidStatusCode });
811860 return error .InvalidStatusCode ;
812861 }
813862
814- var self : * Blocking = @ptrCast (@alignCast (transfer .ctx ));
815- self .buffer = self .buffer_pool .get ();
863+ self .buffer = self .manager .buffer_pool .get ();
816864 }
817865
818866 fn dataCallback (transfer : * Http.Transfer , data : []const u8 ) ! void {
@@ -822,8 +870,8 @@ const Blocking = struct {
822870 // .blocking = true,
823871 // });
824872
825- var self : * Blocking = @ptrCast (@alignCast (transfer .ctx ));
826- self .buffer .appendSlice (self .allocator , data ) catch | err | {
873+ var self : * SyncModule = @ptrCast (@alignCast (transfer .ctx ));
874+ self .buffer .appendSlice (self .manager . allocator , data ) catch | err | {
827875 log .err (.http , "SM.dataCallback" , .{
828876 .err = err ,
829877 .len = data .len ,
@@ -835,19 +883,17 @@ const Blocking = struct {
835883 }
836884
837885 fn doneCallback (ctx : * anyopaque ) ! void {
838- var self : * Blocking = @ptrCast (@alignCast (ctx ));
839- self .state = .{ .done = .{
840- .buffer = self .buffer ,
841- .buffer_pool = self .buffer_pool ,
842- } };
886+ var self : * SyncModule = @ptrCast (@alignCast (ctx ));
887+ self .finished (.done );
843888 }
844889
845890 fn errorCallback (ctx : * anyopaque , err : anyerror ) void {
846- var self : * Blocking = @ptrCast (@alignCast (ctx ));
847- self .state = .{ .err = err };
848- if (self .buffer .items .len > 0 ) {
849- self .buffer_pool .release (self .buffer );
850- }
891+ var self : * SyncModule = @ptrCast (@alignCast (ctx ));
892+ self .finished (.{ .err = err });
893+ }
894+
895+ fn finished (self : * SyncModule , state : State ) void {
896+ self .state = state ;
851897 }
852898};
853899
0 commit comments