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 {