diff --git a/src/browser/Page.zig b/src/browser/Page.zig
index 435685c7..9075c2a5 100644
--- a/src/browser/Page.zig
+++ b/src/browser/Page.zig
@@ -723,23 +723,13 @@ pub fn documentIsComplete(self: *Page) void {
fn _documentIsComplete(self: *Page) !void {
self.document._ready_state = .complete;
+ // Run load events before window.load.
+ try self.dispatchLoad();
+
var ls: JS.Local.Scope = undefined;
self.js.localScope(&ls);
defer ls.deinit();
- {
- // Dispatch `_to_load` events before window.load.
- const has_dom_load_listener = self._event_manager.has_dom_load_listener;
- for (self._to_load.items) |html_element| {
- if (has_dom_load_listener or html_element.hasAttributeFunction(.onload, self)) {
- const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
- try self._event_manager.dispatch(html_element.asEventTarget(), event);
- }
- }
- }
- // `_to_load` can be cleaned here.
- self._to_load.clearAndFree(self.arena);
-
// Dispatch window.load event.
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
// This event is weird, it's dispatched directly on the window, but
@@ -1217,6 +1207,18 @@ pub fn checkIntersections(self: *Page) !void {
}
}
+pub fn dispatchLoad(self: *Page) !void {
+ const has_dom_load_listener = self._event_manager.has_dom_load_listener;
+ for (self._to_load.items) |html_element| {
+ if (has_dom_load_listener or html_element.hasAttributeFunction(.onload, self)) {
+ const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
+ try self._event_manager.dispatch(html_element.asEventTarget(), event);
+ }
+ }
+ // We drained everything.
+ self._to_load.clearRetainingCapacity();
+}
+
pub fn scheduleMutationDelivery(self: *Page) !void {
if (self._mutation_delivery_scheduled) {
return;
@@ -2842,6 +2844,16 @@ fn nodeIsReady(self: *Page, comptime from_parser: bool, node: *Node) !void {
log.err(.page, "page.nodeIsReady", .{ .err = err, .element = "iframe", .type = self._type, .url = self.url });
return err;
};
+ } else if (node.is(Element.Html.Link)) |link| {
+ link.linkAddedCallback(self) catch |err| {
+ log.err(.page, "page.nodeIsReady", .{ .err = err, .element = "link", .type = self._type });
+ return error.LinkLoadError;
+ };
+ } else if (node.is(Element.Html.Style)) |style| {
+ style.styleAddedCallback(self) catch |err| {
+ log.err(.page, "page.nodeIsReady", .{ .err = err, .element = "style", .type = self._type });
+ return error.StyleLoadError;
+ };
}
}
diff --git a/src/browser/Session.zig b/src/browser/Session.zig
index 18468933..540ba520 100644
--- a/src/browser/Session.zig
+++ b/src/browser/Session.zig
@@ -241,6 +241,9 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
// it AFTER.
const ms_to_next_task = try browser.runMacrotasks();
+ // Each call to this runs scheduled load events.
+ try page.dispatchLoad();
+
const http_active = http_client.active;
const total_network_activity = http_active + http_client.intercepted;
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
diff --git a/src/browser/tests/element/html/image.html b/src/browser/tests/element/html/image.html
index e7868229..92cd947d 100644
--- a/src/browser/tests/element/html/image.html
+++ b/src/browser/tests/element/html/image.html
@@ -114,48 +114,15 @@
}
-
-
-
-
-
+
+
+
+
diff --git a/src/browser/tests/element/html/link.html b/src/browser/tests/element/html/link.html
index 2031e5fe..bed5e6ab 100644
--- a/src/browser/tests/element/html/link.html
+++ b/src/browser/tests/element/html/link.html
@@ -19,3 +19,68 @@
l2.crossOrigin = '';
testing.expectEqual('anonymous', l2.crossOrigin);
+
+
+
+
+
+
+
+
diff --git a/src/browser/tests/element/html/style.html b/src/browser/tests/element/html/style.html
index 713d8e2e..8abbb229 100644
--- a/src/browser/tests/element/html/style.html
+++ b/src/browser/tests/element/html/style.html
@@ -106,3 +106,28 @@
testing.expectEqual(true, style.disabled);
}
+
+
diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig
index 9a413170..7849622a 100644
--- a/src/browser/webapi/Node.zig
+++ b/src/browser/webapi/Node.zig
@@ -722,6 +722,8 @@ const CloneError = error{
CloneError,
IFrameLoadError,
TooManyContexts,
+ LinkLoadError,
+ StyleLoadError,
};
pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) CloneError!*Node {
const deep = deep_ orelse false;
diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig
index 1e33b3c0..c8fba91e 100644
--- a/src/browser/webapi/element/html/Image.zig
+++ b/src/browser/webapi/element/html/Image.zig
@@ -50,7 +50,10 @@ pub fn getSrc(self: *const Image, page: *Page) ![]const u8 {
}
pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void {
- try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), page);
+ const element = self.asElement();
+ try element.setAttributeSafe(comptime .wrap("src"), .wrap(value), page);
+ // No need to check if `Image` is connected to DOM; this is a special case.
+ return self.imageAddedCallback(page);
}
pub fn getAlt(self: *const Image) []const u8 {
@@ -120,6 +123,21 @@ pub fn getComplete(_: *const Image) bool {
return true;
}
+/// Used in `Page.nodeIsReady`.
+pub fn imageAddedCallback(self: *Image, page: *Page) !void {
+ // if we're planning on navigating to another page, don't trigger load event.
+ if (page.isGoingAway()) {
+ return;
+ }
+
+ const element = self.asElement();
+ // Exit if src not set.
+ const src = element.getAttributeSafe(comptime .wrap("src")) orelse return;
+ if (src.len == 0) return;
+
+ try page._to_load.append(page.arena, self._proto);
+}
+
pub const JsApi = struct {
pub const bridge = js.Bridge(Image);
@@ -145,13 +163,7 @@ pub const JsApi = struct {
pub const Build = struct {
pub fn created(node: *Node, page: *Page) !void {
const self = node.as(Image);
- const image = self.asElement();
- // Exit if src not set.
- // TODO: We might want to check if src point to valid image.
- _ = image.getAttributeSafe(comptime .wrap("src")) orelse return;
-
- // Push to `_to_load` to dispatch load event just before window load event.
- return page._to_load.append(page.arena, self._proto);
+ return self.imageAddedCallback(page);
}
};
diff --git a/src/browser/webapi/element/html/Link.zig b/src/browser/webapi/element/html/Link.zig
index 7f6b48fc..fe56bd89 100644
--- a/src/browser/webapi/element/html/Link.zig
+++ b/src/browser/webapi/element/html/Link.zig
@@ -50,7 +50,12 @@ pub fn getHref(self: *Link, page: *Page) ![]const u8 {
}
pub fn setHref(self: *Link, value: []const u8, page: *Page) !void {
- try self.asElement().setAttributeSafe(comptime .wrap("href"), .wrap(value), page);
+ const element = self.asElement();
+ try element.setAttributeSafe(comptime .wrap("href"), .wrap(value), page);
+
+ if (element.asNode().isConnected()) {
+ try self.linkAddedCallback(page);
+ }
}
pub fn getRel(self: *Link) []const u8 {
@@ -81,6 +86,24 @@ pub fn setCrossOrigin(self: *Link, value: []const u8, page: *Page) !void {
return self.asElement().setAttributeSafe(comptime .wrap("crossOrigin"), .wrap(normalized), page);
}
+pub fn linkAddedCallback(self: *Link, page: *Page) !void {
+ // if we're planning on navigating to another page, don't trigger load event.
+ if (page.isGoingAway()) {
+ return;
+ }
+
+ const element = self.asElement();
+ // Exit if rel not set.
+ const rel = element.getAttributeSafe(comptime .wrap("rel")) orelse return;
+ // Exit if rel is not stylesheet.
+ if (!std.mem.eql(u8, rel, "stylesheet")) return;
+ // Exit if href not set.
+ const href = element.getAttributeSafe(comptime .wrap("href")) orelse return;
+ if (href.len == 0) return;
+
+ try page._to_load.append(page.arena, self._proto);
+}
+
pub const JsApi = struct {
pub const bridge = js.Bridge(Link);
diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig
index 3dbb288b..131b7634 100644
--- a/src/browser/webapi/element/html/Style.zig
+++ b/src/browser/webapi/element/html/Style.zig
@@ -97,6 +97,15 @@ pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet {
return sheet;
}
+pub fn styleAddedCallback(self: *Style, page: *Page) !void {
+ // if we're planning on navigating to another page, don't trigger load event.
+ if (page.isGoingAway()) {
+ return;
+ }
+
+ try page._to_load.append(page.arena, self._proto);
+}
+
pub const JsApi = struct {
pub const bridge = js.Bridge(Style);
@@ -113,13 +122,6 @@ pub const JsApi = struct {
pub const sheet = bridge.accessor(Style.getSheet, null, .{});
};
-pub const Build = struct {
- pub fn created(node: *Node, page: *Page) !void {
- // Push to `_to_load` to dispatch load event just before window load event.
- return page._to_load.append(page.arena, node.as(Element.Html));
- }
-};
-
const testing = @import("../../../../testing.zig");
test "WebApi: Style" {
try testing.htmlRunner("element/html/style.html", .{});