Skip to content

Commit 7202d75

Browse files
authored
Merge pull request #714 from lightpanda-io/live_scripts
Load dynamically added <script> tags
2 parents dab59ad + 20d0b4a commit 7202d75

File tree

3 files changed

+51
-26
lines changed

3 files changed

+51
-26
lines changed

src/browser/netsurf.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,10 @@ pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: [
21332133
return attr.?;
21342134
}
21352135

2136+
pub fn documentSetScriptAddedCallback(doc: *Document, ctx: *anyopaque, callback: c.dom_script_added_callback,) void {
2137+
c._dom_document_set_script_added_callback(doc, ctx, callback);
2138+
}
2139+
21362140
// DocumentHTML
21372141
pub const DocumentHTML = c.dom_html_document;
21382142

src/browser/page.zig

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)