turbolinks-2.5.3/0000755000004100000410000000000012460734345013755 5ustar www-datawww-dataturbolinks-2.5.3/MIT-LICENSE0000644000004100000410000000205512460734345015413 0ustar www-datawww-dataCopyright 2012-2014 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. turbolinks-2.5.3/lib/0000755000004100000410000000000012460734345014523 5ustar www-datawww-dataturbolinks-2.5.3/lib/turbolinks.rb0000644000004100000410000000175712460734345017256 0ustar www-datawww-datarequire 'turbolinks/version' require 'turbolinks/xhr_headers' require 'turbolinks/xhr_url_for' require 'turbolinks/cookies' require 'turbolinks/x_domain_blocker' require 'turbolinks/redirection' module Turbolinks class Engine < ::Rails::Engine initializer :turbolinks do |config| ActiveSupport.on_load(:action_controller) do ActionController::Base.class_eval do include XHRHeaders, Cookies, XDomainBlocker, Redirection before_filter :set_xhr_redirected_to, :set_request_method_cookie after_filter :abort_xdomain_redirect end ActionDispatch::Request.class_eval do def referer self.headers['X-XHR-Referer'] || super end alias referrer referer end end ActiveSupport.on_load(:action_view) do (ActionView::RoutingUrlFor rescue ActionView::Helpers::UrlHelper).module_eval do include XHRUrlFor end end unless RUBY_VERSION =~ /^1\.8/ end end end turbolinks-2.5.3/lib/assets/0000755000004100000410000000000012460734345016025 5ustar www-datawww-dataturbolinks-2.5.3/lib/assets/javascripts/0000755000004100000410000000000012460734345020356 5ustar www-datawww-dataturbolinks-2.5.3/lib/assets/javascripts/turbolinks.js.coffee0000644000004100000410000004101012460734345024332 0ustar www-datawww-datapageCache = {} cacheSize = 10 transitionCacheEnabled = false progressBar = null currentState = null loadedAssets = null referer = null xhr = null EVENTS = BEFORE_CHANGE: 'page:before-change' FETCH: 'page:fetch' RECEIVE: 'page:receive' CHANGE: 'page:change' UPDATE: 'page:update' LOAD: 'page:load' RESTORE: 'page:restore' BEFORE_UNLOAD: 'page:before-unload' EXPIRE: 'page:expire' fetch = (url) -> url = new ComponentUrl url rememberReferer() cacheCurrentPage() progressBar?.start() if transitionCacheEnabled and cachedPage = transitionCacheFor(url.absolute) fetchHistory cachedPage fetchReplacement url, null, false else fetchReplacement url, resetScrollPosition transitionCacheFor = (url) -> cachedPage = pageCache[url] cachedPage if cachedPage and !cachedPage.transitionCacheDisabled enableTransitionCache = (enable = true) -> transitionCacheEnabled = enable enableProgressBar = (enable = true) -> return unless browserSupportsTurbolinks if enable progressBar ?= new ProgressBar 'html' else progressBar?.uninstall() progressBar = null fetchReplacement = (url, onLoadFunction, showProgressBar = true) -> triggerEvent EVENTS.FETCH, url: url.absolute xhr?.abort() xhr = new XMLHttpRequest xhr.open 'GET', url.withoutHashForIE10compatibility(), true xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml' xhr.setRequestHeader 'X-XHR-Referer', referer xhr.onload = -> triggerEvent EVENTS.RECEIVE, url: url.absolute if doc = processResponse() reflectNewUrl url reflectRedirectedUrl() changePage extractTitleAndBody(doc)... manuallyTriggerHashChangeForFirefox() onLoadFunction?() triggerEvent EVENTS.LOAD else document.location.href = crossOriginRedirect() or url.absolute if progressBar and showProgressBar xhr.onprogress = (event) => percent = if event.lengthComputable event.loaded / event.total * 100 else progressBar.value + (100 - progressBar.value) / 10 progressBar.advanceTo(percent) xhr.onloadend = -> xhr = null xhr.onerror = -> document.location.href = url.absolute xhr.send() fetchHistory = (cachedPage) -> xhr?.abort() changePage cachedPage.title, cachedPage.body recallScrollPosition cachedPage triggerEvent EVENTS.RESTORE cacheCurrentPage = -> currentStateUrl = new ComponentUrl currentState.url pageCache[currentStateUrl.absolute] = url: currentStateUrl.relative, body: document.body, title: document.title, positionY: window.pageYOffset, positionX: window.pageXOffset, cachedAt: new Date().getTime(), transitionCacheDisabled: document.querySelector('[data-no-transition-cache]')? constrainPageCacheTo cacheSize pagesCached = (size = cacheSize) -> cacheSize = parseInt(size) if /^[\d]+$/.test size constrainPageCacheTo = (limit) -> pageCacheKeys = Object.keys pageCache cacheTimesRecentFirst = pageCacheKeys.map (url) -> pageCache[url].cachedAt .sort (a, b) -> b - a for key in pageCacheKeys when pageCache[key].cachedAt <= cacheTimesRecentFirst[limit] triggerEvent EVENTS.EXPIRE, pageCache[key] delete pageCache[key] changePage = (title, body, csrfToken, runScripts) -> triggerEvent EVENTS.BEFORE_UNLOAD document.title = title document.documentElement.replaceChild body, document.body CSRFToken.update csrfToken if csrfToken? setAutofocusElement() executeScriptTags() if runScripts currentState = window.history.state progressBar?.done() triggerEvent EVENTS.CHANGE triggerEvent EVENTS.UPDATE executeScriptTags = -> scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])' for script in scripts when script.type in ['', 'text/javascript'] copy = document.createElement 'script' copy.setAttribute attr.name, attr.value for attr in script.attributes copy.async = false unless script.hasAttribute 'async' copy.appendChild document.createTextNode script.innerHTML { parentNode, nextSibling } = script parentNode.removeChild script parentNode.insertBefore copy, nextSibling return removeNoscriptTags = (node) -> node.innerHTML = node.innerHTML.replace //ig, '' node # Firefox bug: Doesn't autofocus fields that are inserted via JavaScript setAutofocusElement = -> autofocusElement = (list = document.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1] if autofocusElement and document.activeElement isnt autofocusElement autofocusElement.focus() reflectNewUrl = (url) -> if (url = new ComponentUrl url).absolute isnt referer window.history.pushState { turbolinks: true, url: url.absolute }, '', url.absolute reflectRedirectedUrl = -> if location = xhr.getResponseHeader 'X-XHR-Redirected-To' location = new ComponentUrl location preservedHash = if location.hasNoHash() then document.location.hash else '' window.history.replaceState window.history.state, '', location.href + preservedHash crossOriginRedirect = -> redirect if (redirect = xhr.getResponseHeader('Location'))? and (new ComponentUrl(redirect)).crossOrigin() rememberReferer = -> referer = document.location.href rememberCurrentUrl = -> window.history.replaceState { turbolinks: true, url: document.location.href }, '', document.location.href rememberCurrentState = -> currentState = window.history.state # Unlike other browsers, Firefox doesn't trigger hashchange after changing the # location (via pushState) to an anchor on a different page. For example: # # /pages/one => /pages/two#with-hash # # By forcing Firefox to trigger hashchange, the rest of the code can rely on more # consistent behavior across browsers. manuallyTriggerHashChangeForFirefox = -> if navigator.userAgent.match(/Firefox/) and !(url = (new ComponentUrl)).hasNoHash() window.history.replaceState currentState, '', url.withoutHash() document.location.hash = url.hash recallScrollPosition = (page) -> window.scrollTo page.positionX, page.positionY resetScrollPosition = -> if document.location.hash document.location.href = document.location.href else window.scrollTo 0, 0 clone = (original) -> return original if not original? or typeof original isnt 'object' copy = new original.constructor() copy[key] = clone value for key, value of original copy popCookie = (name) -> value = document.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or '' document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/' value triggerEvent = (name, data) -> if typeof Prototype isnt 'undefined' Event.fire document, name, data, true event = document.createEvent 'Events' event.data = data if data event.initEvent name, true, true document.dispatchEvent event pageChangePrevented = (url) -> !triggerEvent EVENTS.BEFORE_CHANGE, url: url processResponse = -> clientOrServerError = -> 400 <= xhr.status < 600 validContent = -> (contentType = xhr.getResponseHeader('Content-Type'))? and contentType.match /^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/ extractTrackAssets = (doc) -> for node in doc.querySelector('head').childNodes when node.getAttribute?('data-turbolinks-track')? node.getAttribute('src') or node.getAttribute('href') assetsChanged = (doc) -> loadedAssets ||= extractTrackAssets document fetchedAssets = extractTrackAssets doc fetchedAssets.length isnt loadedAssets.length or intersection(fetchedAssets, loadedAssets).length isnt loadedAssets.length intersection = (a, b) -> [a, b] = [b, a] if a.length > b.length value for value in a when value in b if not clientOrServerError() and validContent() doc = createDocument xhr.responseText if doc and !assetsChanged doc return doc extractTitleAndBody = (doc) -> title = doc.querySelector 'title' [ title?.textContent, removeNoscriptTags(doc.querySelector('body')), CSRFToken.get(doc).token, 'runScripts' ] CSRFToken = get: (doc = document) -> node: tag = doc.querySelector 'meta[name="csrf-token"]' token: tag?.getAttribute? 'content' update: (latest) -> current = @get() if current.token? and latest? and current.token isnt latest current.node.setAttribute 'content', latest createDocument = (html) -> doc = document.documentElement.cloneNode() doc.innerHTML = html doc.head = doc.querySelector 'head' doc.body = doc.querySelector 'body' doc # The ComponentUrl class converts a basic URL string into an object # that behaves similarly to document.location. # # If an instance is created from a relative URL, the current document # is used to fill in the missing attributes (protocol, host, port). class ComponentUrl constructor: (@original = document.location.href) -> return @original if @original.constructor is ComponentUrl @_parse() withoutHash: -> @href.replace(@hash, '').replace('#', '') # Intention revealing function alias withoutHashForIE10compatibility: -> @withoutHash() hasNoHash: -> @hash.length is 0 crossOrigin: -> @origin isnt (new ComponentUrl).origin _parse: -> (@link ?= document.createElement 'a').href = @original { @href, @protocol, @host, @hostname, @port, @pathname, @search, @hash } = @link @origin = [@protocol, '//', @hostname].join '' @origin += ":#{@port}" unless @port.length is 0 @relative = [@pathname, @search, @hash].join '' @absolute = @href # The Link class derives from the ComponentUrl class, but is built from an # existing link element. Provides verification functionality for Turbolinks # to use in determining whether it should process the link when clicked. class Link extends ComponentUrl @HTML_EXTENSIONS: ['html'] @allowExtensions: (extensions...) -> Link.HTML_EXTENSIONS.push extension for extension in extensions Link.HTML_EXTENSIONS constructor: (@link) -> return @link if @link.constructor is Link @original = @link.href @originalElement = @link @link = @link.cloneNode false super shouldIgnore: -> @crossOrigin() or @_anchored() or @_nonHtml() or @_optOut() or @_target() _anchored: -> (@hash.length > 0 or @href.charAt(@href.length - 1) is '#') and (@withoutHash() is (new ComponentUrl).withoutHash()) _nonHtml: -> @pathname.match(/\.[a-z]+$/g) and not @pathname.match(new RegExp("\\.(?:#{Link.HTML_EXTENSIONS.join('|')})?$", 'g')) _optOut: -> link = @originalElement until ignore or link is document ignore = link.getAttribute('data-no-turbolink')? link = link.parentNode ignore _target: -> @link.target.length isnt 0 # The Click class handles clicked links, verifying if Turbolinks should # take control by inspecting both the event and the link. If it should, # the page change process is initiated. If not, control is passed back # to the browser for default functionality. class Click @installHandlerLast: (event) -> unless event.defaultPrevented document.removeEventListener 'click', Click.handle, false document.addEventListener 'click', Click.handle, false @handle: (event) -> new Click event constructor: (@event) -> return if @event.defaultPrevented @_extractLink() if @_validForTurbolinks() visit @link.href unless pageChangePrevented(@link.absolute) @event.preventDefault() _extractLink: -> link = @event.target link = link.parentNode until !link.parentNode or link.nodeName is 'A' @link = new Link(link) if link.nodeName is 'A' and link.href.length isnt 0 _validForTurbolinks: -> @link? and not (@link.shouldIgnore() or @_nonStandardClick()) _nonStandardClick: -> @event.which > 1 or @event.metaKey or @event.ctrlKey or @event.shiftKey or @event.altKey class ProgressBar className = 'turbolinks-progress-bar' constructor: (@elementSelector) -> @value = 0 @content = '' @speed = 300 # Setting the opacity to a value < 1 fixes a display issue in Safari 6 and # iOS 6 where the progress bar would fill the entire page. @opacity = 0.99 @install() install: -> @element = document.querySelector(@elementSelector) @element.classList.add(className) @styleElement = document.createElement('style') document.head.appendChild(@styleElement) @_updateStyle() uninstall: -> @element.classList.remove(className) document.head.removeChild(@styleElement) start: -> @advanceTo(5) advanceTo: (value) -> if value > @value <= 100 @value = value @_updateStyle() if @value is 100 @_stopTrickle() else if @value > 0 @_startTrickle() done: -> if @value > 0 @advanceTo(100) @_reset() _reset: -> originalOpacity = @opacity setTimeout => @opacity = 0 @_updateStyle() , @speed / 2 setTimeout => @value = 0 @opacity = originalOpacity @_withSpeed(0, => @_updateStyle(true)) , @speed _startTrickle: -> return if @trickling @trickling = true setTimeout(@_trickle, @speed) _stopTrickle: -> delete @trickling _trickle: => return unless @trickling @advanceTo(@value + Math.random() / 2) setTimeout(@_trickle, @speed) _withSpeed: (speed, fn) -> originalSpeed = @speed @speed = speed result = fn() @speed = originalSpeed result _updateStyle: (forceRepaint = false) -> @_changeContentToForceRepaint() if forceRepaint @styleElement.textContent = @_createCSSRule() _changeContentToForceRepaint: -> @content = if @content is '' then ' ' else '' _createCSSRule: -> """ #{@elementSelector}.#{className}::before { content: '#{@content}'; position: fixed; top: 0; left: 0; z-index: 2000; background-color: #0076ff; height: 3px; opacity: #{@opacity}; width: #{@value}%; transition: width #{@speed}ms ease-out, opacity #{@speed / 2}ms ease-in; transform: translate3d(0,0,0); } """ # Delay execution of function long enough to miss the popstate event # some browsers fire on the initial page load. bypassOnLoadPopstate = (fn) -> setTimeout fn, 500 installDocumentReadyPageEventTriggers = -> document.addEventListener 'DOMContentLoaded', ( -> triggerEvent EVENTS.CHANGE triggerEvent EVENTS.UPDATE ), true installJqueryAjaxSuccessPageUpdateTrigger = -> if typeof jQuery isnt 'undefined' jQuery(document).on 'ajaxSuccess', (event, xhr, settings) -> return unless jQuery.trim xhr.responseText triggerEvent EVENTS.UPDATE installHistoryChangeHandler = (event) -> if event.state?.turbolinks if cachedPage = pageCache[(new ComponentUrl(event.state.url)).absolute] cacheCurrentPage() fetchHistory cachedPage else visit event.target.location.href initializeTurbolinks = -> rememberCurrentUrl() rememberCurrentState() document.addEventListener 'click', Click.installHandlerLast, true window.addEventListener 'hashchange', (event) -> rememberCurrentUrl() rememberCurrentState() , false bypassOnLoadPopstate -> window.addEventListener 'popstate', installHistoryChangeHandler, false # Handle bug in Firefox 26/27 where history.state is initially undefined historyStateIsDefined = window.history.state != undefined or navigator.userAgent.match /Firefox\/2[6|7]/ browserSupportsPushState = window.history and window.history.pushState and window.history.replaceState and historyStateIsDefined browserIsntBuggy = !navigator.userAgent.match /CriOS\// requestMethodIsSafe = popCookie('request_method') in ['GET',''] browserSupportsTurbolinks = browserSupportsPushState and browserIsntBuggy and requestMethodIsSafe browserSupportsCustomEvents = document.addEventListener and document.createEvent if browserSupportsCustomEvents installDocumentReadyPageEventTriggers() installJqueryAjaxSuccessPageUpdateTrigger() if browserSupportsTurbolinks visit = fetch initializeTurbolinks() else visit = (url) -> document.location.href = url # Public API # Turbolinks.visit(url) # Turbolinks.pagesCached() # Turbolinks.pagesCached(20) # Turbolinks.enableTransitionCache() # Turbolinks.allowLinkExtensions('md') # Turbolinks.supported # Turbolinks.EVENTS @Turbolinks = { visit, pagesCached, enableTransitionCache, enableProgressBar, allowLinkExtensions: Link.allowExtensions, supported: browserSupportsTurbolinks, EVENTS: clone(EVENTS) } turbolinks-2.5.3/lib/turbolinks/0000755000004100000410000000000012460734345016717 5ustar www-datawww-dataturbolinks-2.5.3/lib/turbolinks/x_domain_blocker.rb0000644000004100000410000000130712460734345022544 0ustar www-datawww-datamodule Turbolinks # Changes the response status to 403 Forbidden if all of these conditions are true: # - The current request originated from Turbolinks # - The request is being redirected to a different domain module XDomainBlocker private def same_origin?(a, b) a = URI.parse URI.escape(a) b = URI.parse URI.escape(b) [a.scheme, a.host, a.port] == [b.scheme, b.host, b.port] end def abort_xdomain_redirect to_uri = response.headers['Location'] || "" current = request.headers['X-XHR-Referer'] || "" unless to_uri.blank? || current.blank? || same_origin?(current, to_uri) self.status = 403 end end end endturbolinks-2.5.3/lib/turbolinks/xhr_url_for.rb0000644000004100000410000000101112460734345021566 0ustar www-datawww-datamodule Turbolinks # Corrects the behavior of url_for (and link_to, which uses url_for) with the :back # option by using the X-XHR-Referer request header instead of the standard Referer # request header. module XHRUrlFor def self.included(base) base.alias_method_chain :url_for, :xhr_referer end def url_for_with_xhr_referer(options = {}) options = (controller.request.headers["X-XHR-Referer"] || options) if options == :back url_for_without_xhr_referer options end end end turbolinks-2.5.3/lib/turbolinks/xhr_headers.rb0000644000004100000410000000316312460734345021543 0ustar www-datawww-datamodule Turbolinks # Intercepts calls to _compute_redirect_to_location (used by redirect_to) for two purposes. # # 1. Corrects the behavior of redirect_to with the :back option by using the X-XHR-Referer # request header instead of the standard Referer request header. # # 2. Stores the return value (the redirect target url) to persist through to the redirect # request, where it will be used to set the X-XHR-Redirected-To response header. The # Turbolinks script will detect the header and use replaceState to reflect the redirected # url. module XHRHeaders extend ActiveSupport::Concern def _compute_redirect_to_location(*args) options, request = _normalize_redirect_params(args) store_for_turbolinks begin if options == :back && request.headers["X-XHR-Referer"] super(*[(request if args.length == 2), request.headers["X-XHR-Referer"]].compact) else super(*args) end end end private def store_for_turbolinks(url) session[:_turbolinks_redirect_to] = url if session && request.headers["X-XHR-Referer"] url end def set_xhr_redirected_to if session && session[:_turbolinks_redirect_to] response.headers['X-XHR-Redirected-To'] = session.delete :_turbolinks_redirect_to end end # Ensure backwards compatibility # Rails < 4.2: _compute_redirect_to_location(options) # Rails >= 4.2: _compute_redirect_to_location(request, options) def _normalize_redirect_params(args) options, req = args.reverse [options, req || request] end end end turbolinks-2.5.3/lib/turbolinks/version.rb0000644000004100000410000000005212460734345020726 0ustar www-datawww-datamodule Turbolinks VERSION = '2.5.3' end turbolinks-2.5.3/lib/turbolinks/redirection.rb0000644000004100000410000000074212460734345021556 0ustar www-datawww-datamodule Turbolinks # Provides a means of using Turbolinks to perform redirects. The server # will respond with a JavaScript call to Turbolinks.visit(url). module Redirection extend ActiveSupport::Concern def redirect_via_turbolinks_to(url = {}, response_status = {}) redirect_to(url, response_status) self.status = 200 self.response_body = "Turbolinks.visit('#{location}');" response.content_type = Mime::JS end end endturbolinks-2.5.3/lib/turbolinks/cookies.rb0000644000004100000410000000066412460734345020706 0ustar www-datawww-datamodule Turbolinks # For non-GET requests, sets a request_method cookie containing # the request method of the current request. The Turbolinks script # will not initialize if this cookie is set. module Cookies private def set_request_method_cookie if request.get? cookies.delete(:request_method) else cookies[:request_method] = request.request_method end end end end turbolinks-2.5.3/metadata.yml0000644000004100000410000000343412460734345016264 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: turbolinks version: !ruby/object:Gem::Version version: 2.5.3 platform: ruby authors: - David Heinemeier Hansson autorequire: bindir: bin cert_chain: [] date: 2014-12-08 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: coffee-rails requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: email: david@loudthinking.com executables: [] extensions: [] extra_rdoc_files: [] files: - MIT-LICENSE - README.md - lib/assets/javascripts/turbolinks.js.coffee - lib/turbolinks.rb - lib/turbolinks/cookies.rb - lib/turbolinks/redirection.rb - lib/turbolinks/version.rb - lib/turbolinks/x_domain_blocker.rb - lib/turbolinks/xhr_headers.rb - lib/turbolinks/xhr_url_for.rb - test/config.ru - test/dummy.gif - test/index.html - test/manifest.appcache - test/offline.html - test/other.html - test/redirect1.html - test/redirect2.html - test/reload.html - test/withoutextension homepage: https://github.com/rails/turbolinks/ licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: Turbolinks makes following links in your web application faster (use with Rails Asset Pipeline) test_files: [] turbolinks-2.5.3/test/0000755000004100000410000000000012460734345014734 5ustar www-datawww-dataturbolinks-2.5.3/test/other.html0000644000004100000410000000115012460734345016740 0ustar www-datawww-data Home turbolinks-2.5.3/test/dummy.gif0000644000004100000410000000345212460734345016562 0ustar www-datawww-dataGIF89a2@@@000 ppp```PPP! XMP DataXMP ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  !,2 $dihlp,tmx|pH,Ȥrl:ШtJZجvzxLnKԞz`0lpr%uqi%k}<pw"""   $=%#Ŕ"s  ##ƻƠʿ)ؚjy$AWV!H`HꖱCP%8AHpшEtƮv 8+v[Fnt(I3EB“)UR4 >@Mw抧. Hܱ%J",z:1|еD6Tۘ-92GP*빱f`iCE7TH`uq,R B`LjH#R%hKBXSQD2ȦGn(!d\O=&oqA3|d:υ+*rFζe{zE w0#TUǂK`e@g"7M5'z6^-IbC2E %` *$\7'Nb"/2ڶHeCR| m(2 q"s,F-YƖ\v`)dihlp)ti$;turbolinks-2.5.3/test/withoutextension0000644000004100000410000000115012460734345020314 0ustar www-datawww-data Home turbolinks-2.5.3/test/redirect2.html0000644000004100000410000000040512460734345017504 0ustar www-datawww-data Home Hit back button twice. It should go back to home page. turbolinks-2.5.3/test/redirect1.html0000644000004100000410000000047612460734345017513 0ustar www-datawww-data Home Should show /redirect1.html as path turbolinks-2.5.3/test/reload.html0000644000004100000410000000067212460734345017075 0ustar www-datawww-data Home turbolinks-2.5.3/test/offline.html0000644000004100000410000000073312460734345017247 0ustar www-datawww-data Home

