Optimize about:blank loading in general and for frames specifically

Instead of going through the parser, just create / append the 3 elements.

iframe without a src automatically loads about:blank. This is important, because
the following is valid:

```js
const iframe = document.createElement('iframe');
document.documentElement.appendChild(iframe);

// documentElement should exist and should be the HTML of the blank page.
iframe.contentDocument.documentElement.appendChild(...);
```

Builds on top of https://github.com/lightpanda-io/browser/pull/1720
This commit is contained in:
Karl Seguin
2026-03-06 11:29:43 +08:00
parent bfe2065b9f
commit cabd62b48f
3 changed files with 63 additions and 29 deletions

View File

@@ -471,12 +471,10 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
// It's important to force a reset during the following navigation. // It's important to force a reset during the following navigation.
self._parse_state = .complete; self._parse_state = .complete;
{ self.document.injectBlank(self) catch |err| {
const parse_arena = try self.getArena(.{ .debug = "about:blank parse" }); log.err(.browser, "inject blank", .{ .err = err });
defer self.releaseArena(parse_arena); return error.InjectBlankFailed;
var parser = Parser.init(parse_arena, self.document.asNode(), self); };
parser.parse("<html><head></head><body></body></html>");
}
self.documentIsComplete(); self.documentIsComplete();
session.notification.dispatch(.page_navigate, &.{ session.notification.dispatch(.page_navigate, &.{
@@ -1029,9 +1027,9 @@ pub fn iframeAddedCallback(self: *Page, iframe: *IFrame) !void {
return; return;
} }
const src = iframe.asElement().getAttributeSafe(comptime .wrap("src")) orelse return; var src = iframe.asElement().getAttributeSafe(comptime .wrap("src")) orelse "";
if (src.len == 0) { if (src.len == 0) {
return; src = "about:blank";
} }
if (iframe._window != null) { if (iframe._window != null) {
@@ -2914,11 +2912,6 @@ fn nodeIsReady(self: *Page, comptime from_parser: bool, node: *Node) !void {
return err; return err;
}; };
} else if (node.is(IFrame)) |iframe| { } else if (node.is(IFrame)) |iframe| {
if ((comptime from_parser == false) and iframe._src.len == 0) {
// iframe was added via JavaScript, but without a src
return;
}
self.iframeAddedCallback(iframe) catch |err| { self.iframeAddedCallback(iframe) catch |err| {
log.err(.page, "page.nodeIsReady", .{ .err = err, .element = "iframe", .type = self._type, .url = self.url }); log.err(.page, "page.nodeIsReady", .{ .err = err, .element = "iframe", .type = self._type, .url = self.url });
return err; return err;

View File

@@ -7,46 +7,59 @@
} }
</script> </script>
<iframe id=f1 onload="frame1Onload" src="support/sub 1.html"></iframe> <iframe id=f0></iframe>
<iframe id=f1 onload="frame1Onload()" src="support/sub 1.html"></iframe>
<iframe id=f2 src="support/sub2.html"></iframe> <iframe id=f2 src="support/sub2.html"></iframe>
<script id=empty>
{
const blank = document.createElement('iframe');
testing.expectEqual(null, blank.contentDocument);
document.documentElement.appendChild(blank);
testing.expectEqual('<html><head></head><body></body></html>', blank.contentDocument.documentElement.outerHTML);
const f0 = $('#f0')
testing.expectEqual('<html><head></head><body></body></html>', f0.contentDocument.documentElement.outerHTML);
}
</script>
<script id="basic"> <script id="basic">
// reload it // reload it
$('#f2').src = 'support/sub2.html'; $('#f2').src = 'support/sub2.html';
testing.expectEqual(true, true); testing.expectEqual(true, true);
testing.eventually(() => { testing.eventually(() => {
testing.expectEqual(undefined, window[10]); testing.expectEqual(undefined, window[20]);
testing.expectEqual(window, window[0].top);
testing.expectEqual(window, window[0].parent);
testing.expectEqual(false, window === window[0]);
testing.expectEqual(window, window[1].top); testing.expectEqual(window, window[1].top);
testing.expectEqual(window, window[1].parent); testing.expectEqual(window, window[1].parent);
testing.expectEqual(false, window === window[1]); testing.expectEqual(false, window === window[1]);
testing.expectEqual(false, window[0] === window[1]);
testing.expectEqual(window, window[2].top);
testing.expectEqual(window, window[2].parent);
testing.expectEqual(false, window === window[2]);
testing.expectEqual(false, window[1] === window[2]);
testing.expectEqual(0, $('#f1').childNodes.length); testing.expectEqual(0, $('#f1').childNodes.length);
testing.expectEqual(testing.BASE_URL + 'frames/support/sub%201.html', $('#f1').src); testing.expectEqual(testing.BASE_URL + 'frames/support/sub%201.html', $('#f1').src);
testing.expectEqual(window[0], $('#f1').contentWindow); testing.expectEqual(window[1], $('#f1').contentWindow);
testing.expectEqual(window[1], $('#f2').contentWindow); testing.expectEqual(window[2], $('#f2').contentWindow);
testing.expectEqual(window[0].document, $('#f1').contentDocument); testing.expectEqual(window[1].document, $('#f1').contentDocument);
testing.expectEqual(window[1].document, $('#f2').contentDocument); testing.expectEqual(window[2].document, $('#f2').contentDocument);
// sibling frames share the same top // sibling frames share the same top
testing.expectEqual(window[0].top, window[1].top); testing.expectEqual(window[1].top, window[2].top);
// child frames have no sub-frames // child frames have no sub-frames
testing.expectEqual(0, window[0].length);
testing.expectEqual(0, window[1].length); testing.expectEqual(0, window[1].length);
testing.expectEqual(0, window[2].length);
// self and window are self-referential on child frames // self and window are self-referential on child frames
testing.expectEqual(window[0], window[0].self);
testing.expectEqual(window[0], window[0].window);
testing.expectEqual(window[1], window[1].self); testing.expectEqual(window[1], window[1].self);
testing.expectEqual(window[1], window[1].window);
testing.expectEqual(window[2], window[2].self);
// child frame's top.parent is itself (root has no parent) // child frame's top.parent is itself (root has no parent)
testing.expectEqual(window, window[0].top.parent); testing.expectEqual(window, window[0].top.parent);
@@ -127,6 +140,6 @@
<script id=count> <script id=count>
testing.eventually(() => { testing.eventually(() => {
testing.expectEqual(6, window.length); testing.expectEqual(8, window.length);
}); });
</script> </script>

View File

@@ -40,6 +40,8 @@ const Selection = @import("Selection.zig");
pub const XMLDocument = @import("XMLDocument.zig"); pub const XMLDocument = @import("XMLDocument.zig");
pub const HTMLDocument = @import("HTMLDocument.zig"); pub const HTMLDocument = @import("HTMLDocument.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
const Document = @This(); const Document = @This();
_type: Type, _type: Type,
@@ -937,6 +939,32 @@ fn validateElementName(name: []const u8) !void {
} }
} }
// When a page or frame's URL is about:blank, or as soon as a frame is
// programmatically created, it has this default "blank" content
pub fn injectBlank(self: *Document, page: *Page) error{InjectBlankError}!void {
self._injectBlank(page) catch |err| {
// we wrap _injectBlank like this so that injectBlank can only return an
// InjectBlankError. injectBlank is used in when nodes are inserted
// as since it inserts node itself, Zig can't infer the error set.
log.err(.browser, "inject blank", .{ .err = err });
return error.InjectBlankError;
};
}
fn _injectBlank(self: *Document, page: *Page) !void {
if (comptime IS_DEBUG) {
// should only be called on an empty document
std.debug.assert(self.asNode()._children == null);
}
const html = try page.createElementNS(.html, "html", null);
const head = try page.createElementNS(.html, "head", null);
const body = try page.createElementNS(.html, "body", null);
try page.appendNode(html, head, .{});
try page.appendNode(html, body, .{});
try page.appendNode(self.asNode(), html, .{});
}
const ReadyState = enum { const ReadyState = enum {
loading, loading,
interactive, interactive,