mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Prevent unintended mouse keys from firing click events
Firefox fires click events on left-, right- and scroll-wheel (any non-primary mouse key) clicks while other browsers don't.
This commit is contained in:
parent
5b75408e32
commit
a2612622e8
7 changed files with 100 additions and 6 deletions
|
@ -1,3 +1,16 @@
|
||||||
|
* Prevent non-primary mouse keys from triggering Rails UJS click handlers.
|
||||||
|
Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks.
|
||||||
|
For example, right-clicking a link such as the one described below (with an underlying ajax request registered on click) should not cause that request to occur.
|
||||||
|
|
||||||
|
```
|
||||||
|
<%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %>
|
||||||
|
```
|
||||||
|
|
||||||
|
Fixes #34541
|
||||||
|
|
||||||
|
*Wolfgang Hobmaier*
|
||||||
|
|
||||||
|
|
||||||
* Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines.
|
* Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines.
|
||||||
|
|
||||||
For example, given input like this:
|
For example, given input like this:
|
||||||
|
|
|
@ -82,9 +82,12 @@ Rails.formSubmitButtonClick = (e) ->
|
||||||
setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'))
|
setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'))
|
||||||
setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'))
|
setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'))
|
||||||
|
|
||||||
Rails.handleMetaClick = (e) ->
|
Rails.preventInsignificantClick = (e) ->
|
||||||
link = this
|
link = this
|
||||||
method = (link.getAttribute('data-method') or 'GET').toUpperCase()
|
method = (link.getAttribute('data-method') or 'GET').toUpperCase()
|
||||||
data = link.getAttribute('data-params')
|
data = link.getAttribute('data-params')
|
||||||
metaClick = e.metaKey or e.ctrlKey
|
metaClick = e.metaKey or e.ctrlKey
|
||||||
e.stopImmediatePropagation() if metaClick and method is 'GET' and not data
|
insignificantMetaClick = metaClick and method is 'GET' and not data
|
||||||
|
primaryMouseKey = e.button is 0
|
||||||
|
e.stopImmediatePropagation() if not primaryMouseKey or insignificantMetaClick
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
getData, $
|
getData, $
|
||||||
refreshCSRFTokens, CSRFProtection
|
refreshCSRFTokens, CSRFProtection
|
||||||
enableElement, disableElement, handleDisabledElement
|
enableElement, disableElement, handleDisabledElement
|
||||||
handleConfirm
|
handleConfirm, preventInsignificantClick
|
||||||
handleRemote, formSubmitButtonClick, handleMetaClick
|
handleRemote, formSubmitButtonClick,
|
||||||
handleMethod
|
handleMethod
|
||||||
} = Rails
|
} = Rails
|
||||||
|
|
||||||
|
@ -35,13 +35,14 @@ Rails.start = ->
|
||||||
delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
|
delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
|
||||||
delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
|
delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
|
||||||
|
|
||||||
|
delegate document, Rails.linkClickSelector, 'click', preventInsignificantClick
|
||||||
delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
|
delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
|
||||||
delegate document, Rails.linkClickSelector, 'click', handleConfirm
|
delegate document, Rails.linkClickSelector, 'click', handleConfirm
|
||||||
delegate document, Rails.linkClickSelector, 'click', handleMetaClick
|
|
||||||
delegate document, Rails.linkClickSelector, 'click', disableElement
|
delegate document, Rails.linkClickSelector, 'click', disableElement
|
||||||
delegate document, Rails.linkClickSelector, 'click', handleRemote
|
delegate document, Rails.linkClickSelector, 'click', handleRemote
|
||||||
delegate document, Rails.linkClickSelector, 'click', handleMethod
|
delegate document, Rails.linkClickSelector, 'click', handleMethod
|
||||||
|
|
||||||
|
delegate document, Rails.buttonClickSelector, 'click', preventInsignificantClick
|
||||||
delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
|
delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
|
||||||
delegate document, Rails.buttonClickSelector, 'click', handleConfirm
|
delegate document, Rails.buttonClickSelector, 'click', handleConfirm
|
||||||
delegate document, Rails.buttonClickSelector, 'click', disableElement
|
delegate document, Rails.buttonClickSelector, 'click', disableElement
|
||||||
|
@ -60,6 +61,7 @@ Rails.start = ->
|
||||||
delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
|
delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
|
||||||
delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
|
delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
|
||||||
|
|
||||||
|
delegate document, Rails.formInputClickSelector, 'click', preventInsignificantClick
|
||||||
delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
|
delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
|
||||||
delegate document, Rails.formInputClickSelector, 'click', handleConfirm
|
delegate document, Rails.formInputClickSelector, 'click', handleConfirm
|
||||||
delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
|
delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
|
||||||
|
|
|
@ -322,6 +322,25 @@ asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
|
||||||
start()
|
start()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
asyncTest('right/mouse-wheel-clicking on a link does not disables the link', 10, function() {
|
||||||
|
var link = $('a[data-disable-with]')
|
||||||
|
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 1 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 1 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 2 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 2 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
start()
|
||||||
|
})
|
||||||
|
|
||||||
asyncTest('button[data-remote][data-disable-with] disables and re-enables', 6, function() {
|
asyncTest('button[data-remote][data-disable-with] disables and re-enables', 6, function() {
|
||||||
var button = $('button[data-remote][data-disable-with]')
|
var button = $('button[data-remote][data-disable-with]')
|
||||||
|
|
||||||
|
|
|
@ -250,6 +250,25 @@ asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
|
||||||
start()
|
start()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
asyncTest('right/mouse-wheel-clicking on a link does not disable the link', 10, function() {
|
||||||
|
var link = $('a[data-disable]')
|
||||||
|
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 1 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 1 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 2 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 2 })
|
||||||
|
App.checkEnabledState(link, 'Click me')
|
||||||
|
start()
|
||||||
|
})
|
||||||
|
|
||||||
asyncTest('button[data-remote][data-disable] disables and re-enables', 6, function() {
|
asyncTest('button[data-remote][data-disable] disables and re-enables', 6, function() {
|
||||||
var button = $('button[data-remote][data-disable]')
|
var button = $('button[data-remote][data-disable]')
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,25 @@ asyncTest('ctrl-clicking on a link does not fire ajaxyness', 0, function() {
|
||||||
setTimeout(function() { start() }, 13)
|
setTimeout(function() { start() }, 13)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
asyncTest('right/mouse-wheel-clicking on a link does not fire ajaxyness', 0, function() {
|
||||||
|
var link = $('a[data-remote]')
|
||||||
|
|
||||||
|
// Ideally, we'd setup an iframe to intercept normal link clicks
|
||||||
|
// and add a test to make sure the iframe:loaded event is triggered.
|
||||||
|
// However, jquery doesn't actually cause a native `click` event and
|
||||||
|
// follow links using `trigger('click')`, it only fires bindings.
|
||||||
|
link
|
||||||
|
.removeAttr('data-params')
|
||||||
|
.bindNative('ajax:beforeSend', function() {
|
||||||
|
ok(false, 'ajax should not be triggered')
|
||||||
|
})
|
||||||
|
|
||||||
|
link.triggerNative('click', { button: 1 })
|
||||||
|
link.triggerNative('click', { button: 2 })
|
||||||
|
|
||||||
|
setTimeout(function() { start() }, 13)
|
||||||
|
})
|
||||||
|
|
||||||
asyncTest('ctrl-clicking on a link still fires ajax for non-GET links and for links with "data-params"', 2, function() {
|
asyncTest('ctrl-clicking on a link still fires ajax for non-GET links and for links with "data-params"', 2, function() {
|
||||||
var link = $('a[data-remote]')
|
var link = $('a[data-remote]')
|
||||||
|
|
||||||
|
@ -148,6 +167,25 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() {
|
||||||
.triggerNative('click')
|
.triggerNative('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
asyncTest('right/mouse-wheel-clicking on a button with data-remote attribute does not fire ajaxyness', 0, function() {
|
||||||
|
var button = $('button[data-remote]')
|
||||||
|
|
||||||
|
// Ideally, we'd setup an iframe to intercept normal link clicks
|
||||||
|
// and add a test to make sure the iframe:loaded event is triggered.
|
||||||
|
// However, jquery doesn't actually cause a native `click` event and
|
||||||
|
// follow links using `trigger('click')`, it only fires bindings.
|
||||||
|
button
|
||||||
|
.removeAttr('data-params')
|
||||||
|
.bindNative('ajax:beforeSend', function() {
|
||||||
|
ok(false, 'ajax should not be triggered')
|
||||||
|
})
|
||||||
|
|
||||||
|
button.triggerNative('click', { button: 1 })
|
||||||
|
button.triggerNative('click', { button: 2 })
|
||||||
|
|
||||||
|
setTimeout(function() { start() }, 13)
|
||||||
|
})
|
||||||
|
|
||||||
asyncTest('changing a select option with data-remote attribute', 5, function() {
|
asyncTest('changing a select option with data-remote attribute', 5, function() {
|
||||||
buildSelect()
|
buildSelect()
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ try {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_MouseEvent = function(type, options) {
|
_MouseEvent = function(type, options) {
|
||||||
var evt = document.createEvent('MouseEvents')
|
var evt = document.createEvent('MouseEvents')
|
||||||
evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, null)
|
evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, null)
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue