1717// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
1919const std = @import ("std" );
20+ const builtin = @import ("builtin" );
2021
2122const js = @import ("js/js.zig" );
2223const log = @import ("../log.zig" );
@@ -31,11 +32,13 @@ const Element = @import("webapi/Element.zig");
3132const Allocator = std .mem .Allocator ;
3233const ArrayListUnmanaged = std .ArrayListUnmanaged ;
3334
35+ const IS_DEBUG = builtin .mode == .Debug ;
36+
3437const ScriptManager = @This ();
3538
3639page : * Page ,
3740
38- // used to prevent recursive evalutaion
41+ // used to prevent recursive evaluation
3942is_evaluating : bool ,
4043
4144// Only once this is true can deferred scripts be run
@@ -45,9 +48,6 @@ static_scripts_done: bool,
4548// on shutdown/abort, we need to cleanup any pending ones.
4649asyncs : OrderList ,
4750
48- // Normal scripts (non-deferred & non-async). These must be executed in order
49- scripts : OrderList ,
50-
5151// List of deferred scripts. These must be executed in order, but only once
5252// dom_loaded == true,
5353deferreds : OrderList ,
@@ -85,7 +85,6 @@ pub fn init(page: *Page) ScriptManager {
8585 return .{
8686 .page = page ,
8787 .asyncs = .{},
88- .scripts = .{},
8988 .deferreds = .{},
9089 .importmap = .empty ,
9190 .sync_modules = .empty ,
@@ -130,7 +129,6 @@ pub fn reset(self: *ScriptManager) void {
130129 self .importmap = .empty ;
131130
132131 self .clearList (& self .asyncs );
133- self .clearList (& self .scripts );
134132 self .clearList (& self .deferreds );
135133 self .static_scripts_done = false ;
136134}
@@ -212,57 +210,78 @@ pub fn add(self: *ScriptManager, script_element: *Element.Html.Script, comptime
212210 .is_async = if (remote_url == null ) false else element .getAttributeSafe ("async" ) != null ,
213211 };
214212
215- if (source == .@"inline" and self .scripts .first == null ) {
216- // inline script with no pending scripts, execute it immediately.
217- // (if there is a pending script, then we cannot execute this immediately
218- // as it needs to be executed in order)
213+ if (source == .@"inline" ) {
214+ // inline script gets executed immediately
219215 return script .eval (page );
220216 }
221217
222- const pending_script = try self .script_pool .create ();
223- errdefer self .script_pool .destroy (pending_script );
224- pending_script .* = .{
225- .script = script ,
226- .complete = false ,
227- .manager = self ,
228- .node = .{},
229- };
218+ const pending_script = blk : {
219+ // Done in a block this way so that, if something fails in this block
220+ // it's cleaned up with these errdefers
221+ // BUT, if we need to load/execute the script immediately, cleanup/lifetimes
222+ // become the responsibility of the outer block.
223+ const pending_script = try self .script_pool .create ();
224+ errdefer self .script_pool .destroy (pending_script );
225+
226+ pending_script .* = .{
227+ .script = script ,
228+ .complete = false ,
229+ .manager = self ,
230+ .node = .{},
231+ };
232+ errdefer pending_script .deinit ();
230233
231- if (source == .@"inline" ) {
232- // if we're here, it means that we have pending scripts (i.e. self.scripts
233- // is not empty). Because the script is inline, it's complete/ready, but
234- // we need to process them in order
235- pending_script .complete = true ;
236- self .scripts .append (& pending_script .node );
237- return ;
238- } else {
239- log .debug (.http , "script queue" , .{
240- .ctx = ctx ,
234+ if (comptime IS_DEBUG ) {
235+ log .debug (.http , "script queue" , .{
236+ .ctx = ctx ,
237+ .url = remote_url .? ,
238+ .stack = page .js .stackTrace () catch "???" ,
239+ });
240+ }
241+
242+ var headers = try self .client .newHeaders ();
243+ try page .requestCookie (.{}).headersForRequest (page .arena , remote_url .? , & headers );
244+
245+ try self .client .request (.{
241246 .url = remote_url .? ,
242- .stack = page .js .stackTrace () catch "???" ,
247+ .ctx = pending_script ,
248+ .method = .GET ,
249+ .headers = headers ,
250+ .resource_type = .script ,
251+ .cookie_jar = & page ._session .cookie_jar ,
252+ .start_callback = if (log .enabled (.http , .debug )) startCallback else null ,
253+ .header_callback = headerCallback ,
254+ .data_callback = dataCallback ,
255+ .done_callback = doneCallback ,
256+ .error_callback = errorCallback ,
243257 });
244- }
245258
246- pending_script .getList ().append (& pending_script .node );
259+ if (script .is_defer ) {
260+ // non-blocking loading, track the list this belongs to, and return
261+ pending_script .list = & self .deferreds ;
262+ return ;
263+ }
247264
248- errdefer pending_script .deinit ();
265+ if (script .is_async ) {
266+ // non-blocking loading, track the list this belongs to, and return
267+ pending_script .list = & self .asyncs ;
268+ return ;
269+ }
249270
250- var headers = try self . client . newHeaders () ;
251- try page . requestCookie (.{}). headersForRequest ( page . arena , remote_url .? , & headers ) ;
271+ break : blk pending_script ;
272+ } ;
252273
253- try self .client .request (.{
254- .url = remote_url .? ,
255- .ctx = pending_script ,
256- .method = .GET ,
257- .headers = headers ,
258- .resource_type = .script ,
259- .cookie_jar = & page ._session .cookie_jar ,
260- .start_callback = if (log .enabled (.http , .debug )) startCallback else null ,
261- .header_callback = headerCallback ,
262- .data_callback = dataCallback ,
263- .done_callback = doneCallback ,
264- .error_callback = errorCallback ,
265- });
274+ defer pending_script .deinit ();
275+
276+ // this is <script src="..."></script>, it needs to block the caller
277+ // until it's evaluated
278+ var client = self .client ;
279+ while (true ) {
280+ if (pending_script .complete ) {
281+ return pending_script .script .eval (page );
282+ }
283+ _ = try client .tick (200 );
284+ }
266285}
267286
268287// Resolve a module specifier to an valid URL.
@@ -394,6 +413,7 @@ pub fn getAsyncModule(self: *ScriptManager, url: [:0]const u8, cb: AsyncModule.C
394413 .error_callback = AsyncModule .errorCallback ,
395414 });
396415}
416+
397417pub fn pageIsLoaded (self : * ScriptManager ) void {
398418 std .debug .assert (self .static_scripts_done == false );
399419 self .static_scripts_done = true ;
@@ -415,15 +435,6 @@ fn evaluate(self: *ScriptManager) void {
415435 self .is_evaluating = true ;
416436 defer self .is_evaluating = false ;
417437
418- while (self .scripts .first ) | n | {
419- var pending_script : * PendingScript = @fieldParentPtr ("node" , n );
420- if (pending_script .complete == false ) {
421- return ;
422- }
423- defer pending_script .deinit ();
424- pending_script .script .eval (page );
425- }
426-
427438 if (self .static_scripts_done == false ) {
428439 // We can only execute deferred scripts if
429440 // 1 - all the normal scripts are done
@@ -460,7 +471,6 @@ fn evaluate(self: *ScriptManager) void {
460471pub fn isDone (self : * const ScriptManager ) bool {
461472 return self .asyncs .first == null and // there are no more async scripts
462473 self .static_scripts_done and // and we've finished parsing the HTML to queue all <scripts>
463- self .scripts .first == null and // and there are no more <script src=> to wait for
464474 self .deferreds .first == null ; // and there are no more <script defer src=> to wait for
465475}
466476
@@ -536,12 +546,13 @@ fn parseImportmap(self: *ScriptManager, script: *const Script) !void {
536546// A script which is pending execution.
537547// It could be pending because:
538548// (a) we're still downloading its content or
539- // (b) this is a non-async script that has to be executed in order
549+ // (b) it's a deferred script which has to be executed in order
540550pub const PendingScript = struct {
541551 script : Script ,
542552 complete : bool ,
543553 node : OrderList.Node ,
544554 manager : * ScriptManager ,
555+ list : ? * std.DoublyLinkedList = null ,
545556
546557 fn deinit (self : * PendingScript ) void {
547558 const script = & self .script ;
@@ -550,14 +561,11 @@ pub const PendingScript = struct {
550561 if (script .source == .remote ) {
551562 manager .buffer_pool .release (script .source .remote );
552563 }
553- self .getList ().remove (& self .node );
554- }
555564
556- fn remove (self : * PendingScript ) void {
557- if (self .node ) | * node | {
558- self .getList ().remove (node );
559- self .node = null ;
565+ if (self .list ) | list | {
566+ list .remove (& self .node );
560567 }
568+ manager .script_pool .destroy (self );
561569 }
562570
563571 fn startCallback (self : * PendingScript , transfer : * Http.Transfer ) ! void {
@@ -614,6 +622,7 @@ pub const PendingScript = struct {
614622 manager .evaluate ();
615623 return ;
616624 }
625+
617626 // async script can be evaluated immediately
618627 self .script .eval (manager .page );
619628 self .deinit ();
@@ -634,23 +643,6 @@ pub const PendingScript = struct {
634643
635644 manager .evaluate ();
636645 }
637-
638- fn getList (self : * const PendingScript ) * OrderList {
639- // When a script has both the async and defer flag set, it should be
640- // treated as async. Async is newer, so some websites use both so that
641- // if async isn't known, it'll fallback to defer.
642-
643- const script = & self .script ;
644- if (script .is_async ) {
645- return & self .manager .asyncs ;
646- }
647-
648- if (script .is_defer ) {
649- return & self .manager .deferreds ;
650- }
651-
652- return & self .manager .scripts ;
653- }
654646};
655647
656648const Script = struct {
0 commit comments