From ed59697118df9f512167ce6fcdeeb5a78cd7978b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 31 Dec 2024 12:37:05 +0100 Subject: [PATCH 1/3] browser: refacto script --- src/browser/browser.zig | 154 ++++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 1e74a8a9..586e6911 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -380,7 +380,7 @@ pub const Page = struct { // sasync stores scripts which can be run asynchronously. // for now they are just run after the non-async one in order to // dispatch DOMContentLoaded the sooner as possible. - var sasync = std.ArrayList(*parser.Element).init(alloc); + var sasync = std.ArrayList(Script).init(alloc); defer sasync.deinit(); const root = parser.documentToNode(doc); @@ -395,21 +395,11 @@ pub const Page = struct { } const e = parser.nodeToElement(next.?); - const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e))); - - // ignore non-script tags - if (tag != .script) continue; // ignore non-js script. - // > type - // > Attribute is not set (default), an empty string, or a JavaScript MIME - // > type indicates that the script is a "classic script", containing - // > JavaScript code. - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type - const stype = try parser.elementGetAttribute(e, "type"); - if (!isJS(stype)) { - continue; - } + const script = try Script.init(e) orelse continue; + if (script.kind == .unknown) continue; + if (script.kind == .module) continue; // Ignore the defer attribute b/c we analyze all script // after the document has been parsed. @@ -423,8 +413,8 @@ pub const Page = struct { // > then the classic script will be fetched in parallel to // > parsing and evaluated as soon as it is available. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async - if (try parser.elementGetAttribute(e, "async") != null) { - try sasync.append(e); + if (script.isasync) { + try sasync.append(script); continue; } @@ -447,7 +437,7 @@ pub const Page = struct { // > page. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#notes try parser.documentHTMLSetCurrentScript(html_doc, @ptrCast(e)); - self.evalScript(e) catch |err| log.warn("evaljs: {any}", .{err}); + self.evalScript(script) catch |err| log.warn("evaljs: {any}", .{err}); try parser.documentHTMLSetCurrentScript(html_doc, null); } @@ -464,9 +454,9 @@ pub const Page = struct { _ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, html_doc), evt); // eval async scripts. - for (sasync.items) |e| { - try parser.documentHTMLSetCurrentScript(html_doc, @ptrCast(e)); - self.evalScript(e) catch |err| log.warn("evaljs: {any}", .{err}); + for (sasync.items) |s| { + try parser.documentHTMLSetCurrentScript(html_doc, @ptrCast(s.element)); + self.evalScript(s) catch |err| log.warn("evaljs: {any}", .{err}); try parser.documentHTMLSetCurrentScript(html_doc, null); } @@ -488,15 +478,15 @@ pub const Page = struct { // evalScript evaluates the src in priority. // if no src is present, we evaluate the text source. // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model - fn evalScript(self: *Page, e: *parser.Element) !void { + fn evalScript(self: *Page, s: Script) !void { const alloc = self.arena.allocator(); // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script - const opt_src = try parser.elementGetAttribute(e, "src"); + const opt_src = try parser.elementGetAttribute(s.element, "src"); if (opt_src) |src| { log.debug("starting GET {s}", .{src}); - self.fetchScript(src) catch |err| { + self.fetchScript(s) catch |err| { switch (err) { FetchError.BadStatusCode => return err, @@ -515,26 +505,10 @@ pub const Page = struct { return; } - var try_catch: jsruntime.TryCatch = undefined; - try_catch.init(self.session.env); - defer try_catch.deinit(); - - const opt_text = try parser.nodeTextContent(parser.elementToNode(e)); + // TODO handle charset attribute + const opt_text = try parser.nodeTextContent(parser.elementToNode(s.element)); if (opt_text) |text| { - // TODO handle charset attribute - const res = self.session.env.exec(text, "") catch { - if (try try_catch.err(alloc, self.session.env)) |msg| { - defer alloc.free(msg); - log.info("eval inline {s}: {s}", .{ text, msg }); - } - return; - }; - - if (builtin.mode == .Debug) { - const msg = try res.toString(alloc, self.session.env); - defer alloc.free(msg); - log.debug("eval inline {s}", .{msg}); - } + try s.eval(alloc, self.session.env, text); return; } @@ -551,14 +525,14 @@ pub const Page = struct { // fetchScript senf a GET request to the src and execute the script // received. - fn fetchScript(self: *Page, src: []const u8) !void { + fn fetchScript(self: *Page, s: Script) !void { const alloc = self.arena.allocator(); - log.debug("starting fetch script {s}", .{src}); + log.debug("starting fetch script {s}", .{s.src}); var buffer: [1024]u8 = undefined; var b: []u8 = buffer[0..]; - const u = try std.Uri.resolve_inplace(self.uri, src, &b); + const u = try std.Uri.resolve_inplace(self.uri, s.src, &b); var fetchres = try self.session.loader.get(alloc, u); defer fetchres.deinit(); @@ -576,35 +550,73 @@ pub const Page = struct { // check no body if (body.len == 0) return FetchError.NoBody; - var try_catch: jsruntime.TryCatch = undefined; - try_catch.init(self.session.env); - defer try_catch.deinit(); + try s.eval(alloc, self.session.env, body); + } - const res = self.session.env.exec(body, src) catch { - if (try try_catch.err(alloc, self.session.env)) |msg| { - defer alloc.free(msg); - log.info("eval remote {s}: {s}", .{ src, msg }); - } - return FetchError.JsErr; + const Script = struct { + element: *parser.Element, + kind: Kind, + isasync: bool, + + src: []const u8, + + const Kind = enum { + unknown, + javascript, + module, }; - if (builtin.mode == .Debug) { - const msg = try res.toString(alloc, self.session.env); - defer alloc.free(msg); - log.debug("eval remote {s}: {s}", .{ src, msg }); + fn init(e: *parser.Element) !?Script { + // ignore non-script tags + const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e))); + if (tag != .script) return null; + + return .{ + .element = e, + .kind = kind(try parser.elementGetAttribute(e, "type")), + .isasync = try parser.elementGetAttribute(e, "async") != null, + + .src = try parser.elementGetAttribute(e, "src") orelse "inline", + }; } - } - // > type - // > Attribute is not set (default), an empty string, or a JavaScript MIME - // > type indicates that the script is a "classic script", containing - // > JavaScript code. - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type - fn isJS(stype: ?[]const u8) bool { - if (stype == null or stype.?.len == 0) return true; - if (std.mem.eql(u8, stype.?, "application/javascript")) return true; - if (!std.mem.eql(u8, stype.?, "module")) return true; - - return false; - } + // > type + // > Attribute is not set (default), an empty string, or a JavaScript MIME + // > type indicates that the script is a "classic script", containing + // > JavaScript code. + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type + fn kind(stype: ?[]const u8) Kind { + if (stype == null or stype.?.len == 0) return .javascript; + if (std.mem.eql(u8, stype.?, "application/javascript")) return .javascript; + if (!std.mem.eql(u8, stype.?, "module")) return .module; + + return .unknown; + } + + fn eval(self: Script, alloc: std.mem.Allocator, env: Env, body: []const u8) !void { + switch (self.kind) { + .unknown => return error.UnknownScript, + .javascript => {}, + .module => {}, + } + + var try_catch: jsruntime.TryCatch = undefined; + try_catch.init(env); + defer try_catch.deinit(); + + const res = env.exec(body, self.src) catch { + if (try try_catch.err(alloc, env)) |msg| { + defer alloc.free(msg); + log.info("eval script {s}: {s}", .{ self.src, msg }); + } + return FetchError.JsErr; + }; + + if (builtin.mode == .Debug) { + const msg = try res.toString(alloc, env); + defer alloc.free(msg); + log.debug("eval script {s}: {s}", .{ self.src, msg }); + } + } + }; }; From d9a07beae3584d4ba195da32bfb10d5c8d944724 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 2 Jan 2025 17:08:10 +0100 Subject: [PATCH 2/3] update zig-js-runtime --- vendor/zig-js-runtime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/zig-js-runtime b/vendor/zig-js-runtime index 599c173b..3be71ed6 160000 --- a/vendor/zig-js-runtime +++ b/vendor/zig-js-runtime @@ -1 +1 @@ -Subproject commit 599c173b346461f3e8deb5aca2e2e004cb51733e +Subproject commit 3be71ed62b8d1790a69958a143ec5f7fc5989068 From 9c97654a00e53fbbf544303cbbd93b7589a18c82 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 2 Jan 2025 17:07:49 +0100 Subject: [PATCH 3/3] browser: load module --- src/browser/browser.zig | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 586e6911..834317de 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -399,7 +399,6 @@ pub const Page = struct { // ignore non-js script. const script = try Script.init(e) orelse continue; if (script.kind == .unknown) continue; - if (script.kind == .module) continue; // Ignore the defer attribute b/c we analyze all script // after the document has been parsed. @@ -588,23 +587,21 @@ pub const Page = struct { fn kind(stype: ?[]const u8) Kind { if (stype == null or stype.?.len == 0) return .javascript; if (std.mem.eql(u8, stype.?, "application/javascript")) return .javascript; - if (!std.mem.eql(u8, stype.?, "module")) return .module; + if (std.mem.eql(u8, stype.?, "module")) return .module; return .unknown; } fn eval(self: Script, alloc: std.mem.Allocator, env: Env, body: []const u8) !void { - switch (self.kind) { - .unknown => return error.UnknownScript, - .javascript => {}, - .module => {}, - } - var try_catch: jsruntime.TryCatch = undefined; try_catch.init(env); defer try_catch.deinit(); - const res = env.exec(body, self.src) catch { + const res = switch (self.kind) { + .unknown => return error.UnknownScript, + .javascript => env.exec(body, self.src), + .module => env.module(body, self.src), + } catch { if (try try_catch.err(alloc, env)) |msg| { defer alloc.free(msg); log.info("eval script {s}: {s}", .{ self.src, msg });