Skip to content

Commit 3582ccf

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 32cadc0 commit 3582ccf

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

src/core/frames/frame_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class FrameController {
9494
this.element.removeAttribute("complete")
9595
})
9696
this.element.src = null
97-
this.element.src = src
97+
this.element.src = src || this.element.baseURI
9898
return this.element.loaded
9999
}
100100

src/tests/functional/frame_tests.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,15 +515,45 @@ test("test navigating a frame targeting _top from an outer link fires events", a
515515
assert.equal(otherEvents.length, 0, "no more events")
516516
})
517517

518-
test("test invoking .reload() re-fetches the frame's content", async ({ page }) => {
518+
test("test invoking .reload() re-fetches the content of a <turbo-frame> element with a [src] attribute", async ({ page }) => {
519519
await page.click("#link-frame")
520520
await nextEventOnTarget(page, "frame", "turbo:frame-load")
521521
await page.evaluate(() => document.getElementById("frame").reload())
522522

523523
const dispatchedEvents = await readEventLogs(page)
524524

525525
assert.deepEqual(
526-
dispatchedEvents.map(([name, _, id]) => [id, name]),
526+
dispatchedEvents
527+
.map(([name, _, id]) => [id, name])
528+
.filter(([id]) => id === "frame"),
529+
[
530+
["frame", "turbo:before-fetch-request"],
531+
["frame", "turbo:before-fetch-response"],
532+
["frame", "turbo:before-frame-render"],
533+
["frame", "turbo:frame-render"],
534+
["frame", "turbo:frame-load"]
535+
]
536+
)
537+
})
538+
539+
test("test invoking .reload() re-fetches the content of a <turbo-frame> element without a [src] attribute", async ({ page }) => {
540+
const frame = await page.locator("turbo-frame#frame")
541+
const heading = await frame.locator("h2")
542+
543+
assert.match(await heading.textContent(), /Frames: #frame/)
544+
545+
await heading.evaluate((element) => element.textContent = "Not yet refreshed")
546+
547+
assert.match(await heading.textContent(), /Not yet refreshed/)
548+
549+
await frame.evaluate((element) => element.reload())
550+
551+
const dispatchedEvents = await readEventLogs(page)
552+
553+
assert.deepEqual(
554+
dispatchedEvents
555+
.map(([name, _, id]) => [id, name])
556+
.filter(([id]) => id === "frame"),
527557
[
528558
["frame", "turbo:before-fetch-request"],
529559
["frame", "turbo:before-fetch-response"],

0 commit comments

Comments
 (0)