@@ -277,6 +277,10 @@ pub const Page = struct {
277277 const html_doc = self .window .document ;
278278 const doc = parser .documentHTMLToDocument (html_doc );
279279
280+ // we want to be notified of any dynamically added script tags
281+ // so that we can load the script
282+ parser .documentSetScriptAddedCallback (doc , self , scriptAddedCallback );
283+
280284 const document_element = (try parser .documentGetDocumentElement (doc )) orelse return error .DocumentElementError ;
281285 _ = try parser .eventTargetAddEventListener (
282286 parser .toEventTarget (parser .Element , document_element ),
@@ -317,8 +321,12 @@ pub const Page = struct {
317321 }
318322
319323 const e = parser .nodeToElement (next .? );
324+ const tag = try parser .elementHTMLGetTagType (@as (* parser .ElementHTML , @ptrCast (e )));
325+ if (tag != .script ) {
326+ // ignore non-js script.
327+ continue ;
328+ }
320329
321- // ignore non-js script.
322330 const script = try Script .init (e ) orelse continue ;
323331
324332 // TODO use fetchpriority
@@ -348,19 +356,11 @@ pub const Page = struct {
348356 // > immediately before the browser continues to parse the
349357 // > page.
350358 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#notes
351- try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (e ));
352- self .evalScript (& script ) catch | err | {
353- log .err (.page , "eval script error" , .{ .err = err });
354- };
355- try parser .documentHTMLSetCurrentScript (html_doc , null );
359+ self .evalScript (& script );
356360 }
357361
358- for (defer_scripts .items ) | script | {
359- try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (script .element ));
360- self .evalScript (& script ) catch | err | {
361- log .err (.page , "eval script error" , .{ .err = err });
362- };
363- try parser .documentHTMLSetCurrentScript (html_doc , null );
362+ for (defer_scripts .items ) | * script | {
363+ self .evalScript (script );
364364 }
365365 // dispatch DOMContentLoaded before the transition to "complete",
366366 // at the point where all subresources apart from async script elements
@@ -369,12 +369,8 @@ pub const Page = struct {
369369 try HTMLDocument .documentIsLoaded (html_doc , self );
370370
371371 // eval async scripts.
372- for (async_scripts .items ) | script | {
373- try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (script .element ));
374- self .evalScript (& script ) catch | err | {
375- log .err (.page , "eval script error" , .{ .err = err });
376- };
377- try parser .documentHTMLSetCurrentScript (html_doc , null );
372+ for (async_scripts .items ) | * script | {
373+ self .evalScript (script );
378374 }
379375
380376 try HTMLDocument .documentIsComplete (html_doc , self );
@@ -390,10 +386,24 @@ pub const Page = struct {
390386 );
391387 }
392388
389+ fn evalScript (self : * Page , script : * const Script ) void {
390+ self .tryEvalScript (script ) catch | err | {
391+ log .err (.page , "eval script error" , .{ .err = err });
392+ };
393+ }
394+
395+
393396 // evalScript evaluates the src in priority.
394397 // if no src is present, we evaluate the text source.
395398 // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
396- fn evalScript (self : * Page , script : * const Script ) ! void {
399+ fn tryEvalScript (self : * Page , script : * const Script ) ! void {
400+ const html_doc = self .window .document ;
401+ try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (script .element ));
402+
403+ defer parser .documentHTMLSetCurrentScript (html_doc , null ) catch | err | {
404+ log .err (.page , "clear document script" , .{.err = err });
405+ };
406+
397407 const src = script .src orelse {
398408 // source is inline
399409 // TODO handle charset attribute
@@ -613,12 +623,6 @@ const Script = struct {
613623 };
614624
615625 fn init (e : * parser.Element ) ! ? Script {
616- // ignore non-script tags
617- const tag = try parser .elementHTMLGetTagType (@as (* parser .ElementHTML , @ptrCast (e )));
618- if (tag != .script ) {
619- return null ;
620- }
621-
622626 if (try parser .elementGetAttribute (e , "nomodule" ) != null ) {
623627 // these scripts should only be loaded if we don't support modules
624628 // but since we do support modules, we can just skip them.
@@ -707,3 +711,20 @@ fn timestamp() u32 {
707711 const ts = std .posix .clock_gettime (std .posix .CLOCK .MONOTONIC ) catch unreachable ;
708712 return @intCast (ts .sec );
709713}
714+
715+ // A callback from libdom whenever a script tag is added to the DOM.
716+ // element is guaranteed to be a script element.
717+ // The script tag might not have a src. It might be any attribute, like
718+ // `nomodule`, `defer` and `async`. `Script.init` will return null on `nomodule`
719+ // so that's handled. And because we're only executing the inline <script> tags
720+ // after the document is loaded, it's ok to execute any async and defer scripts
721+ // immediately.
722+ pub export fn scriptAddedCallback (ctx : ? * anyopaque , element : ? * parser.Element ) callconv (.C ) void {
723+ var script = Script .init (element .? ) catch | err | {
724+ log .warn (.page , "script added init error" , .{.err = err });
725+ return ;
726+ } orelse return ;
727+
728+ const self : * Page = @alignCast (@ptrCast (ctx .? ));
729+ self .evalScript (& script );
730+ }
0 commit comments