diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 788f7661c..4d8596aed 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -22,18 +22,22 @@ import { Snapshot } from "../snapshot" import { ViewDelegate, ViewRenderOptions } from "../view" import { Locatable, getAction, expandURL, urlsAreEqual, locationIsVisitable } from "../url" import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/form_submit_observer" -import { FrameView } from "./frame_view" +import { FrameView, FrameViewRenderOptions } from "./frame_view" import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { FormLinkClickObserver, FormLinkClickObserverDelegate } from "../../observers/form_link_click_observer" import { FrameRenderer } from "./frame_renderer" import { session } from "../index" import { Action } from "../types" import { VisitOptions } from "../drive/visit" -import { TurboBeforeFrameRenderEvent } from "../session" +import { DriveDelegate } from "../session" import { StreamMessage } from "../streams/stream_message" import { PageSnapshot } from "../drive/page_snapshot" type VisitFallback = (location: Response | Locatable, options: Partial) => Promise + +export type TurboBeforeFrameRenderEvent = CustomEvent<{ newFrame: FrameElement } & FrameViewRenderOptions> +export type TurboFrameRenderEvent = CustomEvent<{ fetchResponse: FetchResponse }> +export type TurboFrameLoadEvent = CustomEvent export type TurboFrameMissingEvent = CustomEvent<{ response: Response; visit: VisitFallback }> export class FrameController @@ -48,6 +52,7 @@ export class FrameController ViewDelegate> { readonly element: FrameElement + readonly delegate: DriveDelegate readonly view: FrameView readonly appearanceObserver: AppearanceObserver readonly formLinkClickObserver: FormLinkClickObserver @@ -66,8 +71,9 @@ export class FrameController private currentNavigationElement?: Element pageSnapshot?: PageSnapshot - constructor(element: FrameElement) { + constructor(element: FrameElement, delegate: DriveDelegate = 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) @@ -178,8 +184,8 @@ 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) this.fetchResponseLoaded(fetchResponse) } else if (this.willHandleFrameMissingFromResponse(fetchResponse)) { console.warn( @@ -329,7 +335,7 @@ export class FrameController viewRenderedSnapshot(_snapshot: Snapshot, _isPreview: boolean) {} preloadOnLoadLinksForView(element: Element) { - session.preloadOnLoadLinksForView(element) + this.delegate.preloadOnLoadLinksForView(element) } viewInvalidated() {} @@ -351,6 +357,18 @@ export class FrameController // Private + private frameLoaded(frame: FrameElement) { + return dispatch("turbo:frame-load", { target: frame }) + } + + private frameRendered(frame: FrameElement, fetchResponse: FetchResponse) { + return dispatch("turbo:frame-render", { + detail: { fetchResponse }, + target: frame, + cancelable: true, + }) + } + private async visit(url: URL) { const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element) @@ -400,7 +418,7 @@ export class FrameController if (this.action) options.action = this.action - session.visit(frame.src, options) + this.delegate.visit(frame.src, options) } } } @@ -409,7 +427,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) } } @@ -439,7 +457,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 } }) } private findFrameElement(element: Element, submitter?: HTMLElement) { @@ -494,11 +512,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 } diff --git a/src/core/frames/frame_redirector.ts b/src/core/frames/frame_redirector.ts index 3175e9fe0..0b904240b 100644 --- a/src/core/frames/frame_redirector.ts +++ b/src/core/frames/frame_redirector.ts @@ -2,15 +2,16 @@ import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/ import { FrameElement } from "../../elements/frame_element" import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { expandURL, getAction, locationIsVisitable } from "../url" -import { Session } from "../session" +import { DriveDelegate } from "../session" + export class FrameRedirector implements LinkInterceptorDelegate, FormSubmitObserverDelegate { - readonly session: Session + readonly delegate: DriveDelegate readonly element: Element readonly linkInterceptor: LinkInterceptor readonly formSubmitObserver: FormSubmitObserver - constructor(session: Session, element: Element) { - this.session = session + constructor(delegate: DriveDelegate, element: Element) { + this.delegate = delegate this.element = element this.linkInterceptor = new LinkInterceptor(this, element) this.formSubmitObserver = new FormSubmitObserver(this, element) @@ -63,8 +64,8 @@ export class FrameRedirector implements LinkInterceptorDelegate, FormSubmitObser private shouldRedirect(element: Element, submitter?: HTMLElement) { 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) diff --git a/src/core/index.ts b/src/core/index.ts index 3ab8d8ff5..1efd69aea 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -19,16 +19,18 @@ export { TurboBeforeRenderEvent, TurboBeforeVisitEvent, TurboClickEvent, - TurboBeforeFrameRenderEvent, - TurboFrameLoadEvent, - TurboFrameRenderEvent, TurboLoadEvent, TurboRenderEvent, TurboVisitEvent, } from "./session" export { TurboSubmitStartEvent, TurboSubmitEndEvent } from "./drive/form_submission" -export { TurboFrameMissingEvent } from "./frames/frame_controller" +export { + TurboBeforeFrameRenderEvent, + TurboFrameLoadEvent, + TurboFrameMissingEvent, + TurboFrameRenderEvent, +} from "./frames/frame_controller" export { StreamActions, TurboStreamAction, TurboStreamActions } from "./streams/stream_actions" diff --git a/src/core/session.ts b/src/core/session.ts index ee94b5043..3415e6783 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -19,8 +19,6 @@ import { PageView, PageViewDelegate, PageViewRenderOptions } from "./drive/page_ import { Visit, VisitOptions } from "./drive/visit" import { PageSnapshot } from "./drive/page_snapshot" import { FrameElement } from "../elements/frame_element" -import { FrameViewRenderOptions } from "./frames/frame_view" -import { FetchResponse } from "../http/fetch_response" import { Preloader, PreloaderDelegate } from "./drive/preloader" export type FormMode = "on" | "off" | "optin" @@ -29,15 +27,22 @@ export type TurboBeforeCacheEvent = CustomEvent export type TurboBeforeRenderEvent = CustomEvent<{ newBody: HTMLBodyElement } & PageViewRenderOptions> export type TurboBeforeVisitEvent = CustomEvent<{ url: string }> export type TurboClickEvent = CustomEvent<{ url: string; originalEvent: MouseEvent }> -export type TurboFrameLoadEvent = CustomEvent -export type TurboBeforeFrameRenderEvent = CustomEvent<{ newFrame: FrameElement } & FrameViewRenderOptions> -export type TurboFrameRenderEvent = CustomEvent<{ fetchResponse: FetchResponse }> export type TurboLoadEvent = CustomEvent<{ url: string; timing: TimingData }> export type TurboRenderEvent = CustomEvent export type TurboVisitEvent = CustomEvent<{ url: string; action: Action }> +export interface DriveDelegate { + readonly history: History + + elementIsNavigatable(element: Element): boolean + submissionIsNavigatable(element: HTMLFormElement, submitter?: HTMLElement): boolean + preloadOnLoadLinksForView(element: Element): void + visit(location: Locatable, options: Partial): void +} + export class Session implements + DriveDelegate, FormSubmitObserverDelegate, HistoryDelegate, FormLinkClickObserverDelegate, @@ -303,16 +308,6 @@ export class Session this.adapter.pageInvalidated(reason) } - // Frame element - - frameLoaded(frame: FrameElement) { - this.notifyApplicationAfterFrameLoad(frame) - } - - frameRendered(fetchResponse: FetchResponse, frame: FrameElement) { - this.notifyApplicationAfterFrameRender(fetchResponse, frame) - } - // Application events applicationAllowsFollowingLinkToLocation(link: Element, location: URL, ev: MouseEvent) { @@ -374,19 +369,7 @@ export class Session ) } - notifyApplicationAfterFrameLoad(frame: FrameElement) { - return dispatch("turbo:frame-load", { target: frame }) - } - - notifyApplicationAfterFrameRender(fetchResponse: FetchResponse, frame: FrameElement) { - return dispatch("turbo:frame-render", { - detail: { fetchResponse }, - target: frame, - cancelable: true, - }) - } - - // Helpers + // Drive delegate submissionIsNavigatable(form: HTMLFormElement, submitter?: HTMLElement): boolean { if (this.formMode == "off") {