diff --git a/src/core/drive/morph_renderer.js b/src/core/drive/morph_renderer.js
index c3379be2b..8bb09bde9 100644
--- a/src/core/drive/morph_renderer.js
+++ b/src/core/drive/morph_renderer.js
@@ -44,10 +44,9 @@ export class MorphRenderer extends Renderer {
}
#shouldMorphElement = (oldNode, newNode) => {
- if (!(oldNode instanceof HTMLElement) || this.isMorphingTurboFrame) {
+ if (this.isMorphingTurboFrame) {
return true
- }
- else if (oldNode.hasAttribute("data-turbo-permanent")) {
+ } else if (!this.#morphableNode(oldNode)) {
return false
} else {
return !this.#remoteFrameReplacement(oldNode, newNode)
@@ -59,7 +58,11 @@ export class MorphRenderer extends Renderer {
}
#shouldRemoveElement = (node) => {
- return this.#shouldMorphElement(node)
+ return this.#morphableNode(node) && !this.#isRemoteFrame(node)
+ }
+
+ #morphableNode(node) {
+ return !(node instanceof HTMLElement) || !node.hasAttribute("data-turbo-permanent")
}
#reloadRemoteFrames() {
@@ -89,6 +92,7 @@ export class MorphRenderer extends Renderer {
return node instanceof HTMLElement && node.nodeName.toLowerCase() === "turbo-frame" && node.getAttribute("src")
}
+
#remoteFrames() {
return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => {
return !frame.closest('[data-turbo-permanent]')
diff --git a/src/tests/fixtures/page_refresh.html b/src/tests/fixtures/page_refresh.html
index ec28092a4..b483ac96d 100644
--- a/src/tests/fixtures/page_refresh.html
+++ b/src/tests/fixtures/page_refresh.html
@@ -24,6 +24,10 @@
Page to be refreshed
+
+ A regular frame
+
+
Frame to be morphed
diff --git a/src/tests/functional/page_refresh_tests.js b/src/tests/functional/page_refresh_tests.js
index a17cad790..931cc7b63 100644
--- a/src/tests/functional/page_refresh_tests.js
+++ b/src/tests/functional/page_refresh_tests.js
@@ -69,6 +69,51 @@ test("remote frames are excluded from full page morphing", async ({ page }) => {
await expect(page.locator("#remote-frame")).toHaveText("Loaded morphed frame")
})
+test("remote frames are reloaded if their src changes", async ({ page }) => {
+ await page.goto("/src/tests/fixtures/page_refresh.html")
+
+ await page.evaluate(() => {
+ const frame = document.getElementById("frame")
+ frame.setAttribute("src", "/src/tests/fixtures/frames.html")
+ })
+ await expect(page.locator("#frame")).toHaveText(/Frames: #frame/)
+
+ await page.evaluate(() => {
+ document.getElementById("frame").innerHTML = `Modified frame!`
+ })
+
+ await page.click("#form-submit")
+ await nextEventNamed(page, "turbo:render", { renderMethod: "morph" })
+ await nextBeat()
+
+ await expect(page.locator("#frame")).not.toHaveText(/Frames: #frame/)
+ await expect(page.locator("#frame")).toHaveText(/Frame: Loaded/)
+})
+
+test("dynamically created remote frames are kept and reloaded", async ({ page }) => {
+ await page.goto("/src/tests/fixtures/page_refresh.html")
+
+ await page.evaluate(() => {
+ const container = document.getElementById("container")
+ container.innerHTML = ``
+ })
+ await expect(page.locator("#hello")).toHaveText(/Hello from a frame/)
+
+ await page.evaluate(() => {
+ document.getElementById("hello").innerHTML = `Modified frame!`
+ })
+
+ await expect(page.locator("#hello")).not.toHaveText(/Hello from a frame/)
+ await expect(page.locator("#hello")).toHaveText(/Modified frame!/)
+
+ await page.click("#form-submit")
+ await nextEventNamed(page, "turbo:render", { renderMethod: "morph" })
+ await nextBeat()
+
+ await expect(page.locator("#hello")).not.toHaveText(/Modified frame!/)
+ await expect(page.locator("#hello")).toHaveText(/Hello from a frame/)
+})
+
test("it preserves the scroll position when the turbo-refresh-scroll meta tag is 'preserve'", async ({ page }) => {
await page.goto("/src/tests/fixtures/page_refresh.html")