1
1
/* @flow */
2
2
3
- import config from 'core/config'
4
3
import { isDef , isUndef } from 'shared/util'
5
4
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'
7
6
import { RANGE_TOKEN , CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
8
7
9
8
// normalize v-model event tokens that can only be determined at runtime.
@@ -39,114 +38,34 @@ function createOnceHandler (event, handler, capture) {
39
38
}
40
39
}
41
40
42
- const delegateRE = / ^ (?: c l i c k | d b l c l i c k | s u b m i t | (?: k e y | m o u s e | t o u c h | p o i n t e r ) .* ) $ /
43
- const eventCounts = { }
44
- const attachedGlobalHandlers = { }
45
-
46
- type TargetRef = { el : Element | Document }
47
-
48
41
function add (
49
42
name : string ,
50
43
handler : Function ,
51
44
capture : boolean ,
52
45
passive : boolean
53
46
) {
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 )
141
59
}
142
60
}
143
- el = el . parentNode
144
61
}
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
+ )
150
69
}
151
70
152
71
function remove (
@@ -155,19 +74,11 @@ function remove (
155
74
capture : boolean ,
156
75
_target ?: HTMLElement
157
76
) {
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
+ )
171
82
}
172
83
173
84
function updateDOMListeners ( oldVnode : VNodeWithData , vnode : VNodeWithData ) {
0 commit comments