diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 672321ab6..4990bfe80 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -27,6 +27,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest private connected = false private hasBeenLoaded = false private ignoredAttributes: Set = new Set + private previousContents?: DocumentFragment constructor(element: FrameElement) { this.element = element @@ -110,7 +111,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest 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.ignoringChangesToAttribute("loaded", () => this.loaded = true) @@ -234,6 +235,11 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest viewInvalidated() { } + // Frame renderer delegate + frameContentsExtracted(fragment: DocumentFragment) { + this.previousContents = fragment + } + // Private private async visit(url: URL) { @@ -264,7 +270,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest const action = getAttribute("data-turbo-action", submitter, element, frame) if (isAction(action)) { - const { visitCachedSnapshot } = new SnapshotSubstitution(frame) + const { visitCachedSnapshot } = this + frame.delegate.fetchResponseLoaded = (fetchResponse: FetchResponse) => { if (frame.src) { const { statusCode, redirected } = fetchResponse @@ -277,6 +284,17 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest } } + private visitCachedSnapshot = ({ element }: Snapshot) => { + const frame = element.querySelector("#" + this.element.id) + + if (frame && this.previousContents) { + frame.innerHTML = "" + frame.append(this.previousContents) + } + + delete this.previousContents + } + private findFrameElement(element: Element, submitter?: HTMLElement) { const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target") return getFrameElementById(id) ?? this.element @@ -404,22 +422,6 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest } } -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 398d08a99..f8fa08b60 100644 --- a/src/core/frames/frame_renderer.ts +++ b/src/core/frames/frame_renderer.ts @@ -1,8 +1,20 @@ 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 +34,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/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts index b2550e832..e0dd608af 100644 --- a/src/tests/functional/frame_tests.ts +++ b/src/tests/functional/frame_tests.ts @@ -515,11 +515,12 @@ export class FrameTests extends TurboDriveTestCase { const title = await this.querySelector("h1") const frameTitle = await this.querySelector("#frame h2") + const src = new URL(await this.attributeForSelector("#frame", "src") || "") this.assert.equal(await title.getVisibleText(), "Frames") this.assert.equal(await frameTitle.getVisibleText(), "Frames: #frame") this.assert.equal(await this.pathname, "/src/tests/fixtures/frames.html") - this.assert.equal(await this.propertyForSelector("#frame", "src"), null) + this.assert.equal(src.pathname, "/src/tests/fixtures/frames/frame.html") } async "test navigating back then forward after pushing URL state from a turbo-frame[data-turbo-action=advance] restores the frames next contents"() { diff --git a/src/tests/functional/loading_tests.ts b/src/tests/functional/loading_tests.ts index 98f9bfd1e..8cbb8359f 100644 --- a/src/tests/functional/loading_tests.ts +++ b/src/tests/functional/loading_tests.ts @@ -133,7 +133,7 @@ export class LoadingTests extends TurboDriveTestCase { await this.clickSelector("#one") await this.nextEventNamed("turbo:load") await this.goBack() - await this.nextBody + await this.nextEventNamed("turbo:load") await this.noNextEventNamed("turbo:frame-load") let src = new URL(await this.attributeForSelector("#hello", "src") || "")