diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index 694e091f..92b19535 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -278,15 +278,17 @@ pub const HTMLDocument = struct { const state = try page.getOrCreateNodeState(@alignCast(@ptrCast(self))); state.ready_state = .interactive; - const evt = try parser.eventCreate(); - defer parser.eventDestroy(evt); - log.debug(.script_event, "dispatch event", .{ .type = "DOMContentLoaded", .source = "document", }); + + const evt = try parser.eventCreate(); + defer parser.eventDestroy(evt); try parser.eventInit(evt, "DOMContentLoaded", .{ .bubbles = true, .cancelable = true }); _ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, self), evt); + + try page.window.dispatchForDocumentTarget(evt); } pub fn documentIsComplete(self: *parser.DocumentHTML, page: *Page) !void { diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 0a8494b6..ef688b52 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -334,6 +334,23 @@ pub const Window = struct { ); } } + + // libdom's document doesn't have a parent, which is correct, but + // breaks the event bubbling that happens for many events from + // document -> window. + // We need to force dispatch this event on the window, with the + // document target. + // In theory, we should do this for a lot of events and might need + // to come up with a good way to solve this more generically. But + // this specific event, and maybe a few others in the near future, + // are blockers. + // Worth noting that NetSurf itself appears to do something similar: + // https://github.com/netsurf-browser/netsurf/blob/a32e1a03e1c91ee9f0aa211937dbae7a96831149/content/handlers/html/html.c#L380 + pub fn dispatchForDocumentTarget(self: *Window, evt: *parser.Event) !void { + // we assume that this evt has already been dispatched on the document + // and thus the target has already been set to the document. + return self.base.redispatchEvent(evt); + } }; const TimerCallback = struct { @@ -491,4 +508,21 @@ test "Browser.HTML.Window" { .{ "var qm = false; window.queueMicrotask(() => {qm = true });", null }, .{ "qm", "true" }, }, .{}); + + { + try runner.testCases(&.{ + .{ + \\ let dcl = false; + \\ window.addEventListener('DOMContentLoaded', (e) => { + \\ dcl = e.target == document; + \\ }); + , + null, + }, + }, .{}); + try runner.dispatchDOMContentLoaded(); + try runner.testCases(&.{ + .{ "dcl", "true" }, + }, .{}); + } } diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index f0af5280..506e0358 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -846,6 +846,14 @@ pub const EventTargetTBase = extern struct { internal_type_.* = @intFromEnum(self.internal_target_type); return c.DOM_NO_ERR; } + + // Called to simulate bubbling from a libdom node (e.g. the Document) to a + // Zig instance (e.g. the Window). + pub fn redispatchEvent(self: *EventTargetTBase, evt: *Event) !void { + var res: bool = undefined; + const err = c._dom_event_target_dispatch(@ptrCast(self), &self.eti, evt, c.DOM_BUBBLING_PHASE, &res); + try DOMErr(err); + } }; // MouseEvent diff --git a/src/testing.zig b/src/testing.zig index f05f9023..65d24f8d 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -474,6 +474,12 @@ pub const JsRunner = struct { return err; }; } + + pub fn dispatchDOMContentLoaded(self: *JsRunner) !void { + const HTMLDocument = @import("browser/html/document.zig").HTMLDocument; + const html_doc = self.page.window.document; + try HTMLDocument.documentIsLoaded(html_doc, self.page); + } }; const RunnerOpts = struct {