-
Notifications
You must be signed in to change notification settings - Fork 20.6k
Event: Increase robustness of an inner native event in leverageNative #5466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the | ||
// bubbling surrogate propagates *after* the non-bubbling base), but that seems | ||
// less bad than duplication. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part of the comment is no longer valid since the spec now mandates what browsers ship, i.e. that the bubbling events are fired after the non-bubbling ones.
} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { | ||
event.stopPropagation(); | ||
} | ||
|
||
// If this is a native event triggered above, everything is now in order | ||
// Fire an inner synthetic event with the original arguments | ||
} else if ( saved ) { | ||
} else if ( saved.length ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some very edge-case'y code, it's possible to have a blur
handler returning an array and falling into this check, destructuring the incorrect array. But guarding against that would be way more complex and I doubt many people will hit such an issue.
In Firefox, alert displayed just before blurring an element dispatches the native blur event twice which tripped the jQuery logic if a jQuery blur handler was not attached before the trigger call. This was because the `leverageNative` logic part for triggering first checked if setup was done before (which, for example, is done if a jQuery handler was registered before for this element+event pair) and - if it was not - added a dummy handler that just returned `true`. The `leverageNative` logic made that `true` then saved into private data, replacing the previous `saved` array. Since `true` passed the truthy check, the second native inner handler treated `true` as an array, crashing on the `slice` call. The same issue could happen if a handler returning `true` is attached before triggering. A bare `length` check would not be enough as the user handler may return an array-like as well. To remove this potential data shape clash, capture the inner result in an object with a `value` property instead of saving it directly. Since it's impossible to call `alert()` in unit tests, simulate the issue by replacing the `addEventListener` method on a test button with a version that calls attached blur handlers twice. Fixes jquerygh-5459 Closes jquerygh-5466 Ref jquerygh-5236
efb6e46
to
ee787ff
Compare
In Firefox, alert displayed just before blurring an element dispatches the native blur event twice which tripped the jQuery logic if a jQuery blur handler was not attached before the trigger call. This was because the `leverageNative` logic part for triggering first checked if setup was done before (which, for example, is done if a jQuery handler was registered before for this element+event pair) and - if it was not - added a dummy handler that just returned `true`. The `leverageNative` logic made that `true` then saved into private data, replacing the previous `saved` array. Since `true` passed the truthy check, the second native inner handler treated `true` as an array, crashing on the `slice` call. The same issue could happen if a handler returning `true` is attached before triggering. A bare `length` check would not be enough as the user handler may return an array-like as well. To remove this potential data shape clash, capture the inner result in an object with a `value` property instead of saving it directly. Since it's impossible to call `alert()` in unit tests, simulate the issue by replacing the `addEventListener` method on a test button with a version that calls attached blur handlers twice. Fixes jquerygh-5459 Closes jquerygh-5466 Ref jquerygh-5236
ee787ff
to
53b647c
Compare
In Firefox, alert displayed just before blurring an element dispatches the native blur event twice which tripped the jQuery logic if a jQuery blur handler was not attached before the trigger call. This was because the `leverageNative` logic part for triggering first checked if setup was done before (which, for example, is done if a jQuery handler was registered before for this element+event pair) and - if it was not - added a dummy handler that just returned `true`. The `leverageNative` logic made that `true` then saved into private data, replacing the previous `saved` array. Since `true` passed the truthy check, the second native inner handler treated `true` as an array, crashing on the `slice` call. The same issue could happen if a handler returning `true` is attached before triggering. A bare `length` check would not be enough as the user handler may return an array-like as well. To remove this potential data shape clash, capture the inner result in an object with a `value` property instead of saving it directly. Since it's impossible to call `alert()` in unit tests, simulate the issue by replacing the `addEventListener` method on a test button with a version that calls attached blur handlers twice. Fixes jquerygh-5459 Closes jquerygh-5466 Ref jquerygh-5236
53b647c
to
e66d718
Compare
@timmywil @gibson042 PR updated & ready for another review. |
@@ -522,11 +522,12 @@ function leverageNative( el, type, isSetup ) { | |||
if ( ( event.isTrigger & 1 ) && this[ type ] ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should explain the relevance of saved
up here, rather than by implication scattered throughout the code branches.
if ( ( event.isTrigger & 1 ) && this[ type ] ) { | |
// This controller function is invoked under multiple circumstances, | |
// differentiated by the stored value in `saved`: | |
// 1. For an outer synthetic .trigger()ed event (detected by | |
// `event.isTrigger & 1` and non-array `saved`), it records arguments | |
// as an array and fires an [inner] native event to prompt state | |
// changes that should be observed by registered listeners (such as | |
// checkbox toggling and focus updating), then clears the stored value. | |
// 2. For an [inner] native event (detected by `saved` being | |
// an array), it triggers an inner synthetic event, records the | |
// result, and preempts propagation to further jQuery listeners. | |
// 3. For an inner synthetic event (detected by `event.isTrigger & 1` and | |
// array `saved`), it prevents double-propagation of surrogate events | |
// but otherwise allows everything to proceed (particularly including | |
// further listeners). | |
if ( ( event.isTrigger & 1 ) && this[ type ] ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, can we be more specific than "non-array saved" in the first case? What other types might it be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added Richard's comment plus a short listing of possible saved
data types, PTAL.
// There will always be at least one argument (an event object), | ||
// so this array will not be confused with a leftover capture object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should experiment with checking isArray(saved)
rather than saved.length
(as both point replacements in the if
conditions and as a dedicated variable like savedIsArgs = Array.isArray(saved)
). If there's no size increase, then this commentary would be unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing saved.length
checks to Array.isArray( saved )
results in a size increase:
raw gz Filename
+16 +7 dist/jquery.min.js
+16 +1 dist/jquery.slim.min.js
+16 +4 dist-module/jquery.module.min.js
+16 0 dist-module/jquery.slim.module.min.js
In Firefox, alert displayed just before blurring an element dispatches the native blur event twice which tripped the jQuery logic if a jQuery blur handler was not attached before the trigger call. This was because the `leverageNative` logic part for triggering first checked if setup was done before (which, for example, is done if a jQuery handler was registered before for this element+event pair) and - if it was not - added a dummy handler that just returned `true`. The `leverageNative` logic made that `true` then saved into private data, replacing the previous `saved` array. Since `true` passed the truthy check, the second native inner handler treated `true` as an array, crashing on the `slice` call. The same issue could happen if a handler returning `true` is attached before triggering. A bare `length` check would not be enough as the user handler may return an array-like as well. To remove this potential data shape clash, capture the inner result in an object with a `value` property instead of saving it directly. Since it's impossible to call `alert()` in unit tests, simulate the issue by replacing the `addEventListener` method on a test button with a version that calls attached blur handlers twice. Fixes jquerygh-5459 Closes jquerygh-5466 Ref jquerygh-5236
e66d718
to
4b3bb8a
Compare
|
In Firefox, alert displayed just before blurring an element dispatches the native blur event twice which tripped the jQuery logic if a jQuery blur handler was not attached before the trigger call. This was because the `leverageNative` logic part for triggering first checked if setup was done before (which, for example, is done if a jQuery handler was registered before for this element+event pair) and - if it was not - added a dummy handler that just returned `true`. The `leverageNative` logic made that `true` then saved into private data, replacing the previous `saved` array. Since `true` passed the truthy check, the second native inner handler treated `true` as an array, crashing on the `slice` call. The same issue could happen if a handler returning `true` is attached before triggering. A bare `length` check would not be enough as the user handler may return an array-like as well. To remove this potential data shape clash, capture the inner result in an object with a `value` property instead of saving it directly. Since it's impossible to call `alert()` in unit tests, simulate the issue by replacing the `addEventListener` method on a test button with a version that calls attached blur handlers twice. Fixes jquerygh-5459 Closes jquerygh-5466 Ref jquerygh-5236
4b3bb8a
to
1952220
Compare
In Firefox, alert displayed just before blurring an element dispatches the native blur event twice which tripped the jQuery logic if a jQuery blur handler was not attached before the trigger call. This was because the `leverageNative` logic part for triggering first checked if setup was done before (which, for example, is done if a jQuery handler was registered before for this element+event pair) and - if it was not - added a dummy handler that just returned `true`. The `leverageNative` logic made that `true` then saved into private data, replacing the previous `saved` array. Since `true` passed the truthy check, the second native inner handler treated `true` as an array, crashing on the `slice` call. The same issue could happen if a handler returning `true` is attached before triggering. A bare `length` check would not be enough as the user handler may return an array-like as well. To remove this potential data shape clash, capture the inner result in an object with a `value` property instead of saving it directly. Since it's impossible to call `alert()` in unit tests, simulate the issue by replacing the `addEventListener` method on a test button with a version that calls attached blur handlers twice. Fixes gh-5459 Closes gh-5466 Ref gh-5236 (cherry picked from commit 527fb3d)
Summary
In Firefox, alert displayed just before blurring an element dispatches
the native blur event twice which tripped the jQuery logic if a jQuery blur
handler was not attached before the trigger call.
This was because the
leverageNative
logic part for triggering first checked ifsetup was done before (which, for example, is done if a jQuery handler was
registered before for this element+event pair) and - if it was not - added
a dummy handler that just returned
true
. TheleverageNative
logic made thattrue
then saved into private data, replacing the previoussaved
array. Sincetrue
passed the truthy check, the second native inner handler treatedtrue
as an array, crashing on the
slice
call.The same issue could happen if a handler returning
true
is attached beforetriggering. A bare
length
check would not be enough as the user handler mayreturn an array-like as well. To remove this potential data shape clash, capture
the inner result in an object with a
value
property instead of saving itdirectly.
Since it's impossible to call
alert()
in unit tests, simulate the issue byreplacing the
addEventListener
method on a test button with a version thatcalls attached blur handlers twice.
Fixes gh-5459
Ref gh-5236
Checklist
If needed, a docs issue/PR was created at https://github.com/jquery/api.jquery.com