@@ -29,6 +29,7 @@ const Mime = @import("mime.zig");
2929const jsruntime = @import ("jsruntime" );
3030const Loop = jsruntime .Loop ;
3131const Env = jsruntime .Env ;
32+ const Module = jsruntime .Module ;
3233
3334const apiweb = @import ("../apiweb.zig" );
3435
@@ -125,6 +126,21 @@ pub const Session = struct {
125126 try self .env .load (& self .jstypes );
126127 }
127128
129+ fn fetchModule (ctx : * anyopaque , referrer : ? jsruntime.Module , specifier : []const u8 ) ! jsruntime.Module {
130+ _ = referrer ;
131+
132+ const self : * Session = @ptrCast (@alignCast (ctx ));
133+
134+ if (self .page == null ) return error .NoPage ;
135+
136+ log .debug ("fetch module: specifier: {s}" , .{specifier });
137+ const alloc = self .arena .allocator ();
138+ const body = try self .page .? .fetchData (alloc , specifier );
139+ defer alloc .free (body );
140+
141+ return self .env .compileModule (body , specifier );
142+ }
143+
128144 fn deinit (self : * Session ) void {
129145 if (self .page ) | * p | p .end ();
130146
@@ -362,6 +378,9 @@ pub const Page = struct {
362378 log .debug ("start js env" , .{});
363379 try self .session .env .start ();
364380
381+ // register the module loader
382+ try self .session .env .setModuleLoadFn (self .session , Session .fetchModule );
383+
365384 // load polyfills
366385 try polyfill .load (alloc , self .session .env );
367386
@@ -388,7 +407,7 @@ pub const Page = struct {
388407 // sasync stores scripts which can be run asynchronously.
389408 // for now they are just run after the non-async one in order to
390409 // dispatch DOMContentLoaded the sooner as possible.
391- var sasync = std .ArrayList (* parser . Element ).init (alloc );
410+ var sasync = std .ArrayList (Script ).init (alloc );
392411 defer sasync .deinit ();
393412
394413 const root = parser .documentToNode (doc );
@@ -403,21 +422,10 @@ pub const Page = struct {
403422 }
404423
405424 const e = parser .nodeToElement (next .? );
406- const tag = try parser .elementHTMLGetTagType (@as (* parser .ElementHTML , @ptrCast (e )));
407-
408- // ignore non-script tags
409- if (tag != .script ) continue ;
410425
411426 // ignore non-js script.
412- // > type
413- // > Attribute is not set (default), an empty string, or a JavaScript MIME
414- // > type indicates that the script is a "classic script", containing
415- // > JavaScript code.
416- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
417- const stype = try parser .elementGetAttribute (e , "type" );
418- if (! isJS (stype )) {
419- continue ;
420- }
427+ const script = try Script .init (e ) orelse continue ;
428+ if (script .kind == .unknown ) continue ;
421429
422430 // Ignore the defer attribute b/c we analyze all script
423431 // after the document has been parsed.
@@ -431,8 +439,8 @@ pub const Page = struct {
431439 // > then the classic script will be fetched in parallel to
432440 // > parsing and evaluated as soon as it is available.
433441 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async
434- if (try parser . elementGetAttribute ( e , "async" ) != null ) {
435- try sasync .append (e );
442+ if (script . isasync ) {
443+ try sasync .append (script );
436444 continue ;
437445 }
438446
@@ -455,7 +463,7 @@ pub const Page = struct {
455463 // > page.
456464 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#notes
457465 try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (e ));
458- self .evalScript (e ) catch | err | log .warn ("evaljs: {any}" , .{err });
466+ self .evalScript (script ) catch | err | log .warn ("evaljs: {any}" , .{err });
459467 try parser .documentHTMLSetCurrentScript (html_doc , null );
460468 }
461469
@@ -472,9 +480,9 @@ pub const Page = struct {
472480 _ = try parser .eventTargetDispatchEvent (parser .toEventTarget (parser .DocumentHTML , html_doc ), evt );
473481
474482 // eval async scripts.
475- for (sasync .items ) | e | {
476- try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (e ));
477- self .evalScript (e ) catch | err | log .warn ("evaljs: {any}" , .{err });
483+ for (sasync .items ) | s | {
484+ try parser .documentHTMLSetCurrentScript (html_doc , @ptrCast (s . element ));
485+ self .evalScript (s ) catch | err | log .warn ("evaljs: {any}" , .{err });
478486 try parser .documentHTMLSetCurrentScript (html_doc , null );
479487 }
480488
@@ -496,15 +504,15 @@ pub const Page = struct {
496504 // evalScript evaluates the src in priority.
497505 // if no src is present, we evaluate the text source.
498506 // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
499- fn evalScript (self : * Page , e : * parser.Element ) ! void {
507+ fn evalScript (self : * Page , s : Script ) ! void {
500508 const alloc = self .arena .allocator ();
501509
502510 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
503- const opt_src = try parser .elementGetAttribute (e , "src" );
511+ const opt_src = try parser .elementGetAttribute (s . element , "src" );
504512 if (opt_src ) | src | {
505513 log .debug ("starting GET {s}" , .{src });
506514
507- self .fetchScript (src ) catch | err | {
515+ self .fetchScript (s ) catch | err | {
508516 switch (err ) {
509517 FetchError .BadStatusCode = > return err ,
510518
@@ -523,26 +531,10 @@ pub const Page = struct {
523531 return ;
524532 }
525533
526- var try_catch : jsruntime.TryCatch = undefined ;
527- try_catch .init (self .session .env );
528- defer try_catch .deinit ();
529-
530- const opt_text = try parser .nodeTextContent (parser .elementToNode (e ));
534+ // TODO handle charset attribute
535+ const opt_text = try parser .nodeTextContent (parser .elementToNode (s .element ));
531536 if (opt_text ) | text | {
532- // TODO handle charset attribute
533- const res = self .session .env .exec (text , "" ) catch {
534- if (try try_catch .err (alloc , self .session .env )) | msg | {
535- defer alloc .free (msg );
536- log .info ("eval inline {s}: {s}" , .{ text , msg });
537- }
538- return ;
539- };
540-
541- if (builtin .mode == .Debug ) {
542- const msg = try res .toString (alloc , self .session .env );
543- defer alloc .free (msg );
544- log .debug ("eval inline {s}" , .{msg });
545- }
537+ try s .eval (alloc , self .session .env , text );
546538 return ;
547539 }
548540
@@ -557,12 +549,9 @@ pub const Page = struct {
557549 JsErr ,
558550 };
559551
560- // fetchScript senf a GET request to the src and execute the script
561- // received.
562- fn fetchScript (self : * Page , src : []const u8 ) ! void {
563- const alloc = self .arena .allocator ();
564-
565- log .debug ("starting fetch script {s}" , .{src });
552+ // the caller owns the returned string
553+ fn fetchData (self : * Page , alloc : std.mem.Allocator , src : []const u8 ) ! []const u8 {
554+ log .debug ("starting fetch {s}" , .{src });
566555
567556 var buffer : [1024 ]u8 = undefined ;
568557 var b : []u8 = buffer [0.. ];
@@ -573,46 +562,91 @@ pub const Page = struct {
573562
574563 const resp = fetchres .req .response ;
575564
576- log .info ("fetch script {any}: {d}" , .{ u , resp .status });
565+ log .info ("fetch {any}: {d}" , .{ u , resp .status });
577566
578567 if (resp .status != .ok ) return FetchError .BadStatusCode ;
579568
580569 // TODO check content-type
581570 const body = try fetchres .req .reader ().readAllAlloc (alloc , 16 * 1024 * 1024 );
582- defer alloc .free (body );
583571
584572 // check no body
585573 if (body .len == 0 ) return FetchError .NoBody ;
586574
587- var try_catch : jsruntime.TryCatch = undefined ;
588- try_catch .init (self .session .env );
589- defer try_catch .deinit ();
575+ return body ;
576+ }
590577
591- const res = self .session .env .exec (body , src ) catch {
592- if (try try_catch .err (alloc , self .session .env )) | msg | {
593- defer alloc .free (msg );
594- log .info ("eval remote {s}: {s}" , .{ src , msg });
595- }
596- return FetchError .JsErr ;
578+ // fetchScript senf a GET request to the src and execute the script
579+ // received.
580+ fn fetchScript (self : * Page , s : Script ) ! void {
581+ const alloc = self .arena .allocator ();
582+ const body = try self .fetchData (alloc , s .src );
583+ defer alloc .free (body );
584+
585+ try s .eval (alloc , self .session .env , body );
586+ }
587+
588+ const Script = struct {
589+ element : * parser.Element ,
590+ kind : Kind ,
591+ isasync : bool ,
592+
593+ src : []const u8 ,
594+
595+ const Kind = enum {
596+ unknown ,
597+ javascript ,
598+ module ,
597599 };
598600
599- if (builtin .mode == .Debug ) {
600- const msg = try res .toString (alloc , self .session .env );
601- defer alloc .free (msg );
602- log .debug ("eval remote {s}: {s}" , .{ src , msg });
601+ fn init (e : * parser.Element ) ! ? Script {
602+ // ignore non-script tags
603+ const tag = try parser .elementHTMLGetTagType (@as (* parser .ElementHTML , @ptrCast (e )));
604+ if (tag != .script ) return null ;
605+
606+ return .{
607+ .element = e ,
608+ .kind = kind (try parser .elementGetAttribute (e , "type" )),
609+ .isasync = try parser .elementGetAttribute (e , "async" ) != null ,
610+
611+ .src = try parser .elementGetAttribute (e , "src" ) orelse "inline" ,
612+ };
603613 }
604- }
605614
606- // > type
607- // > Attribute is not set (default), an empty string, or a JavaScript MIME
608- // > type indicates that the script is a "classic script", containing
609- // > JavaScript code.
610- // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
611- fn isJS (stype : ? []const u8 ) bool {
612- if (stype == null or stype .? .len == 0 ) return true ;
613- if (std .mem .eql (u8 , stype .? , "application/javascript" )) return true ;
614- if (! std .mem .eql (u8 , stype .? , "module" )) return true ;
615-
616- return false ;
617- }
615+ // > type
616+ // > Attribute is not set (default), an empty string, or a JavaScript MIME
617+ // > type indicates that the script is a "classic script", containing
618+ // > JavaScript code.
619+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
620+ fn kind (stype : ? []const u8 ) Kind {
621+ if (stype == null or stype .? .len == 0 ) return .javascript ;
622+ if (std .mem .eql (u8 , stype .? , "application/javascript" )) return .javascript ;
623+ if (std .mem .eql (u8 , stype .? , "module" )) return .module ;
624+
625+ return .unknown ;
626+ }
627+
628+ fn eval (self : Script , alloc : std.mem.Allocator , env : Env , body : []const u8 ) ! void {
629+ var try_catch : jsruntime.TryCatch = undefined ;
630+ try_catch .init (env );
631+ defer try_catch .deinit ();
632+
633+ const res = switch (self .kind ) {
634+ .unknown = > return error .UnknownScript ,
635+ .javascript = > env .exec (body , self .src ),
636+ .module = > env .module (body , self .src ),
637+ } catch {
638+ if (try try_catch .err (alloc , env )) | msg | {
639+ defer alloc .free (msg );
640+ log .info ("eval script {s}: {s}" , .{ self .src , msg });
641+ }
642+ return FetchError .JsErr ;
643+ };
644+
645+ if (builtin .mode == .Debug ) {
646+ const msg = try res .toString (alloc , env );
647+ defer alloc .free (msg );
648+ log .debug ("eval script {s}: {s}" , .{ self .src , msg });
649+ }
650+ }
651+ };
618652};
0 commit comments