Ensure about:blank frame visibility

Our navigate on about:blank short-circuits most of the complex logic and loads
the content then and there, not asynchronously. This results in notifications
for the frame navigation/creation that happen immediately. With the current
code ordering though, the frame isn't entered in child_frames until AFTER
navigation, resulting in these events trigger on a frame which can't be found
via Session.findFrameByFrameId

This code adds the frame to child_frames BEFORE navigate (and removes it on
error).

Note: about:blank iframe navigation is more common than you probably think. As
soon as a frame is [dynamically] created, it navigates to about:blank, e.g.:

let iframe = document.createElement('iframe');
// IMPLICIT navigation to about:blank happens here

// and this will be another navigation event
iframe.src = "keemun.php";
This commit is contained in:
Karl Seguin
2026-05-21 12:33:50 +08:00
parent e4171bc694
commit ca0eaa5f1e

View File

@@ -1379,6 +1379,11 @@ pub fn iframeAddedCallback(self: *Frame, iframe: *IFrame) !void {
);
};
// Append the new frame before navigate() so synchronous navigation paths
// (about:blank, blob:) and the notifications they dispatch can see this
// frame in self.child_frames.
try self.child_frames.append(self.arena, new_frame);
// Iframe's initial src request carries the parent's URL as Referer and
// as the SameSite initiator. Parent frame outlives this navigate()
// call, so the slice is safe.
@@ -1388,20 +1393,22 @@ pub fn iframeAddedCallback(self: *Frame, iframe: *IFrame) !void {
.referer = parent_url,
.initiator_url = parent_url,
}) catch |err| {
// extra defensive..maybe navigate added a new fame, and the index it
// was added at was removed. Or maybe this frame was removed somehow
// (which I don't think is possible)
if (std.mem.indexOfScalar(*Frame, self.child_frames.items, new_frame)) |idx| {
_ = self.child_frames.swapRemove(idx);
}
log.warn(.frame, "iframe navigate failure", .{ .url = url, .err = err });
self._pending_loads -= 1;
iframe._window = null;
return error.IFrameLoadError;
};
// window[N] is based on document order. For now we'll just append the frame
// at the end of our list and set child_frames_sorted == false. window.getFrame
// will check this flag to decide if it needs to sort the frames or not.
// But, we can optimize this a bit. Since we expect frames to often be
// added in document order, we can do a quick check to see whether the list
// is sorted or not.
try self.child_frames.append(self.arena, new_frame);
// window[N] is based on document order. We appended above and rely on
// child_frames_sorted to tell window.getFrame whether it has to sort.
// Since we expect frames to often be added in document order, do a quick
// check to keep the list flagged as sorted when possible.
const frames_len = self.child_frames.items.len;
if (frames_len == 1) {
// this is the only frame, it must be sorted.