From 2b6cf95752c78a245d692f37424e426e72ba0e6d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 19 May 2025 10:54:37 +0800 Subject: [PATCH] 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. --- src/browser/env.zig | 12 ++++++++++ src/browser/html/document.zig | 43 +++++++++++++++++++++++++++++++++++ src/browser/netsurf.zig | 8 +++++++ src/browser/page.zig | 12 +++------- vendor/netsurf/libdom | 2 +- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/browser/env.zig b/src/browser/env.zig index 3269255f..70724fed 100644 --- a/src/browser/env.zig +++ b/src/browser/env.zig @@ -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; + } }; diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index 82617af4..7d5b7a6c 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -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" }, + }, .{}); } diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 6e7dbeb6..edf02e6a 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -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)); diff --git a/src/browser/page.zig b/src/browser/page.zig index e2b0b914..79cf372f 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -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(); diff --git a/vendor/netsurf/libdom b/vendor/netsurf/libdom index b2c17b14..b0f4a431 160000 --- a/vendor/netsurf/libdom +++ b/vendor/netsurf/libdom @@ -1 +1 @@ -Subproject commit b2c17b1476d1bb273d9e92eae32ae576998465cf +Subproject commit b0f4a4314711ed9a91bf3f40f0e0354b8f4706f2