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

Add a turbo:reload event that returns the reason why turbo needed to do a hard reload. #556

Merged
merged 8 commits into from
Apr 30, 2022
15 changes: 15 additions & 0 deletions src/core/drive/page_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { Renderer } from "../renderer"
import { PageSnapshot } from "./page_snapshot"
import { ReloadReason } from "../native/browser_adapter"

export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
get shouldRender() {
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical
}

get reloadReason(): ReloadReason {
if (!this.newSnapshot.isVisitable) {
return {
reason: "turbo_visit_control_is_reload"
}
}

if (!this.trackedElementsAreIdentical) {
return {
reason: "tracked_element_mismatch"
}
}
}

prepareToRender() {
this.mergeHead()
}
Expand Down
3 changes: 2 additions & 1 deletion src/core/native/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Visit, VisitOptions } from "../drive/visit"
import { FormSubmission } from "../drive/form_submission"
import { ReloadReason } from "./browser_adapter"

export interface Adapter {
visitProposedToLocation(location: URL, options?: Partial<VisitOptions>): void
Expand All @@ -13,5 +14,5 @@ export interface Adapter {
visitRendered(visit: Visit): void
formSubmissionStarted?(formSubmission: FormSubmission): void
formSubmissionFinished?(formSubmission: FormSubmission): void
pageInvalidated(): void
pageInvalidated(reason: ReloadReason): void
}
22 changes: 17 additions & 5 deletions src/core/native/browser_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { ProgressBar } from "../drive/progress_bar"
import { SystemStatusCode, Visit, VisitOptions } from "../drive/visit"
import { FormSubmission } from "../drive/form_submission"
import { Session } from "../session"
import { uuid } from "../../util"
import { uuid, dispatch } from "../../util"

export type ReloadReason = StructuredReason | undefined
interface StructuredReason {
reason: string
context?: {[key: string]: any}
}

export class BrowserAdapter implements Adapter {
readonly session: Session
Expand Down Expand Up @@ -45,7 +51,12 @@ export class BrowserAdapter implements Adapter {
case SystemStatusCode.networkFailure:
case SystemStatusCode.timeoutFailure:
case SystemStatusCode.contentTypeMismatch:
return this.reload()
return this.reload({
reason: "request_failed",
context: {
statusCode
}
})
default:
return visit.loadResponse()
}
Expand All @@ -60,8 +71,8 @@ export class BrowserAdapter implements Adapter {

}

pageInvalidated() {
this.reload()
pageInvalidated(reason: ReloadReason) {
this.reload(reason)
}

visitFailed(visit: Visit) {
Expand Down Expand Up @@ -114,7 +125,8 @@ export class BrowserAdapter implements Adapter {
this.progressBar.show()
}

reload() {
reload(reason: ReloadReason) {
dispatch("turbo:reload", { detail: reason })
window.location.reload()
}

Expand Down
5 changes: 5 additions & 0 deletions src/core/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Bardo } from "./bardo"
import { Snapshot } from "./snapshot"
import { ReloadReason } from "./native/browser_adapter"

type ResolvingFunctions<T = unknown> = {
resolve(value: T | PromiseLike<T>): void
Expand All @@ -26,6 +27,10 @@ export abstract class Renderer<E extends Element, S extends Snapshot<E> = Snapsh
return true
}

get reloadReason(): ReloadReason {
return
}

prepareToRender() {
return
}
Expand Down
10 changes: 6 additions & 4 deletions src/core/session.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Adapter } from "./native/adapter"
import { BrowserAdapter } from "./native/browser_adapter"
import { BrowserAdapter, ReloadReason } from "./native/browser_adapter"
import { CacheObserver } from "../observers/cache_observer"
import { FormSubmitObserver, FormSubmitObserverDelegate } from "../observers/form_submit_observer"
import { FrameRedirector } from "./frames/frame_redirector"
Expand Down Expand Up @@ -116,7 +116,9 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
if (this.enabled) {
this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true })
} else {
this.adapter.pageInvalidated()
this.adapter.pageInvalidated({
reason: "turbo_disabled"
})
}
}

Expand Down Expand Up @@ -250,8 +252,8 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
this.notifyApplicationAfterRender()
}

viewInvalidated() {
this.adapter.pageInvalidated()
viewInvalidated(reason: ReloadReason) {
this.adapter.pageInvalidated(reason)
}

// Frame element
Expand Down
9 changes: 5 additions & 4 deletions src/core/view.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ReloadReason } from "./native/browser_adapter"
import { Renderer } from "./renderer"
import { Snapshot } from "./snapshot"
import { Position } from "./types"
Expand All @@ -6,7 +7,7 @@ import { getAnchor } from "./url"
export interface ViewDelegate<S extends Snapshot> {
allowsImmediateRender(snapshot: S, resume: (value: any) => void): boolean
viewRenderedSnapshot(snapshot: S, isPreview: boolean): void
viewInvalidated(): void
viewInvalidated(reason: ReloadReason): void
}

export abstract class View<E extends Element, S extends Snapshot<E> = Snapshot<E>, R extends Renderer<E, S> = Renderer<E, S>, D extends ViewDelegate<S> = ViewDelegate<S>> {
Expand Down Expand Up @@ -90,12 +91,12 @@ export abstract class View<E extends Element, S extends Snapshot<E> = Snapshot<E
delete this.renderPromise
}
} else {
this.invalidate()
this.invalidate(renderer.reloadReason)
}
}

invalidate() {
this.delegate.viewInvalidated()
invalidate(reason: ReloadReason) {
this.delegate.viewInvalidated(reason)
}

prepareToRenderSnapshot(renderer: R) {
Expand Down
1 change: 1 addition & 0 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@
"turbo:visit",
"turbo:frame-load",
"turbo:frame-render",
"turbo:reload"
])
24 changes: 24 additions & 0 deletions src/tests/functional/rendering_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export class RenderingTests extends TurboDriveTestCase {
await this.goToLocation("/src/tests/fixtures/rendering.html")
}

async teardown() {
await this.remote.execute(() => localStorage.clear())
}

async "test triggers before-render and render events"() {
this.clickSelector("#same-origin-link")
const { newBody } = await this.nextEventNamed("turbo:before-render")
Expand Down Expand Up @@ -35,10 +39,20 @@ export class RenderingTests extends TurboDriveTestCase {
}

async "test reloads when tracked elements change"() {
await this.remote.execute(() =>
window.addEventListener("turbo:reload", (e: any) => {
localStorage.setItem("reloadReason", e.detail.reason)
})
)

this.clickSelector("#tracked-asset-change-link")
await this.nextBody

const reason = await this.remote.execute(() => localStorage.getItem("reloadReason"))

this.assert.equal(await this.pathname, "/src/tests/fixtures/tracked_asset_change.html")
this.assert.equal(await this.visitAction, "load")
this.assert.equal(reason, "tracked_element_mismatch")
}

async "test wont reload when tracked elements has a nonce"() {
Expand All @@ -49,10 +63,20 @@ export class RenderingTests extends TurboDriveTestCase {
}

async "test reloads when turbo-visit-control setting is reload"() {
await this.remote.execute(() =>
window.addEventListener("turbo:reload", (e: any) => {
localStorage.setItem("reloadReason", e.detail.reason)
})
)

this.clickSelector("#visit-control-reload-link")
await this.nextBody

const reason = await this.remote.execute(() => localStorage.getItem("reloadReason"))

this.assert.equal(await this.pathname, "/src/tests/fixtures/visit_control_reload.html")
this.assert.equal(await this.visitAction, "load")
this.assert.equal(reason, "turbo_visit_control_is_reload")
}

async "test accumulates asset elements in head"() {
Expand Down