Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communicate Visit direction with html[data-turbo-visit-direction] #1007

Merged
merged 9 commits into from
Dec 4, 2023
12 changes: 9 additions & 3 deletions src/core/drive/history.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export class History {
restorationData = {}
started = false
pageLoaded = false
currentIndex = 0

constructor(delegate) {
this.delegate = delegate
Expand All @@ -15,6 +16,7 @@ export class History {
if (!this.started) {
addEventListener("popstate", this.onPopState, false)
addEventListener("load", this.onPageLoad, false)
this.currentIndex = history.state?.turbo?.restorationIndex || 0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware of this until today, but browsers persist the state between refreshes (🤯). This means this feature should still work after a reload (e.g. after assets are invalidated and a user taps Back)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, fascinating.

this.started = true
this.replace(new URL(window.location.href))
}
Expand All @@ -37,7 +39,9 @@ export class History {
}

update(method, location, restorationIdentifier = uuid()) {
const state = { turbo: { restorationIdentifier } }
if (method === history.pushState) ++this.currentIndex

const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } }
method.call(history, state, "", location.href)
this.location = location
this.restorationIdentifier = restorationIdentifier
Expand Down Expand Up @@ -81,9 +85,11 @@ export class History {
const { turbo } = event.state || {}
if (turbo) {
this.location = new URL(window.location.href)
const { restorationIdentifier } = turbo
const { restorationIdentifier, restorationIndex } = turbo
this.restorationIdentifier = restorationIdentifier
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier)
const direction = restorationIndex > this.currentIndex ? "forward" : "back"
this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction)
this.currentIndex = restorationIndex
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/core/drive/visit.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export const SystemStatusCode = {
contentTypeMismatch: -2
}

export const Direction = {
advance: "forward",
restore: "back",
replace: "none"
}

export class Visit {
identifier = uuid() // Required by turbo-ios
timingMetrics = {}
Expand Down Expand Up @@ -65,7 +71,8 @@ export class Visit {
willRender,
updateHistory,
shouldCacheSnapshot,
acceptsStreamResponse
acceptsStreamResponse,
direction
} = {
...defaultOptions,
...options
Expand All @@ -83,6 +90,7 @@ export class Visit {
this.scrolled = !willRender
this.shouldCacheSnapshot = shouldCacheSnapshot
this.acceptsStreamResponse = acceptsStreamResponse
this.direction = direction || Direction[action]
}

get adapter() {
Expand Down
11 changes: 6 additions & 5 deletions src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,12 @@ export class Session {

// History delegate

historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {
if (this.enabled) {
this.navigator.startVisit(location, restorationIdentifier, {
action: "restore",
historyChanged: true
historyChanged: true,
direction
})
} else {
this.adapter.pageInvalidated({
Expand Down Expand Up @@ -188,7 +189,7 @@ export class Session {
}
extendURLWithDeprecatedProperties(visit.location)
if (!visit.silent) {
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action)
this.notifyApplicationAfterVisitingLocation(visit.location, visit.action, visit.direction)
}
}

Expand Down Expand Up @@ -313,8 +314,8 @@ export class Session {
})
}

notifyApplicationAfterVisitingLocation(location, action) {
return dispatch("turbo:visit", { detail: { url: location.href, action } })
notifyApplicationAfterVisitingLocation(location, action, direction) {
return dispatch("turbo:visit", { detail: { url: location.href, action, direction } })
}

notifyApplicationBeforeCachingSnapshot() {
Expand Down
1 change: 1 addition & 0 deletions src/tests/fixtures/visit.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<section>
<h1>Visit</h1>
<p><a id="same-origin-link" href="/src/tests/fixtures/one.html">Same-origin link</a></p>
<p><a id="same-origin-replace-link" href="/src/tests/fixtures/one.html" data-turbo-action="replace">Same-origin replace link</a></p>
<p><a id="same-origin-link-search-params" href="/src/tests/fixtures/one.html?key=value">Same-origin link with ?key=value</a></p>
<p><a id="sample-response" href="/src/tests/fixtures/one.html">Sample response</a></p>
<p><a id="same-page-link" href="/src/tests/fixtures/visit.html">Same page link</a></p>
Expand Down
23 changes: 23 additions & 0 deletions src/tests/functional/visit_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,29 @@ test("test Visit with network error", async ({ page }) => {
await nextEventNamed(page, "turbo:fetch-request-error")
})

test("test turbo:visit direction details", async ({ page }) => {
await page.click("#same-origin-link")
let details = await nextEventNamed(page, "turbo:visit")
assert.equal(details.direction, "forward")

await nextEventNamed(page, "turbo:load")
await page.goBack()
details = await nextEventNamed(page, "turbo:visit")
assert.equal(details.direction, "back")

await nextEventNamed(page, "turbo:load")
await page.goForward()
details = await nextEventNamed(page, "turbo:visit")
assert.equal(details.direction, "forward")

await nextEventNamed(page, "turbo:load")
await page.goBack()
await nextEventNamed(page, "turbo:load")
await page.click("#same-origin-replace-link")
details = await nextEventNamed(page, "turbo:visit")
assert.equal(details.direction, "none")
})

async function visitLocation(page, location) {
return page.evaluate((location) => window.Turbo.visit(location), location)
}