Skip to content

Commit 18585fc

Browse files
committed
Replace Location class with browser-provided URL
The Turbo-internal `Location` class' responsibilities overlap almost entirely with the browser-provided [URL][] class. In addition, Turbo's `Location` class name conflicts with the browser-provided [Location][] class, which is itself an almost `URL`-compliant interface. This commit removes the Turbo-internal `Location` class, and modifies all consuming interfaces to use `URL` instead. The properties and functions that do not have corresponding [URL][]-provided versions have been extracted to the `src/core/url.ts` module. [URL]: https://developer.mozilla.org/en-US/docs/Web/API/URL [Location]: https://developer.mozilla.org/en-US/docs/Web/API/Location
1 parent 178c817 commit 18585fc

17 files changed

Lines changed: 166 additions & 204 deletions

src/core/drive/form_submission.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FetchRequest, FetchMethod, fetchMethodFromString, FetchRequestHeaders } from "../../http/fetch_request"
22
import { FetchResponse } from "../../http/fetch_response"
3-
import { Location } from "../location"
3+
import { expandPath } from "../url"
44
import { dispatch } from "../../util"
55

66
export interface FormSubmissionDelegate {
@@ -52,8 +52,8 @@ export class FormSubmission {
5252
return this.submitter?.getAttribute("formaction") || this.formElement.action
5353
}
5454

55-
get location() {
56-
return Location.wrap(this.action)
55+
get location(): URL {
56+
return expandPath(this.action)
5757
}
5858

5959
// The submission process

src/core/drive/history.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { Location } from "../location"
21
import { Position } from "../types"
32
import { nextMicrotask, uuid } from "../../util"
43

54
export interface HistoryDelegate {
6-
historyPoppedToLocationWithRestorationIdentifier(location: Location, restorationIdentifier: string): void
5+
historyPoppedToLocationWithRestorationIdentifier(location: URL, restorationIdentifier: string): void
76
}
87

98
type HistoryMethod = (this: typeof history, state: any, title: string, url?: string | null | undefined) => void
@@ -14,7 +13,7 @@ export type RestorationDataMap = { [restorationIdentifier: string]: RestorationD
1413

1514
export class History {
1615
readonly delegate: HistoryDelegate
17-
location!: Location
16+
location!: URL
1817
restorationIdentifier = uuid()
1918
restorationData: RestorationDataMap = {}
2019
started = false
@@ -30,7 +29,7 @@ export class History {
3029
addEventListener("popstate", this.onPopState, false)
3130
addEventListener("load", this.onPageLoad, false)
3231
this.started = true
33-
this.replace(Location.currentLocation)
32+
this.replace(new URL(window.location.href))
3433
}
3534
}
3635

@@ -42,17 +41,17 @@ export class History {
4241
}
4342
}
4443

45-
push(location: Location, restorationIdentifier?: string) {
44+
push(location: URL, restorationIdentifier?: string) {
4645
this.update(history.pushState, location, restorationIdentifier)
4746
}
4847

49-
replace(location: Location, restorationIdentifier?: string) {
48+
replace(location: URL, restorationIdentifier?: string) {
5049
this.update(history.replaceState, location, restorationIdentifier)
5150
}
5251

53-
update(method: HistoryMethod, location: Location, restorationIdentifier = uuid()) {
52+
update(method: HistoryMethod, location: URL, restorationIdentifier = uuid()) {
5453
const state = { turbo: { restorationIdentifier } }
55-
method.call(history, state, "", location.absoluteURL)
54+
method.call(history, state, "", location.href)
5655
this.location = location
5756
this.restorationIdentifier = restorationIdentifier
5857
}
@@ -91,11 +90,10 @@ export class History {
9190
if (this.shouldHandlePopState()) {
9291
const { turbo } = event.state || {}
9392
if (turbo) {
94-
const location = Location.currentLocation
95-
this.location = location
93+
this.location = new URL(window.location.href)
9694
const { restorationIdentifier } = turbo
9795
this.restorationIdentifier = restorationIdentifier
98-
this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier)
96+
this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier)
9997
}
10098
}
10199
}

src/core/drive/navigator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { FetchMethod } from "../../http/fetch_request"
22
import { FetchResponse } from "../../http/fetch_response"
33
import { FormSubmission } from "./form_submission"
4-
import { Locatable, Location } from "../location"
4+
import { expandPath, Locatable } from "../url"
55
import { Visit, VisitDelegate, VisitOptions } from "./visit"
66
import { Snapshot } from "./snapshot"
77

88
export type NavigatorDelegate = VisitDelegate & {
9-
allowsVisitingLocation(location: Location): boolean
10-
visitProposedToLocation(location: Location, options: Partial<VisitOptions>): void
9+
allowsVisitingLocation(location: URL): boolean
10+
visitProposedToLocation(location: URL, options: Partial<VisitOptions>): void
1111
}
1212

1313
export class Navigator {
@@ -19,15 +19,15 @@ export class Navigator {
1919
this.delegate = delegate
2020
}
2121

22-
proposeVisit(location: Location, options: Partial<VisitOptions> = {}) {
22+
proposeVisit(location: URL, options: Partial<VisitOptions> = {}) {
2323
if (this.delegate.allowsVisitingLocation(location)) {
2424
this.delegate.visitProposedToLocation(location, options)
2525
}
2626
}
2727

2828
startVisit(location: Locatable, restorationIdentifier: string, options: Partial<VisitOptions> = {}) {
2929
this.stop()
30-
this.currentVisit = new Visit(this, Location.wrap(location), restorationIdentifier, {
30+
this.currentVisit = new Visit(this, expandPath(location), restorationIdentifier, {
3131
referrer: this.location,
3232
...options
3333
})

src/core/drive/snapshot.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HeadDetails } from "./head_details"
2-
import { Location } from "../location"
2+
import { expandPath } from "../url"
33

44
export class Snapshot {
55
static wrap(value: Snapshot | string | HTMLHtmlElement) {
@@ -37,9 +37,9 @@ export class Snapshot {
3737
return new Snapshot(this.headDetails, bodyElement)
3838
}
3939

40-
getRootLocation() {
40+
getRootLocation(): URL {
4141
const root = this.getSetting("root", "/")
42-
return new Location(root)
42+
return expandPath(root)
4343
}
4444

4545
getCacheControlValue() {

src/core/drive/snapshot_cache.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Location } from "../location"
1+
import { toCacheKey } from "../url"
22
import { Snapshot } from "./snapshot"
33

44
export class SnapshotCache {
@@ -10,19 +10,19 @@ export class SnapshotCache {
1010
this.size = size
1111
}
1212

13-
has(location: Location) {
14-
return location.toCacheKey() in this.snapshots
13+
has(location: URL) {
14+
return toCacheKey(location) in this.snapshots
1515
}
1616

17-
get(location: Location): Snapshot | undefined {
17+
get(location: URL): Snapshot | undefined {
1818
if (this.has(location)) {
1919
const snapshot = this.read(location)
2020
this.touch(location)
2121
return snapshot
2222
}
2323
}
2424

25-
put(location: Location, snapshot: Snapshot) {
25+
put(location: URL, snapshot: Snapshot) {
2626
this.write(location, snapshot)
2727
this.touch(location)
2828
return snapshot
@@ -34,16 +34,16 @@ export class SnapshotCache {
3434

3535
// Private
3636

37-
read(location: Location) {
38-
return this.snapshots[location.toCacheKey()]
37+
read(location: URL) {
38+
return this.snapshots[toCacheKey(location)]
3939
}
4040

41-
write(location: Location, snapshot: Snapshot) {
42-
this.snapshots[location.toCacheKey()] = snapshot
41+
write(location: URL, snapshot: Snapshot) {
42+
this.snapshots[toCacheKey(location)] = snapshot
4343
}
4444

45-
touch(location: Location) {
46-
const key = location.toCacheKey()
45+
touch(location: URL) {
46+
const key = toCacheKey(location)
4747
const index = this.keys.indexOf(key)
4848
if (index > -1) this.keys.splice(index, 1)
4949
this.keys.unshift(key)

src/core/drive/view.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ErrorRenderer } from "./error_renderer"
2-
import { Location } from "../location"
32
import { Snapshot } from "./snapshot"
43
import { SnapshotCache } from "./snapshot_cache"
54
import { RenderCallback, RenderDelegate, SnapshotRenderer } from "./snapshot_renderer"
@@ -16,13 +15,13 @@ export class View {
1615
readonly delegate: ViewDelegate
1716
readonly htmlElement = document.documentElement as HTMLHtmlElement
1817
readonly snapshotCache = new SnapshotCache(10)
19-
lastRenderedLocation?: Location
18+
lastRenderedLocation?: URL
2019

2120
constructor(delegate: ViewDelegate) {
2221
this.delegate = delegate
2322
}
2423

25-
getRootLocation(): Location {
24+
getRootLocation(): URL {
2625
return this.getSnapshot().getRootLocation()
2726
}
2827

@@ -46,13 +45,13 @@ export class View {
4645
if (this.shouldCacheSnapshot()) {
4746
this.delegate.viewWillCacheSnapshot()
4847
const snapshot = this.getSnapshot()
49-
const location = this.lastRenderedLocation || Location.currentLocation
48+
const location = this.lastRenderedLocation || new URL(window.location.href)
5049
await nextMicrotask()
5150
this.snapshotCache.put(location, snapshot.clone())
5251
}
5352
}
5453

55-
getCachedSnapshotForLocation(location: Location) {
54+
getCachedSnapshotForLocation(location: URL) {
5655
return this.snapshotCache.get(location)
5756
}
5857

src/core/drive/visit.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Adapter } from "../native/adapter"
22
import { FetchMethod, FetchRequest, FetchRequestDelegate } from "../../http/fetch_request"
33
import { FetchResponse } from "../../http/fetch_response"
44
import { History } from "./history"
5-
import { Location } from "../location"
5+
import { anchor } from "../url"
66
import { RenderCallback } from "./renderer"
77
import { Snapshot } from "./snapshot"
88
import { Action } from "../types"
@@ -38,7 +38,7 @@ export enum VisitState {
3838
export type VisitOptions = {
3939
action: Action,
4040
historyChanged: boolean,
41-
referrer?: Location,
41+
referrer?: URL,
4242
snapshotHTML?: string,
4343
response?: VisitResponse
4444
}
@@ -64,22 +64,22 @@ export class Visit implements FetchRequestDelegate {
6464
readonly identifier = uuid()
6565
readonly restorationIdentifier: string
6666
readonly action: Action
67-
readonly referrer?: Location
67+
readonly referrer?: URL
6868
readonly timingMetrics: TimingMetrics = {}
6969

7070
followedRedirect = false
7171
frame?: number
7272
historyChanged = false
73-
location: Location
74-
redirectedToLocation?: Location
73+
location: URL
74+
redirectedToLocation?: URL
7575
request?: FetchRequest
7676
response?: VisitResponse
7777
scrolled = false
7878
snapshotHTML?: string
7979
snapshotCached = false
8080
state = VisitState.initialized
8181

82-
constructor(delegate: VisitDelegate, location: Location, restorationIdentifier: string | undefined, options: Partial<VisitOptions> = {}) {
82+
constructor(delegate: VisitDelegate, location: URL, restorationIdentifier: string | undefined, options: Partial<VisitOptions> = {}) {
8383
this.delegate = delegate
8484
this.location = location
8585
this.restorationIdentifier = restorationIdentifier || uuid()
@@ -145,7 +145,7 @@ export class Visit implements FetchRequestDelegate {
145145

146146
changeHistory() {
147147
if (!this.historyChanged) {
148-
const actionForHistory = this.location.isEqualTo(this.referrer) ? "replace" : this.action
148+
const actionForHistory = this.location === this.referrer ? "replace" : this.action
149149
const method = this.getHistoryMethodForAction(actionForHistory)
150150
this.history.update(method, this.location, this.restorationIdentifier)
151151
this.historyChanged = true
@@ -212,7 +212,7 @@ export class Visit implements FetchRequestDelegate {
212212
getCachedSnapshot() {
213213
const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot()
214214

215-
if (snapshot && (!this.location.anchor || snapshot.hasAnchor(this.location.anchor))) {
215+
if (snapshot && (!anchor(this.location) || snapshot.hasAnchor(anchor(this.location)))) {
216216
if (this.action == "restore" || snapshot.isPreviewable()) {
217217
return snapshot
218218
}
@@ -311,8 +311,8 @@ export class Visit implements FetchRequestDelegate {
311311
}
312312

313313
scrollToAnchor() {
314-
if (this.location.anchor != null) {
315-
this.view.scrollToAnchor(this.location.anchor)
314+
if (anchor(this.location) != null) {
315+
this.view.scrollToAnchor(anchor(this.location))
316316
return true
317317
}
318318
}

src/core/frames/frame_controller.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { FetchResponse } from "../../http/fetch_response"
44
import { AppearanceObserver, AppearanceObserverDelegate } from "../../observers/appearance_observer"
55
import { nextAnimationFrame } from "../../util"
66
import { FormSubmission, FormSubmissionDelegate } from "../drive/form_submission"
7-
import { Locatable, Location } from "../location"
87
import { FormInterceptor, FormInterceptorDelegate } from "./form_interceptor"
98
import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor"
9+
import { expandPath, Locatable } from "../url"
1010

1111
export class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormInterceptorDelegate, FormSubmissionDelegate, FrameElementDelegate, LinkInterceptorDelegate {
1212
readonly element: FrameElement
@@ -107,7 +107,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
107107

108108
this.formSubmission = new FormSubmission(this, element, submitter)
109109
if (this.formSubmission.fetchRequest.isIdempotent) {
110-
this.navigateFrame(element, this.formSubmission.fetchRequest.url)
110+
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href)
111111
} else {
112112
this.formSubmission.start()
113113
}
@@ -172,7 +172,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
172172
// Private
173173

174174
private async visit(url: Locatable) {
175-
const location = Location.wrap(url)
175+
const location = expandPath(url.toString())
176176
const request = new FetchRequest(this, FetchMethod.get, location)
177177

178178
return new Promise<void>(resolve => {

src/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Adapter } from "./native/adapter"
22
import { Session } from "./session"
3-
import { Locatable } from "./location"
3+
import { Locatable } from "./url"
44
import { StreamMessage } from "./streams/stream_message"
55
import { StreamSource } from "./types"
66
import { VisitOptions } from "./drive/visit"

0 commit comments

Comments
 (0)