2
0

events.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /* @flow */
  2. import { isDef, isUndef } from 'shared/util'
  3. import { updateListeners } from 'core/vdom/helpers/index'
  4. import { isIE, isFF, supportsPassive, isUsingMicroTask } from 'core/util/index'
  5. import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
  6. import { currentFlushTimestamp } from 'core/observer/scheduler'
  7. import { emptyNode } from 'core/vdom/patch'
  8. // normalize v-model event tokens that can only be determined at runtime.
  9. // it's important to place the event as the first in the array because
  10. // the whole point is ensuring the v-model callback gets called before
  11. // user-attached handlers.
  12. function normalizeEvents (on) {
  13. /* istanbul ignore if */
  14. if (isDef(on[RANGE_TOKEN])) {
  15. // IE input[type=range] only supports `change` event
  16. const event = isIE ? 'change' : 'input'
  17. on[event] = [].concat(on[RANGE_TOKEN], on[event] || [])
  18. delete on[RANGE_TOKEN]
  19. }
  20. // This was originally intended to fix #4521 but no longer necessary
  21. // after 2.5. Keeping it for backwards compat with generated code from < 2.4
  22. /* istanbul ignore if */
  23. if (isDef(on[CHECKBOX_RADIO_TOKEN])) {
  24. on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || [])
  25. delete on[CHECKBOX_RADIO_TOKEN]
  26. }
  27. }
  28. let target: any
  29. function createOnceHandler (event, handler, capture) {
  30. const _target = target // save current target element in closure
  31. return function onceHandler () {
  32. const res = handler.apply(null, arguments)
  33. if (res !== null) {
  34. remove(event, onceHandler, capture, _target)
  35. }
  36. }
  37. }
  38. // #9446: Firefox <= 53 (in particular, ESR 52) has incorrect Event.timeStamp
  39. // implementation and does not fire microtasks in between event propagation, so
  40. // safe to exclude.
  41. const useMicrotaskFix = isUsingMicroTask && !(isFF && Number(isFF[1]) <= 53)
  42. function add (
  43. name: string,
  44. handler: Function,
  45. capture: boolean,
  46. passive: boolean
  47. ) {
  48. // async edge case #6566: inner click event triggers patch, event handler
  49. // attached to outer element during patch, and triggered again. This
  50. // happens because browsers fire 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. if (useMicrotaskFix) {
  55. const attachedTimestamp = currentFlushTimestamp
  56. const original = handler
  57. handler = original._wrapper = function (e) {
  58. if (
  59. // no bubbling, should always fire.
  60. // this is just a safety net in case event.timeStamp is unreliable in
  61. // certain weird environments...
  62. e.target === e.currentTarget ||
  63. // event is fired after handler attachment
  64. e.timeStamp >= attachedTimestamp ||
  65. // bail for environments that have buggy event.timeStamp implementations
  66. // #9462 iOS 9 bug: event.timeStamp is 0 after history.pushState
  67. // #9681 QtWebEngine event.timeStamp is negative value
  68. e.timeStamp <= 0 ||
  69. // #9448 bail if event is fired in another document in a multi-page
  70. // electron/nw.js app, since event.timeStamp will be using a different
  71. // starting reference
  72. e.target.ownerDocument !== document
  73. ) {
  74. return original.apply(this, arguments)
  75. }
  76. }
  77. }
  78. target.addEventListener(
  79. name,
  80. handler,
  81. supportsPassive
  82. ? { capture, passive }
  83. : capture
  84. )
  85. }
  86. function remove (
  87. name: string,
  88. handler: Function,
  89. capture: boolean,
  90. _target?: HTMLElement
  91. ) {
  92. (_target || target).removeEventListener(
  93. name,
  94. handler._wrapper || handler,
  95. capture
  96. )
  97. }
  98. function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  99. if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
  100. return
  101. }
  102. const on = vnode.data.on || {}
  103. const oldOn = oldVnode.data.on || {}
  104. // vnode is empty when removing all listeners,
  105. // and use old vnode dom element
  106. target = vnode.elm || oldVnode.elm
  107. normalizeEvents(on)
  108. updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  109. target = undefined
  110. }
  111. export default {
  112. create: updateDOMListeners,
  113. update: updateDOMListeners,
  114. destroy: (vnode: VNodeWithData) => updateDOMListeners(vnode, emptyNode)
  115. }