diff --git a/src/browser/dom/comment.zig b/src/browser/dom/comment.zig index 26bc9e3a1..19aa9781d 100644 --- a/src/browser/dom/comment.zig +++ b/src/browser/dom/comment.zig @@ -30,7 +30,7 @@ pub const Comment = struct { pub fn constructor(data: ?[]const u8, state: *const SessionState) !*parser.Comment { return parser.documentCreateComment( - parser.documentHTMLToDocument(state.window.document.?), + parser.documentHTMLToDocument(state.window.document), data orelse "", ); } diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index 910737cba..e3bf4f6b3 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -41,12 +41,12 @@ pub const Document = struct { pub fn constructor(state: *const SessionState) !*parser.DocumentHTML { const doc = try parser.documentCreateDocument( - try parser.documentHTMLGetTitle(state.window.document.?), + try parser.documentHTMLGetTitle(state.window.document), ); // we have to work w/ document instead of html document. const ddoc = parser.documentHTMLToDocument(doc); - const ccur = parser.documentHTMLToDocument(state.window.document.?); + const ccur = parser.documentHTMLToDocument(state.window.document); try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur)); try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur)); diff --git a/src/browser/dom/document_fragment.zig b/src/browser/dom/document_fragment.zig index daff1009f..7bb0e5c1d 100644 --- a/src/browser/dom/document_fragment.zig +++ b/src/browser/dom/document_fragment.zig @@ -29,7 +29,7 @@ pub const DocumentFragment = struct { pub fn constructor(state: *const SessionState) !*parser.DocumentFragment { return parser.documentCreateDocumentFragment( - parser.documentHTMLToDocument(state.window.document.?), + parser.documentHTMLToDocument(state.window.document), ); } diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig index 77d527ea2..dd2a863d8 100644 --- a/src/browser/dom/element.zig +++ b/src/browser/dom/element.zig @@ -370,7 +370,7 @@ pub const Element = struct { pub fn _getBoundingClientRect(self: *parser.Element, state: *SessionState) !DOMRect { // Since we are lazy rendering we need to do this check. We could store the renderer in a viewport such that it could cache these, but it would require tracking changes. const root = try parser.nodeGetRootNode(parser.elementToNode(self)); - if (root != parser.documentToNode(parser.documentHTMLToDocument(state.window.document.?))) { + if (root != parser.documentToNode(parser.documentHTMLToDocument(state.window.document))) { return DOMRect{ .x = 0, .y = 0, .width = 0, .height = 0 }; } return state.renderer.getRect(self); @@ -381,7 +381,7 @@ pub const Element = struct { // Returns an empty array if the element is eventually detached from the main window pub fn _getClientRects(self: *parser.Element, state: *SessionState) ![]DOMRect { const root = try parser.nodeGetRootNode(parser.elementToNode(self)); - if (root != parser.documentToNode(parser.documentHTMLToDocument(state.window.document.?))) { + if (root != parser.documentToNode(parser.documentHTMLToDocument(state.window.document))) { return &.{}; } const heap_ptr = try state.call_arena.create(DOMRect); diff --git a/src/browser/dom/intersection_observer.zig b/src/browser/dom/intersection_observer.zig index 35ad8bbc8..0608b70d7 100644 --- a/src/browser/dom/intersection_observer.zig +++ b/src/browser/dom/intersection_observer.zig @@ -50,7 +50,7 @@ pub const IntersectionObserver = struct { // new IntersectionObserver(callback, options) [not supported yet] pub fn constructor(callback: Env.Callback, options_: ?IntersectionObserverOptions, state: *SessionState) !IntersectionObserver { var options = IntersectionObserverOptions{ - .root = parser.documentToNode(parser.documentHTMLToDocument(state.window.document.?)), + .root = parser.documentToNode(parser.documentHTMLToDocument(state.window.document)), .rootMargin = "0px 0px 0px 0px", .threshold = &.{0.0}, }; @@ -142,7 +142,7 @@ pub const IntersectionObserverEntry = struct { // Returns a DOMRectReadOnly for the intersection observer's root. pub fn get_rootBounds(self: *const IntersectionObserverEntry) !Element.DOMRect { const root = self.options.root.?; - if (@intFromPtr(root) == @intFromPtr(self.state.window.document.?)) { + if (@intFromPtr(root) == @intFromPtr(self.state.window.document)) { return self.state.renderer.boundingRect(); } diff --git a/src/browser/dom/processing_instruction.zig b/src/browser/dom/processing_instruction.zig index 97a8c6b89..ae4d93f6e 100644 --- a/src/browser/dom/processing_instruction.zig +++ b/src/browser/dom/processing_instruction.zig @@ -41,7 +41,7 @@ pub const ProcessingInstruction = struct { // a simple workaround. pub fn _cloneNode(self: *parser.ProcessingInstruction, _: ?bool, state: *SessionState) !*parser.ProcessingInstruction { return try parser.documentCreateProcessingInstruction( - @ptrCast(state.window.document.?), + @ptrCast(state.window.document), try get_target(self), (try get_data(self)) orelse "", ); diff --git a/src/browser/dom/text.zig b/src/browser/dom/text.zig index 57c380cd6..c12d34020 100644 --- a/src/browser/dom/text.zig +++ b/src/browser/dom/text.zig @@ -34,7 +34,7 @@ pub const Text = struct { pub fn constructor(data: ?[]const u8, state: *const SessionState) !*parser.Text { return parser.documentCreateTextNode( - parser.documentHTMLToDocument(state.window.document.?), + parser.documentHTMLToDocument(state.window.document), data orelse "", ); } diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index c5f6f5721..a46474c0b 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -255,10 +255,10 @@ pub const HTMLDocument = struct { // Thus we can add the HtmlHtmlElement and it's child HTMLBodyElement to the returned list. // TBD Should we instead return every parent that is an element? Note that a child does not physically need to be overlapping the parent. // Should we do a render pass on demand? - const doc_elem = try parser.documentGetDocumentElement(parser.documentHTMLToDocument(state.window.document.?)) orelse { + const doc_elem = try parser.documentGetDocumentElement(parser.documentHTMLToDocument(state.window.document)) orelse { return list.items; }; - if (try parser.documentHTMLBody(state.window.document.?)) |body| { + if (try parser.documentHTMLBody(state.window.document)) |body| { list.appendAssumeCapacity(try Element.toInterface(parser.bodyToElement(body))); } list.appendAssumeCapacity(try Element.toInterface(doc_elem)); @@ -383,12 +383,12 @@ test "Browser.HTML.Document" { .{ "document.readyState", "loading" }, }, .{}); - try HTMLDocument.documentIsLoaded(runner.window.document.?, &runner.state); + try HTMLDocument.documentIsLoaded(runner.window.document, &runner.state); try runner.testCases(&.{ .{ "document.readyState", "interactive" }, }, .{}); - try HTMLDocument.documentIsComplete(runner.window.document.?, &runner.state); + try HTMLDocument.documentIsComplete(runner.window.document, &runner.state); try runner.testCases(&.{ .{ "document.readyState", "complete" }, }, .{}); diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 10caf60eb..e903e06cb 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -44,7 +44,7 @@ pub const Window = struct { // Extend libdom event target for pure zig struct. base: parser.EventTargetTBase = parser.EventTargetTBase{}, - document: ?*parser.DocumentHTML = null, + document: *parser.DocumentHTML, target: []const u8 = "", history: History = .{}, location: Location = .{}, @@ -60,7 +60,13 @@ pub const Window = struct { performance: Performance, pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window { + var fbs = std.io.fixedBufferStream(""); + const html_doc = try parser.documentHTMLParse(fbs.reader(), "utf-8"); + const doc = parser.documentHTMLToDocument(html_doc); + try parser.documentSetDocumentURI(doc, "about:blank"); + return .{ + .document = html_doc, .target = target orelse "", .navigator = navigator orelse .{}, .performance = .{ .time_origin = try std.time.Timer.start() }, @@ -69,9 +75,7 @@ pub const Window = struct { pub fn replaceLocation(self: *Window, loc: Location) !void { self.location = loc; - if (self.document) |doc| { - try parser.documentHTMLSetLocation(Location, doc, &self.location); - } + try parser.documentHTMLSetLocation(Location, self.document, &self.location); } pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void { diff --git a/src/browser/page.zig b/src/browser/page.zig index f34604f31..92ab62d0f 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -60,8 +60,6 @@ pub const Page = struct { // Serves are the root object of our JavaScript environment window: Window, - doc: ?*parser.Document, - // The URL of the page url: URL, @@ -87,7 +85,6 @@ pub const Page = struct { self.* = .{ .window = try Window.create(null, null), .arena = arena, - .doc = null, .raw_data = null, .url = URL.empty, .session = session, @@ -121,15 +118,14 @@ pub const Page = struct { // dump writes the page content into the given file. pub fn dump(self: *const Page, out: std.fs.File) !void { - // if no HTML document pointer available, dump the data content only. - if (self.doc == null) { - // no data loaded, nothing to do. - if (self.raw_data == null) return; - return try out.writeAll(self.raw_data.?); + if (self.raw_data) |raw_data| { + // raw_data was set if the document was not HTML, dump the data content only. + return try out.writeAll(raw_data); } // if the page has a pointer to a document, dumps the HTML. - try Dump.writeHTML(self.doc.?, out); + const doc = parser.documentHTMLToDocument(self.window.document); + try Dump.writeHTML(doc, out); } pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) !?[]const u8 { @@ -183,6 +179,11 @@ pub const Page = struct { // if the url is about:blank, nothing to do. if (std.mem.eql(u8, "about:blank", request_url.raw)) { + var fbs = std.io.fixedBufferStream(""); + try self.loadHTMLDoc(fbs.reader(), "utf-8"); + // We do not processHTMLDoc here as we know we don't have any scripts + // This assumption may be false when CDP Page.addScriptToEvaluateOnNewDocument is implemented + try HTMLDocument.documentIsComplete(self.window.document, &self.state); return; } @@ -224,7 +225,9 @@ pub const Page = struct { } orelse .unknown; if (mime.isHTML()) { + self.raw_data = null; try self.loadHTMLDoc(&response, mime.charset orelse "utf-8"); + try self.processHTMLDoc(); } else { log.info("non-HTML document: {s}", .{content_type orelse "null"}); var arr: std.ArrayListUnmanaged(u8) = .{}; @@ -243,29 +246,13 @@ pub const Page = struct { // https://html.spec.whatwg.org/#read-html fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void { - const arena = self.arena; - log.debug("parse html with charset {s}", .{charset}); - const ccharset = try arena.dupeZ(u8, charset); + const ccharset = try self.arena.dupeZ(u8, charset); const html_doc = try parser.documentHTMLParse(reader, ccharset); const doc = parser.documentHTMLToDocument(html_doc); - // save a document's pointer in the page. - self.doc = doc; - - const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError; - try parser.eventTargetAddEventListener( - parser.toEventTarget(parser.Element, document_element), - "click", - &self.window_clicked_event_node, - false, - ); - - // TODO set document.readyState to interactive - // https://html.spec.whatwg.org/#reporting-document-loading-status - // inject the URL to the document including the fragment. try parser.documentSetDocumentURI(doc, self.url.raw); @@ -274,6 +261,19 @@ pub const Page = struct { self.window.setStorageShelf( try self.session.storage_shed.getOrPut(try self.origin(self.arena)), ); + } + + fn processHTMLDoc(self: *Page) !void { + const html_doc = self.window.document; + const doc = parser.documentHTMLToDocument(html_doc); + + const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError; + try parser.eventTargetAddEventListener( + parser.toEventTarget(parser.Element, document_element), + "click", + &self.window_clicked_event_node, + false, + ); // https://html.spec.whatwg.org/#read-html @@ -320,12 +320,12 @@ pub const Page = struct { // > parsing and evaluated as soon as it is available. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async if (script.is_async) { - try async_scripts.append(arena, script); + try async_scripts.append(self.arena, script); continue; } if (script.is_defer) { - try defer_scripts.append(arena, script); + try defer_scripts.append(self.arena, script); continue; } diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index e6425e076..79410c97f 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -59,7 +59,7 @@ fn getDocument(cmd: anytype) !void { const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; const page = bc.session.currentPage() orelse return error.PageNotLoaded; - const doc = page.doc orelse return error.DocumentNotLoaded; + const doc = parser.documentHTMLToDocument(page.window.document); const node = try bc.node_registry.register(parser.documentToNode(doc)); return cmd.sendResult(.{ .root = bc.nodeWriter(node, .{}) }, .{}); @@ -74,7 +74,7 @@ fn performSearch(cmd: anytype) !void { const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; const page = bc.session.currentPage() orelse return error.PageNotLoaded; - const doc = page.doc orelse return error.DocumentNotLoaded; + const doc = parser.documentHTMLToDocument(page.window.document); const allocator = cmd.cdp.allocator; var list = try css.querySelectorAll(allocator, parser.documentToNode(doc), params.query); diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index 7a6a936c7..ccb933347 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -123,7 +123,7 @@ const TestContext = struct { if (bc.session_id == null) bc.session_id = "SID-X"; parser.deinit(); const page = try bc.session.createPage(); - page.doc = (try Document.init(html)).doc; + page.window.document = (try Document.init(html)).doc; } return bc; } diff --git a/src/main_wpt.zig b/src/main_wpt.zig index 3ad2caf20..cd2b55362 100644 --- a/src/main_wpt.zig +++ b/src/main_wpt.zig @@ -116,7 +116,7 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *? try polyfill.load(arena, runner.scope); // loop over the scripts. - const doc = parser.documentHTMLToDocument(runner.state.window.document.?); + const doc = parser.documentHTMLToDocument(runner.state.window.document); const scripts = try parser.documentGetElementsByTagName(doc, "script"); const script_count = try parser.nodeListLength(scripts); for (0..script_count) |i| { diff --git a/src/testing.zig b/src/testing.zig index c9dc6ecd5..7db16d65c 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -207,7 +207,7 @@ pub const Random = struct { }; pub const Document = struct { - doc: *parser.Document, + doc: *parser.DocumentHTML, arena: std.heap.ArenaAllocator, pub fn init(html: []const u8) !Document { @@ -219,7 +219,7 @@ pub const Document = struct { return .{ .arena = std.heap.ArenaAllocator.init(allocator), - .doc = parser.documentHTMLToDocument(html_doc), + .doc = html_doc, }; } @@ -240,7 +240,7 @@ pub const Document = struct { } pub fn asNode(self: *const Document) *parser.Node { - return parser.documentToNode(self.doc); + return parser.documentHTMLToNode(self.doc); } }; diff --git a/src/url.zig b/src/url.zig index 3313eca5a..fb28c4afd 100644 --- a/src/url.zig +++ b/src/url.zig @@ -9,6 +9,7 @@ pub const URL = struct { raw: []const u8, pub const empty = URL{ .uri = .{ .scheme = "" }, .raw = "" }; + pub const about_blank = URL{ .uri = .{ .scheme = "" }, .raw = "about:blank" }; // We assume str will last as long as the URL // In some cases, this is safe to do, because we know the URL is short lived.