-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,38 +12,6 @@ interface Invoker extends EventListener { | |
|
||
type EventValue = Function | Function[] | ||
|
||
// Async edge case fix requires storing an event listener's attach timestamp. | ||
const [_getNow, skipTimestampCheck] = /*#__PURE__*/ (() => { | ||
let _getNow = Date.now | ||
let skipTimestampCheck = false | ||
if (typeof window !== 'undefined') { | ||
// Determine what event timestamp the browser is using. Annoyingly, the | ||
// timestamp can either be hi-res (relative to page load) or low-res | ||
// (relative to UNIX epoch), so in order to compare time we have to use the | ||
// same timestamp type when saving the flush timestamp. | ||
if (Date.now() > document.createEvent('Event').timeStamp) { | ||
// if the low-res timestamp which is bigger than the event timestamp | ||
// (which is evaluated AFTER) it means the event is using a hi-res timestamp, | ||
// and we need to use the hi-res version for event listeners as well. | ||
_getNow = performance.now.bind(performance) | ||
} | ||
// #3485: Firefox <= 53 has incorrect Event.timeStamp implementation | ||
// and does not fire microtasks in between event propagation, so safe to exclude. | ||
const ffMatch = navigator.userAgent.match(/firefox\/(\d+)/i) | ||
skipTimestampCheck = !!(ffMatch && Number(ffMatch[1]) <= 53) | ||
} | ||
return [_getNow, skipTimestampCheck] | ||
})() | ||
|
||
// To avoid the overhead of repeatedly calling performance.now(), we cache | ||
// and use the same timestamp for all event listeners attached in the same tick. | ||
let cachedNow: number = 0 | ||
const p = /*#__PURE__*/ Promise.resolve() | ||
const reset = () => { | ||
cachedNow = 0 | ||
} | ||
const getNow = () => cachedNow || (p.then(reset), (cachedNow = _getNow())) | ||
|
||
export function addEventListener( | ||
el: Element, | ||
event: string, | ||
|
@@ -105,27 +73,41 @@ function parseName(name: string): [string, EventListenerOptions | undefined] { | |
return [event, options] | ||
} | ||
|
||
// To avoid the overhead of repeatedly calling Date.now(), we cache | ||
// and use the same timestamp for all event listeners attached in the same tick. | ||
let cachedNow: number = 0 | ||
const p = /*#__PURE__*/ Promise.resolve() | ||
const getNow = () => | ||
cachedNow || (p.then(() => (cachedNow = 0)), (cachedNow = Date.now())) | ||
|
||
function createInvoker( | ||
initialValue: EventValue, | ||
instance: ComponentInternalInstance | null | ||
) { | ||
const invoker: Invoker = (e: Event) => { | ||
// async edge case #6566: inner click event triggers patch, event handler | ||
const invoker: Invoker = (e: Event & { _vts?: number }) => { | ||
// async edge case vuejs/vue#6566 | ||
// inner click event triggers patch, event handler | ||
// attached to outer element during patch, and triggered again. This | ||
// happens because browsers fire microtask ticks between event propagation. | ||
// the solution is simple: we save the timestamp when a handler is attached, | ||
// and the handler would only fire if the event passed to it was fired | ||
// this no longer happens for templates in Vue 3, but could still be | ||
// theoretically possible for hand-written render functions. | ||
// the solution: we save the timestamp when a handler is attached, | ||
// and also attach the timestamp to any event that was handled by vue | ||
// for the first time (to avoid inconsistent event timestamp implementations | ||
// or events fired from iframes, e.g. #2513) | ||
// The handler would only fire if the event passed to it was fired | ||
// AFTER it was attached. | ||
const timeStamp = e.timeStamp || _getNow() | ||
|
||
if (skipTimestampCheck || timeStamp >= invoker.attached - 1) { | ||
callWithAsyncErrorHandling( | ||
patchStopImmediatePropagation(e, invoker.value), | ||
instance, | ||
ErrorCodes.NATIVE_EVENT_HANDLER, | ||
[e] | ||
) | ||
if (!e._vts) { | ||
e._vts = Date.now() | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
yyx990803
Author
Member
|
||
} else if (e._vts <= invoker.attached) { | ||
return | ||
} | ||
callWithAsyncErrorHandling( | ||
patchStopImmediatePropagation(e, invoker.value), | ||
instance, | ||
ErrorCodes.NATIVE_EVENT_HANDLER, | ||
[e] | ||
) | ||
} | ||
invoker.value = initialValue | ||
invoker.attached = getNow() | ||
|
@yyx990803 This change causes one of my unit tests to fail.
As described in that snippet Vue Composition API: Unit testing custom events of stubbed child components the test emits
onDirtyChange
with an event valuetrue
.Prior to that
e._vts = Date.now()
line the test is passing.With that change the test fails with the follow error log:
Given the change is a patch level change, I didn't expect a breaking change.