Permalink
Browse files

Don't support pushState when the original request method was anything…

… other than GET

We cannot use pushState if the initial request method is a POST for two reasons:

1. Up.js replaces the initial state so it can handle the pop event when the
   user goes back to the initial URL later. If the initial request was a POST,
   Up.js will wrongly assumed that it can restore the state by reloading with GET.

2. Some browsers have a bug where the initial request method is used for all
   subsequently pushed states. That means if the user reloads the page on a later
   GET state, the browser will wrongly attempt a POST request.
   Modern Firefoxes, Chromes and IE10+ don't seem to be affected by this,
   but we saw this behavior with Safari 8 and IE9 (IE9 can't do pushState anyway).

The way that we work around this is that we don't support pushState if the
initial request method was anything other than GET (but allow the rest of the
Up.js framework to work). This way Up.js will fall back to full page loads until
the framework was booted from a GET request.
  • Loading branch information...
triskweline committed Oct 12, 2015
1 parent 85822e7 commit d81d9007aa3bfae0fca8c55a71d180d1044acae5
@@ -9,7 +9,7 @@ we can't currently get rid off.
@class up.browser
###
up.browser = (->
u = up.util
loadPage = (url, options = {}) ->
@@ -47,7 +47,23 @@ up.browser = (->
window.console.groupEnd ||= noop
canPushState = u.memoize ->
u.isDefined(history.pushState)
# We cannot use pushState if the initial request method is a POST for two reasons:
#
# 1. Up.js replaces the initial state so it can handle the pop event when the
# user goes back to the initial URL later. If the initial request was a POST,
# Up.js will wrongly assumed that it can restore the state by reloading with GET.
#
# 2. Some browsers have a bug where the initial request method is used for all
# subsequently pushed states. That means if the user reloads the page on a later
# GET state, the browser will wrongly attempt a POST request.
# Modern Firefoxes, Chromes and IE10+ don't seem to be affected by this,
# but we saw this behavior with Safari 8 and IE9 (IE9 can't do pushState anyway).
#
# The way that we work around this is that we don't support pushState if the
# initial request method was anything other than GET (but allow the rest of the
# Up.js framework to work). This way Up.js will fall back to full page loads until
# the framework was booted from a GET request.
u.isDefined(history.pushState) && initialRequestMethod == 'get'
canCssAnimation = u.memoize ->
'transition' of document.documentElement.style
@@ -62,7 +78,20 @@ up.browser = (->
minor = parseInt(parts[1])
compatible = major >= 2 || (major == 1 && minor >= 9)
compatible or u.error("jQuery %o found, but Up.js requires 1.9+", version)
# Returns and deletes a cookie with the given name
# Inspired by Turbolinks: https://github.com/rails/turbolinks/blob/83d4b3d2c52a681f07900c28adb28bc8da604733/lib/assets/javascripts/turbolinks.coffee#L292
popCookie = (name) ->
value = document.cookie.match(new RegExp(name+"=(\\w+)"))?[1]
if u.isPresent(value)
document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'
value
# Server-side companion libraries like upjs-rails set this cookie so we
# have a way to detect the request method of the initial page load.
# There is no Javascript API for this.
initialRequestMethod = (popCookie('_up_request_method') || 'get').toLowerCase()
isSupported = u.memoize ->
# This is the most concise way to exclude IE8 and lower
# while keeping all relevant desktop and mobile browsers.
@@ -76,6 +105,5 @@ up.browser = (->
canInputEvent: canInputEvent
isSupported: isSupported
ensureRecentJquery: ensureRecentJquery
)()
)()
@@ -219,6 +219,9 @@ up.modal = (->
height = u.option(options.height, $link.attr('up-height'), config.height)
animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation)
sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'))
# Although we usually fall back to full page loads if a browser doesn't support pushState,
# in the case of modals we assume that the developer would rather see a dialog
# without an URL update.
history = if up.browser.canPushState() then u.option(options.history, u.castedAttr($link, 'up-history'), true) else false
animateOptions = up.motion.animateOptions(options, $link)
@@ -1,4 +1,5 @@
require "upjs/rails/version"
require "upjs/rails/engine"
require "upjs/rails/current_location"
require "upjs/rails/request"
require "upjs/rails/request_echo_headers"
require "upjs/rails/request_method_cookie"
require "upjs/rails/request_ext"
@@ -1,14 +1,14 @@
module Upjs
module Rails
module CurrentLocation
module RequestEchoHeaders
def self.included(base)
base.before_filter :set_header_for_current_location
base.before_filter :set_up_request_echo_headers
end
private
def set_header_for_current_location
def set_up_request_echo_headers
headers['X-Up-Location'] = request.original_url
headers['X-Up-Method'] = request.method
end
File renamed without changes.
@@ -0,0 +1,28 @@
# See
# https://github.com/rails/turbolinks/search?q=request_method&ref=cmdform
# https://github.com/rails/turbolinks/blob/83d4b3d2c52a681f07900c28adb28bc8da604733/README.md#initialization
module Upjs
module Rails
module RequestMethod
COOKIE_NAME = '_up_request_method'
def self.included(base)
base.before_filter :set_up_request_method_cookie
end
private
def set_up_request_method_cookie
if request.get?
cookies.delete(COOKIE_NAME)
else
cookies[COOKIE_NAME] = request.request_method
end
end
ActionController::Base.send(:include, self)
end
end
end

0 comments on commit d81d900

Please sign in to comment.