Skip to content

Commit 3504d7f

Browse files
committed
refactor: better solution for async edge case.
Revert event delegation related changes.
1 parent 90fe749 commit 3504d7f

File tree

5 files changed

+25
-131
lines changed

5 files changed

+25
-131
lines changed

Diff for: src/core/config.js

-10
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export type Config = {
1919
warnHandler: ?(msg: string, vm: Component, trace: string) => void;
2020
ignoredElements: Array<string | RegExp>;
2121
keyCodes: { [key: string]: number | Array<number> };
22-
useEventDelegation: boolean;
2322

2423
// platform
2524
isReservedTag: (x?: string) => boolean;
@@ -84,15 +83,6 @@ export default ({
8483
// $flow-disable-line
8584
keyCodes: Object.create(null),
8685

87-
/**
88-
* Use event delegation - this works around a few async edge cases caused by
89-
* microtask / DOM event racing conditions, and should in theory save some
90-
* memory.
91-
*
92-
* Off by default for backwards compatibility.
93-
*/
94-
useEventDelegation: false,
95-
9686
/**
9787
* Check if a tag is reserved so that it cannot be registered as a
9888
* component. This is platform-dependent and may be overwritten.

Diff for: src/platforms/web/runtime/modules/events.js

+25-114
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/* @flow */
22

3-
import config from 'core/config'
43
import { isDef, isUndef } from 'shared/util'
54
import { updateListeners } from 'core/vdom/helpers/index'
6-
import { isIE, isPhantomJS, supportsPassive } from 'core/util/index'
5+
import { isIE, isChrome, supportsPassive } from 'core/util/index'
76
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
87

98
// normalize v-model event tokens that can only be determined at runtime.
@@ -39,114 +38,34 @@ function createOnceHandler (event, handler, capture) {
3938
}
4039
}
4140

42-
const delegateRE = /^(?:click|dblclick|submit|(?:key|mouse|touch|pointer).*)$/
43-
const eventCounts = {}
44-
const attachedGlobalHandlers = {}
45-
46-
type TargetRef = { el: Element | Document }
47-
4841
function add (
4942
name: string,
5043
handler: Function,
5144
capture: boolean,
5245
passive: boolean
5346
) {
54-
if (
55-
!capture &&
56-
!passive &&
57-
config.useEventDelegation &&
58-
delegateRE.test(name)
59-
) {
60-
const count = eventCounts[name]
61-
let store = target.__events
62-
if (!count) {
63-
attachGlobalHandler(name)
64-
}
65-
if (!store) {
66-
store = target.__events = {}
67-
}
68-
if (!store[name]) {
69-
eventCounts[name]++
70-
}
71-
store[name] = handler
72-
} else {
73-
target.addEventListener(
74-
name,
75-
handler,
76-
supportsPassive
77-
? { capture, passive }
78-
: capture
79-
)
80-
}
81-
}
82-
83-
function attachGlobalHandler(name: string) {
84-
const handler = (attachedGlobalHandlers[name] = (e: any) => {
85-
const isClick = e.type === 'click' || e.type === 'dblclick'
86-
if (isClick && e.button !== 0) {
87-
e.stopPropagation()
88-
return false
89-
}
90-
const targetRef: TargetRef = { el: document }
91-
dispatchEvent(e, name, isClick, targetRef)
92-
})
93-
document.addEventListener(name, handler)
94-
eventCounts[name] = 0
95-
}
96-
97-
function stopPropagation() {
98-
this.cancelBubble = true
99-
if (!this.immediatePropagationStopped) {
100-
this.stopImmediatePropagation()
101-
}
102-
}
103-
104-
function dispatchEvent(
105-
e: Event,
106-
name: string,
107-
isClick: boolean,
108-
targetRef: TargetRef
109-
) {
110-
let el: any = e.target
111-
let userEvent
112-
if (isPhantomJS) {
113-
// in PhantomJS it throws if we try to re-define currentTarget,
114-
// so instead we create a wrapped event to the user
115-
userEvent = Object.create((e: any))
116-
userEvent.stopPropagation = stopPropagation.bind((e: any))
117-
userEvent.preventDefault = e.preventDefault.bind(e)
118-
} else {
119-
userEvent = e
120-
}
121-
Object.defineProperty(userEvent, 'currentTarget', ({
122-
configurable: true,
123-
get() {
124-
return targetRef.el
125-
}
126-
}: any))
127-
while (el != null) {
128-
// Don't process clicks on disabled elements
129-
if (isClick && el.disabled) {
130-
break
131-
}
132-
const store = el.__events
133-
if (store) {
134-
const handler = store[name]
135-
if (handler) {
136-
targetRef.el = el
137-
handler(userEvent)
138-
if (e.cancelBubble) {
139-
break
140-
}
47+
if (isChrome) {
48+
// async edge case #6566: inner click event triggers patch, event handler
49+
// attached to outer element during patch, and triggered again. This only
50+
// happens in Chrome as it fires microtask ticks between event propagation.
51+
// the solution is simple: we save the timestamp when a handler is attached,
52+
// and the handler would only fire if the event passed to it was fired
53+
// AFTER it was attached.
54+
const now = performance.now()
55+
const original = handler
56+
handler = original._wrapper = function (e) {
57+
if (e.timeStamp > now) {
58+
return original.apply(this, arguments)
14159
}
14260
}
143-
el = el.parentNode
14461
}
145-
}
146-
147-
function removeGlobalHandler(name: string) {
148-
document.removeEventListener(name, attachedGlobalHandlers[name])
149-
attachedGlobalHandlers[name] = null
62+
target.addEventListener(
63+
name,
64+
handler,
65+
supportsPassive
66+
? { capture, passive }
67+
: capture
68+
)
15069
}
15170

15271
function remove (
@@ -155,19 +74,11 @@ function remove (
15574
capture: boolean,
15675
_target?: HTMLElement
15776
) {
158-
const el: any = _target || target
159-
if (!capture && config.useEventDelegation && delegateRE.test(name)) {
160-
el.__events[name] = null
161-
if (--eventCounts[name] === 0) {
162-
removeGlobalHandler(name)
163-
}
164-
} else {
165-
el.removeEventListener(
166-
name,
167-
handler._withTask || handler,
168-
capture
169-
)
170-
}
77+
(_target || target).removeEventListener(
78+
name,
79+
handler._wrapper || handler,
80+
capture
81+
)
17182
}
17283

17384
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {

Diff for: test/e2e/specs/async-edge-cases.html

-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66
<script src="../../../dist/vue.min.js"></script>
77
</head>
88
<body>
9-
<script>
10-
// this is necessary to make these cases pass
11-
Vue.config.useEventDelegation = true
12-
</script>
13-
149
<!-- #4510 click and change event on checkbox -->
1510
<div id="case-1">
1611
<div @click="num++">

Diff for: types/test/vue-test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ class Test extends Vue {
7575
config.keyCodes = { esc: 27 };
7676
config.ignoredElements = ['foo', /^ion-/];
7777
config.async = false
78-
config.useEventDelegation = true
7978
}
8079

8180
static testMethods() {

Diff for: types/vue.d.ts

-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export interface VueConfiguration {
7474
warnHandler(msg: string, vm: Vue, trace: string): void;
7575
ignoredElements: (string | RegExp)[];
7676
keyCodes: { [key: string]: number | number[] };
77-
useEventDelegation: boolean;
7877
async: boolean;
7978
}
8079

0 commit comments

Comments
 (0)