Prevent event propogation if element is disabled when event chain begins.

The existing UJS event behavior relies on browsers not sending events for
various events when an element is disabled. For example, imagine the following:

    <button type="submit" disabled="disabled">Click me</button>

The above button is disabled, so browsers will not trigger a click event and
all UJS behavior is prevented. However, imagine a button like this:

    <button type="submit" disabled="disabled"><strong>Click me</strong></button>

The above is treated differently by browsers such as Chrome/Safari. These
browsers do not consider the strong tag to be disabled, and will trigger click
events. UJS has logic to walk up the DOM to find an associated element subject
to UJS behavior. But, this logic does not take into account the disabled
status of the element.

I originally thought we could simply change the selectors used to match
elements to ignore disabled elements. However, UJS disables some elements as
part of the event chain. So, an element might match early in the chain and
then fail to match later. Instead of changing the selectors I added a callback
to the chain that calls `stopEverything` if an element is disabled when the
event chain begins.
This commit is contained in:
Patrick Toomey 2017-03-09 23:06:18 -07:00
parent 9f9e57339b
commit 9ca712348e
No known key found for this signature in database
GPG Key ID: 0096BD01AA78248E
3 changed files with 38 additions and 1 deletions

View File

@ -2,6 +2,10 @@
{ matches, getData, setData, stopEverything, formElements } = Rails
Rails.handleDisabledElement = (e) ->
element = this
stopEverything(e) if element.disabled
# Unified function to enable an element (link, button and form)
Rails.enableElement = (e) ->
element = if e instanceof Event then e.target else e

View File

@ -12,7 +12,7 @@
fire, delegate
getData, $
refreshCSRFTokens, CSRFProtection
enableElement, disableElement
enableElement, disableElement, handleDisabledElement
handleConfirm
handleRemote, formSubmitButtonClick, handleMetaClick
handleMethod
@ -44,19 +44,23 @@ Rails.start = ->
delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
delegate document, Rails.linkClickSelector, 'click', handleConfirm
delegate document, Rails.linkClickSelector, 'click', handleMetaClick
delegate document, Rails.linkClickSelector, 'click', disableElement
delegate document, Rails.linkClickSelector, 'click', handleRemote
delegate document, Rails.linkClickSelector, 'click', handleMethod
delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
delegate document, Rails.buttonClickSelector, 'click', handleConfirm
delegate document, Rails.buttonClickSelector, 'click', disableElement
delegate document, Rails.buttonClickSelector, 'click', handleRemote
delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement
delegate document, Rails.inputChangeSelector, 'change', handleConfirm
delegate document, Rails.inputChangeSelector, 'change', handleRemote
delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement
delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
delegate document, Rails.formSubmitSelector, 'submit', handleRemote
# Normal mode submit
@ -65,6 +69,7 @@ Rails.start = ->
delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
delegate document, Rails.formInputClickSelector, 'click', handleConfirm
delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick

View File

@ -26,6 +26,13 @@ module('data-confirm', {
'data-confirm': 'Are you absolutely sure?'
}))
$('#qunit-fixture').append($('<button />', {
type: 'submit',
form: 'confirm',
disabled: 'disabled',
'data-confirm': 'Are you absolutely sure?'
}))
this.windowConfirm = window.confirm
},
teardown: function() {
@ -286,3 +293,24 @@ asyncTest('clicking on the children of a link should also trigger a confirm', 6,
.find('strong')
.triggerNative('click')
})
asyncTest('clicking on the children of a disabled button should not trigger a confirm.', 1, function() {
var message
// auto-decline:
window.confirm = function(msg) { message = msg; return false }
$('button[data-confirm][disabled]')
.html("<strong>Click me</strong>")
.bindNative('confirm', function() {
App.assertCallbackNotInvoked('confirm')
})
.find('strong')
.bindNative('click', function() {
App.assertCallbackInvoked('click')
})
.triggerNative('click')
setTimeout(function() {
start()
}, 50)
})