diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index d246f74c8..0578872c5 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -44,6 +44,7 @@ export class FrameController private connected = false private hasBeenLoaded = false private ignoredAttributes: Set = new Set() + private previousContents?: DocumentFragment constructor(element: FrameElement) { this.element = element @@ -130,7 +131,7 @@ export class FrameController if (html) { const { body } = parseHTMLDocument(html) const snapshot = new Snapshot(await this.extractForeignFrameElement(body)) - const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false) + const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, false, false) if (this.view.renderPromise) await this.view.renderPromise await this.view.render(renderer) this.complete = true @@ -263,6 +264,22 @@ export class FrameController viewInvalidated() {} + // Frame renderer delegate + frameContentsExtracted(fragment: DocumentFragment) { + this.previousContents = fragment + } + + visitCachedSnapshot = ({ element }: Snapshot) => { + const frame = element.querySelector("#" + this.element.id) + + if (frame && this.previousContents) { + frame.innerHTML = "" + frame.append(this.previousContents) + } + + delete this.previousContents + } + // Private private async visit(url: URL) { @@ -293,7 +310,8 @@ export class FrameController const action = getAttribute("data-turbo-action", submitter, element, frame) if (isAction(action)) { - const { visitCachedSnapshot } = new SnapshotSubstitution(frame) + const { visitCachedSnapshot } = frame.delegate + frame.delegate.fetchResponseLoaded = (fetchResponse: FetchResponse) => { if (frame.src) { const { statusCode, redirected } = fetchResponse @@ -440,22 +458,6 @@ export class FrameController } } -class SnapshotSubstitution { - private readonly clone: Node - private readonly id: string - - constructor(element: FrameElement) { - this.clone = element.cloneNode(true) - this.id = element.id - } - - visitCachedSnapshot = ({ element }: Snapshot) => { - const { id, clone } = this - - element.querySelector("#" + id)?.replaceWith(clone) - } -} - function getFrameElementById(id: string | null) { if (id != null) { const element = document.getElementById(id) diff --git a/src/core/frames/frame_renderer.ts b/src/core/frames/frame_renderer.ts index 559c3bd1e..3e57d6791 100644 --- a/src/core/frames/frame_renderer.ts +++ b/src/core/frames/frame_renderer.ts @@ -1,8 +1,26 @@ import { FrameElement } from "../../elements/frame_element" import { nextAnimationFrame } from "../../util" import { Renderer } from "../renderer" +import { Snapshot } from "../snapshot" + +export interface FrameRendererDelegate { + frameContentsExtracted(fragment: DocumentFragment): void +} export class FrameRenderer extends Renderer { + private readonly delegate: FrameRendererDelegate + + constructor( + delegate: FrameRendererDelegate, + currentSnapshot: Snapshot, + newSnapshot: Snapshot, + isPreview: boolean, + willRender = true + ) { + super(currentSnapshot, newSnapshot, isPreview, willRender) + this.delegate = delegate + } + get shouldRender() { return true } @@ -22,7 +40,7 @@ export class FrameRenderer extends Renderer { loadFrameElement() { const destinationRange = document.createRange() destinationRange.selectNodeContents(this.currentElement) - destinationRange.deleteContents() + this.delegate.frameContentsExtracted(destinationRange.extractContents()) const frameElement = this.newElement const sourceRange = frameElement.ownerDocument?.createRange() diff --git a/src/elements/frame_element.ts b/src/elements/frame_element.ts index 3f40937d7..7e8dfab5c 100644 --- a/src/elements/frame_element.ts +++ b/src/elements/frame_element.ts @@ -1,4 +1,5 @@ import { FetchResponse } from "../http/fetch_response" +import { Snapshot } from "../core/snapshot" export enum FrameLoadingStyle { eager = "eager", @@ -18,6 +19,7 @@ export interface FrameElementDelegate { linkClickIntercepted(element: Element, url: string): void loadResponse(response: FetchResponse): void fetchResponseLoaded: (fetchResponse: FetchResponse) => void + visitCachedSnapshot: (snapshot: Snapshot) => void isLoading: boolean } diff --git a/src/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts index 0f0ec4045..a25ae721c 100644 --- a/src/tests/functional/frame_tests.ts +++ b/src/tests/functional/frame_tests.ts @@ -584,10 +584,12 @@ test("test navigating back after pushing URL state from a turbo-frame[data-turbo const title = await page.textContent("h1") const frameTitle = await page.textContent("#frame h2") + const src = new URL((await attributeForSelector(page, "#frame", "src")) || "") assert.equal(title, "Frames") assert.equal(frameTitle, "Frames: #frame") assert.equal(pathname(page.url()), "/src/tests/fixtures/frames.html") + assert.equal(src.pathname, "/src/tests/fixtures/frames/frame.html") assert.equal(await propertyForSelector(page, "#frame", "src"), null) }) diff --git a/src/tests/functional/loading_tests.ts b/src/tests/functional/loading_tests.ts index 4ea539518..1704a7aff 100644 --- a/src/tests/functional/loading_tests.ts +++ b/src/tests/functional/loading_tests.ts @@ -149,7 +149,7 @@ test("test changing [src] attribute on a [complete] frame with loading=lazy defe await page.click("#one") await nextEventNamed(page, "turbo:load") await page.goBack() - await nextBody(page) + await nextEventNamed(page, "turbo:load") await noNextEventNamed(page, "turbo:frame-load") let src = new URL((await attributeForSelector(page, "#hello", "src")) || "")