Skip to content

Commit

Permalink
Extract DriveDelegate interface
Browse files Browse the repository at this point in the history
The `FrameController` depends on the application's singleton `Session`
instance in several ways:

* it drives the `History`
* it determines whether or not an `Element` is "enabled" to navigate a
  page or frame
* it preloads links
* it dispatches `turbo:frame-render` and `turbo:frame-load` events
* it can initiate a `Visit`

This commit codifies those dependent behaviors by creating the
`DriveDelegate` interface and declaring the `Session` class as an
extension of that interface.

With the codification of the `DriveDelegate` responsibilities
established, managing `turbo:frame-render` and `turbo:frame-load` events
feels out of scope. To resolve that, this commit migrates the
`Session.frameRendered` and `Session.frameLoaded` methods to the
`FrameController`, and omits them from the `DriveDelegate` interface.

The `FrameController` still directly imports the singleton instance of
the `Session` from the `src/core/index` module, but assigns it to a
`delegate: DriveDelegate` property for use throughout the class.

The next step would be to find a way to forward the `session` instance
into the [FrameElement.delegateConstructor][] assignment.

[FrameElement.delegateConstructor]: https://github.com/hotwired/turbo/blob/3a18111412cb4190b818b96c9c6aad7264cc4441/src/elements/index.ts#L6
  • Loading branch information
seanpdoyle committed Sep 14, 2023
1 parent 191c085 commit 317a5cc
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 36 deletions.
32 changes: 23 additions & 9 deletions src/core/frames/frame_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ export class FrameController {
#ignoredAttributes = new Set()
action = null

constructor(element) {
constructor(element, delegate = session) {
this.element = element
this.delegate = delegate
this.view = new FrameView(this, this.element)
this.appearanceObserver = new AppearanceObserver(this, this.element)
this.formLinkClickObserver = new FormLinkClickObserver(this, this.element)
Expand Down Expand Up @@ -279,7 +280,7 @@ export class FrameController {
viewRenderedSnapshot(_snapshot, _isPreview) {}

preloadOnLoadLinksForView(element) {
session.preloadOnLoadLinksForView(element)
this.delegate.preloadOnLoadLinksForView(element)
}

viewInvalidated() {}
Expand Down Expand Up @@ -313,14 +314,27 @@ export class FrameController {

await this.view.render(renderer)
this.complete = true
session.frameRendered(fetchResponse, this.element)
session.frameLoaded(this.element)
this.frameRendered(this.element, fetchResponse)
this.frameLoaded(this.element)

await this.fetchResponseLoaded(fetchResponse)
} else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {
this.#handleFrameMissingFromResponse(fetchResponse)
}
}

frameLoaded(frame) {
return dispatch("turbo:frame-load", { target: frame })
}

frameRendered(frame, fetchResponse) {
return dispatch("turbo:frame-render", {
detail: { fetchResponse },
target: frame,
cancelable: true
})
}

async #visit(url) {
const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element)

Expand Down Expand Up @@ -370,7 +384,7 @@ export class FrameController {

if (this.action) options.action = this.action

session.visit(frame.src, options)
this.delegate.visit(frame.src, options)
}
}
}
Expand All @@ -379,7 +393,7 @@ export class FrameController {
changeHistory() {
if (this.action) {
const method = getHistoryMethodForAction(this.action)
session.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier)
this.delegate.history.update(method, expandURL(this.element.src || ""), this.restorationIdentifier)
}
}

Expand Down Expand Up @@ -427,7 +441,7 @@ export class FrameController {
const responseHTML = await wrapped.responseHTML
const { location, redirected, statusCode } = wrapped

return session.visit(location, { response: { redirected, statusCode, responseHTML } })
return this.delegate.visit(location, { response: { redirected, statusCode, responseHTML } })
}

#findFrameElement(element, submitter) {
Expand Down Expand Up @@ -482,11 +496,11 @@ export class FrameController {
}
}

if (!session.elementIsNavigatable(element)) {
if (!this.delegate.elementIsNavigatable(element)) {
return false
}

if (submitter && !session.elementIsNavigatable(submitter)) {
if (submitter && !this.delegate.elementIsNavigatable(submitter)) {
return false
}

Expand Down
8 changes: 4 additions & 4 deletions src/core/frames/frame_redirector.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { LinkInterceptor } from "./link_interceptor"
import { expandURL, getAction, locationIsVisitable } from "../url"

export class FrameRedirector {
constructor(session, element) {
this.session = session
constructor(delegate, element) {
this.delegate = delegate
this.element = element
this.linkInterceptor = new LinkInterceptor(this, element)
this.formSubmitObserver = new FormSubmitObserver(this, element)
Expand Down Expand Up @@ -62,8 +62,8 @@ export class FrameRedirector {
#shouldRedirect(element, submitter) {
const isNavigatable =
element instanceof HTMLFormElement
? this.session.submissionIsNavigatable(element, submitter)
: this.session.elementIsNavigatable(element)
? this.delegate.submissionIsNavigatable(element, submitter)
: this.delegate.elementIsNavigatable(element)

if (isNavigatable) {
const frame = this.#findFrameElement(element, submitter)
Expand Down
24 changes: 1 addition & 23 deletions src/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,16 +276,6 @@ export class Session {
this.adapter.pageInvalidated(reason)
}

// Frame element

frameLoaded(frame) {
this.notifyApplicationAfterFrameLoad(frame)
}

frameRendered(fetchResponse, frame) {
this.notifyApplicationAfterFrameRender(fetchResponse, frame)
}

// Application events

applicationAllowsFollowingLinkToLocation(link, location, ev) {
Expand Down Expand Up @@ -347,19 +337,7 @@ export class Session {
)
}

notifyApplicationAfterFrameLoad(frame) {
return dispatch("turbo:frame-load", { target: frame })
}

notifyApplicationAfterFrameRender(fetchResponse, frame) {
return dispatch("turbo:frame-render", {
detail: { fetchResponse },
target: frame,
cancelable: true
})
}

// Helpers
// Drive delegate

submissionIsNavigatable(form, submitter) {
if (this.formMode == "off") {
Expand Down

0 comments on commit 317a5cc

Please sign in to comment.