Skip to content

Commit

Permalink
Ensure that the turbo-frame header is not sent when the turbo-frame h…
Browse files Browse the repository at this point in the history
…as a target of _top (#1138)

* Ensure that the turbo-frame header is not sent when the turbo-frame has a target of _top

Given an HTML structure like this:

```html
<turbo-frame id="frame" target="_top">
  <a href="/src/tests/fixtures/prefetched.html">Hover to prefetch me</a>
</turbo-frame>
```

When the user hovers over the link, the turbo-frame header should not be sent
in a prefetch request because the wrapping turbo-frame has a target of `_top`.

If we don't respect this turbo-rails sees the turbo-frame header and renders
a turbo-frame response with a minimal layout that doesn't include any assets
in the head. When turbo processes the response it may find a missmatch in
tracked assets and cause a reload.

* Prefer `getAttribute` over `dataset` for `data-turbo-frame` attribute
  • Loading branch information
Alberto Fernández-Capel authored Jan 24, 2024
1 parent 95c98d4 commit 5902f3b
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 10 deletions.
11 changes: 4 additions & 7 deletions src/observers/link_prefetch_observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,11 @@ export class LinkPrefetchObserver {

request.headers["Sec-Purpose"] = "prefetch"

if (link.dataset.turboFrame && link.dataset.turboFrame !== "_top") {
request.headers["Turbo-Frame"] = link.dataset.turboFrame
} else if (link.dataset.turboFrame !== "_top") {
const turboFrame = link.closest("turbo-frame")
const turboFrame = link.closest("turbo-frame")
const turboFrameTarget = link.getAttribute("data-turbo-frame") || turboFrame?.getAttribute("target") || turboFrame?.id

if (turboFrame) {
request.headers["Turbo-Frame"] = turboFrame.id
}
if (turboFrameTarget && turboFrameTarget !== "_top") {
request.headers["Turbo-Frame"] = turboFrameTarget
}

if (link.hasAttribute("data-turbo-stream")) {
Expand Down
8 changes: 8 additions & 0 deletions src/tests/fixtures/hover_to_prefetch.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@
<a href="/src/tests/fixtures/prefetched.html" data-turbo-method="post" id="anchor_with_post_method"
>Won't prefetch when hovering me</a>
<iframe src="/src/tests/fixtures/hover_to_prefetch_iframe.html" name="prefetch_iframe"> </iframe>

<turbo-frame id="frame_for_prefetch">
<a href="/src/tests/fixtures/prefetched.html" id="anchor_for_prefetch_in_frame">Hover to prefetch me</a>
</turbo-frame>

<turbo-frame id="frame_for_prefetch_top" target="_top">
<a href="/src/tests/fixtures/prefetched.html" id="anchor_for_prefetch_in_frame_target_top">Hover to prefetch me</a>
</turbo-frame>
</body>
</html>
28 changes: 25 additions & 3 deletions src/tests/functional/link_prefetch_observer_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,25 @@ test("it prefetches links with inner elements", async ({ page }) => {
await assertPrefetchedOnHover({ page, selector: "#anchor_with_inner_elements" })
})

test("it prefetches links inside a turbo frame", async ({ page }) => {
await goTo({ page, path: "/hover_to_prefetch.html" })

await assertPrefetchedOnHover({ page, selector: "#anchor_for_prefetch_in_frame", callback: (request) => {
const turboFrameHeader = request.headers()["turbo-frame"]
assert.equal(turboFrameHeader, "frame_for_prefetch")
}})
})


test("doesn't include a turbo-frame header when the link is inside a turbo frame with a target=_top", async ({ page}) => {
await goTo({ page, path: "/hover_to_prefetch.html" })

await assertPrefetchedOnHover({ page, selector: "#anchor_for_prefetch_in_frame_target_top", callback: (request) => {
const turboFrameHeader = request.headers()["turbo-frame"]
assert.equal(undefined, turboFrameHeader)
}})
})

test("it prefetches links with a delay", async ({ page }) => {
await goTo({ page, path: "/hover_to_prefetch.html" })

Expand Down Expand Up @@ -253,15 +272,18 @@ const assertPrefetchedOnHover = async ({ page, selector, callback }) => {
let requestMade = false

page.on("request", (request) => {
callback && callback(request)
requestMade = true
requestMade = request
})

await hoverSelector({ page, selector })

await sleep(100)

assertRequestMade(requestMade)
if (callback) {
await callback(requestMade)
}

assertRequestMade(!!requestMade)
}

const assertNotPrefetchedOnHover = async ({ page, selector, callback }) => {
Expand Down

0 comments on commit 5902f3b

Please sign in to comment.