]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: drop event delegation and use simple async edge case fix
authorEvan You <yyx990803@gmail.com>
Tue, 22 Jan 2019 21:03:37 +0000 (16:03 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 22 Jan 2019 21:03:37 +0000 (16:03 -0500)
packages/runtime-core/src/createRenderer.ts
packages/runtime-dom/src/index.ts
packages/runtime-dom/src/modules/events.ts
packages/runtime-dom/src/modules/style.ts
packages/runtime-dom/src/teardownVNode.ts [deleted file]
packages/runtime-dom/src/ua.ts [new file with mode: 0644]

index 8abdf01f6197c82c3e90dd5651a3311599bd4fde..b9331017f3013e67de77c08c6c798556da21135d 100644 (file)
@@ -72,7 +72,6 @@ export interface PatchDataFunction {
 export interface RendererOptions {
   nodeOps: NodeOps
   patchData: PatchDataFunction
-  teardownVNode?: (vnode: VNode) => void
 }
 
 export interface FunctionalHandle {
@@ -102,8 +101,7 @@ export function createRenderer(options: RendererOptions) {
       nextSibling: platformNextSibling,
       querySelector: platformQuerySelector
     },
-    patchData: platformPatchData,
-    teardownVNode
+    patchData: platformPatchData
   } = options
 
   function queueInsertOrAppend(
@@ -1138,9 +1136,6 @@ export function createRenderer(options: RendererOptions) {
         data.vnodeBeforeUnmount(vnode)
       }
       unmountChildren(children as VNodeChildren, childFlags)
-      if (teardownVNode !== void 0) {
-        teardownVNode(vnode)
-      }
       if (isElement && data != null && data.vnodeUnmounted) {
         data.vnodeUnmounted(vnode)
       }
index 38e3532650d46389658a2c27b3cdb9ff7dbe3d24..8b8f591da4b5272f347044ded98f77f78644611b 100644 (file)
@@ -1,12 +1,10 @@
 import { createRenderer, Component } from '@vue/runtime-core'
 import { nodeOps } from './nodeOps'
 import { patchData } from './patchData'
-import { teardownVNode } from './teardownVNode'
 
 const { render: _render } = createRenderer({
   nodeOps,
-  patchData,
-  teardownVNode
+  patchData
 })
 
 type publicRender = (
index a23e1166a9b565b488a7842ec605bb2831d89e7b..77fbce8c1e7e46193cc2ae07aea929ed819673dc 100644 (file)
@@ -1,7 +1,13 @@
-const delegateRE = /^(?:click|dblclick|submit|(?:key|mouse|touch|pointer).*)$/
+import { isChrome } from '../ua'
 
-type EventValue = Function | Function[]
-type TargetRef = { el: Element | Document }
+interface Invoker extends Function {
+  value: EventValue
+  lastUpdated?: number
+}
+
+type EventValue = (Function | Function[]) & {
+  invoker?: Invoker | null
+}
 
 export function patchEvent(
   el: Element,
@@ -9,98 +15,46 @@ export function patchEvent(
   prevValue: EventValue | null,
   nextValue: EventValue | null
 ) {
-  if (delegateRE.test(name) && !__JSDOM__) {
-    handleDelegatedEvent(el, name, nextValue)
-  } else {
-    handleNormalEvent(el, name, prevValue, nextValue)
-  }
-}
-
-const eventCounts: Record<string, number> = {}
-const attachedGlobalHandlers: Record<string, Function | null> = {}
-
-export function handleDelegatedEvent(
-  el: any,
-  name: string,
-  value: EventValue | null
-) {
-  const count = eventCounts[name]
-  let store = el.__events
-  if (value) {
-    if (!count) {
-      attachGlobalHandler(name)
-    }
-    if (!store) {
-      store = el.__events = {}
-    }
-    if (!store[name]) {
-      eventCounts[name]++
-    }
-    store[name] = value
-  } else if (store && store[name]) {
-    if (--eventCounts[name] === 0) {
-      removeGlobalHandler(name)
+  const invoker = prevValue && prevValue.invoker
+  if (nextValue) {
+    if (invoker) {
+      ;(prevValue as EventValue).invoker = null
+      invoker.value = nextValue
+      nextValue.invoker = invoker
+      if (isChrome) {
+        invoker.lastUpdated = performance.now()
+      }
+    } else {
+      el.addEventListener(name, createInvoker(nextValue))
     }
-    store[name] = null
+  } else if (invoker) {
+    el.removeEventListener(name, invoker as any)
   }
 }
 
-function attachGlobalHandler(name: string) {
-  const handler = (attachedGlobalHandlers[name] = (e: Event) => {
-    const isClick = e.type === 'click' || e.type === 'dblclick'
-    if (isClick && (e as MouseEvent).button !== 0) {
-      e.stopPropagation()
-      return false
-    }
-    e.stopPropagation = stopPropagation
-    const targetRef: TargetRef = { el: document }
-    Object.defineProperty(e, 'currentTarget', {
-      configurable: true,
-      get() {
-        return targetRef.el
-      }
-    })
-    dispatchEvent(e, name, isClick, targetRef)
-  })
-  document.addEventListener(name, handler)
-  eventCounts[name] = 0
-}
-
-function stopPropagation() {
-  this.cancelBubble = true
-  if (!this.immediatePropagationStopped) {
-    this.stopImmediatePropagation()
+function createInvoker(value: any) {
+  const invoker = ((e: Event) => {
+    invokeEvents(e, invoker.value, invoker.lastUpdated)
+  }) as any
+  invoker.value = value
+  value.invoker = invoker
+  if (isChrome) {
+    invoker.lastUpdated = performance.now()
   }
+  return invoker
 }
 
-function dispatchEvent(
-  e: Event,
-  name: string,
-  isClick: boolean,
-  targetRef: TargetRef
-) {
-  let el = e.target as any
-  while (el != null) {
-    // Don't process clicks on disabled elements
-    if (isClick && el.disabled) {
-      break
-    }
-    const store = el.__events
-    if (store) {
-      const value = store[name]
-      if (value) {
-        targetRef.el = el
-        invokeEvents(e, value)
-        if (e.cancelBubble) {
-          break
-        }
-      }
-    }
-    el = el.parentNode
+function invokeEvents(e: Event, value: EventValue, lastUpdated: number) {
+  // 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.
+  if (isChrome && e.timeStamp < lastUpdated) {
+    return
   }
-}
 
-function invokeEvents(e: Event, value: EventValue) {
   if (Array.isArray(value)) {
     for (let i = 0; i < value.length; i++) {
       value[i](e)
@@ -109,32 +63,3 @@ function invokeEvents(e: Event, value: EventValue) {
     value(e)
   }
 }
-
-function removeGlobalHandler(name: string) {
-  document.removeEventListener(name, attachedGlobalHandlers[name] as any)
-  attachedGlobalHandlers[name] = null
-}
-
-function handleNormalEvent(el: Element, name: string, prev: any, next: any) {
-  const invoker = prev && prev.invoker
-  if (next) {
-    if (invoker) {
-      prev.invoker = null
-      invoker.value = next
-      next.invoker = invoker
-    } else {
-      el.addEventListener(name, createInvoker(next))
-    }
-  } else if (invoker) {
-    el.removeEventListener(name, invoker)
-  }
-}
-
-function createInvoker(value: any) {
-  const invoker = ((e: Event) => {
-    invokeEvents(e, invoker.value)
-  }) as any
-  invoker.value = value
-  value.invoker = invoker
-  return invoker
-}
index e5c3e0ee9bf62a08bafbe75788bf4bc3c6fd9d93..c55afd3dabcfb65f653319b6cc44f96645abacc9 100644 (file)
@@ -1,8 +1,5 @@
 import { isString } from '@vue/shared'
 
-// style properties that should NOT have "px" added when numeric
-const nonNumericRE = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i
-
 export function patchStyle(el: any, prev: any, next: any, data: any) {
   const { style } = el
   if (!next) {
@@ -11,11 +8,7 @@ export function patchStyle(el: any, prev: any, next: any, data: any) {
     style.cssText = next
   } else {
     for (const key in next) {
-      let value = next[key]
-      if (typeof value === 'number' && !nonNumericRE.test(key)) {
-        value = value + 'px'
-      }
-      style[key] = value
+      style[key] = next[key]
     }
     if (prev && !isString(prev)) {
       for (const key in prev) {
diff --git a/packages/runtime-dom/src/teardownVNode.ts b/packages/runtime-dom/src/teardownVNode.ts
deleted file mode 100644 (file)
index d53a0ef..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-import { VNode } from '@vue/runtime-core'
-import { handleDelegatedEvent } from './modules/events'
-import { isOn } from '@vue/shared'
-
-export function teardownVNode(vnode: VNode) {
-  const { el, data } = vnode
-  if (data != null) {
-    for (const key in data) {
-      if (isOn(key)) {
-        handleDelegatedEvent(el, key.slice(2).toLowerCase(), null)
-      }
-    }
-  }
-}
diff --git a/packages/runtime-dom/src/ua.ts b/packages/runtime-dom/src/ua.ts
new file mode 100644 (file)
index 0000000..62bfee8
--- /dev/null
@@ -0,0 +1,3 @@
+export const UA = window.navigator.userAgent.toLowerCase()
+export const isEdge = UA.indexOf('edge/') > 0
+export const isChrome = /chrome\/\d+/.test(UA) && !isEdge