Add document.readyState

To support this, add the ability to embedded data into libdom nodes, so that
we can extend libdom without having to alter it.
This commit is contained in:
Karl Seguin
2025-05-19 10:54:37 +08:00
parent a99d193b12
commit 2b6cf95752
5 changed files with 67 additions and 10 deletions

View File

@@ -62,4 +62,16 @@ pub const SessionState = struct {
// shorter-lived than the arena above, which
// exists for the entire rendering of the page
call_arena: std.mem.Allocator = undefined,
pub fn getNodeWrapper(self: *SessionState, comptime T: type, node: *parser.Node) !*T {
if (parser.nodeGetEmbedderData(node)) |wrap| {
return @alignCast(@ptrCast(wrap));
}
const wrap = try self.arena.create(T);
wrap.* = T{};
parser.nodeSetEmbedderData(node, wrap);
return wrap;
}
};

View File

@@ -36,6 +36,14 @@ pub const HTMLDocument = struct {
pub const prototype = *Document;
pub const subtype = .node;
ready_state: ReadyState = .loading,
const ReadyState = enum {
loading,
interactive,
complete,
};
// JS funcs
// --------
@@ -176,6 +184,11 @@ pub const HTMLDocument = struct {
return state.window;
}
pub fn get_readyState(node: *parser.DocumentHTML, state: *SessionState) ![]const u8 {
const self = try state.getNodeWrapper(HTMLDocument, @ptrCast(node));
return @tagName(self.ready_state);
}
// noop legacy functions
// https://html.spec.whatwg.org/#Document-partial
pub fn _clear(_: *parser.DocumentHTML) void {}
@@ -212,6 +225,22 @@ pub const HTMLDocument = struct {
pub fn set_bgColor(_: *parser.DocumentHTML, _: []const u8) []const u8 {
return "";
}
pub fn documentIsLoaded(html_doc: *parser.DocumentHTML, state: *SessionState) !void {
const self = try state.getNodeWrapper(HTMLDocument, @ptrCast(html_doc));
self.ready_state = .interactive;
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, html_doc), evt);
}
pub fn documentIsComplete(html_doc: *parser.DocumentHTML, state: *SessionState) !void {
const self = try state.getNodeWrapper(HTMLDocument, @ptrCast(html_doc));
self.ready_state = .complete;
}
};
// Tests
@@ -276,4 +305,18 @@ test "Browser.HTML.Document" {
try runner.testCases(&.{
.{ "document.defaultView.document == document", "true" },
}, .{});
try runner.testCases(&.{
.{ "document.readyState", "loading" },
}, .{});
try HTMLDocument.documentIsLoaded(runner.state.document.?, &runner.state);
try runner.testCases(&.{
.{ "document.readyState", "interactive" },
}, .{});
try HTMLDocument.documentIsComplete(runner.state.document.?, &runner.state);
try runner.testCases(&.{
.{ "document.readyState", "complete" },
}, .{});
}

View File

@@ -1274,6 +1274,14 @@ pub fn nodeGetPrefix(node: *Node) !?[]const u8 {
return strToData(s.?);
}
pub fn nodeGetEmbedderData(node: *Node) ?*anyopaque {
return c._dom_node_get_embedder_data(node);
}
pub fn nodeSetEmbedderData(node: *Node, data: *anyopaque) void {
c._dom_node_set_embedder_data(node, data);
}
// nodeToElement is an helper to convert a node to an element.
pub inline fn nodeToElement(node: *Node) *Element {
return @as(*Element, @ptrCast(node));

View File

@@ -31,6 +31,7 @@ const Window = @import("html/window.zig").Window;
const Walker = @import("dom/walker.zig").WalkerDepthFirst;
const Env = @import("env.zig").Env;
const Loop = @import("../runtime/loop.zig").Loop;
const HTMLDocument = @import("html/document.zig").HTMLDocument;
const URL = @import("../url.zig").URL;
@@ -351,16 +352,11 @@ pub const Page = struct {
self.evalScript(&s) catch |err| log.warn("evaljs: {any}", .{err});
try parser.documentHTMLSetCurrentScript(html_doc, null);
}
// dispatch DOMContentLoaded before the transition to "complete",
// at the point where all subresources apart from async script elements
// have loaded.
// https://html.spec.whatwg.org/#reporting-document-loading-status
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, html_doc), evt);
try HTMLDocument.documentIsLoaded(html_doc, &self.state);
// eval async scripts.
for (async_scripts.items) |s| {
@@ -369,9 +365,7 @@ pub const Page = struct {
try parser.documentHTMLSetCurrentScript(html_doc, null);
}
// TODO wait for async scripts
// TODO set document.readyState to complete
try HTMLDocument.documentIsComplete(html_doc, &self.state);
// dispatch window.load event
const loadevt = try parser.eventCreate();