Offline Mode Fallback

turbolinks-2.5.3/test/manifest.appcache0000644000004100000410000000013012460734345020222 0ustar www-datawww-dataCACHE MANIFEST CACHE: /offline.html NETWORK: * FALLBACK: /fallback.html /offline.htmlturbolinks-2.5.3/test/index.html0000644000004100000410000000421312460734345016731 0ustar www-datawww-data Home
turbolinks-2.5.3/test/config.ru0000644000004100000410000000223012460734345016546 0ustar www-datawww-datarequire 'sprockets' require 'coffee-script' Root = File.expand_path("../..", __FILE__) Assets = Sprockets::Environment.new do |env| env.append_path File.join(Root, "lib", "assets", "javascripts") end class SlowResponse CHUNKS = ['', '.'*50, '.'*20, 'Home'] def call(env) [200, headers, self] end def each CHUNKS.each do |part| sleep rand(0.3..0.8) yield part end end def length CHUNKS.join.length end def headers { "Content-Length" => length.to_s, "Content-Type" => "text/html", "Cache-Control" => "no-cache, no-store, must-revalidate" } end end map "/js" do run Assets end map "/500" do # throw Internal Server Error (500) end map "/withoutextension" do run Rack::File.new(File.join(Root, "test", "withoutextension"), "Content-Type" => "text/html") end map "/slow-response" do run SlowResponse.new end map "/bounce" do run Proc.new{ [200, { "X-XHR-Redirected-To" => "redirect1.html", "Content-Type" => "text/html" }, File.open( File.join( Root, "test", "redirect1.html" ) ) ] } end map "/" do run Rack::Directory.new(File.join(Root, "test")) end turbolinks-2.5.3/README.md0000644000004100000410000003270612460734345015244 0ustar www-datawww-dataTurbolinks =========== Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, it keeps the current page instance alive and replaces only the body and the title in the head. Think CGI vs persistent process. This is similar to [pjax](https://github.com/defunkt/jquery-pjax), but instead of worrying about what element on the page to replace, and tailoring the server-side response to fit, we replace the entire body. This means that you get the bulk of the speed benefits from pjax (no recompiling of the JavaScript or CSS) without having to tailor the server-side response. It just works. Do note that this of course means that you'll have a long-running, persistent session with maintained state. That's what's making it so fast. But it also means that you may have to pay additional care not to leak memory or otherwise bloat that long-running state. That should rarely be a problem unless you're doing something really funky, but you do have to be aware of it. Your memory leaking sins will not be swept away automatically by the cleansing page change any more. How much faster is it really? ----------------------------- 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. 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. The best way to find out just how fast it is? Try it on your own application. It hardly takes any effort at all. No jQuery or any other library -------------------------------- Turbolinks is designed to be as light-weight as possible (so you won't think twice about using it even for mobile stuff). It does not require jQuery or any other library to work. But it works great _with_ the jQuery or Prototype framework, or whatever else have you. Events ------ With Turbolinks pages will change without a full reload, so you can't rely on `DOMContentLoaded` or `jQuery.ready()` to trigger your code. Instead Turbolinks fires events on `document` to provide hooks into the lifecycle of the page. ***Load* a fresh version of a page from the server:** * `page:before-change` a Turbolinks-enabled link has been clicked *(see below for more details)* * `page:fetch` starting to fetch a new target page * `page:receive` the page has been fetched from the server, but not yet parsed * `page:before-unload` the page has been parsed and is about to be changed * `page:change` the page has been changed to the new version (and on DOMContentLoaded) * `page:update` is triggered alongside both page:change and jQuery's ajaxSuccess (if jQuery is available - otherwise you can manually trigger it when calling XMLHttpRequest in your own code) * `page:load` is fired at the end of the loading process. Handlers bound to the `page:before-change` event may return `false`, which will cancel the Turbolinks process. By default, Turbolinks caches 10 of these page loads. It listens to the [popstate](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history#The_popstate_event) event and attempts to restore page state from the cache when it's triggered. When `popstate` is fired the following process happens: ***Restore* a cached page from the client-side cache:** * `page:before-unload` page has been fetched from the cache and is about to be changed * `page:change` page has changed to the cached page. * `page:restore` is fired at the end of restore process. The number of pages Turbolinks caches can be configured to suit your application's needs: ```javascript // View the current cache size Turbolinks.pagesCached(); // Set the cache size Turbolinks.pagesCached(20); ``` When a page is removed from the cache due to the cache reaching its size limit, the `page:expire` event is triggered. Listeners bound to this event can access the cached page object using `event.originalEvent.data`. Keys of note for this page cache object include `url`, `body`, and `title`. To implement a client-side spinner, you could listen for `page:fetch` to start it and `page:receive` to stop it. ```javascript // using jQuery for simplicity $(document).on("page:fetch", startSpinner); $(document).on("page:receive", stopSpinner); ``` DOM transformations that are idempotent are best. If you have transformations that are not, bind them to `page:load` (in addition to the initial page load) instead of `page:change` (as that would run them again on the cached pages): ```javascript // using jQuery for simplicity $(document).on("ready page:load", nonIdempotentFunction); ``` Transition Cache: A Speed Boost ------------------------------- Transition Cache, added in v2.2.0, makes loading cached pages instantaneous. Once a user has visited a page, returning later to the page results in an instant load. For example, if Page A is already cached by Turbolinks and you are on Page B, clicking a link to Page A will *immediately* display the cached copy of Page A. Turbolinks will then fetch Page A from the server and replace the cached page once the new copy is returned. To enable Transition Cache, include the following in your javascript: ```javascript Turbolinks.enableTransitionCache(); ``` The one drawback is that dramatic differences in appearance between a cached copy and new copy may lead to a jarring affect for the end-user. This will be especially true for pages that have many moving parts (expandable sections, sortable tables, infinite scrolling, etc.). If you find that a page is causing problems, you can have Turbolinks skip displaying the cached copy by adding `data-no-transition-cache` to any DOM element on the offending page. Progress Bar ------------ Because Turbolinks skips the traditional full page reload, browsers won't display their native progress bar when changing pages. To fill this void, Turbolinks offers an optional JavaScript-and-CSS-based progress bar to display page loading progress. To enable the progress bar, include the following in your JavaScript: ```javascript Turbolinks.enableProgressBar(); ``` The progress bar is implemented on the `` element's pseudo `:before` element and can be **customized** by including CSS with higher specificity than the included styles. For example: ```css html.turbolinks-progress-bar::before { background-color: red !important; height: 5px !important; } ``` In Turbolinks 3.0, the progress bar will be turned on by default. Initialization -------------- Turbolinks will be enabled **only** if the server has rendered a `GET` request. Some examples, given a standard RESTful resource: * `POST :create` => resource successfully created => redirect to `GET :show` * Turbolinks **ENABLED** * `POST :create` => resource creation failed => render `:new` * Turbolinks **DISABLED** **Why not all request types?** Some browsers track the request method of each page load, but triggering pushState methods don't change this value. This could lead to the situation where pressing the browser's reload button on a page that was fetched with Turbolinks would attempt a `POST` (or something other than `GET`) because the last full page load used that method. Opting out of Turbolinks ------------------------ By default, all internal HTML links will be funneled through Turbolinks, but you can opt out by marking links or their parent container with `data-no-turbolink`. For example, if you mark a div with `data-no-turbolink`, then all links inside of that div will be treated as regular links. If you mark the body, every link on that entire page will be treated as regular links. ```html Home (via Turbolinks)
Home (without Turbolinks)
``` Note that internal links to files containing a file extension other than **.html** will automatically be opted out of Turbolinks. So links to /images/panda.gif will just work as expected. To whitelist additional file extensions to be processed by Turbolinks, use `Turbolinks.allowLinkExtensions()`. ```javascript Turbolinks.allowLinkExtensions(); // => ['html'] Turbolinks.allowLinkExtensions('md'); // => ['html', 'md'] Turbolinks.allowLinkExtensions('coffee', 'scss'); // => ['html', 'md', 'coffee', 'scss'] ``` 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. jquery.turbolinks ----------------- 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 `page:load` event. It may restore functionality of some libraries. Add the gem to your project, then add the following line to your JavaScript manifest file, after `jquery.js` but before `turbolinks.js`: ``` js //= require jquery.turbolinks ``` Additional details and configuration options can be found in the [jquery.turbolinks README](https://github.com/kossnocorp/jquery.turbolinks/blob/master/README.md). Asset change detection ---------------------- You can track certain assets, like application.js and application.css, that you want to ensure are always of the latest version inside a Turbolinks session. This is done by marking those asset links with data-turbolinks-track, like so: ```html ``` If those assets change URLs (embed an md5 stamp to ensure this), the page will do a full reload instead of going through Turbolinks. This ensures that all Turbolinks sessions will always be running off your latest JavaScript and CSS. When this happens, you'll technically be requesting the same page twice. Once through Turbolinks to detect that the assets changed, and then again when we do a full redirect to that page. Evaluating script tags ---------------------- Turbolinks will evaluate any script tags in pages it visits, if those tags do not have a type or if the type is text/javascript. All other script tags will be ignored. 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. If you have any script tags in the body you do not want to be re-evaluated then you can set the `data-turbolinks-eval` attribute to `false`: ```html ``` Triggering a Turbolinks visit manually --------------------------------------- You can use `Turbolinks.visit(path)` to go to a URL through Turbolinks. You can also use `redirect_via_turbolinks_to` in Rails to perform a redirect via Turbolinks. Full speed for pushState browsers, graceful fallback for everything else ------------------------------------------------------------------------ Like pjax, this naturally only works with browsers capable of pushState. But of course we fall back gracefully to full page reloads for browsers that do not support it. Compatibility ------------- 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. 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.io/turbolinks-compatibility) project. Installation ------------ 1. Add `gem 'turbolinks'` to your Gemfile. 1. Run `bundle install`. 1. Add `//= require turbolinks` to your Javascript manifest file (usually found at `app/assets/javascripts/application.js`). If your manifest requires both turbolinks and jQuery, make sure turbolinks is listed *after* jQuery. 1. Restart your server and you're now using turbolinks! Language Ports -------------- *These projects are not affiliated with or endorsed by the Rails Turbolinks team.* * [Flask Turbolinks](https://github.com/lepture/flask-turbolinks) (Python Flask) * [Django Turbolinks](https://github.com/dgladkov/django-turbolinks) (Python Django) * [ASP.NET MVC Turbolinks](https://github.com/kazimanzurrashid/aspnetmvcturbolinks) * [PHP Turbolinks Component](https://github.com/helthe/Turbolinks) (Symfony Component) * [PHP Turbolinks Package](https://github.com/frenzyapp/turbolinks) (Laravel Package) * [Grails Turbolinks](http://grails.org/plugin/turbolinks) (Grails Plugin) Credits ------- 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!