Skip to content

Commit

Permalink
Support FrameElement.reload() without an initial [src] attribute
Browse files Browse the repository at this point in the history
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.#clearInterval()
  }

  frequencyValueChanged(frequencyInMilliseconds) {
    this.#clearInterval()

    this.intervalID = setInterval(() => this.element.reload(), frequencyInMilliseconds)
  }

  #clearInterval() {
    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 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
  • Loading branch information
seanpdoyle committed Sep 14, 2023
1 parent 32cadc0 commit da72b1c
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/core/frames/frame_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class FrameController {
this.element.removeAttribute("complete")
})
this.element.src = null
this.element.src = src
this.element.src = src || this.element.baseURI
return this.element.loaded
}

Expand Down
34 changes: 32 additions & 2 deletions src/tests/functional/frame_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,15 +515,45 @@ test("test navigating a frame targeting _top from an outer link fires events", a
assert.equal(otherEvents.length, 0, "no more events")
})

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

const dispatchedEvents = await readEventLogs(page)

assert.deepEqual(
dispatchedEvents.map(([name, _, id]) => [id, name]),
dispatchedEvents
.map(([name, _, id]) => [id, name])
.filter(([id]) => id === "frame"),
[
["frame", "turbo:before-fetch-request"],
["frame", "turbo:before-fetch-response"],
["frame", "turbo:before-frame-render"],
["frame", "turbo:frame-render"],
["frame", "turbo:frame-load"]
]
)
})

test("test invoking .reload() re-fetches the content of a <turbo-frame> element without a [src] attribute", async ({ page }) => {
const frame = await page.locator("turbo-frame#frame")
const heading = await frame.locator("h2")

assert.match(await heading.textContent(), /Frames: #frame/)

await heading.evaluate((element) => element.textContent = "Not yet refreshed")

assert.match(await heading.textContent(), /Not yet refreshed/)

await frame.evaluate((element) => element.reload())

const dispatchedEvents = await readEventLogs(page)

assert.deepEqual(
dispatchedEvents
.map(([name, _, id]) => [id, name])
.filter(([id]) => id === "frame"),
[
["frame", "turbo:before-fetch-request"],
["frame", "turbo:before-fetch-response"],
Expand Down

0 comments on commit da72b1c

Please sign in to comment.