diff --git a/src/TestHTTPServer.zig b/src/TestHTTPServer.zig
index 85abaf06..21d9fa78 100644
--- a/src/TestHTTPServer.zig
+++ b/src/TestHTTPServer.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const URL = @import("browser/URL.zig");
const TestHTTPServer = @This();
@@ -97,7 +98,10 @@ fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !voi
}
pub fn sendFile(req: *std.http.Server.Request, file_path: []const u8) !void {
- var file = std.fs.cwd().openFile(file_path, .{}) catch |err| switch (err) {
+ var url_buf: [1024]u8 = undefined;
+ var fba = std.heap.FixedBufferAllocator.init(&url_buf);
+ const unescaped_file_path = try URL.unescape(fba.allocator(), file_path);
+ var file = std.fs.cwd().openFile(unescaped_file_path, .{}) catch |err| switch (err) {
error.FileNotFound => return req.respond("server error", .{ .status = .not_found }),
else => return err,
};
diff --git a/src/browser/Page.zig b/src/browser/Page.zig
index 6e80c2de..101cb522 100644
--- a/src/browser/Page.zig
+++ b/src/browser/Page.zig
@@ -346,7 +346,10 @@ pub fn deinit(self: *Page) void {
session.browser.env.destroyContext(self.js);
self._script_manager.shutdown = true;
- session.browser.http_client.abort();
+ if (self.parent == null) {
+ // only the root frame needs to abort this. It's more efficient this way
+ session.browser.http_client.abort();
+ }
self._script_manager.deinit();
if (comptime IS_DEBUG) {
@@ -796,7 +799,12 @@ fn pageDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
} orelse .unknown;
if (comptime IS_DEBUG) {
- log.debug(.page, "navigate first chunk", .{ .content_type = mime.content_type, .len = data.len, .type = self._type, .url = self.url });
+ log.debug(.page, "navigate first chunk", .{
+ .content_type = mime.content_type,
+ .len = data.len,
+ .type = self._type,
+ .url = self.url,
+ });
}
switch (mime.content_type) {
@@ -850,7 +858,11 @@ fn pageDoneCallback(ctx: *anyopaque) !void {
try self._session.navigation.commitNavigation(self);
defer if (comptime IS_DEBUG) {
- log.debug(.page, "page.load.complete", .{ .url = self.url, .type = self._type });
+ log.debug(.page, "page load complete", .{
+ .url = self.url,
+ .type = self._type,
+ .state = std.meta.activeTag(self._parse_state),
+ });
};
const parse_arena = try self.getArena(.{ .debug = "Page.parse" });
@@ -962,21 +974,28 @@ pub fn iframeAddedCallback(self: *Page, iframe: *Element.Html.IFrame) !void {
}
iframe._executed = true;
-
const session = self._session;
- const frame_id = session.nextFrameId();
+
+ // A frame can be re-navigated by setting the src.
+ const existing_window = iframe._content_window;
+
const page_frame = try self.arena.create(Page);
+ const frame_id = blk: {
+ if (existing_window) |w| {
+ const existing_frame_id = w._page._frame_id;
+ session.browser.http_client.abortFrame(existing_frame_id);
+ break :blk existing_frame_id;
+ }
+ break :blk session.nextFrameId();
+ };
+
try Page.init(page_frame, frame_id, session, self);
+ errdefer page_frame.deinit();
self._pending_loads += 1;
page_frame.iframe = iframe;
iframe._content_window = page_frame.window;
-
- self._session.notification.dispatch(.page_frame_created, &.{
- .frame_id = frame_id,
- .parent_id = self._frame_id,
- .timestamp = timestamp(.monotonic),
- });
+ errdefer iframe._content_window = null;
// navigate will dupe the url
const url = try URL.resolve(
@@ -986,6 +1005,15 @@ pub fn iframeAddedCallback(self: *Page, iframe: *Element.Html.IFrame) !void {
.{ .encode = true },
);
+ if (existing_window == null) {
+ // on first load, dispatch frame_created evnet
+ self._session.notification.dispatch(.page_frame_created, &.{
+ .page_id = page_id,
+ .parent_id = self.id,
+ .timestamp = timestamp(.monotonic),
+ });
+ }
+
page_frame.navigate(url, .{ .reason = .initialFrameNavigation }) catch |err| {
log.warn(.page, "iframe navigate failure", .{ .url = url, .err = err });
self._pending_loads -= 1;
@@ -994,6 +1022,25 @@ pub fn iframeAddedCallback(self: *Page, iframe: *Element.Html.IFrame) !void {
return error.IFrameLoadError;
};
+ if (existing_window) |w| {
+ const existing_page = w._page;
+ if (existing_page._parent_notified == false) {
+ self._pending_loads -= 1;
+ }
+
+ for (self.frames.items, 0..) |p, i| {
+ if (p == existing_page) {
+ self.frames.items[i] = page_frame;
+ break;
+ }
+ } else {
+ lp.assert(false, "Existing frame not found", .{ .len = self.frames.items.len });
+ }
+
+ existing_page.deinit();
+ return;
+ }
+
// window[N] is based on document order. For now we'll just append the frame
// at the end of our list and set frames_sorted == false. window.getFrame
// will check this flag to decide if it needs to sort the frames or not.
diff --git a/src/browser/tests/frames/frames.html b/src/browser/tests/frames/frames.html
index 1aa81b21..15b59677 100644
--- a/src/browser/tests/frames/frames.html
+++ b/src/browser/tests/frames/frames.html
@@ -11,6 +11,9 @@
diff --git a/src/browser/tests/frames/support/sub 1.html b/src/browser/tests/frames/support/sub 1.html
index f6b8ec4b..c516a6b7 100644
--- a/src/browser/tests/frames/support/sub 1.html
+++ b/src/browser/tests/frames/support/sub 1.html
@@ -3,4 +3,5 @@
diff --git a/src/browser/webapi/element/html/IFrame.zig b/src/browser/webapi/element/html/IFrame.zig
index 1a5498a3..e9cffc6e 100644
--- a/src/browser/webapi/element/html/IFrame.zig
+++ b/src/browser/webapi/element/html/IFrame.zig
@@ -58,6 +58,9 @@ pub fn setSrc(self: *IFrame, src: []const u8, page: *Page) !void {
try element.setAttributeSafe(comptime .wrap("src"), .wrap(src), page);
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse unreachable;
if (element.asNode().isConnected()) {
+ // unlike script, an iframe is reloaded every time the src is set
+ // even if it's set to the same URL.
+ self._executed = false;
try page.iframeAddedCallback(self);
}
}