Просмотр исходного кода

refactor: better solution for async edge case.

Revert event delegation related changes.
Evan You 7 лет назад
Родитель
Сommit
3504d7f6d6

+ 0 - 10
src/core/config.js

@@ -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.

+ 25 - 114
src/platforms/web/runtime/modules/events.js

@@ -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) {

+ 0 - 5
test/e2e/specs/async-edge-cases.html

@@ -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++">

+ 0 - 1
types/test/vue-test.ts

@@ -75,7 +75,6 @@ class Test extends Vue {
     config.keyCodes = { esc: 27 };
     config.ignoredElements = ['foo', /^ion-/];
     config.async = false
-    config.useEventDelegation = true
   }
 
   static testMethods() {

+ 0 - 1
types/vue.d.ts

@@ -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;
 }