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:
WoH 2018-11-30 01:26:22 +01:00
parent 5b75408e32
commit a2612622e8
No known key found for this signature in database
GPG Key ID: 5462396E362CCE98
7 changed files with 100 additions and 6 deletions

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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]')

View File

@ -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]')

View File

@ -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()

View File

@ -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
} }
} }