Skip to content

Commit

Permalink
Add a turbo:reload event that returns the reason why turbo needed t…
Browse files Browse the repository at this point in the history
…o do a hard reload. (#556)

* Return why the page needed a reload

* Dispatch an event turbo:reload when forcing a reload

* Improve error message

* Add tests for turbo:reload

Co-authored-by: dgreif <[email protected]>

* check turbo:reload event when visit-reload

* Change errors to use codes instead of sentences. Also support adding context to the error

* cleanup unused function

* use " instead of ' for strings

Co-authored-by: dgreif <[email protected]>
  • Loading branch information
manuelpuyol and dgreif authored Apr 30, 2022
1 parent da35d5b commit 41119f9
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 14 deletions.
15 changes: 15 additions & 0 deletions src/core/drive/page_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Renderer } from "../renderer"
import { PageSnapshot } from "./page_snapshot"
import { ReloadReason } from "../native/browser_adapter"

const INTERNAL_ATTRIBUTES = ["aria-busy", "data-turbo-preview"]

Expand All @@ -8,6 +9,20 @@ export class PageRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
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()
this.updateHtmlElementAttributes()
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 All @@ -28,10 +32,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 @@ -42,10 +56,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

0 comments on commit 41119f9

Please sign in to comment.