/* @flow */
|
|
import { isDef, isUndef } from 'shared/util'
|
import { updateListeners } from 'core/vdom/helpers/index'
|
import { isIE, isFF, supportsPassive, isUsingMicroTask } from 'core/util/index'
|
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
|
import { currentFlushTimestamp } from 'core/observer/scheduler'
|
|
// normalize v-model event tokens that can only be determined at runtime.
|
// it's important to place the event as the first in the array because
|
// the whole point is ensuring the v-model callback gets called before
|
// user-attached handlers.
|
function normalizeEvents (on) {
|
/* istanbul ignore if */
|
if (isDef(on[RANGE_TOKEN])) {
|
// IE input[type=range] only supports `change` event
|
const event = isIE ? 'change' : 'input'
|
on[event] = [].concat(on[RANGE_TOKEN], on[event] || [])
|
delete on[RANGE_TOKEN]
|
}
|
// This was originally intended to fix #4521 but no longer necessary
|
// after 2.5. Keeping it for backwards compat with generated code from < 2.4
|
/* istanbul ignore if */
|
if (isDef(on[CHECKBOX_RADIO_TOKEN])) {
|
on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || [])
|
delete on[CHECKBOX_RADIO_TOKEN]
|
}
|
}
|
|
let target: any
|
|
function createOnceHandler (event, handler, capture) {
|
const _target = target // save current target element in closure
|
return function onceHandler () {
|
const res = handler.apply(null, arguments)
|
if (res !== null) {
|
remove(event, onceHandler, capture, _target)
|
}
|
}
|
}
|
|
// #9446: Firefox <= 53 (in particular, ESR 52) has incorrect Event.timeStamp
|
// implementation and does not fire microtasks in between event propagation, so
|
// safe to exclude.
|
const useMicrotaskFix = isUsingMicroTask && !(isFF && Number(isFF[1]) <= 53)
|
|
function add (
|
name: string,
|
handler: Function,
|
capture: boolean,
|
passive: boolean
|
) {
|
// async edge case #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
|
// AFTER it was attached.
|
if (useMicrotaskFix) {
|
const attachedTimestamp = currentFlushTimestamp
|
const original = handler
|
handler = original._wrapper = function (e) {
|
if (
|
// no bubbling, should always fire.
|
// this is just a safety net in case event.timeStamp is unreliable in
|
// certain weird environments...
|
e.target === e.currentTarget ||
|
// event is fired after handler attachment
|
e.timeStamp >= attachedTimestamp ||
|
// bail for environments that have buggy event.timeStamp implementations
|
// #9462 iOS 9 bug: event.timeStamp is 0 after history.pushState
|
// #9681 QtWebEngine event.timeStamp is negative value
|
e.timeStamp <= 0 ||
|
// #9448 bail if event is fired in another document in a multi-page
|
// electron/nw.js app, since event.timeStamp will be using a different
|
// starting reference
|
e.target.ownerDocument !== document
|
) {
|
return original.apply(this, arguments)
|
}
|
}
|
}
|
target.addEventListener(
|
name,
|
handler,
|
supportsPassive
|
? { capture, passive }
|
: capture
|
)
|
}
|
|
function remove (
|
name: string,
|
handler: Function,
|
capture: boolean,
|
_target?: HTMLElement
|
) {
|
(_target || target).removeEventListener(
|
name,
|
handler._wrapper || handler,
|
capture
|
)
|
}
|
|
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
|
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
|
return
|
}
|
const on = vnode.data.on || {}
|
const oldOn = oldVnode.data.on || {}
|
target = vnode.elm
|
normalizeEvents(on)
|
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
|
target = undefined
|
}
|
|
export default {
|
create: updateDOMListeners,
|
update: updateDOMListeners
|
}
|