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

revert nextTick to microtask semantics by using Promise.then

Evan You 9 лет назад
Родитель
Сommit
64f711376e
1 измененных файлов с 48 добавлено и 19 удалено
  1. 48 19
      src/util/env.js

+ 48 - 19
src/util/env.js

@@ -1,3 +1,5 @@
+/* globals MutationObserver */
+
 // can we use __proto__?
 export const hasProto = '__proto__' in {}
 
@@ -14,6 +16,7 @@ const UA = inBrowser && window.navigator.userAgent.toLowerCase()
 export const isIE = UA && UA.indexOf('trident') > 0
 export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
 export const isAndroid = UA && UA.indexOf('android') > 0
+export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
 
 let transitionProp
 let transitionEndEvent
@@ -49,6 +52,11 @@ export {
   animationEndEvent
 }
 
+/* istanbul ignore next */
+function isNative (Ctor) {
+  return /native code/.test(Ctor.toString())
+}
+
 /**
  * Defer a task to execute it asynchronously. Ideally this
  * should be executed as a microtask, so we leverage
@@ -60,34 +68,55 @@ export {
  */
 
 export const nextTick = (function () {
-  var callbacks = []
-  var pending = false
-  var timerFunc
+  const callbacks = []
+  let pending = false
+  let timerFunc
+
   function nextTickHandler () {
     pending = false
-    var copies = callbacks.slice(0)
-    callbacks = []
-    for (var i = 0; i < copies.length; i++) {
+    const copies = callbacks.slice(0)
+    callbacks.length = 0
+    for (let i = 0; i < copies.length; i++) {
       copies[i]()
     }
   }
 
-  /* istanbul ignore else */
-  if (inBrowser && window.postMessage &&
-    !window.importScripts && // not in WebWorker
-    !(isAndroid && !window.requestAnimationFrame) // not in Android <= 4.3
-  ) {
-    const NEXT_TICK_TOKEN = '__vue__nextTick__'
-    window.addEventListener('message', e => {
-      if (e.source === window && e.data === NEXT_TICK_TOKEN) {
-        nextTickHandler()
-      }
+  // the nextTick behavior leverages the microtask queue, which can be accessed
+  // via either native Promise.then or MutationObserver.
+  // MutationObserver has wider support, however it is seriously bugged in
+  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
+  // completely stops working after triggering a few times... so, if native
+  // Promise is available, we will use it:
+  /* istanbul ignore if */
+  if (typeof Promise !== 'undefined' && isNative(Promise)) {
+    var p = Promise.resolve()
+    var noop = function () {}
+    timerFunc = () => {
+      p.then(nextTickHandler)
+      // in problematic UIWebViews, Promise.then doesn't completely break, but
+      // it can get stuck in a weird state where callbacks are pushed into the
+      // microtask queue but the queue isn't being flushed, until the browser
+      // needs to do some other work, e.g. handle a timer. Therefore we can
+      // "force" the microtask queue to be flushed by adding an empty timer.
+      if (isIOS) setTimeout(noop)
+    }
+  } else if (typeof MutationObserver !== 'undefined') {
+    // use MutationObserver where native Promise is not available,
+    // e.g. IE11, iOS7, Android 4.4
+    var counter = 1
+    var observer = new MutationObserver(nextTickHandler)
+    var textNode = document.createTextNode(String(counter))
+    observer.observe(textNode, {
+      characterData: true
     })
     timerFunc = () => {
-      window.postMessage(NEXT_TICK_TOKEN, '*')
+      counter = (counter + 1) % 2
+      textNode.data = String(counter)
     }
   } else {
-    timerFunc = (typeof global !== 'undefined' && global.setImmediate) || setTimeout
+    // fallback to setTimeout
+    /* istanbul ignore next */
+    timerFunc = setTimeout
   }
 
   return function (cb, ctx) {
@@ -103,7 +132,7 @@ export const nextTick = (function () {
 
 let _Set
 /* istanbul ignore if */
-if (typeof Set !== 'undefined' && Set.toString().match(/native code/)) {
+if (typeof Set !== 'undefined' && isNative(Set)) {
   // use native Set when available.
   _Set = Set
 } else {