mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
page: replay POST method/body/header on Page.reload
doReload built a NavigateOpts with only url + kind=.reload; method/body/header defaulted to GET/null/null, so any prior POST navigation regressed to a GET on reload. The HTML reload navigation re-fetches the document that produced the current entry, and Chrome replays the same HTTP request that loaded the page (including method, body, and Content-Type) — Lightpanda dropped all three. Retain the prior request body and content-type header in Frame.NavigatedOpts (duped into the frame arena), and have doReload capture them into the CDP command's arena before replacePage() frees the old frame. The reload's frame.navigate call carries the replayed method/body/header so the request the page was loaded with is the request that runs again. Closes #2258
This commit is contained in:
@@ -614,6 +614,8 @@ pub fn navigate(self: *Frame, request_url: [:0]const u8, opts: NavigateOpts) !vo
|
||||
.cdp_id = opts.cdp_id,
|
||||
.reason = opts.reason,
|
||||
.method = opts.method,
|
||||
.body = if (opts.body) |b| try self.arena.dupe(u8, b) else null,
|
||||
.header = if (opts.header) |h| try self.arena.dupeZ(u8, h) else null,
|
||||
};
|
||||
|
||||
var headers = try http_client.newHeaders();
|
||||
@@ -3443,6 +3445,10 @@ pub const NavigatedOpts = struct {
|
||||
cdp_id: ?i64 = null,
|
||||
reason: NavigateReason = .address_bar,
|
||||
method: HttpClient.Method = .GET,
|
||||
// Retained on the frame's arena so Page.reload can replay the prior
|
||||
// navigation's HTTP method — matches Chrome's F5 behavior on POST pages.
|
||||
body: ?[]const u8 = null,
|
||||
header: ?[:0]const u8 = null,
|
||||
};
|
||||
|
||||
const NavigationType = enum {
|
||||
|
||||
@@ -333,8 +333,20 @@ fn doReload(cmd: *CDP.Command) !void {
|
||||
const session = bc.session;
|
||||
var frame = session.currentFrame() orelse return error.FrameNotLoaded;
|
||||
|
||||
// Dupe URL before replacePage() frees the old frame's arena.
|
||||
// Capture URL plus the prior navigation's method/body/header before
|
||||
// replacePage() frees the old frame's arena. Replaying the same HTTP
|
||||
// method on reload matches Chrome's F5 behavior — POST navigations
|
||||
// re-submit, GET navigations re-fetch.
|
||||
const reload_url = try cmd.arena.dupeZ(u8, frame.url);
|
||||
const prev_nav = frame._navigated_options;
|
||||
const prev_body: ?[]const u8 = if (prev_nav) |p|
|
||||
if (p.body) |b| try cmd.arena.dupe(u8, b) else null
|
||||
else
|
||||
null;
|
||||
const prev_header: ?[:0]const u8 = if (prev_nav) |p|
|
||||
if (p.header) |h| try cmd.arena.dupeZ(u8, h) else null
|
||||
else
|
||||
null;
|
||||
|
||||
if (frame._load_state != .waiting) {
|
||||
// Reset isolated world identities to disable V8 weak callbacks before
|
||||
@@ -351,6 +363,9 @@ fn doReload(cmd: *CDP.Command) !void {
|
||||
.cdp_id = cmd.input.id,
|
||||
.kind = .reload,
|
||||
.force = if (params) |p| p.ignoreCache orelse false else false,
|
||||
.method = if (prev_nav) |p| p.method else .GET,
|
||||
.body = prev_body,
|
||||
.header = prev_header,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1003,6 +1018,61 @@ test "cdp.frame: reload" {
|
||||
}
|
||||
}
|
||||
|
||||
test "cdp.frame: reload replays POST navigation" {
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
// Manually wire up the browser context: loadBrowserContext only does GET
|
||||
// navigations, but we need the first navigation to be POST.
|
||||
const cdp_inst = ctx.cdp();
|
||||
_ = try cdp_inst.createBrowserContext();
|
||||
var bc = &cdp_inst.browser_context.?;
|
||||
bc.id = "BID-A6";
|
||||
bc.session_id = "SID-X";
|
||||
bc.target_id = "TID-A6-0000000".*;
|
||||
|
||||
// First navigation: POST a form-style payload to /echo_method.
|
||||
{
|
||||
const f = try bc.session.createPage();
|
||||
try f.navigate("http://127.0.0.1:9582/echo_method", .{
|
||||
.method = .POST,
|
||||
.body = "key=value",
|
||||
.header = "Content-Type: application/x-www-form-urlencoded",
|
||||
});
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
}
|
||||
|
||||
// Sanity: the body confirms a POST round-tripped.
|
||||
{
|
||||
const f = bc.session.currentFrame() orelse unreachable;
|
||||
var ls: js.Local.Scope = undefined;
|
||||
f.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const v = try ls.local.exec("document.body.innerText.includes('method=POST')", null);
|
||||
try testing.expect(v.toBool());
|
||||
}
|
||||
|
||||
// Trigger a CDP reload. With the fix in place, doReload captures the
|
||||
// prior POST method/body/header and replays them. Without it (regression
|
||||
// guard), the second request would silently fall back to GET.
|
||||
try ctx.processMessage(.{ .id = 50, .method = "Page.reload" });
|
||||
|
||||
{
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
}
|
||||
|
||||
{
|
||||
const f = bc.session.currentFrame() orelse unreachable;
|
||||
var ls: js.Local.Scope = undefined;
|
||||
f.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const v = try ls.local.exec("document.body.innerText.includes('method=POST')", null);
|
||||
try testing.expect(v.toBool());
|
||||
}
|
||||
}
|
||||
|
||||
test "cdp.frame: addScriptToEvaluateOnNewDocument" {
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
@@ -636,6 +636,19 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/echo_method")) {
|
||||
// Echo the request method back as HTML so tests can assert on what
|
||||
// method the navigation used. Used by the Page.reload-replays-POST test.
|
||||
const method_name = @tagName(req.head.method);
|
||||
var html_buf: [128]u8 = undefined;
|
||||
const html = try std.fmt.bufPrint(&html_buf, "<html><body>method={s}</body></html>", .{method_name});
|
||||
return req.respond(html, .{
|
||||
.extra_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.startsWith(u8, path, "/src/browser/tests/")) {
|
||||
// strip off leading / so that it's relative to CWD
|
||||
return TestHTTPServer.sendFile(req, path[1..]);
|
||||
|
||||
Reference in New Issue
Block a user