diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 6815bc349..e251eee6d 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -175,8 +175,12 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest } async requestSucceededWithResponse(request: FetchRequest, response: FetchResponse) { - await this.loadResponse(response) - this.resolveVisitPromise() + if (response.redirected && response.header("Turbo-Frame") == "_top") { + session.visit(response.location) + } else { + await this.loadResponse(response) + this.resolveVisitPromise() + } } requestFailedWithResponse(request: FetchRequest, response: FetchResponse) { @@ -201,8 +205,13 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest } formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) { - const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter) - frame.delegate.loadResponse(response) + if (response.redirected && response.header("Turbo-Frame") == "_top") { + session.view.clearSnapshotCache() + session.visit(response.location) + } else { + const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter) + frame.delegate.loadResponse(response) + } } formSubmissionFailedWithResponse(formSubmission: FormSubmission, fetchResponse: FetchResponse) { diff --git a/src/tests/fixtures/form.html b/src/tests/fixtures/form.html index 1729d7a70..d34a96007 100644 --- a/src/tests/fixtures/form.html +++ b/src/tests/fixtures/form.html @@ -163,6 +163,11 @@

Frame: Form

+
+ + + +
diff --git a/src/tests/fixtures/frames.html b/src/tests/fixtures/frames.html index 069387c4b..b4e19be9b 100644 --- a/src/tests/fixtures/frames.html +++ b/src/tests/fixtures/frames.html @@ -54,6 +54,7 @@

Frames: #nested-child

Visit self.html + Response with Turbo-Frame: _top Visit form-redirect.html diff --git a/src/tests/functional/form_submission_tests.ts b/src/tests/functional/form_submission_tests.ts index 358fe5043..9107481b8 100644 --- a/src/tests/functional/form_submission_tests.ts +++ b/src/tests/functional/form_submission_tests.ts @@ -348,6 +348,14 @@ export class FormSubmissionTests extends TurboDriveTestCase { this.assert.equal(await title.getVisibleText(), "Frame: Unprocessable Entity") } + async "test frame form submission response with Turbo-Frame=_top header"() { + await this.clickSelector("#frame form.redirect.turbo-frame-header button") + await this.nextBeat + + const title = await this.querySelector("h1") + this.assert.equal(await title.getVisibleText(), "Request Headers") + } + async "test invalid frame form submission with internal server errror status"() { await this.clickSelector("#frame form.internal_server_error input[type=submit]") await this.nextBeat diff --git a/src/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts index 9b1e687a7..03dce65a3 100644 --- a/src/tests/functional/frame_tests.ts +++ b/src/tests/functional/frame_tests.ts @@ -25,6 +25,14 @@ export class FrameTests extends TurboDriveTestCase { this.assert.equal(otherEvents.length, 0, "no more events") } + async "test a frame request with Turbo-Frame=_top header in response"() { + await this.clickSelector("#navigate-form-redirect-top") + await this.nextBeat + + const title = await this.querySelector("h1") + this.assert.equal(await title.getVisibleText(), "Request Headers") + } + async "test following a link driving a frame toggles the [busy] attribute"() { await this.clickSelector("#hello a") diff --git a/src/tests/server.ts b/src/tests/server.ts index 7c576a6a8..aca9176e6 100644 --- a/src/tests/server.ts +++ b/src/tests/server.ts @@ -53,6 +53,10 @@ router.post("/reject", (request, response) => { router.get("/headers", (request, response) => { const template = fs.readFileSync("src/tests/fixtures/headers.html").toString() + const { turbo_frame } = request.query + + if (typeof turbo_frame == "string") response.set("Turbo-Frame", turbo_frame) + response.type("html").status(200).send(template.replace('$HEADERS', JSON.stringify(request.headers, null, 4))) })