Skip to content

Commit e4da725

Browse files
committed
Support FrameElement.reload() without an initial [src] attribute
The problem --- If a `<turbo-frame>` element is rendered without a `[src]` attribute, calls to `.reload()` will have no effect. If a `<turbo-frame>` is to be its own browsing context, it should be able to apply its current location (that is, it's owning document's current location) to its browsing context. For example, if a page has a `<turbo-frame>` element that contains text that's typically updated by a Web Socket-delivered `<turbo-stream>`, it might be useful to [gracefully degrade][] to periodic long-polling if that Web Socket connection were to fail. That might involve something like a `reload` Stimulus controller with a delay: ```html <script type="module"> import { Application, Controller } from "@hotwired/stimulus" const application = // boot up a Stimulus application application.register("reload", class extends Controller { static values = { frequency: Number } disconnect() { this.#reset() } frequencyValueChanged(frequencyInMilliseconds) { this.#reset() if (frequencyInMilliseconds) { this.intervalID = setInterval(() => this.element.reload(), frequencyInMilliseconds) } } #reset() { if (this.intervalID) clearInterval(this.intervalID) } }) </script> <turbo-frame id="dynamic-data" data-controller="reload" data-reload-frequency-value="30000"> <h1>This data will refresh every 30 seconds</h1> </turbo-frame> ``` The fact that the `<turbo-frame id="dynamic-data">` element doesn't have a `[src]` attribute shouldn't prevent the page from being able to re-fetch its content. The solution --- When `FrameElement.reload()` is invoked, it delegates to its delegate instance's `sourceURLReloaded()` method. In all cases, `FrameElement.delegate` is an instance of `FrameController`. This commit extends the `FrameController.sourceURLReloaded()` implementation to set the element's `[src]` attribute to the element's [baseURI][] value, which sets off the usual attribute change listeners and `<turbo-frame>` navigation logic. [baseURI]: https://developer.mozilla.org/en-US/docs/Web/API/Node/baseURI [gracefully degrade]: https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation
1 parent 9a79b30 commit e4da725

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

src/core/frames/frame_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class FrameController {
9797

9898
this.element.removeAttribute("complete")
9999
this.element.src = null
100-
this.element.src = src
100+
this.element.src = src || this.element.baseURI
101101
return this.element.loaded
102102
}
103103

src/tests/functional/frame_tests.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ test("navigating a frame targeting _top from an outer link fires events", async
624624
expect(otherEvents.length, "no more events").toEqual(0)
625625
})
626626

627-
test("invoking .reload() re-fetches the frame's content", async ({ page }) => {
627+
test("invoking .reload() re-fetches the content of a <turbo-frame> element with a [src] attribute", async ({ page }) => {
628628
await page.click("#link-frame")
629629
await nextEventOnTarget(page, "frame", "turbo:frame-load")
630630
await page.evaluate(() => document.getElementById("frame").reload())
@@ -644,6 +644,35 @@ test("invoking .reload() re-fetches the frame's content", async ({ page }) => {
644644
)
645645
})
646646

647+
test("invoking .reload() re-fetches the content of a <turbo-frame> element without a [src] attribute", async ({ page }) => {
648+
const frame = await page.locator("turbo-frame#frame")
649+
const heading = await frame.locator("h2")
650+
651+
await expect(heading).toHaveText("Frames: #frame")
652+
653+
await heading.evaluate((h2) => h2.textContent = "Not yet refreshed")
654+
await expect(heading).toHaveText("Not yet refreshed")
655+
656+
await frame.evaluate((element) => element.reload())
657+
await expect(heading).toHaveText("Frames: #frame")
658+
659+
const dispatchedEvents = await readEventLogs(page)
660+
661+
expect(
662+
dispatchedEvents
663+
.map(([name, _, id]) => [id, name])
664+
.filter(([id]) => id === "frame")
665+
).toEqual(
666+
[
667+
["frame", "turbo:before-fetch-request"],
668+
["frame", "turbo:before-fetch-response"],
669+
["frame", "turbo:before-frame-render"],
670+
["frame", "turbo:frame-render"],
671+
["frame", "turbo:frame-load"]
672+
]
673+
)
674+
})
675+
647676
test("following inner link reloads frame on every click", async ({ page }) => {
648677
await page.click("#hello a")
649678
await nextEventNamed(page, "turbo:before-fetch-request")

0 commit comments

Comments
 (0)