@@ -268,7 +268,7 @@ pub const Page = struct {
268268 // load polyfills
269269 try polyfill .load (self .arena , self .scope );
270270
271- _ = try session .browser .app .loop .timeout (1 * std .time .ns_per_ms , & self .microtask_node );
271+ // _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
272272 }
273273
274274 fn microtaskCallback (node : * Loop.CallbackNode , repeat_delay : * ? u63 ) void {
@@ -290,7 +290,7 @@ pub const Page = struct {
290290 try Dump .writeHTML (self .doc .? , out );
291291 }
292292
293- pub fn fetchModuleSource (ctx : * anyopaque , specifier : []const u8 ) ! []const u8 {
293+ pub fn fetchModuleSource (ctx : * anyopaque , specifier : []const u8 ) ! ? []const u8 {
294294 const self : * Page = @ptrCast (@alignCast (ctx ));
295295
296296 log .debug ("fetch module: specifier: {s}" , .{specifier });
@@ -436,10 +436,18 @@ pub const Page = struct {
436436 // TODO fetch the script resources concurrently but execute them in the
437437 // declaration order for synchronous ones.
438438
439- // sasync stores scripts which can be run asynchronously.
439+ // async_scripts stores scripts which can be run asynchronously.
440440 // for now they are just run after the non-async one in order to
441441 // dispatch DOMContentLoaded the sooner as possible.
442- var sasync : std .ArrayListUnmanaged (Script ) = .{};
442+ var async_scripts : std .ArrayListUnmanaged (Script ) = .{};
443+
444+ // defer_scripts stores scripts which are meant to be deferred. For now
445+ // this doesn't have a huge impact, since normal scripts are parsed
446+ // after the document is loaded. But (a) we should fix that and (b)
447+ // this results in JavaScript being loaded in the same order as browsers
448+ // which can help debug issues (and might actually fix issues if websites
449+ // are expecting this execution order)
450+ var defer_scripts : std .ArrayListUnmanaged (Script ) = .{};
443451
444452 const root = parser .documentToNode (doc );
445453 const walker = Walker {};
@@ -456,11 +464,6 @@ pub const Page = struct {
456464
457465 // ignore non-js script.
458466 const script = try Script .init (e ) orelse continue ;
459- if (script .kind == .unknown ) continue ;
460-
461- // Ignore the defer attribute b/c we analyze all script
462- // after the document has been parsed.
463- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
464467
465468 // TODO use fetchpriority
466469 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#fetchpriority
@@ -471,22 +474,18 @@ pub const Page = struct {
471474 // > parsing and evaluated as soon as it is available.
472475 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async
473476 if (script .is_async ) {
474- try sasync .append (arena , script );
477+ try async_scripts .append (arena , script );
478+ continue ;
479+ }
480+
481+ if (script .is_defer ) {
482+ try defer_scripts .append (arena , script );
475483 continue ;
476484 }
477485
478486 // TODO handle for attribute
479487 // TODO handle event attribute
480488
481- // TODO defer
482- // > This Boolean attribute is set to indicate to a browser
483- // > that the script is meant to be executed after the
484- // > document has been parsed, but before firing
485- // > DOMContentLoaded.
486- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
487- // defer allow us to load a script w/o blocking the rest of
488- // evaluations.
489-
490489 // > Scripts without async, defer or type="module"
491490 // > attributes, as well as inline scripts without the
492491 // > type="module" attribute, are fetched and executed
@@ -498,7 +497,11 @@ pub const Page = struct {
498497 try parser .documentHTMLSetCurrentScript (html_doc , null );
499498 }
500499
501- // TODO wait for deferred scripts
500+ for (defer_scripts .items ) | s | {
501+ try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (s .element ));
502+ self .evalScript (& s ) catch | err | log .warn ("evaljs: {any}" , .{err });
503+ try parser .documentHTMLSetCurrentScript (html_doc , null );
504+ }
502505
503506 // dispatch DOMContentLoaded before the transition to "complete",
504507 // at the point where all subresources apart from async script elements
@@ -511,7 +514,7 @@ pub const Page = struct {
511514 _ = try parser .eventTargetDispatchEvent (parser .toEventTarget (parser .DocumentHTML , html_doc ), evt );
512515
513516 // eval async scripts.
514- for (sasync .items ) | s | {
517+ for (async_scripts .items ) | s | {
515518 try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (s .element ));
516519 self .evalScript (& s ) catch | err | log .warn ("evaljs: {any}" , .{err });
517520 try parser .documentHTMLSetCurrentScript (html_doc , null );
@@ -535,57 +538,42 @@ pub const Page = struct {
535538 // evalScript evaluates the src in priority.
536539 // if no src is present, we evaluate the text source.
537540 // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
538- fn evalScript (self : * Page , s : * const Script ) ! void {
539- self .current_script = s ;
540- defer self .current_script = null ;
541-
542- // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
543- const opt_src = try parser .elementGetAttribute (s .element , "src" );
544- if (opt_src ) | src | {
545- log .debug ("starting GET {s}" , .{src });
546-
547- self .fetchScript (s ) catch | err | {
548- switch (err ) {
549- FetchError .BadStatusCode = > return err ,
550-
551- // TODO If el's result is null, then fire an event named error at
552- // el, and return.
553- FetchError .NoBody = > return ,
541+ fn evalScript (self : * Page , script : * const Script ) ! void {
542+ const src = script .src orelse {
543+ // source is inline
544+ // TODO handle charset attribute
545+ if (try parser .nodeTextContent (parser .elementToNode (script .element ))) | text | {
546+ try script .eval (self , text );
547+ }
548+ return ;
549+ };
554550
555- FetchError .JsErr = > {}, // nothing to do here.
556- else = > return err ,
557- }
558- };
551+ self .current_script = script ;
552+ defer self .current_script = null ;
559553
560- // TODO If el's from an external file is true, then fire an event
561- // named load at el.
554+ log .debug ("starting GET {s}" , .{src });
562555
556+ // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
557+ const body = (try self .fetchData (src , null )) orelse {
558+ // TODO If el's result is null, then fire an event named error at
559+ // el, and return
563560 return ;
564- }
561+ };
565562
566- // TODO handle charset attribute
567- const opt_text = try parser .nodeTextContent (parser .elementToNode (s .element ));
568- if (opt_text ) | text | {
569- try s .eval (self , text );
570- return ;
571- }
563+ script .eval (self , body ) catch | err | switch (err ) {
564+ error .JsErr = > {}, // nothing to do here.
565+ else = > return err ,
566+ };
572567
573- // nothing has been loaded.
574- // TODO If el's result is null, then fire an event named error at
575- // el, and return.
568+ // TODO If el's from an external file is true, then fire an event
569+ // named load at el.
576570 }
577571
578- const FetchError = error {
579- BadStatusCode ,
580- NoBody ,
581- JsErr ,
582- };
583-
584572 // fetchData returns the data corresponding to the src target.
585573 // It resolves src using the page's uri.
586574 // If a base path is given, src is resolved according to the base first.
587575 // the caller owns the returned string
588- fn fetchData (self : * const Page , src : []const u8 , base : ? []const u8 ) ! []const u8 {
576+ fn fetchData (self : * const Page , src : []const u8 , base : ? []const u8 ) ! ? []const u8 {
589577 log .debug ("starting fetch {s}" , .{src });
590578
591579 const arena = self .arena ;
@@ -620,7 +608,7 @@ pub const Page = struct {
620608 log .info ("fetch {any}: {d}" , .{ url , header .status });
621609
622610 if (header .status != 200 ) {
623- return FetchError .BadStatusCode ;
611+ return error .BadStatusCode ;
624612 }
625613
626614 var arr : std .ArrayListUnmanaged (u8 ) = .{};
@@ -632,17 +620,12 @@ pub const Page = struct {
632620
633621 // check no body
634622 if (arr .items .len == 0 ) {
635- return FetchError . NoBody ;
623+ return null ;
636624 }
637625
638626 return arr .items ;
639627 }
640628
641- fn fetchScript (self : * Page , s : * const Script ) ! void {
642- const body = try self .fetchData (s .src , null );
643- try s .eval (self , body );
644- }
645-
646629 fn newHTTPRequest (self : * const Page , method : http.Request.Method , url : * const URL , opts : storage.cookie.LookupOpts ) ! http.Request {
647630 var request = try self .state .http_client .request (method , & url .uri );
648631 errdefer request .deinit ();
@@ -738,28 +721,42 @@ pub const Page = struct {
738721 };
739722
740723 const Script = struct {
741- element : * parser.Element ,
742724 kind : Kind ,
743725 is_async : bool ,
726+ is_defer : bool ,
727+ src : ? []const u8 ,
728+ element : * parser.Element ,
729+ // The javascript to load after we successfully load the script
730+ onload : ? []const u8 ,
744731
745- src : []const u8 ,
732+ // The javascript to load if we have an error executing the script
733+ // For now, we ignore this, since we still have a lot of errors that we
734+ // shouldn't
735+ //onerror: ?[]const u8,
746736
747737 const Kind = enum {
748- unknown ,
749- javascript ,
750738 module ,
739+ javascript ,
751740 };
752741
753742 fn init (e : * parser.Element ) ! ? Script {
754743 // ignore non-script tags
755744 const tag = try parser .elementHTMLGetTagType (@as (* parser .ElementHTML , @ptrCast (e )));
756- if (tag != .script ) return null ;
745+ if (tag != .script ) {
746+ return null ;
747+ }
748+
749+ const kind = parseKind (try parser .elementGetAttribute (e , "type" )) orelse {
750+ return null ;
751+ };
757752
758753 return .{
754+ .kind = kind ,
759755 .element = e ,
760- .kind = parseKind (try parser .elementGetAttribute (e , "type" )),
756+ .src = try parser .elementGetAttribute (e , "src" ),
757+ .onload = try parser .elementGetAttribute (e , "onload" ),
761758 .is_async = try parser .elementGetAttribute (e , "async" ) != null ,
762- .src = try parser .elementGetAttribute (e , "src " ) orelse "inline" ,
759+ .is_defer = try parser .elementGetAttribute (e , "defer " ) != null ,
763760 };
764761 }
765762
@@ -768,34 +765,47 @@ pub const Page = struct {
768765 // > type indicates that the script is a "classic script", containing
769766 // > JavaScript code.
770767 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
771- fn parseKind (stype : ? []const u8 ) Kind {
772- if ( stype == null or stype .? . len == 0 ) return .javascript ;
773- if (std . mem . eql ( u8 , stype .? , "application/javascript" )) return .javascript ;
774- if ( std . mem . eql ( u8 , stype .? , "text/javascript" )) return .javascript ;
775- if ( std . mem . eql ( u8 , stype .? , "module" )) return .module ;
768+ fn parseKind (script_type_ : ? []const u8 ) ? Kind {
769+ const script_type = script_type_ orelse return .javascript ;
770+ if (script_type . len == 0 ) {
771+ return .javascript ;
772+ }
776773
777- return .unknown ;
774+ if (std .mem .eql (u8 , script_type , "application/javascript" )) return .javascript ;
775+ if (std .mem .eql (u8 , script_type , "text/javascript" )) return .javascript ;
776+ if (std .mem .eql (u8 , script_type , "module" )) return .module ;
777+
778+ return null ;
778779 }
779780
780- fn eval (self : Script , page : * Page , body : []const u8 ) ! void {
781+ fn eval (self : * const Script , page : * Page , body : []const u8 ) ! void {
781782 var try_catch : Env.TryCatch = undefined ;
782783 try_catch .init (page .scope );
783784 defer try_catch .deinit ();
784785
786+ const src = self .src orelse "inline" ;
785787 const res = switch (self .kind ) {
786- .unknown = > return error .UnknownScript ,
787- .javascript = > page .scope .exec (body , self .src ),
788- .module = > page .scope .module (body , self .src ),
788+ .javascript = > page .scope .exec (body , src ),
789+ .module = > page .scope .module (body , src ),
789790 } catch {
790791 if (try try_catch .err (page .arena )) | msg | {
791- log .info ("eval script {s}: {s}" , .{ self . src , msg });
792+ log .info ("eval script {s}: {s}" , .{ src , msg });
792793 }
793- return FetchError .JsErr ;
794+ return error .JsErr ;
794795 };
795796
796797 if (builtin .mode == .Debug ) {
797798 const msg = try res .toString (page .arena );
798- log .debug ("eval script {s}: {s}" , .{ self .src , msg });
799+ log .debug ("eval script {s}: {s}" , .{ src , msg });
800+ }
801+
802+ if (self .onload ) | onload | {
803+ _ = page .scope .exec (onload , "script_on_load" ) catch {
804+ if (try try_catch .err (page .arena )) | msg | {
805+ log .info ("eval script onload {s}: {s}" , .{ src , msg });
806+ }
807+ return error .JsErr ;
808+ };
799809 }
800810 }
801811 };
0 commit comments