turbolinks 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +18 -3
- data/lib/assets/javascripts/turbolinks.js.coffee +48 -21
- data/lib/turbolinks.rb +27 -2
- metadata +9 -13
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b2bb9ae664cef07039bfb5326ff85ebcfbbca0e8
|
4
|
+
data.tar.gz: 2b55c3358f99361d016329ccfa661872599069ba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: edd2489816d02cbfd4754b28f82a95d64c4b1290c41ee28984d3333c432c339c437d27c9d4cef1b526c0b9d076ea8d0c2f5fd50a0807cd5c98aed5cec8389af7
|
7
|
+
data.tar.gz: 1fbb34bbd7831d6e72ebfc36a9aba4a019535118e0fc95db586d445f74700261a6f6bf9f26936019726f8dad510aede638671245369e43c406cb3f6744033a07
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@ How much faster is it really?
|
|
13
13
|
|
14
14
|
It depends. The more CSS and JavaScript you have, the bigger the benefit of not throwing away the browser instance and recompiling all of it for every page. Just like a CGI script that says "hello world" will be fast, but a CGI script loading Rails on every request will not.
|
15
15
|
|
16
|
-
In any case, the benefit
|
16
|
+
In any case, the benefit can be up to [twice as fast](https://github.com/steveklabnik/turbolinks_test/tree/all_the_assets) in apps with lots of JS and CSS. Of course, your mileage may vary, be dependent on your browser version, the moon cycle, and all other factors affecting performance testing. But at least it's a yardstick.
|
17
17
|
|
18
18
|
The best way to find out just how fast it is? Try it on your own application. It hardly takes any effort at all.
|
19
19
|
|
@@ -33,6 +33,7 @@ Since pages will change without a full reload with Turbolinks, you can't by defa
|
|
33
33
|
* `page:load` fetched page is being retrieved fresh from the server.
|
34
34
|
* `page:restore` fetched page is being retrieved from the 10-slot client-side cache.
|
35
35
|
* `page:change` page has changed to the newly fetched version.
|
36
|
+
* `page:receive` page has been fetched from the server, but not yet parsed.
|
36
37
|
|
37
38
|
So if you wanted to have a client-side spinner, you could listen for `page:fetch` to start it and `page:change` to stop it. If you have DOM transformation that are not idempotent (the best way), you can hook them to happen only on `page:load` instead of `page:change` (as that would run them again on the cached pages).
|
38
39
|
|
@@ -62,6 +63,19 @@ Note that internal links to files not ending in .html, or having no extension, w
|
|
62
63
|
Also, Turbolinks is installed as the last click handler for links. So if you install another handler that calls event.preventDefault(), Turbolinks will not run. This ensures that you can safely use Turbolinks with stuff like `data-method`, `data-remote`, or `data-confirm` from Rails.
|
63
64
|
|
64
65
|
|
66
|
+
jquery.turbolinks
|
67
|
+
-----------------
|
68
|
+
|
69
|
+
If you have a lot of existing JavaScript that binds elements on jQuery.ready(), you can pull the [jquery.turbolinks](https://github.com/kossnocorp/jquery.turbolinks) library into your project that will trigger ready() when Turbolinks triggers the the `page:load` event. It may restore functionality of some libraries.
|
70
|
+
|
71
|
+
Add the gem to your project, then add the following line to your JavaScript manifest file, after `jquery.js` but before `turbolinks.js`:
|
72
|
+
|
73
|
+
``` js
|
74
|
+
//= require jquery.turbolinks
|
75
|
+
```
|
76
|
+
|
77
|
+
Additional details and configuration options can be found in the [jquery.turbolinks README](https://github.com/kossnocorp/jquery.turbolinks/blob/master/README.md).
|
78
|
+
|
65
79
|
Asset change detection
|
66
80
|
----------------------
|
67
81
|
|
@@ -79,6 +93,7 @@ Evaluating script tags
|
|
79
93
|
|
80
94
|
Turbolinks will evaluate any script tags in pages it visit, if those tags do not have a type or if the type is text/javascript. All other script tags will be ignored.
|
81
95
|
|
96
|
+
As a rule of thumb when switching to Turbolinks, move all of your javascript tags inside the `head` and then work backwards, only moving javascript code back to the body if absolutely necessary.
|
82
97
|
|
83
98
|
Triggering a Turbolinks visit manually
|
84
99
|
---------------------------------------
|
@@ -97,7 +112,7 @@ Compatibility
|
|
97
112
|
|
98
113
|
Turbolinks is designed to work with any browser that fully supports pushState and all the related APIs. This includes Safari 6.0+ (but not Safari 5.1.x!), IE10, and latest Chromes and Firefoxes.
|
99
114
|
|
100
|
-
Do note that existing JavaScript libraries may not all be compatible with Turbolinks out of the box due to the change in instantiation cycle. You might very well have to modify them to work with Turbolinks' new set of events.
|
115
|
+
Do note that existing JavaScript libraries may not all be compatible with Turbolinks out of the box due to the change in instantiation cycle. You might very well have to modify them to work with Turbolinks' new set of events. For help with this, check out the [Turbolinks Compatibility](http://reed.github.com/turbolinks-compatibility) project.
|
101
116
|
|
102
117
|
|
103
118
|
Installation
|
@@ -112,4 +127,4 @@ Installation
|
|
112
127
|
Credits
|
113
128
|
-------
|
114
129
|
|
115
|
-
Thanks to Chris Wanstrath for his original work on Pjax. Thanks to Sam Stephenson and Josh Peek for their additional work on Pjax and Stacker and their help with getting Turbolinks released. Thanks to David Estes for handling the lion's share of post-release issues and feature requests. And thanks to everyone else who's fixed or reported an issue!
|
130
|
+
Thanks to Chris Wanstrath for his original work on Pjax. Thanks to Sam Stephenson and Josh Peek for their additional work on Pjax and Stacker and their help with getting Turbolinks released. Thanks to David Estes and Nick Reed for handling the lion's share of post-release issues and feature requests. And thanks to everyone else who's fixed or reported an issue!
|
@@ -22,17 +22,16 @@ fetchReplacement = (url) ->
|
|
22
22
|
# Remove hash from url to ensure IE 10 compatibility
|
23
23
|
safeUrl = removeHash url
|
24
24
|
|
25
|
-
xhr
|
25
|
+
xhr?.abort()
|
26
26
|
xhr = new XMLHttpRequest
|
27
27
|
xhr.open 'GET', safeUrl, true
|
28
28
|
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
29
29
|
xhr.setRequestHeader 'X-XHR-Referer', referer
|
30
|
-
xhr.onabort = ->
|
31
|
-
xhr = null
|
32
|
-
xhr.onload = =>
|
33
|
-
doc = createDocument xhr.responseText
|
34
30
|
|
35
|
-
|
31
|
+
xhr.onload = =>
|
32
|
+
triggerEvent 'page:receive'
|
33
|
+
|
34
|
+
if invalidContent(xhr) or assetsChanged (doc = createDocument xhr.responseText)
|
36
35
|
document.location.reload()
|
37
36
|
else
|
38
37
|
changePage extractTitleAndBody(doc)...
|
@@ -42,11 +41,10 @@ fetchReplacement = (url) ->
|
|
42
41
|
else
|
43
42
|
resetScrollPosition()
|
44
43
|
triggerEvent 'page:load'
|
45
|
-
xhr = null
|
46
44
|
|
47
|
-
xhr.
|
48
|
-
|
49
|
-
|
45
|
+
xhr.onloadend = -> xhr = null
|
46
|
+
xhr.onabort = -> rememberCurrentUrl()
|
47
|
+
xhr.onerror = -> document.location.href = url
|
50
48
|
|
51
49
|
xhr.send()
|
52
50
|
|
@@ -54,6 +52,7 @@ fetchHistory = (state) ->
|
|
54
52
|
cacheCurrentPage()
|
55
53
|
|
56
54
|
if page = pageCache[state.position]
|
55
|
+
xhr?.abort()
|
57
56
|
changePage page.title, page.body
|
58
57
|
recallScrollPosition page
|
59
58
|
triggerEvent 'page:restore'
|
@@ -80,12 +79,14 @@ constrainPageCacheTo = (limit) ->
|
|
80
79
|
changePage = (title, body, runScripts) ->
|
81
80
|
document.title = title
|
82
81
|
document.documentElement.replaceChild body, document.body
|
82
|
+
removeNoscriptTags()
|
83
83
|
executeScriptTags() if runScripts
|
84
84
|
currentState = window.history.state
|
85
85
|
triggerEvent 'page:change'
|
86
86
|
|
87
87
|
executeScriptTags = ->
|
88
|
-
|
88
|
+
scripts = Array::slice.call document.body.getElementsByTagName 'script'
|
89
|
+
for script in scripts when script.type in ['', 'text/javascript']
|
89
90
|
copy = document.createElement 'script'
|
90
91
|
copy.setAttribute attr.name, attr.value for attr in script.attributes
|
91
92
|
copy.appendChild document.createTextNode script.innerHTML
|
@@ -93,6 +94,9 @@ executeScriptTags = ->
|
|
93
94
|
parentNode.removeChild script
|
94
95
|
parentNode.insertBefore copy, nextSibling
|
95
96
|
|
97
|
+
removeNoscriptTags = ->
|
98
|
+
noscriptTags = Array::slice.call document.body.getElementsByTagName 'noscript'
|
99
|
+
noscript.parentNode.removeChild noscript for noscript in noscriptTags
|
96
100
|
|
97
101
|
reflectNewUrl = (url) ->
|
98
102
|
if url isnt document.location.href
|
@@ -136,6 +140,9 @@ triggerEvent = (name) ->
|
|
136
140
|
document.dispatchEvent event
|
137
141
|
|
138
142
|
|
143
|
+
invalidContent = (xhr) ->
|
144
|
+
!xhr.getResponseHeader('Content-Type').match /^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/
|
145
|
+
|
139
146
|
extractTrackAssets = (doc) ->
|
140
147
|
(node.src || node.href) for node in doc.head.childNodes when node.getAttribute?('data-turbolinks-track')?
|
141
148
|
|
@@ -156,6 +163,11 @@ browserCompatibleDocumentParser = ->
|
|
156
163
|
createDocumentUsingParser = (html) ->
|
157
164
|
(new DOMParser).parseFromString html, 'text/html'
|
158
165
|
|
166
|
+
createDocumentUsingDOM = (html) ->
|
167
|
+
doc = document.implementation.createHTMLDocument ''
|
168
|
+
doc.documentElement.innerHTML = html
|
169
|
+
doc
|
170
|
+
|
159
171
|
createDocumentUsingWrite = (html) ->
|
160
172
|
doc = document.implementation.createHTMLDocument ''
|
161
173
|
doc.open 'replace'
|
@@ -163,19 +175,32 @@ browserCompatibleDocumentParser = ->
|
|
163
175
|
doc.close()
|
164
176
|
doc
|
165
177
|
|
166
|
-
if
|
167
|
-
|
168
|
-
|
169
|
-
if
|
170
|
-
|
171
|
-
|
172
|
-
|
178
|
+
# Use createDocumentUsingParser if DOMParser is defined and natively
|
179
|
+
# supports 'text/html' parsing (Firefox 12+, IE 10)
|
180
|
+
#
|
181
|
+
# Use createDocumentUsingDOM if createDocumentUsingParser throws an exception
|
182
|
+
# due to unsupported type 'text/html' (Firefox < 12, Opera)
|
183
|
+
#
|
184
|
+
# Use createDocumentUsingWrite if:
|
185
|
+
# - DOMParser isn't defined
|
186
|
+
# - createDocumentUsingParser returns null due to unsupported type 'text/html' (Chrome, Safari)
|
187
|
+
# - createDocumentUsingDOM doesn't create a valid HTML document (safeguarding against potential edge cases)
|
188
|
+
try
|
189
|
+
if window.DOMParser
|
190
|
+
testDoc = createDocumentUsingParser '<html><body><p>test'
|
191
|
+
createDocumentUsingParser
|
192
|
+
catch e
|
193
|
+
testDoc = createDocumentUsingDOM '<html><body><p>test'
|
194
|
+
createDocumentUsingDOM
|
195
|
+
finally
|
196
|
+
unless testDoc?.body?.childNodes.length is 1
|
197
|
+
return createDocumentUsingWrite
|
173
198
|
|
174
199
|
|
175
200
|
installClickHandlerLast = (event) ->
|
176
201
|
unless event.defaultPrevented
|
177
|
-
document.removeEventListener 'click', handleClick
|
178
|
-
document.addEventListener 'click', handleClick
|
202
|
+
document.removeEventListener 'click', handleClick, false
|
203
|
+
document.addEventListener 'click', handleClick, false
|
179
204
|
|
180
205
|
handleClick = (event) ->
|
181
206
|
unless event.defaultPrevented
|
@@ -198,7 +223,8 @@ anchoredLink = (link) ->
|
|
198
223
|
(link.href is location.href + '#')
|
199
224
|
|
200
225
|
nonHtmlLink = (link) ->
|
201
|
-
|
226
|
+
url = removeHash link
|
227
|
+
url.match(/\.[a-z]+(\?.*)?$/g) and not url.match(/\.html?(\?.*)?$/g)
|
202
228
|
|
203
229
|
noTurbolink = (link) ->
|
204
230
|
until ignore or link is document
|
@@ -220,6 +246,7 @@ initializeTurbolinks = ->
|
|
220
246
|
document.addEventListener 'click', installClickHandlerLast, true
|
221
247
|
window.addEventListener 'popstate', (event) ->
|
222
248
|
fetchHistory event.state if event.state?.turbolinks
|
249
|
+
, false
|
223
250
|
|
224
251
|
browserSupportsPushState =
|
225
252
|
window.history and window.history.pushState and window.history.replaceState and window.history.state != undefined
|
data/lib/turbolinks.rb
CHANGED
@@ -26,12 +26,37 @@ module Turbolinks
|
|
26
26
|
cookies[:request_method] = request.request_method
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
|
+
module XDomainBlocker
|
31
|
+
private
|
32
|
+
def same_origin?(a, b)
|
33
|
+
a = URI.parse(a)
|
34
|
+
b = URI.parse(b)
|
35
|
+
[a.scheme, a.host, a.port] == [b.scheme, b.host, b.port]
|
36
|
+
end
|
37
|
+
|
38
|
+
def abort_xdomain_redirect
|
39
|
+
to_uri = response.headers['Location'] || ""
|
40
|
+
current = request.headers['X-XHR-Referer'] || ""
|
41
|
+
unless to_uri.blank? || current.blank? || same_origin?(current, to_uri)
|
42
|
+
self.status = 403
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
30
47
|
class Engine < ::Rails::Engine
|
31
48
|
initializer :turbolinks_xhr_headers do |config|
|
32
49
|
ActionController::Base.class_eval do
|
33
|
-
include XHRHeaders, Cookies
|
50
|
+
include XHRHeaders, Cookies, XDomainBlocker
|
34
51
|
before_filter :set_xhr_current_location, :set_request_method_cookie
|
52
|
+
after_filter :abort_xdomain_redirect
|
53
|
+
end
|
54
|
+
|
55
|
+
ActionDispatch::Request.class_eval do
|
56
|
+
def referer
|
57
|
+
self.headers['X-XHR-Referer'] || super
|
58
|
+
end
|
59
|
+
alias referrer referer
|
35
60
|
end
|
36
61
|
end
|
37
62
|
end
|
metadata
CHANGED
@@ -1,30 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbolinks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- David Heinemeier Hansson
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-03-24 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: coffee-rails
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
description:
|
@@ -46,27 +43,26 @@ files:
|
|
46
43
|
- test/reload.html
|
47
44
|
homepage:
|
48
45
|
licenses: []
|
46
|
+
metadata: {}
|
49
47
|
post_install_message:
|
50
48
|
rdoc_options: []
|
51
49
|
require_paths:
|
52
50
|
- lib
|
53
51
|
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
-
none: false
|
55
52
|
requirements:
|
56
|
-
- -
|
53
|
+
- - '>='
|
57
54
|
- !ruby/object:Gem::Version
|
58
55
|
version: '0'
|
59
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
57
|
requirements:
|
62
|
-
- -
|
58
|
+
- - '>='
|
63
59
|
- !ruby/object:Gem::Version
|
64
60
|
version: '0'
|
65
61
|
requirements: []
|
66
62
|
rubyforge_project:
|
67
|
-
rubygems_version:
|
63
|
+
rubygems_version: 2.0.0
|
68
64
|
signing_key:
|
69
|
-
specification_version:
|
65
|
+
specification_version: 4
|
70
66
|
summary: Turbolinks makes following links in your web application faster (use with
|
71
67
|
Rails Asset Pipeline)
|
72
68
|
test_files: []
|