-
Notifications
You must be signed in to change notification settings - Fork 476
Open
Description
Bug Description
When hovering over links with Turbo prefetch enabled, a TypeError occurs in HeadSnapshot.detailsByOuterHTML when processing the <head> element.
Error Message
TypeError: e.hasAttribute is not a function
at elementWithoutNonce (turbo.es2017-esm.js:3763)
at Array.map (<anonymous>)
at HeadSnapshot.detailsByOuterHTML (turbo.es2017-esm.js:3660)
Turbo Version
- v8.0.20 (latest, released 3 weeks ago)
Environment
- Browser: Chrome 142.0.7444.176
- Framework: Ruby on Rails 7.1 with Turbo 8.0.20
- Occurs in all environments: production, staging, development, and test
- Particularly problematic in test environment where it causes flaky tests
Steps to Reproduce
- Create a page with a
<head>element containing HTML comments or ERB comments:
<head>
<script>...</script>
<!-- Some HTML comment -->
<%= content %>
</head>- Enable Turbo prefetch on navigation links
- Hover over a link to trigger prefetch
- Check browser console for the TypeError
Root Cause
The bug is in src/core/drive/head_snapshot.js:
Problem Location 1: detailsByOuterHTML property (L3-22)
detailsByOuterHTML = this.children
.filter((element) => !elementIsNoscript(element))
.map((element) => elementWithoutNonce(element)) // ← TypeError occurs here
.reduce((result, element) => {
// ...
}, {})Problem Location 2: elementWithoutNonce function (L107-113)
function elementWithoutNonce(element) {
if (element.hasAttribute("nonce")) { // ← Assumes element is always an Element
element.setAttribute("nonce", "")
}
return element
}Why it happens:
this.childrencan include non-Element nodes (text nodes, comment nodes)- The
.filter()only removes<noscript>elements, not other node types - Text nodes and comment nodes don't have
hasAttribute()method - Particularly, HTML comments (
<!-- -->) and ERB comments (<%# %>) in<head>trigger this error
Expected Behavior
HeadSnapshot should safely handle all node types in <head> element without throwing errors.
Proposed Fix
Add a type check before calling elementWithoutNonce:
detailsByOuterHTML = this.children
.filter((element) => !elementIsNoscript(element))
.filter((element) => element instanceof Element) // ← Add this line
.map((element) => elementWithoutNonce(element))
.reduce((result, element) => {
// ...
}, {})Or alternatively, add defensive check in elementWithoutNonce:
function elementWithoutNonce(element) {
if (element instanceof Element && element.hasAttribute("nonce")) {
element.setAttribute("nonce", "")
}
return element
}Workaround
We've implemented a client-side patch that removes non-Element nodes from <head> before Turbo processes it:
const cleanupHeadElement = () => {
if (!document.head) return
const nodesToRemove = []
Array.from(document.head.childNodes).forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) {
nodesToRemove.push(node)
}
})
nodesToRemove.forEach(node => {
node.parentNode.removeChild(node)
})
}
// Call before Turbo events
document.addEventListener('turbo:before-visit', cleanupHeadElement)
document.addEventListener('turbo:before-fetch-request', cleanupHeadElement)Additional Context
- This appears to be an unreported bug - we couldn't find similar issues in the repository
- Causes flaky tests in system/feature tests that use Selenium/Capybara with Turbo
- Affects users across all environments (production, staging, development, test)
- Error is caught by Sentry in production
- The error doesn't break navigation completely, but appears in error monitoring and test failures
Related
- PR Refactor head_snapshot #832 "Refactor head_snapshot" doesn't fix this issue (still calls
elementWithoutNoncewithout type check) - Similar to CSP nonce issues (Content Security Policy support #809, Apply CSP nonce when createElement is called #602, CSP not working on Turbo loaded content #1431) but different root cause
Metadata
Metadata
Assignees
Labels
No labels