mirror of https://github.com/vuejs/vue.git
refactor: better solution for async edge case.
Revert event delegation related changes.
This commit is contained in:
parent
90fe749091
commit
3504d7f6d6
|
|
@ -19,7 +19,6 @@ export type Config = {
|
|||
warnHandler: ?(msg: string, vm: Component, trace: string) => void;
|
||||
ignoredElements: Array<string | RegExp>;
|
||||
keyCodes: { [key: string]: number | Array<number> };
|
||||
useEventDelegation: boolean;
|
||||
|
||||
// platform
|
||||
isReservedTag: (x?: string) => boolean;
|
||||
|
|
@ -84,15 +83,6 @@ export default ({
|
|||
// $flow-disable-line
|
||||
keyCodes: Object.create(null),
|
||||
|
||||
/**
|
||||
* Use event delegation - this works around a few async edge cases caused by
|
||||
* microtask / DOM event racing conditions, and should in theory save some
|
||||
* memory.
|
||||
*
|
||||
* Off by default for backwards compatibility.
|
||||
*/
|
||||
useEventDelegation: false,
|
||||
|
||||
/**
|
||||
* Check if a tag is reserved so that it cannot be registered as a
|
||||
* component. This is platform-dependent and may be overwritten.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
/* @flow */
|
||||
|
||||
import config from 'core/config'
|
||||
import { isDef, isUndef } from 'shared/util'
|
||||
import { updateListeners } from 'core/vdom/helpers/index'
|
||||
import { isIE, isPhantomJS, supportsPassive } from 'core/util/index'
|
||||
import { isIE, isChrome, supportsPassive } from 'core/util/index'
|
||||
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
|
||||
|
||||
// normalize v-model event tokens that can only be determined at runtime.
|
||||
|
|
@ -39,114 +38,34 @@ function createOnceHandler (event, handler, capture) {
|
|||
}
|
||||
}
|
||||
|
||||
const delegateRE = /^(?:click|dblclick|submit|(?:key|mouse|touch|pointer).*)$/
|
||||
const eventCounts = {}
|
||||
const attachedGlobalHandlers = {}
|
||||
|
||||
type TargetRef = { el: Element | Document }
|
||||
|
||||
function add (
|
||||
name: string,
|
||||
handler: Function,
|
||||
capture: boolean,
|
||||
passive: boolean
|
||||
) {
|
||||
if (
|
||||
!capture &&
|
||||
!passive &&
|
||||
config.useEventDelegation &&
|
||||
delegateRE.test(name)
|
||||
) {
|
||||
const count = eventCounts[name]
|
||||
let store = target.__events
|
||||
if (!count) {
|
||||
attachGlobalHandler(name)
|
||||
}
|
||||
if (!store) {
|
||||
store = target.__events = {}
|
||||
}
|
||||
if (!store[name]) {
|
||||
eventCounts[name]++
|
||||
}
|
||||
store[name] = handler
|
||||
} else {
|
||||
target.addEventListener(
|
||||
name,
|
||||
handler,
|
||||
supportsPassive
|
||||
? { capture, passive }
|
||||
: capture
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function attachGlobalHandler(name: string) {
|
||||
const handler = (attachedGlobalHandlers[name] = (e: any) => {
|
||||
const isClick = e.type === 'click' || e.type === 'dblclick'
|
||||
if (isClick && e.button !== 0) {
|
||||
e.stopPropagation()
|
||||
return false
|
||||
}
|
||||
const targetRef: TargetRef = { el: document }
|
||||
dispatchEvent(e, name, isClick, targetRef)
|
||||
})
|
||||
document.addEventListener(name, handler)
|
||||
eventCounts[name] = 0
|
||||
}
|
||||
|
||||
function stopPropagation() {
|
||||
this.cancelBubble = true
|
||||
if (!this.immediatePropagationStopped) {
|
||||
this.stopImmediatePropagation()
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchEvent(
|
||||
e: Event,
|
||||
name: string,
|
||||
isClick: boolean,
|
||||
targetRef: TargetRef
|
||||
) {
|
||||
let el: any = e.target
|
||||
let userEvent
|
||||
if (isPhantomJS) {
|
||||
// in PhantomJS it throws if we try to re-define currentTarget,
|
||||
// so instead we create a wrapped event to the user
|
||||
userEvent = Object.create((e: any))
|
||||
userEvent.stopPropagation = stopPropagation.bind((e: any))
|
||||
userEvent.preventDefault = e.preventDefault.bind(e)
|
||||
} else {
|
||||
userEvent = e
|
||||
}
|
||||
Object.defineProperty(userEvent, 'currentTarget', ({
|
||||
configurable: true,
|
||||
get() {
|
||||
return targetRef.el
|
||||
}
|
||||
}: any))
|
||||
while (el != null) {
|
||||
// Don't process clicks on disabled elements
|
||||
if (isClick && el.disabled) {
|
||||
break
|
||||
}
|
||||
const store = el.__events
|
||||
if (store) {
|
||||
const handler = store[name]
|
||||
if (handler) {
|
||||
targetRef.el = el
|
||||
handler(userEvent)
|
||||
if (e.cancelBubble) {
|
||||
break
|
||||
}
|
||||
if (isChrome) {
|
||||
// async edge case #6566: inner click event triggers patch, event handler
|
||||
// attached to outer element during patch, and triggered again. This only
|
||||
// happens in Chrome as it fires 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
|
||||
// AFTER it was attached.
|
||||
const now = performance.now()
|
||||
const original = handler
|
||||
handler = original._wrapper = function (e) {
|
||||
if (e.timeStamp > now) {
|
||||
return original.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
el = el.parentNode
|
||||
}
|
||||
}
|
||||
|
||||
function removeGlobalHandler(name: string) {
|
||||
document.removeEventListener(name, attachedGlobalHandlers[name])
|
||||
attachedGlobalHandlers[name] = null
|
||||
target.addEventListener(
|
||||
name,
|
||||
handler,
|
||||
supportsPassive
|
||||
? { capture, passive }
|
||||
: capture
|
||||
)
|
||||
}
|
||||
|
||||
function remove (
|
||||
|
|
@ -155,19 +74,11 @@ function remove (
|
|||
capture: boolean,
|
||||
_target?: HTMLElement
|
||||
) {
|
||||
const el: any = _target || target
|
||||
if (!capture && config.useEventDelegation && delegateRE.test(name)) {
|
||||
el.__events[name] = null
|
||||
if (--eventCounts[name] === 0) {
|
||||
removeGlobalHandler(name)
|
||||
}
|
||||
} else {
|
||||
el.removeEventListener(
|
||||
name,
|
||||
handler._withTask || handler,
|
||||
capture
|
||||
)
|
||||
}
|
||||
(_target || target).removeEventListener(
|
||||
name,
|
||||
handler._wrapper || handler,
|
||||
capture
|
||||
)
|
||||
}
|
||||
|
||||
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,6 @@
|
|||
<script src="../../../dist/vue.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// this is necessary to make these cases pass
|
||||
Vue.config.useEventDelegation = true
|
||||
</script>
|
||||
|
||||
<!-- #4510 click and change event on checkbox -->
|
||||
<div id="case-1">
|
||||
<div @click="num++">
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ class Test extends Vue {
|
|||
config.keyCodes = { esc: 27 };
|
||||
config.ignoredElements = ['foo', /^ion-/];
|
||||
config.async = false
|
||||
config.useEventDelegation = true
|
||||
}
|
||||
|
||||
static testMethods() {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ export interface VueConfiguration {
|
|||
warnHandler(msg: string, vm: Vue, trace: string): void;
|
||||
ignoredElements: (string | RegExp)[];
|
||||
keyCodes: { [key: string]: number | number[] };
|
||||
useEventDelegation: boolean;
|
||||
async: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue