turbolinks 2.5.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/{MIT-LICENSE → LICENSE} +1 -1
- data/README.md +0 -241
- data/lib/turbolinks/redirection.rb +42 -9
- data/lib/turbolinks/version.rb +1 -1
- data/lib/turbolinks.rb +16 -22
- metadata +12 -28
- data/lib/assets/javascripts/turbolinks.js.coffee +0 -558
- data/lib/turbolinks/cookies.rb +0 -15
- data/lib/turbolinks/x_domain_blocker.rb +0 -21
- data/lib/turbolinks/xhr_headers.rb +0 -46
- data/lib/turbolinks/xhr_url_for.rb +0 -15
- data/test/config.ru +0 -55
- data/test/dummy.gif +0 -0
- data/test/index.html +0 -51
- data/test/manifest.appcache +0 -10
- data/test/offline.html +0 -19
- data/test/other.html +0 -26
- data/test/redirect1.html +0 -15
- data/test/redirect2.html +0 -11
- data/test/reload.html +0 -18
- data/test/withoutextension +0 -26
@@ -1,558 +0,0 @@
|
|
1
|
-
pageCache = {}
|
2
|
-
cacheSize = 10
|
3
|
-
transitionCacheEnabled = false
|
4
|
-
progressBar = null
|
5
|
-
|
6
|
-
currentState = null
|
7
|
-
loadedAssets = null
|
8
|
-
|
9
|
-
referer = null
|
10
|
-
|
11
|
-
xhr = null
|
12
|
-
|
13
|
-
EVENTS =
|
14
|
-
BEFORE_CHANGE: 'page:before-change'
|
15
|
-
FETCH: 'page:fetch'
|
16
|
-
RECEIVE: 'page:receive'
|
17
|
-
CHANGE: 'page:change'
|
18
|
-
UPDATE: 'page:update'
|
19
|
-
LOAD: 'page:load'
|
20
|
-
RESTORE: 'page:restore'
|
21
|
-
BEFORE_UNLOAD: 'page:before-unload'
|
22
|
-
EXPIRE: 'page:expire'
|
23
|
-
|
24
|
-
fetch = (url) ->
|
25
|
-
url = new ComponentUrl url
|
26
|
-
|
27
|
-
rememberReferer()
|
28
|
-
cacheCurrentPage()
|
29
|
-
progressBar?.start()
|
30
|
-
|
31
|
-
if transitionCacheEnabled and cachedPage = transitionCacheFor(url.absolute)
|
32
|
-
fetchHistory cachedPage
|
33
|
-
fetchReplacement url, null, false
|
34
|
-
else
|
35
|
-
fetchReplacement url, resetScrollPosition
|
36
|
-
|
37
|
-
transitionCacheFor = (url) ->
|
38
|
-
cachedPage = pageCache[url]
|
39
|
-
cachedPage if cachedPage and !cachedPage.transitionCacheDisabled
|
40
|
-
|
41
|
-
enableTransitionCache = (enable = true) ->
|
42
|
-
transitionCacheEnabled = enable
|
43
|
-
|
44
|
-
enableProgressBar = (enable = true) ->
|
45
|
-
return unless browserSupportsTurbolinks
|
46
|
-
if enable
|
47
|
-
progressBar ?= new ProgressBar 'html'
|
48
|
-
else
|
49
|
-
progressBar?.uninstall()
|
50
|
-
progressBar = null
|
51
|
-
|
52
|
-
fetchReplacement = (url, onLoadFunction, showProgressBar = true) ->
|
53
|
-
triggerEvent EVENTS.FETCH, url: url.absolute
|
54
|
-
|
55
|
-
xhr?.abort()
|
56
|
-
xhr = new XMLHttpRequest
|
57
|
-
xhr.open 'GET', url.withoutHashForIE10compatibility(), true
|
58
|
-
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
59
|
-
xhr.setRequestHeader 'X-XHR-Referer', referer
|
60
|
-
|
61
|
-
xhr.onload = ->
|
62
|
-
triggerEvent EVENTS.RECEIVE, url: url.absolute
|
63
|
-
|
64
|
-
if doc = processResponse()
|
65
|
-
reflectNewUrl url
|
66
|
-
reflectRedirectedUrl()
|
67
|
-
changePage extractTitleAndBody(doc)...
|
68
|
-
manuallyTriggerHashChangeForFirefox()
|
69
|
-
onLoadFunction?()
|
70
|
-
triggerEvent EVENTS.LOAD
|
71
|
-
else
|
72
|
-
document.location.href = crossOriginRedirect() or url.absolute
|
73
|
-
|
74
|
-
if progressBar and showProgressBar
|
75
|
-
xhr.onprogress = (event) =>
|
76
|
-
percent = if event.lengthComputable
|
77
|
-
event.loaded / event.total * 100
|
78
|
-
else
|
79
|
-
progressBar.value + (100 - progressBar.value) / 10
|
80
|
-
progressBar.advanceTo(percent)
|
81
|
-
|
82
|
-
xhr.onloadend = -> xhr = null
|
83
|
-
xhr.onerror = -> document.location.href = url.absolute
|
84
|
-
|
85
|
-
xhr.send()
|
86
|
-
|
87
|
-
fetchHistory = (cachedPage) ->
|
88
|
-
xhr?.abort()
|
89
|
-
changePage cachedPage.title, cachedPage.body
|
90
|
-
recallScrollPosition cachedPage
|
91
|
-
triggerEvent EVENTS.RESTORE
|
92
|
-
|
93
|
-
|
94
|
-
cacheCurrentPage = ->
|
95
|
-
currentStateUrl = new ComponentUrl currentState.url
|
96
|
-
|
97
|
-
pageCache[currentStateUrl.absolute] =
|
98
|
-
url: currentStateUrl.relative,
|
99
|
-
body: document.body,
|
100
|
-
title: document.title,
|
101
|
-
positionY: window.pageYOffset,
|
102
|
-
positionX: window.pageXOffset,
|
103
|
-
cachedAt: new Date().getTime(),
|
104
|
-
transitionCacheDisabled: document.querySelector('[data-no-transition-cache]')?
|
105
|
-
|
106
|
-
constrainPageCacheTo cacheSize
|
107
|
-
|
108
|
-
pagesCached = (size = cacheSize) ->
|
109
|
-
cacheSize = parseInt(size) if /^[\d]+$/.test size
|
110
|
-
|
111
|
-
constrainPageCacheTo = (limit) ->
|
112
|
-
pageCacheKeys = Object.keys pageCache
|
113
|
-
|
114
|
-
cacheTimesRecentFirst = pageCacheKeys.map (url) ->
|
115
|
-
pageCache[url].cachedAt
|
116
|
-
.sort (a, b) -> b - a
|
117
|
-
|
118
|
-
for key in pageCacheKeys when pageCache[key].cachedAt <= cacheTimesRecentFirst[limit]
|
119
|
-
triggerEvent EVENTS.EXPIRE, pageCache[key]
|
120
|
-
delete pageCache[key]
|
121
|
-
|
122
|
-
changePage = (title, body, csrfToken, runScripts) ->
|
123
|
-
triggerEvent EVENTS.BEFORE_UNLOAD
|
124
|
-
document.title = title
|
125
|
-
document.documentElement.replaceChild body, document.body
|
126
|
-
CSRFToken.update csrfToken if csrfToken?
|
127
|
-
setAutofocusElement()
|
128
|
-
executeScriptTags() if runScripts
|
129
|
-
currentState = window.history.state
|
130
|
-
progressBar?.done()
|
131
|
-
triggerEvent EVENTS.CHANGE
|
132
|
-
triggerEvent EVENTS.UPDATE
|
133
|
-
|
134
|
-
executeScriptTags = ->
|
135
|
-
scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])'
|
136
|
-
for script in scripts when script.type in ['', 'text/javascript']
|
137
|
-
copy = document.createElement 'script'
|
138
|
-
copy.setAttribute attr.name, attr.value for attr in script.attributes
|
139
|
-
copy.async = false unless script.hasAttribute 'async'
|
140
|
-
copy.appendChild document.createTextNode script.innerHTML
|
141
|
-
{ parentNode, nextSibling } = script
|
142
|
-
parentNode.removeChild script
|
143
|
-
parentNode.insertBefore copy, nextSibling
|
144
|
-
return
|
145
|
-
|
146
|
-
removeNoscriptTags = (node) ->
|
147
|
-
node.innerHTML = node.innerHTML.replace /<noscript[\S\s]*?<\/noscript>/ig, ''
|
148
|
-
node
|
149
|
-
|
150
|
-
# Firefox bug: Doesn't autofocus fields that are inserted via JavaScript
|
151
|
-
setAutofocusElement = ->
|
152
|
-
autofocusElement = (list = document.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1]
|
153
|
-
if autofocusElement and document.activeElement isnt autofocusElement
|
154
|
-
autofocusElement.focus()
|
155
|
-
|
156
|
-
reflectNewUrl = (url) ->
|
157
|
-
if (url = new ComponentUrl url).absolute isnt referer
|
158
|
-
window.history.pushState { turbolinks: true, url: url.absolute }, '', url.absolute
|
159
|
-
|
160
|
-
reflectRedirectedUrl = ->
|
161
|
-
if location = xhr.getResponseHeader 'X-XHR-Redirected-To'
|
162
|
-
location = new ComponentUrl location
|
163
|
-
preservedHash = if location.hasNoHash() then document.location.hash else ''
|
164
|
-
window.history.replaceState window.history.state, '', location.href + preservedHash
|
165
|
-
|
166
|
-
crossOriginRedirect = ->
|
167
|
-
redirect if (redirect = xhr.getResponseHeader('Location'))? and (new ComponentUrl(redirect)).crossOrigin()
|
168
|
-
|
169
|
-
rememberReferer = ->
|
170
|
-
referer = document.location.href
|
171
|
-
|
172
|
-
rememberCurrentUrl = ->
|
173
|
-
window.history.replaceState { turbolinks: true, url: document.location.href }, '', document.location.href
|
174
|
-
|
175
|
-
rememberCurrentState = ->
|
176
|
-
currentState = window.history.state
|
177
|
-
|
178
|
-
# Unlike other browsers, Firefox doesn't trigger hashchange after changing the
|
179
|
-
# location (via pushState) to an anchor on a different page. For example:
|
180
|
-
#
|
181
|
-
# /pages/one => /pages/two#with-hash
|
182
|
-
#
|
183
|
-
# By forcing Firefox to trigger hashchange, the rest of the code can rely on more
|
184
|
-
# consistent behavior across browsers.
|
185
|
-
manuallyTriggerHashChangeForFirefox = ->
|
186
|
-
if navigator.userAgent.match(/Firefox/) and !(url = (new ComponentUrl)).hasNoHash()
|
187
|
-
window.history.replaceState currentState, '', url.withoutHash()
|
188
|
-
document.location.hash = url.hash
|
189
|
-
|
190
|
-
recallScrollPosition = (page) ->
|
191
|
-
window.scrollTo page.positionX, page.positionY
|
192
|
-
|
193
|
-
resetScrollPosition = ->
|
194
|
-
if document.location.hash
|
195
|
-
document.location.href = document.location.href
|
196
|
-
else
|
197
|
-
window.scrollTo 0, 0
|
198
|
-
|
199
|
-
|
200
|
-
clone = (original) ->
|
201
|
-
return original if not original? or typeof original isnt 'object'
|
202
|
-
copy = new original.constructor()
|
203
|
-
copy[key] = clone value for key, value of original
|
204
|
-
copy
|
205
|
-
|
206
|
-
popCookie = (name) ->
|
207
|
-
value = document.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or ''
|
208
|
-
document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'
|
209
|
-
value
|
210
|
-
|
211
|
-
triggerEvent = (name, data) ->
|
212
|
-
if typeof Prototype isnt 'undefined'
|
213
|
-
Event.fire document, name, data, true
|
214
|
-
|
215
|
-
event = document.createEvent 'Events'
|
216
|
-
event.data = data if data
|
217
|
-
event.initEvent name, true, true
|
218
|
-
document.dispatchEvent event
|
219
|
-
|
220
|
-
pageChangePrevented = (url) ->
|
221
|
-
!triggerEvent EVENTS.BEFORE_CHANGE, url: url
|
222
|
-
|
223
|
-
processResponse = ->
|
224
|
-
clientOrServerError = ->
|
225
|
-
400 <= xhr.status < 600
|
226
|
-
|
227
|
-
validContent = ->
|
228
|
-
(contentType = xhr.getResponseHeader('Content-Type'))? and
|
229
|
-
contentType.match /^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/
|
230
|
-
|
231
|
-
extractTrackAssets = (doc) ->
|
232
|
-
for node in doc.querySelector('head').childNodes when node.getAttribute?('data-turbolinks-track')?
|
233
|
-
node.getAttribute('src') or node.getAttribute('href')
|
234
|
-
|
235
|
-
assetsChanged = (doc) ->
|
236
|
-
loadedAssets ||= extractTrackAssets document
|
237
|
-
fetchedAssets = extractTrackAssets doc
|
238
|
-
fetchedAssets.length isnt loadedAssets.length or intersection(fetchedAssets, loadedAssets).length isnt loadedAssets.length
|
239
|
-
|
240
|
-
intersection = (a, b) ->
|
241
|
-
[a, b] = [b, a] if a.length > b.length
|
242
|
-
value for value in a when value in b
|
243
|
-
|
244
|
-
if not clientOrServerError() and validContent()
|
245
|
-
doc = createDocument xhr.responseText
|
246
|
-
if doc and !assetsChanged doc
|
247
|
-
return doc
|
248
|
-
|
249
|
-
extractTitleAndBody = (doc) ->
|
250
|
-
title = doc.querySelector 'title'
|
251
|
-
[ title?.textContent, removeNoscriptTags(doc.querySelector('body')), CSRFToken.get(doc).token, 'runScripts' ]
|
252
|
-
|
253
|
-
CSRFToken =
|
254
|
-
get: (doc = document) ->
|
255
|
-
node: tag = doc.querySelector 'meta[name="csrf-token"]'
|
256
|
-
token: tag?.getAttribute? 'content'
|
257
|
-
|
258
|
-
update: (latest) ->
|
259
|
-
current = @get()
|
260
|
-
if current.token? and latest? and current.token isnt latest
|
261
|
-
current.node.setAttribute 'content', latest
|
262
|
-
|
263
|
-
createDocument = (html) ->
|
264
|
-
doc = document.documentElement.cloneNode()
|
265
|
-
doc.innerHTML = html
|
266
|
-
doc.head = doc.querySelector 'head'
|
267
|
-
doc.body = doc.querySelector 'body'
|
268
|
-
doc
|
269
|
-
|
270
|
-
# The ComponentUrl class converts a basic URL string into an object
|
271
|
-
# that behaves similarly to document.location.
|
272
|
-
#
|
273
|
-
# If an instance is created from a relative URL, the current document
|
274
|
-
# is used to fill in the missing attributes (protocol, host, port).
|
275
|
-
class ComponentUrl
|
276
|
-
constructor: (@original = document.location.href) ->
|
277
|
-
return @original if @original.constructor is ComponentUrl
|
278
|
-
@_parse()
|
279
|
-
|
280
|
-
withoutHash: -> @href.replace(@hash, '').replace('#', '')
|
281
|
-
|
282
|
-
# Intention revealing function alias
|
283
|
-
withoutHashForIE10compatibility: -> @withoutHash()
|
284
|
-
|
285
|
-
hasNoHash: -> @hash.length is 0
|
286
|
-
|
287
|
-
crossOrigin: ->
|
288
|
-
@origin isnt (new ComponentUrl).origin
|
289
|
-
|
290
|
-
_parse: ->
|
291
|
-
(@link ?= document.createElement 'a').href = @original
|
292
|
-
{ @href, @protocol, @host, @hostname, @port, @pathname, @search, @hash } = @link
|
293
|
-
@origin = [@protocol, '//', @hostname].join ''
|
294
|
-
@origin += ":#{@port}" unless @port.length is 0
|
295
|
-
@relative = [@pathname, @search, @hash].join ''
|
296
|
-
@absolute = @href
|
297
|
-
|
298
|
-
# The Link class derives from the ComponentUrl class, but is built from an
|
299
|
-
# existing link element. Provides verification functionality for Turbolinks
|
300
|
-
# to use in determining whether it should process the link when clicked.
|
301
|
-
class Link extends ComponentUrl
|
302
|
-
@HTML_EXTENSIONS: ['html']
|
303
|
-
|
304
|
-
@allowExtensions: (extensions...) ->
|
305
|
-
Link.HTML_EXTENSIONS.push extension for extension in extensions
|
306
|
-
Link.HTML_EXTENSIONS
|
307
|
-
|
308
|
-
constructor: (@link) ->
|
309
|
-
return @link if @link.constructor is Link
|
310
|
-
@original = @link.href
|
311
|
-
@originalElement = @link
|
312
|
-
@link = @link.cloneNode false
|
313
|
-
super
|
314
|
-
|
315
|
-
shouldIgnore: ->
|
316
|
-
@crossOrigin() or
|
317
|
-
@_anchored() or
|
318
|
-
@_nonHtml() or
|
319
|
-
@_optOut() or
|
320
|
-
@_target()
|
321
|
-
|
322
|
-
_anchored: ->
|
323
|
-
(@hash.length > 0 or @href.charAt(@href.length - 1) is '#') and
|
324
|
-
(@withoutHash() is (new ComponentUrl).withoutHash())
|
325
|
-
|
326
|
-
_nonHtml: ->
|
327
|
-
@pathname.match(/\.[a-z]+$/g) and not @pathname.match(new RegExp("\\.(?:#{Link.HTML_EXTENSIONS.join('|')})?$", 'g'))
|
328
|
-
|
329
|
-
_optOut: ->
|
330
|
-
link = @originalElement
|
331
|
-
until ignore or link is document
|
332
|
-
ignore = link.getAttribute('data-no-turbolink')?
|
333
|
-
link = link.parentNode
|
334
|
-
ignore
|
335
|
-
|
336
|
-
_target: ->
|
337
|
-
@link.target.length isnt 0
|
338
|
-
|
339
|
-
|
340
|
-
# The Click class handles clicked links, verifying if Turbolinks should
|
341
|
-
# take control by inspecting both the event and the link. If it should,
|
342
|
-
# the page change process is initiated. If not, control is passed back
|
343
|
-
# to the browser for default functionality.
|
344
|
-
class Click
|
345
|
-
@installHandlerLast: (event) ->
|
346
|
-
unless event.defaultPrevented
|
347
|
-
document.removeEventListener 'click', Click.handle, false
|
348
|
-
document.addEventListener 'click', Click.handle, false
|
349
|
-
|
350
|
-
@handle: (event) ->
|
351
|
-
new Click event
|
352
|
-
|
353
|
-
constructor: (@event) ->
|
354
|
-
return if @event.defaultPrevented
|
355
|
-
@_extractLink()
|
356
|
-
if @_validForTurbolinks()
|
357
|
-
visit @link.href unless pageChangePrevented(@link.absolute)
|
358
|
-
@event.preventDefault()
|
359
|
-
|
360
|
-
_extractLink: ->
|
361
|
-
link = @event.target
|
362
|
-
link = link.parentNode until !link.parentNode or link.nodeName is 'A'
|
363
|
-
@link = new Link(link) if link.nodeName is 'A' and link.href.length isnt 0
|
364
|
-
|
365
|
-
_validForTurbolinks: ->
|
366
|
-
@link? and not (@link.shouldIgnore() or @_nonStandardClick())
|
367
|
-
|
368
|
-
_nonStandardClick: ->
|
369
|
-
@event.which > 1 or
|
370
|
-
@event.metaKey or
|
371
|
-
@event.ctrlKey or
|
372
|
-
@event.shiftKey or
|
373
|
-
@event.altKey
|
374
|
-
|
375
|
-
|
376
|
-
class ProgressBar
|
377
|
-
className = 'turbolinks-progress-bar'
|
378
|
-
|
379
|
-
constructor: (@elementSelector) ->
|
380
|
-
@value = 0
|
381
|
-
@content = ''
|
382
|
-
@speed = 300
|
383
|
-
# Setting the opacity to a value < 1 fixes a display issue in Safari 6 and
|
384
|
-
# iOS 6 where the progress bar would fill the entire page.
|
385
|
-
@opacity = 0.99
|
386
|
-
@install()
|
387
|
-
|
388
|
-
install: ->
|
389
|
-
@element = document.querySelector(@elementSelector)
|
390
|
-
@element.classList.add(className)
|
391
|
-
@styleElement = document.createElement('style')
|
392
|
-
document.head.appendChild(@styleElement)
|
393
|
-
@_updateStyle()
|
394
|
-
|
395
|
-
uninstall: ->
|
396
|
-
@element.classList.remove(className)
|
397
|
-
document.head.removeChild(@styleElement)
|
398
|
-
|
399
|
-
start: ->
|
400
|
-
@advanceTo(5)
|
401
|
-
|
402
|
-
advanceTo: (value) ->
|
403
|
-
if value > @value <= 100
|
404
|
-
@value = value
|
405
|
-
@_updateStyle()
|
406
|
-
|
407
|
-
if @value is 100
|
408
|
-
@_stopTrickle()
|
409
|
-
else if @value > 0
|
410
|
-
@_startTrickle()
|
411
|
-
|
412
|
-
done: ->
|
413
|
-
if @value > 0
|
414
|
-
@advanceTo(100)
|
415
|
-
@_reset()
|
416
|
-
|
417
|
-
_reset: ->
|
418
|
-
originalOpacity = @opacity
|
419
|
-
|
420
|
-
setTimeout =>
|
421
|
-
@opacity = 0
|
422
|
-
@_updateStyle()
|
423
|
-
, @speed / 2
|
424
|
-
|
425
|
-
setTimeout =>
|
426
|
-
@value = 0
|
427
|
-
@opacity = originalOpacity
|
428
|
-
@_withSpeed(0, => @_updateStyle(true))
|
429
|
-
, @speed
|
430
|
-
|
431
|
-
_startTrickle: ->
|
432
|
-
return if @trickling
|
433
|
-
@trickling = true
|
434
|
-
setTimeout(@_trickle, @speed)
|
435
|
-
|
436
|
-
_stopTrickle: ->
|
437
|
-
delete @trickling
|
438
|
-
|
439
|
-
_trickle: =>
|
440
|
-
return unless @trickling
|
441
|
-
@advanceTo(@value + Math.random() / 2)
|
442
|
-
setTimeout(@_trickle, @speed)
|
443
|
-
|
444
|
-
_withSpeed: (speed, fn) ->
|
445
|
-
originalSpeed = @speed
|
446
|
-
@speed = speed
|
447
|
-
result = fn()
|
448
|
-
@speed = originalSpeed
|
449
|
-
result
|
450
|
-
|
451
|
-
_updateStyle: (forceRepaint = false) ->
|
452
|
-
@_changeContentToForceRepaint() if forceRepaint
|
453
|
-
@styleElement.textContent = @_createCSSRule()
|
454
|
-
|
455
|
-
_changeContentToForceRepaint: ->
|
456
|
-
@content = if @content is '' then ' ' else ''
|
457
|
-
|
458
|
-
_createCSSRule: ->
|
459
|
-
"""
|
460
|
-
#{@elementSelector}.#{className}::before {
|
461
|
-
content: '#{@content}';
|
462
|
-
position: fixed;
|
463
|
-
top: 0;
|
464
|
-
left: 0;
|
465
|
-
z-index: 2000;
|
466
|
-
background-color: #0076ff;
|
467
|
-
height: 3px;
|
468
|
-
opacity: #{@opacity};
|
469
|
-
width: #{@value}%;
|
470
|
-
transition: width #{@speed}ms ease-out, opacity #{@speed / 2}ms ease-in;
|
471
|
-
transform: translate3d(0,0,0);
|
472
|
-
}
|
473
|
-
"""
|
474
|
-
|
475
|
-
|
476
|
-
# Delay execution of function long enough to miss the popstate event
|
477
|
-
# some browsers fire on the initial page load.
|
478
|
-
bypassOnLoadPopstate = (fn) ->
|
479
|
-
setTimeout fn, 500
|
480
|
-
|
481
|
-
installDocumentReadyPageEventTriggers = ->
|
482
|
-
document.addEventListener 'DOMContentLoaded', ( ->
|
483
|
-
triggerEvent EVENTS.CHANGE
|
484
|
-
triggerEvent EVENTS.UPDATE
|
485
|
-
), true
|
486
|
-
|
487
|
-
installJqueryAjaxSuccessPageUpdateTrigger = ->
|
488
|
-
if typeof jQuery isnt 'undefined'
|
489
|
-
jQuery(document).on 'ajaxSuccess', (event, xhr, settings) ->
|
490
|
-
return unless jQuery.trim xhr.responseText
|
491
|
-
triggerEvent EVENTS.UPDATE
|
492
|
-
|
493
|
-
installHistoryChangeHandler = (event) ->
|
494
|
-
if event.state?.turbolinks
|
495
|
-
if cachedPage = pageCache[(new ComponentUrl(event.state.url)).absolute]
|
496
|
-
cacheCurrentPage()
|
497
|
-
fetchHistory cachedPage
|
498
|
-
else
|
499
|
-
visit event.target.location.href
|
500
|
-
|
501
|
-
initializeTurbolinks = ->
|
502
|
-
rememberCurrentUrl()
|
503
|
-
rememberCurrentState()
|
504
|
-
|
505
|
-
document.addEventListener 'click', Click.installHandlerLast, true
|
506
|
-
|
507
|
-
window.addEventListener 'hashchange', (event) ->
|
508
|
-
rememberCurrentUrl()
|
509
|
-
rememberCurrentState()
|
510
|
-
, false
|
511
|
-
bypassOnLoadPopstate ->
|
512
|
-
window.addEventListener 'popstate', installHistoryChangeHandler, false
|
513
|
-
|
514
|
-
# Handle bug in Firefox 26/27 where history.state is initially undefined
|
515
|
-
historyStateIsDefined =
|
516
|
-
window.history.state != undefined or navigator.userAgent.match /Firefox\/2[6|7]/
|
517
|
-
|
518
|
-
browserSupportsPushState =
|
519
|
-
window.history and window.history.pushState and window.history.replaceState and historyStateIsDefined
|
520
|
-
|
521
|
-
browserIsntBuggy =
|
522
|
-
!navigator.userAgent.match /CriOS\//
|
523
|
-
|
524
|
-
requestMethodIsSafe =
|
525
|
-
popCookie('request_method') in ['GET','']
|
526
|
-
|
527
|
-
browserSupportsTurbolinks = browserSupportsPushState and browserIsntBuggy and requestMethodIsSafe
|
528
|
-
|
529
|
-
browserSupportsCustomEvents =
|
530
|
-
document.addEventListener and document.createEvent
|
531
|
-
|
532
|
-
if browserSupportsCustomEvents
|
533
|
-
installDocumentReadyPageEventTriggers()
|
534
|
-
installJqueryAjaxSuccessPageUpdateTrigger()
|
535
|
-
|
536
|
-
if browserSupportsTurbolinks
|
537
|
-
visit = fetch
|
538
|
-
initializeTurbolinks()
|
539
|
-
else
|
540
|
-
visit = (url) -> document.location.href = url
|
541
|
-
|
542
|
-
# Public API
|
543
|
-
# Turbolinks.visit(url)
|
544
|
-
# Turbolinks.pagesCached()
|
545
|
-
# Turbolinks.pagesCached(20)
|
546
|
-
# Turbolinks.enableTransitionCache()
|
547
|
-
# Turbolinks.allowLinkExtensions('md')
|
548
|
-
# Turbolinks.supported
|
549
|
-
# Turbolinks.EVENTS
|
550
|
-
@Turbolinks = {
|
551
|
-
visit,
|
552
|
-
pagesCached,
|
553
|
-
enableTransitionCache,
|
554
|
-
enableProgressBar,
|
555
|
-
allowLinkExtensions: Link.allowExtensions,
|
556
|
-
supported: browserSupportsTurbolinks,
|
557
|
-
EVENTS: clone(EVENTS)
|
558
|
-
}
|
data/lib/turbolinks/cookies.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
module Turbolinks
|
2
|
-
# For non-GET requests, sets a request_method cookie containing
|
3
|
-
# the request method of the current request. The Turbolinks script
|
4
|
-
# will not initialize if this cookie is set.
|
5
|
-
module Cookies
|
6
|
-
private
|
7
|
-
def set_request_method_cookie
|
8
|
-
if request.get?
|
9
|
-
cookies.delete(:request_method)
|
10
|
-
else
|
11
|
-
cookies[:request_method] = request.request_method
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Turbolinks
|
2
|
-
# Changes the response status to 403 Forbidden if all of these conditions are true:
|
3
|
-
# - The current request originated from Turbolinks
|
4
|
-
# - The request is being redirected to a different domain
|
5
|
-
module XDomainBlocker
|
6
|
-
private
|
7
|
-
def same_origin?(a, b)
|
8
|
-
a = URI.parse URI.escape(a)
|
9
|
-
b = URI.parse URI.escape(b)
|
10
|
-
[a.scheme, a.host, a.port] == [b.scheme, b.host, b.port]
|
11
|
-
end
|
12
|
-
|
13
|
-
def abort_xdomain_redirect
|
14
|
-
to_uri = response.headers['Location'] || ""
|
15
|
-
current = request.headers['X-XHR-Referer'] || ""
|
16
|
-
unless to_uri.blank? || current.blank? || same_origin?(current, to_uri)
|
17
|
-
self.status = 403
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Turbolinks
|
2
|
-
# Intercepts calls to _compute_redirect_to_location (used by redirect_to) for two purposes.
|
3
|
-
#
|
4
|
-
# 1. Corrects the behavior of redirect_to with the :back option by using the X-XHR-Referer
|
5
|
-
# request header instead of the standard Referer request header.
|
6
|
-
#
|
7
|
-
# 2. Stores the return value (the redirect target url) to persist through to the redirect
|
8
|
-
# request, where it will be used to set the X-XHR-Redirected-To response header. The
|
9
|
-
# Turbolinks script will detect the header and use replaceState to reflect the redirected
|
10
|
-
# url.
|
11
|
-
module XHRHeaders
|
12
|
-
extend ActiveSupport::Concern
|
13
|
-
|
14
|
-
def _compute_redirect_to_location(*args)
|
15
|
-
options, request = _normalize_redirect_params(args)
|
16
|
-
|
17
|
-
store_for_turbolinks begin
|
18
|
-
if options == :back && request.headers["X-XHR-Referer"]
|
19
|
-
super(*[(request if args.length == 2), request.headers["X-XHR-Referer"]].compact)
|
20
|
-
else
|
21
|
-
super(*args)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
def store_for_turbolinks(url)
|
28
|
-
session[:_turbolinks_redirect_to] = url if session && request.headers["X-XHR-Referer"]
|
29
|
-
url
|
30
|
-
end
|
31
|
-
|
32
|
-
def set_xhr_redirected_to
|
33
|
-
if session && session[:_turbolinks_redirect_to]
|
34
|
-
response.headers['X-XHR-Redirected-To'] = session.delete :_turbolinks_redirect_to
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Ensure backwards compatibility
|
39
|
-
# Rails < 4.2: _compute_redirect_to_location(options)
|
40
|
-
# Rails >= 4.2: _compute_redirect_to_location(request, options)
|
41
|
-
def _normalize_redirect_params(args)
|
42
|
-
options, req = args.reverse
|
43
|
-
[options, req || request]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Turbolinks
|
2
|
-
# Corrects the behavior of url_for (and link_to, which uses url_for) with the :back
|
3
|
-
# option by using the X-XHR-Referer request header instead of the standard Referer
|
4
|
-
# request header.
|
5
|
-
module XHRUrlFor
|
6
|
-
def self.included(base)
|
7
|
-
base.alias_method_chain :url_for, :xhr_referer
|
8
|
-
end
|
9
|
-
|
10
|
-
def url_for_with_xhr_referer(options = {})
|
11
|
-
options = (controller.request.headers["X-XHR-Referer"] || options) if options == :back
|
12
|
-
url_for_without_xhr_referer options
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|