1
1
import {
2
- doesNotTargetIFrame ,
2
+ dispatch ,
3
3
getLocationForLink ,
4
4
getMetaContent ,
5
5
findClosestRecursively
6
6
} from "../util"
7
7
8
- import { StreamMessage } from "../core/streams/stream_message"
9
8
import { FetchMethod , FetchRequest } from "../http/fetch_request"
10
9
import { prefetchCache , cacheTtl } from "../core/drive/prefetch_cache"
11
10
12
11
export class LinkPrefetchObserver {
13
12
started = false
14
- hoverTriggerEvent = "mouseenter"
15
- touchTriggerEvent = "touchstart"
13
+ #prefetchedLink = null
16
14
17
15
constructor ( delegate , eventTarget ) {
18
16
this . delegate = delegate
@@ -32,33 +30,35 @@ export class LinkPrefetchObserver {
32
30
stop ( ) {
33
31
if ( ! this . started ) return
34
32
35
- this . eventTarget . removeEventListener ( this . hoverTriggerEvent , this . #tryToPrefetchRequest, {
33
+ this . eventTarget . removeEventListener ( "mouseenter" , this . #tryToPrefetchRequest, {
36
34
capture : true ,
37
35
passive : true
38
36
} )
39
- this . eventTarget . removeEventListener ( this . touchTriggerEvent , this . #tryToPrefetchRequest , {
37
+ this . eventTarget . removeEventListener ( "mouseleave" , this . #cancelRequestIfObsolete , {
40
38
capture : true ,
41
39
passive : true
42
40
} )
41
+
43
42
this . eventTarget . removeEventListener ( "turbo:before-fetch-request" , this . #tryToUsePrefetchedRequest, true )
44
43
this . started = false
45
44
}
46
45
47
46
#enable = ( ) => {
48
- this . eventTarget . addEventListener ( this . hoverTriggerEvent , this . #tryToPrefetchRequest, {
47
+ this . eventTarget . addEventListener ( "mouseenter" , this . #tryToPrefetchRequest, {
49
48
capture : true ,
50
49
passive : true
51
50
} )
52
- this . eventTarget . addEventListener ( this . touchTriggerEvent , this . #tryToPrefetchRequest , {
51
+ this . eventTarget . addEventListener ( "mouseleave" , this . #cancelRequestIfObsolete , {
53
52
capture : true ,
54
53
passive : true
55
54
} )
55
+
56
56
this . eventTarget . addEventListener ( "turbo:before-fetch-request" , this . #tryToUsePrefetchedRequest, true )
57
57
this . started = true
58
58
}
59
59
60
60
#tryToPrefetchRequest = ( event ) => {
61
- if ( getMetaContent ( "turbo-prefetch" ) !== "true ") return
61
+ if ( getMetaContent ( "turbo-prefetch" ) === "false ") return
62
62
63
63
const target = event . target
64
64
const isLink = target . matches && target . matches ( "a[href]:not([target^=_]):not([download])" )
@@ -68,6 +68,8 @@ export class LinkPrefetchObserver {
68
68
const location = getLocationForLink ( link )
69
69
70
70
if ( this . delegate . canPrefetchRequestToLocation ( link , location ) ) {
71
+ this . #prefetchedLink = link
72
+
71
73
const fetchRequest = new FetchRequest (
72
74
this ,
73
75
FetchMethod . get ,
@@ -85,6 +87,15 @@ export class LinkPrefetchObserver {
85
87
}
86
88
}
87
89
90
+ #cancelRequestIfObsolete = ( event ) => {
91
+ if ( event . target === this . #prefetchedLink) this . #cancelPrefetchRequest( )
92
+ }
93
+
94
+ #cancelPrefetchRequest = ( ) => {
95
+ prefetchCache . clear ( )
96
+ this . #prefetchedLink = null
97
+ }
98
+
88
99
#tryToUsePrefetchedRequest = ( event ) => {
89
100
if ( event . target . tagName !== "FORM" && event . detail . fetchOptions . method === "get" ) {
90
101
const cached = prefetchCache . get ( event . detail . url . toString ( ) )
@@ -109,10 +120,6 @@ export class LinkPrefetchObserver {
109
120
if ( turboFrameTarget && turboFrameTarget !== "_top" ) {
110
121
request . headers [ "Turbo-Frame" ] = turboFrameTarget
111
122
}
112
-
113
- if ( link . hasAttribute ( "data-turbo-stream" ) ) {
114
- request . acceptResponseType ( StreamMessage . contentType )
115
- }
116
123
}
117
124
118
125
// Fetch request interface
@@ -136,41 +143,52 @@ export class LinkPrefetchObserver {
136
143
#isPrefetchable( link ) {
137
144
const href = link . getAttribute ( "href" )
138
145
139
- if ( ! href || href === "#" || link . getAttribute ( "data-turbo" ) === "false" || link . getAttribute ( "data-turbo-prefetch" ) === "false" ) {
140
- return false
141
- }
146
+ if ( ! href ) return false
142
147
143
- if ( link . origin !== document . location . origin ) {
144
- return false
145
- }
148
+ if ( unfetchableLink ( link ) ) return false
149
+ if ( linkToTheSamePage ( link ) ) return false
150
+ if ( linkOptsOut ( link ) ) return false
151
+ if ( nonSafeLink ( link ) ) return false
152
+ if ( eventPrevented ( link ) ) return false
146
153
147
- if ( ! [ "http:" , "https:" ] . includes ( link . protocol ) ) {
148
- return false
149
- }
154
+ return true
155
+ }
156
+ }
150
157
151
- if ( link . pathname + link . search === document . location . pathname + document . location . search ) {
152
- return false
153
- }
158
+ const unfetchableLink = ( link ) => {
159
+ return link . origin !== document . location . origin || ! [ "http:" , "https:" ] . includes ( link . protocol ) || link . hasAttribute ( "target" )
160
+ }
154
161
155
- const turboMethod = link . getAttribute ( "data-turbo-method" )
156
- if ( turboMethod && turboMethod !== "get" ) {
157
- return false
158
- }
162
+ const linkToTheSamePage = ( link ) => {
163
+ return ( link . pathname + link . search === document . location . pathname + document . location . search ) || link . href . startsWith ( "#" )
164
+ }
159
165
160
- if ( targetsIframe ( link ) ) {
161
- return false
162
- }
166
+ const linkOptsOut = ( link ) => {
167
+ if ( link . getAttribute ( "data-turbo-prefetch" ) === "false" ) return true
168
+ if ( link . getAttribute ( "data-turbo" ) === "false" ) return true
163
169
164
- const turboPrefetchParent = findClosestRecursively ( link , "[data-turbo-prefetch]" )
170
+ const turboPrefetchParent = findClosestRecursively ( link , "[data-turbo-prefetch]" )
171
+ if ( turboPrefetchParent && turboPrefetchParent . getAttribute ( "data-turbo-prefetch" ) === "false" ) return true
165
172
166
- if ( turboPrefetchParent && turboPrefetchParent . getAttribute ( "data-turbo-prefetch" ) === "false" ) {
167
- return false
168
- }
173
+ return false
174
+ }
169
175
170
- return true
171
- }
176
+ const nonSafeLink = ( link ) => {
177
+ const turboMethod = link . getAttribute ( "data-turbo-method" )
178
+ if ( turboMethod && turboMethod . toLowerCase ( ) !== "get" ) return true
179
+
180
+ if ( isUJS ( link ) ) return true
181
+ if ( link . hasAttribute ( "data-turbo-confirm" ) ) return true
182
+ if ( link . hasAttribute ( "data-turbo-stream" ) ) return true
183
+
184
+ return false
185
+ }
186
+
187
+ const isUJS = ( link ) => {
188
+ return link . hasAttribute ( "data-remote" ) || link . hasAttribute ( "data-behavior" ) || link . hasAttribute ( "data-confirm" ) || link . hasAttribute ( "data-method" )
172
189
}
173
190
174
- const targetsIframe = ( link ) => {
175
- return ! doesNotTargetIFrame ( link )
191
+ const eventPrevented = ( link ) => {
192
+ const event = dispatch ( "turbo:before-prefetch" , { target : link , cancelable : true } )
193
+ return event . defaultPrevented
176
194
}
0 commit comments