rstacruz-turbolinks 3.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.
@@ -0,0 +1,64 @@
1
+ require 'turbolinks/version'
2
+ require 'turbolinks/xhr_headers'
3
+ require 'turbolinks/xhr_redirect'
4
+ require 'turbolinks/xhr_url_for'
5
+ require 'turbolinks/cookies'
6
+ require 'turbolinks/x_domain_blocker'
7
+ require 'turbolinks/redirection'
8
+
9
+ module Turbolinks
10
+ module Controller
11
+ include XHRHeaders, Cookies, XDomainBlocker, Redirection
12
+
13
+ def self.included(base)
14
+ if base.respond_to?(:before_action)
15
+ base.before_action :set_xhr_redirected_to, :set_request_method_cookie
16
+ base.after_action :abort_xdomain_redirect
17
+ else
18
+ base.before_filter :set_xhr_redirected_to, :set_request_method_cookie
19
+ base.after_filter :abort_xdomain_redirect
20
+ end
21
+ end
22
+ end
23
+
24
+ class Engine < ::Rails::Engine
25
+ config.turbolinks = ActiveSupport::OrderedOptions.new
26
+ config.turbolinks.auto_include = true
27
+
28
+ initializer :turbolinks do |app|
29
+ ActiveSupport.on_load(:action_controller) do
30
+ next if self != ActionController::Base
31
+
32
+ if app.config.turbolinks.auto_include
33
+ include Controller
34
+ end
35
+
36
+ ActionDispatch::Request.class_eval do
37
+ def referer
38
+ self.headers['X-XHR-Referer'] || super
39
+ end
40
+ alias referrer referer
41
+ end
42
+
43
+ require 'action_dispatch/routing/redirection'
44
+ ActionDispatch::Routing::Redirect.class_eval do
45
+ if defined?(prepend)
46
+ prepend XHRRedirect
47
+ else
48
+ include LegacyXHRRedirect
49
+ end
50
+ end
51
+ end
52
+
53
+ ActiveSupport.on_load(:action_view) do
54
+ (ActionView::RoutingUrlFor rescue ActionView::Helpers::UrlHelper).module_eval do
55
+ if defined?(prepend) && Rails.version >= '4'
56
+ prepend XHRUrlFor
57
+ else
58
+ include LegacyXHRUrlFor
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,77 @@
1
+ module Turbolinks
2
+ # Provides a means of using Turbolinks to perform renders and redirects.
3
+ # The server will respond with a JavaScript call to Turbolinks.visit/replace().
4
+ module Redirection
5
+ MUTATION_MODES = [:change, :append, :prepend].freeze
6
+
7
+ def redirect_to(url = {}, response_status = {})
8
+ turbolinks, options = _extract_turbolinks_options!(response_status)
9
+ turbolinks = (request.xhr? && (options.size > 0 || !request.get?)) if turbolinks.nil?
10
+
11
+ if turbolinks
12
+ response.content_type = Mime[:js]
13
+ end
14
+
15
+ return_value = super(url, response_status)
16
+
17
+ if turbolinks
18
+ self.status = 200
19
+ self.response_body = "Turbolinks.visit('#{location}'#{_turbolinks_js_options(options)});"
20
+ end
21
+
22
+ return_value
23
+ end
24
+
25
+ def render(*args, &block)
26
+ render_options = args.extract_options!
27
+ turbolinks, options = _extract_turbolinks_options!(render_options)
28
+ turbolinks = (request.xhr? && options.size > 0) if turbolinks.nil?
29
+
30
+ if turbolinks
31
+ response.content_type = Mime[:js]
32
+
33
+ render_options = _normalize_render(*args, render_options, &block)
34
+ body = render_to_body(render_options)
35
+
36
+ self.status = 200
37
+ self.response_body = "Turbolinks.replace('#{view_context.j(body)}'#{_turbolinks_js_options(options)});"
38
+ else
39
+ super(*args, render_options, &block)
40
+ end
41
+
42
+ self.response_body
43
+ end
44
+
45
+ def redirect_via_turbolinks_to(url = {}, response_status = {})
46
+ ActiveSupport::Deprecation.warn("`redirect_via_turbolinks_to` is deprecated and will be removed in Turbolinks 3.1. Use redirect_to(url, turbolinks: true) instead.")
47
+ redirect_to(url, response_status.merge!(turbolinks: true))
48
+ end
49
+
50
+ private
51
+ def _extract_turbolinks_options!(options)
52
+ turbolinks = options.delete(:turbolinks)
53
+ options = options.extract!(:keep, :change, :append, :prepend, :flush).delete_if { |_, value| value.nil? }
54
+
55
+ raise ArgumentError, "cannot combine :keep and :flush options" if options[:keep] && options[:flush]
56
+
57
+ MUTATION_MODES.each do |mutation_mode_option|
58
+ raise ArgumentError, "cannot combine :keep and :#{mutation_mode_option} options" if options[:keep] && options[mutation_mode_option]
59
+ raise ArgumentError, "cannot combine :flush and :#{mutation_mode_option} options" if options[:flush] && options[mutation_mode_option]
60
+ end if options[:keep] || options[:flush]
61
+
62
+ [turbolinks, options]
63
+ end
64
+
65
+ def _turbolinks_js_options(options)
66
+ js_options = {}
67
+
68
+ js_options[:change] = Array(options[:change]) if options[:change]
69
+ js_options[:append] = Array(options[:append]) if options[:append]
70
+ js_options[:prepend] = Array(options[:prepend]) if options[:prepend]
71
+ js_options[:keep] = Array(options[:keep]) if options[:keep]
72
+ js_options[:flush] = true if options[:flush]
73
+
74
+ ", #{js_options.to_json}" if js_options.present?
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,3 @@
1
+ module Turbolinks
2
+ VERSION = '3.0.0'
3
+ end
@@ -0,0 +1,22 @@
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
+ rescue URI::InvalidURIError
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
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
+ def _compute_redirect_to_location(*args)
13
+ options, request = _normalize_redirect_params(args)
14
+
15
+ store_for_turbolinks begin
16
+ if options == :back && request.headers["X-XHR-Referer"]
17
+ super(*[(request if args.length == 2), request.headers["X-XHR-Referer"]].compact)
18
+ else
19
+ super(*args)
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+ def store_for_turbolinks(url)
26
+ session[:_turbolinks_redirect_to] = url if session && request.headers["X-XHR-Referer"]
27
+ url
28
+ end
29
+
30
+ def set_xhr_redirected_to
31
+ if session && session[:_turbolinks_redirect_to]
32
+ response.headers['X-XHR-Redirected-To'] = session.delete :_turbolinks_redirect_to
33
+ end
34
+ end
35
+
36
+ # Ensure backwards compatibility
37
+ # Rails < 4.2: _compute_redirect_to_location(options)
38
+ # Rails >= 4.2: _compute_redirect_to_location(request, options)
39
+ def _normalize_redirect_params(args)
40
+ options, req = args.reverse
41
+ [options, req || request]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ module Turbolinks
2
+ module XHRRedirect
3
+ def call(env)
4
+ status, headers, body = super(env)
5
+
6
+ if env['rack.session'] && env['HTTP_X_XHR_REFERER']
7
+ env['rack.session'][:_turbolinks_redirect_to] = headers['Location']
8
+ end
9
+
10
+ [status, headers, body]
11
+ end
12
+ end
13
+
14
+ # TODO: Remove me when support for Ruby < 2 && Rails < 4 is dropped
15
+ module LegacyXHRRedirect
16
+ def self.included(base)
17
+ base.alias_method_chain :call, :turbolinks
18
+ end
19
+
20
+ def call_with_turbolinks(env)
21
+ status, headers, body = call_without_turbolinks(env)
22
+
23
+ if env['rack.session'] && env['HTTP_X_XHR_REFERER']
24
+ env['rack.session'][:_turbolinks_redirect_to] = headers['Location']
25
+ end
26
+
27
+ [status, headers, body]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
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 url_for(options = {})
7
+ options = (controller.request.headers["X-XHR-Referer"] || options) if options == :back
8
+ super
9
+ end
10
+ end
11
+
12
+ # TODO: Remove me when support for Ruby < 2 && Rails < 4 is dropped
13
+ module LegacyXHRUrlFor
14
+ def self.included(base)
15
+ base.alias_method_chain :url_for, :xhr_referer
16
+ end
17
+
18
+ def url_for_with_xhr_referer(options = {})
19
+ options = (controller.request.headers["X-XHR-Referer"] || options) if options == :back
20
+ url_for_without_xhr_referer options
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <script language="javascript">alert("you shouldn't see this");</script>
4
+ </body>
5
+ </html>
@@ -0,0 +1,65 @@
1
+ require 'sprockets'
2
+ require 'coffee-script'
3
+
4
+ Root = File.expand_path("../..", __FILE__)
5
+
6
+ Assets = Sprockets::Environment.new do |env|
7
+ env.append_path File.join(Root, "lib", "assets", "javascripts")
8
+ env.append_path File.join(Root, "node_modules", "mocha")
9
+ env.append_path File.join(Root, "node_modules", "chai")
10
+ env.append_path File.join(Root, "node_modules", "jquery", "dist")
11
+ env.append_path File.join(Root, "test", "javascript")
12
+ end
13
+
14
+ class SlowResponse
15
+ CHUNKS = ['<html><body>', '.'*50, '.'*20, '<a href="/index.html">Home</a></body></html>']
16
+
17
+ def call(env)
18
+ [200, headers, self]
19
+ end
20
+
21
+ def each
22
+ CHUNKS.each do |part|
23
+ sleep rand(0.3..0.8)
24
+ yield part
25
+ end
26
+ end
27
+
28
+ def length
29
+ CHUNKS.join.length
30
+ end
31
+
32
+ def headers
33
+ { "Content-Length" => length.to_s, "Content-Type" => "text/html", "Cache-Control" => "no-cache, no-store, must-revalidate" }
34
+ end
35
+ end
36
+
37
+ map "/js" do
38
+ run Assets
39
+ end
40
+
41
+ map "/500" do
42
+ # throw Internal Server Error (500)
43
+ end
44
+
45
+ map "/withoutextension" do
46
+ run Rack::File.new(File.join(Root, "test", "withoutextension"), "Content-Type" => "text/html")
47
+ end
48
+
49
+ map "/slow-response" do
50
+ run SlowResponse.new
51
+ end
52
+
53
+ map "/bounce" do
54
+ run Proc.new{ [200, { "X-XHR-Redirected-To" => "redirect1.html", "Content-Type" => "text/html" }, File.open( File.join( Root, "test", "redirect1.html" ) ) ] }
55
+ end
56
+
57
+ map "/attachment.txt" do
58
+ run Rack::File.new(File.join(Root, "test", "attachment.html"), "Content-Type" => "text/plain")
59
+ end
60
+
61
+ map "/attachment.html" do
62
+ run Rack::File.new(File.join(Root, "test", "attachment.html"), "Content-Type" => "text/html", "Content-Disposition" => "attachment; filename=attachment.html")
63
+ end
64
+
65
+ run Rack::Directory.new(File.join(Root, "test"))
Binary file
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Home</title>
6
+ <script type="text/javascript" src="/js/turbolinks.js"></script>
7
+ </head>
8
+ <body class="page-other">
9
+ <form action="form.html" method="get">
10
+ <input type="text" name="value" value="42" />
11
+ <button type="submit">Submit</button>
12
+ </form>
13
+ <ul>
14
+ <li><a href="/index.html">Home</a></li>
15
+ </ul>
16
+ </body>
17
+ </html>
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Home</title>
6
+ <script type="text/javascript" src="/js/turbolinks.js"></script>
7
+ <script type="text/javascript">
8
+ document.addEventListener("page:change", function() {
9
+ console.log("page changed");
10
+ });
11
+
12
+ document.addEventListener("page:update", function() {
13
+ console.log("page updated");
14
+ });
15
+
16
+ document.addEventListener("page:restore", function() {
17
+ console.log("page restored");
18
+ });
19
+ </script>
20
+ </head>
21
+ <body class="page-index">
22
+ <ul style="margin-top:20px;">
23
+ <li><a href="/other.html">Other page</a></li>
24
+ <li><a href="/form.html">Form</a></li>
25
+ <li><a href="/slow-response">Slow loading page for progress bar</a></li>
26
+ <li><a href="/other.html"><span>Wrapped link</span></a></li>
27
+ <li><a href="/withoutextension">Without extension</a></li>
28
+ <li><a href="/withoutextension?sort=user.name">Without extension with query params</a></li>
29
+ <li><a href="http://www.google.com/">Cross origin</a></li>
30
+ <li><a href="/other.html" onclick="if(!confirm('follow link?')) { return false}">Confirm Fire Order</a></li>
31
+ <li><a href="/reload.html"><span>New assets track </span></a></li>
32
+ <li><a href="/partial1.html" data-no-turbolink>Partial replacement</a></li>
33
+ <li><a href="/dummy.gif?12345">Query Param Image Link</a></li>
34
+ <li><a href="/bounce">Redirect</a></li>
35
+ <li><a href="#">Hash link</a></li>
36
+ <li><a href="/reload.html#foo">New assets track with hash link</a></li>
37
+ <li><a href="/attachment.txt">A text response should load normally</a></li>
38
+ <li><a href="/attachment.html">An html response with Content-Disposition: attachment should load normally</a></li>
39
+ <li><h5>If you stop the server or go into airplane/offline mode</h5></li>
40
+ <li><a href="/doesnotexist.html">A page with client error (4xx, rfc2616 sec. 10.4) should error out</a></li>
41
+ <li><a href="/500">Also server errors (5xx, rfc2616 sec. 10.5) should error out</a></li>
42
+ <li><a href="/fallback.html">A page that has a fallback in appcache should fallback</a></li>
43
+ </ul>
44
+
45
+ <div style="background:#ccc;height:5000px;width:200px;">
46
+ </div>
47
+ <iframe height='1' scrolling='no' src='/offline.html' style='display: none;' width='1'></iframe>
48
+
49
+ <script type="text/javascript" data-turbolinks-eval=false>
50
+ console.log("turbolinks-eval-false script fired. This should only happen on the initial page load.");
51
+ </script>
52
+ </body>
53
+ </html>
@@ -0,0 +1,10 @@
1
+ CACHE MANIFEST
2
+
3
+ CACHE:
4
+ /offline.html
5
+
6
+ NETWORK:
7
+ *
8
+
9
+ FALLBACK:
10
+ /fallback.html /offline.html