diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 06efcc4b6..da8c7a847 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -172,8 +172,16 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest } async requestSucceededWithResponse(request: FetchRequest, response: FetchResponse) { - await this.loadResponse(response) - this.resolveVisitPromise() + const { location, redirected, statusCode } = response + + if (redirected && response.header("Turbo-Frame") == "_top") { + const responseHTML = await response.responseHTML + + session.visit(location, { response: { statusCode, redirected, responseHTML } }) + } else { + await this.loadResponse(response) + this.resolveVisitPromise() + } } requestFailedWithResponse(request: FetchRequest, response: FetchResponse) { @@ -198,8 +206,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 079ca9a01..095e146ff 100644 --- a/src/tests/fixtures/form.html +++ b/src/tests/fixtures/form.html @@ -181,6 +181,11 @@

Frame: Form

+
+ + + +
diff --git a/src/tests/fixtures/frames.html b/src/tests/fixtures/frames.html index ed786a2a9..69490632b 100644 --- a/src/tests/fixtures/frames.html +++ b/src/tests/fixtures/frames.html @@ -65,6 +65,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 390ddc38d..bd2e01356 100644 --- a/src/tests/functional/form_submission_tests.ts +++ b/src/tests/functional/form_submission_tests.ts @@ -451,6 +451,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 61f008622..4a058452f 100644 --- a/src/tests/functional/frame_tests.ts +++ b/src/tests/functional/frame_tests.ts @@ -25,6 +25,19 @@ 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.nextEventOnTarget("frame", "turbo:before-fetch-request") + await this.nextEventOnTarget("frame", "turbo:before-fetch-response") + const eventLog = await this.eventLogChannel.read() + await this.nextBody + + this.assert.notOk(await this.hasSelector("#frame"), "Navigates entire page") + this.assert.equal(await (await this.querySelector("h1")).getVisibleText(), "Request Headers") + this.assert.notOk(eventLog.some(([ name ]) => name == "turbo:before-fetch-request" || name == "turbo:before-fetch-response"), "does not make subsequent requests") + } + 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))) })