diff --git a/src/core/drive/visit.ts b/src/core/drive/visit.ts index 7b050c1cb..0fb1bac00 100644 --- a/src/core/drive/visit.ts +++ b/src/core/drive/visit.ts @@ -39,6 +39,7 @@ export enum VisitState { export type VisitOptions = { action: Action, historyChanged: boolean, + willRender: boolean referrer?: URL, snapshotHTML?: string, response?: VisitResponse @@ -46,7 +47,8 @@ export type VisitOptions = { const defaultOptions: VisitOptions = { action: "advance", - historyChanged: false + historyChanged: false, + willRender: true } export type VisitResponse = { @@ -68,6 +70,7 @@ export class Visit implements FetchRequestDelegate { readonly referrer?: URL readonly timingMetrics: TimingMetrics = {} + willRender: boolean followedRedirect = false frame?: number historyChanged = false @@ -86,7 +89,8 @@ export class Visit implements FetchRequestDelegate { this.location = location this.restorationIdentifier = restorationIdentifier || uuid() - const { action, historyChanged, referrer, snapshotHTML, response } = { ...defaultOptions, ...options } + const { action, historyChanged, referrer, snapshotHTML, response, willRender } = { ...defaultOptions, ...options } + this.willRender = willRender this.action = action this.historyChanged = historyChanged this.referrer = referrer @@ -200,7 +204,7 @@ export class Visit implements FetchRequestDelegate { } loadResponse() { - if (this.response) { + if (this.response && this.willRender) { const { statusCode, responseHTML } = this.response this.render(async () => { this.cacheSnapshot() diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 6815bc349..c585f78cf 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -12,6 +12,7 @@ import { FrameView } from "./frame_view" import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { FrameRenderer } from "./frame_renderer" import { session } from "../index" +import { isAction } from "../types" export class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormInterceptorDelegate, FormSubmissionDelegate, FrameElementDelegate, LinkInterceptorDelegate, ViewDelegate> { readonly element: FrameElement @@ -202,6 +203,9 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) { const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter) + + this.proposeVisitFromFrameResponse(frame, formSubmission.formElement, formSubmission.submitter) + frame.delegate.loadResponse(response) } @@ -246,10 +250,29 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest private navigateFrame(element: Element, url: string, submitter?: HTMLElement) { const frame = this.findFrameElement(element, submitter) + + this.proposeVisitFromFrameResponse(frame, element, submitter) + frame.setAttribute("reloadable", "") frame.src = url } + private proposeVisitFromFrameResponse(frame: FrameElement, element: Element, submitter?: HTMLElement) { + const action = submitter?.getAttribute("data-turbo-action") || element.getAttribute("data-turbo-action") || frame.getAttribute("data-turbo-action") + + if (isAction(action)) { + const proposeVisit = async (event: Event) => { + const { detail: { fetchResponse } } = event as CustomEvent + const responseHTML = await fetchResponse.responseHTML + const statusCode = fetchResponse.statusCode + + session.visit(fetchResponse.location, { willRender: false, response: { statusCode, responseHTML }, action }) + } + + frame.addEventListener("turbo:frame-render", proposeVisit , { once: true }) + } + } + private findFrameElement(element: Element, submitter?: HTMLElement) { const id = submitter?.getAttribute("data-turbo-frame") || element.getAttribute("data-turbo-frame") || this.element.getAttribute("target") return getFrameElementById(id) ?? this.element diff --git a/src/tests/fixtures/frames.html b/src/tests/fixtures/frames.html index 069387c4b..2af311341 100644 --- a/src/tests/fixtures/frames.html +++ b/src/tests/fixtures/frames.html @@ -11,8 +11,27 @@

Frames

Frames: #frame

+ + Navigate #frame from within + Navigate #frame from within with a[data-turbo-action="advance"]
+ Navigate #frame with data-turbo-action="advance" +
+ + +
+ +
+ + +
+ +
+ + +
+

Frames: #hello

diff --git a/src/tests/fixtures/frames/frame.html b/src/tests/fixtures/frames/frame.html index a15134676..914cec830 100644 --- a/src/tests/fixtures/frames/frame.html +++ b/src/tests/fixtures/frames/frame.html @@ -6,6 +6,8 @@ +

Frames: #frame

+

Frame: Loaded

diff --git a/src/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts index 9b1e687a7..9879d34b4 100644 --- a/src/tests/functional/frame_tests.ts +++ b/src/tests/functional/frame_tests.ts @@ -236,6 +236,61 @@ export class FrameTests extends TurboDriveTestCase { this.assert.equal(requestLogs.length, 0) } + async "test navigating turbo-frame[data-turbo-action=advance] from within pushes URL state"() { + await this.remote.execute(() => document.getElementById("frame")?.setAttribute("data-turbo-action", "advance")) + await this.clickSelector("#link-frame") + + const title = await this.querySelector("h1") + + this.assert.equal(await title.getVisibleText(), "Frames") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") + } + + async "test navigating turbo-frame from within with a[data-turbo-action=advance] pushes URL state"() { + await this.clickSelector("#link-frame-action-advance") + + const title = await this.querySelector("h1") + + this.assert.equal(await title.getVisibleText(), "Frames") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") + } + + async "test navigating frame with a[data-turbo-action=advance] pushes URL state"() { + await this.clickSelector("#link-frame-action-advance") + + const title = await this.querySelector("h1") + + this.assert.equal(await title.getVisibleText(), "Frames") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") + } + + async "test navigating frame with form[method=get][data-turbo-action=advance] pushes URL state"() { + await this.clickSelector("#form-get-frame-action-advance button") + + const title = await this.querySelector("h1") + + this.assert.equal(await title.getVisibleText(), "Frames") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") + } + + async "test navigating frame with form[method=post][data-turbo-action=advance] pushes URL state"() { + await this.clickSelector("#form-post-frame-action-advance button") + + const title = await this.querySelector("h1") + + this.assert.equal(await title.getVisibleText(), "Frames") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") + } + + async "test navigating frame with button[data-turbo-action=advance] pushes URL state"() { + await this.clickSelector("#button-frame-action-advance") + + const title = await this.querySelector("h1") + + this.assert.equal(await title.getVisibleText(), "Frames") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") + } + async "test turbo:before-fetch-request fires on the frame element"() { await this.clickSelector("#hello a") this.assert.ok(await this.nextEventOnTarget("frame", "turbo:before-fetch-request"))