@@ -35,7 +35,7 @@ const ScriptManager = @This();
3535
3636page : * Page ,
3737
38- // used to prevent recursive evalution
38+ // used to prevent recursive evalutaion
3939is_evaluating : bool ,
4040
4141// used to prevent executing scripts while we're doing a blocking load
@@ -45,10 +45,16 @@ is_blocking: bool = false,
4545static_scripts_done : bool ,
4646
4747// List of async scripts. We don't care about the execution order of these, but
48- // on shutdown/abort, we need to co cleanup any pending ones.
48+ // on shutdown/abort, we need to cleanup any pending ones.
4949asyncs : OrderList ,
5050
51- // Normal scripts (non-deffered & non-async). These must be executed ni order
51+ // When an async script is ready to be evaluated, it's moved from asyncs to
52+ // this list. You might think we can evaluate an async script as soon as it's
53+ // done, but we can only evaluate scripts when `is_blocking == false`. So this
54+ // becomes a list of scripts to execute on the next evaluate().
55+ asyncs_ready : OrderList ,
56+
57+ // Normal scripts (non-deferred & non-async). These must be executed in order
5258scripts : OrderList ,
5359
5460// List of deferred scripts. These must be executed in order, but only once
@@ -72,6 +78,7 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager {
7278 .asyncs = .{},
7379 .scripts = .{},
7480 .deferreds = .{},
81+ .asyncs_ready = .{},
7582 .is_evaluating = false ,
7683 .allocator = allocator ,
7784 .client = browser .http_client ,
@@ -91,6 +98,7 @@ pub fn reset(self: *ScriptManager) void {
9198 self .clearList (& self .asyncs );
9299 self .clearList (& self .scripts );
93100 self .clearList (& self .deferreds );
101+ self .clearList (& self .asyncs_ready );
94102 self .static_scripts_done = false ;
95103}
96104
@@ -114,7 +122,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
114122 // document.getElementsByTagName('head')[0].appendChild(script)
115123 // that script tag will immediately get executed by our scriptAddedCallback.
116124 // However, if the location where the script tag is inserted happens to be
117- // below where processHTMLDoc curently is, then we'll re-run that same script
125+ // below where processHTMLDoc currently is, then we'll re-run that same script
118126 // again in processHTMLDoc. This flag is used to let us know if a specific
119127 // <script> has already been processed.
120128 if (try parser .scriptGetProcessed (@ptrCast (element ))) {
@@ -169,7 +177,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
169177 if (source == .@"inline" and self .scripts .first == null ) {
170178 // inline script with no pending scripts, execute it immediately.
171179 // (if there is a pending script, then we cannot execute this immediately
172- // as it needs to be executed in order)
180+ // as it needs to best executed in order)
173181 return script .eval (page );
174182 }
175183
@@ -193,7 +201,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
193201 log .debug (.http , "script queue" , .{ .url = remote_url .? });
194202 }
195203
196- self .getList (& pending_script . script ).append (& pending_script .node );
204+ pending_script .getList ().append (& pending_script .node );
197205
198206 errdefer pending_script .deinit ();
199207
@@ -313,14 +321,21 @@ fn evaluate(self: *ScriptManager) void {
313321 if (self .is_blocking ) {
314322 // Cannot evaluate scripts while a blocking-load is in progress. Not
315323 // only could that result in incorrect evaluation order, it could
316- // triger another blocking request, while we're doing a blocking request.
324+ // trigger another blocking request, while we're doing a blocking request.
317325 return ;
318326 }
319327
320328 const page = self .page ;
321329 self .is_evaluating = true ;
322330 defer self .is_evaluating = false ;
323331
332+ // every script in asyncs_ready is ready to be evaluated.
333+ while (self .asyncs_ready .first ) | n | {
334+ var pending_script : * PendingScript = @fieldParentPtr ("node" , n );
335+ defer pending_script .deinit ();
336+ pending_script .script .eval (page );
337+ }
338+
324339 while (self .scripts .first ) | n | {
325340 var pending_script : * PendingScript = @fieldParentPtr ("node" , n );
326341 if (pending_script .complete == false ) {
@@ -355,7 +370,6 @@ fn evaluate(self: *ScriptManager) void {
355370 page .documentIsLoaded ();
356371
357372 if (self .asyncs .first == null ) {
358- // if we're here, then its like `asyncDone`
359373 // 1 - there are no async scripts pending
360374 // 2 - we checkecked static_scripts_done == true above
361375 // 3 - we drained self.scripts above
@@ -371,28 +385,6 @@ pub fn isDone(self: *const ScriptManager) bool {
371385 self .deferreds .first == null ; // and there are no more <script defer src=> to wait for
372386}
373387
374- fn asyncDone (self : * ScriptManager ) void {
375- if (self .isDone ()) {
376- // then the document is considered complete
377- self .page .documentIsComplete ();
378- }
379- }
380-
381- fn getList (self : * ScriptManager , script : * const Script ) * OrderList {
382- // When a script has both the async and defer flag set, it should be
383- // treated as async. Async is newer, so some websites use both so that
384- // if async isn't known, it'll fallback to defer.
385- if (script .is_async ) {
386- return & self .asyncs ;
387- }
388-
389- if (script .is_defer ) {
390- return & self .deferreds ;
391- }
392-
393- return & self .scripts ;
394- }
395-
396388fn startCallback (transfer : * Http.Transfer ) ! void {
397389 const script : * PendingScript = @ptrCast (@alignCast (transfer .ctx ));
398390 script .startCallback (transfer ) catch | err | {
@@ -441,12 +433,12 @@ pub const PendingScript = struct {
441433 if (script .source == .remote ) {
442434 manager .buffer_pool .release (script .source .remote );
443435 }
444- manager .getList (script ).remove (& self .node );
436+ self .getList ().remove (& self .node );
445437 }
446438
447439 fn remove (self : * PendingScript ) void {
448440 if (self .node ) | * node | {
449- self .manager . getList (& self . script ).remove (node );
441+ self .getList ().remove (node );
450442 self .node = null ;
451443 }
452444 }
@@ -500,15 +492,12 @@ pub const PendingScript = struct {
500492 log .debug (.http , "script fetch complete" , .{ .req = self .script .url });
501493
502494 const manager = self .manager ;
495+ self .complete = true ;
503496 if (self .script .is_async ) {
504- // async script can be evaluated immediately
505- self .script .eval (self .manager .page );
506- self .deinit ();
507- manager .asyncDone ();
508- } else {
509- self .complete = true ;
510- manager .evaluate ();
497+ manager .asyncs .remove (& self .node );
498+ manager .asyncs_ready .append (& self .node );
511499 }
500+ manager .evaluate ();
512501 }
513502
514503 fn errorCallback (self : * PendingScript , err : anyerror ) void {
@@ -524,6 +513,23 @@ pub const PendingScript = struct {
524513
525514 manager .evaluate ();
526515 }
516+
517+ fn getList (self : * const PendingScript ) * OrderList {
518+ // When a script has both the async and defer flag set, it should be
519+ // treated as async. Async is newer, so some websites use both so that
520+ // if async isn't known, it'll fallback to defer.
521+
522+ const script = & self .script ;
523+ if (script .is_async ) {
524+ return if (self .complete ) & self .manager .asyncs_ready else & self .manager .asyncs ;
525+ }
526+
527+ if (script .is_defer ) {
528+ return & self .manager .deferreds ;
529+ }
530+
531+ return & self .manager .scripts ;
532+ }
527533};
528534
529535const Script = struct {
0 commit comments