Skip to content

Commit 64f7113

Browse files
committed
revert nextTick to microtask semantics by using Promise.then
1 parent baa92ca commit 64f7113

File tree

1 file changed

+48
-19
lines changed

1 file changed

+48
-19
lines changed

src/util/env.js

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* globals MutationObserver */
2+
13
// can we use __proto__?
24
export const hasProto = '__proto__' in {}
35

@@ -14,6 +16,7 @@ const UA = inBrowser && window.navigator.userAgent.toLowerCase()
1416
export const isIE = UA && UA.indexOf('trident') > 0
1517
export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
1618
export const isAndroid = UA && UA.indexOf('android') > 0
19+
export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
1720

1821
let transitionProp
1922
let transitionEndEvent
@@ -49,6 +52,11 @@ export {
4952
animationEndEvent
5053
}
5154

55+
/* istanbul ignore next */
56+
function isNative (Ctor) {
57+
return /native code/.test(Ctor.toString())
58+
}
59+
5260
/**
5361
* Defer a task to execute it asynchronously. Ideally this
5462
* should be executed as a microtask, so we leverage
@@ -60,34 +68,55 @@ export {
6068
*/
6169

6270
export const nextTick = (function () {
63-
var callbacks = []
64-
var pending = false
65-
var timerFunc
71+
const callbacks = []
72+
let pending = false
73+
let timerFunc
74+
6675
function nextTickHandler () {
6776
pending = false
68-
var copies = callbacks.slice(0)
69-
callbacks = []
70-
for (var i = 0; i < copies.length; i++) {
77+
const copies = callbacks.slice(0)
78+
callbacks.length = 0
79+
for (let i = 0; i < copies.length; i++) {
7180
copies[i]()
7281
}
7382
}
7483

75-
/* istanbul ignore else */
76-
if (inBrowser && window.postMessage &&
77-
!window.importScripts && // not in WebWorker
78-
!(isAndroid && !window.requestAnimationFrame) // not in Android <= 4.3
79-
) {
80-
const NEXT_TICK_TOKEN = '__vue__nextTick__'
81-
window.addEventListener('message', e => {
82-
if (e.source === window && e.data === NEXT_TICK_TOKEN) {
83-
nextTickHandler()
84-
}
84+
// the nextTick behavior leverages the microtask queue, which can be accessed
85+
// via either native Promise.then or MutationObserver.
86+
// MutationObserver has wider support, however it is seriously bugged in
87+
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
88+
// completely stops working after triggering a few times... so, if native
89+
// Promise is available, we will use it:
90+
/* istanbul ignore if */
91+
if (typeof Promise !== 'undefined' && isNative(Promise)) {
92+
var p = Promise.resolve()
93+
var noop = function () {}
94+
timerFunc = () => {
95+
p.then(nextTickHandler)
96+
// in problematic UIWebViews, Promise.then doesn't completely break, but
97+
// it can get stuck in a weird state where callbacks are pushed into the
98+
// microtask queue but the queue isn't being flushed, until the browser
99+
// needs to do some other work, e.g. handle a timer. Therefore we can
100+
// "force" the microtask queue to be flushed by adding an empty timer.
101+
if (isIOS) setTimeout(noop)
102+
}
103+
} else if (typeof MutationObserver !== 'undefined') {
104+
// use MutationObserver where native Promise is not available,
105+
// e.g. IE11, iOS7, Android 4.4
106+
var counter = 1
107+
var observer = new MutationObserver(nextTickHandler)
108+
var textNode = document.createTextNode(String(counter))
109+
observer.observe(textNode, {
110+
characterData: true
85111
})
86112
timerFunc = () => {
87-
window.postMessage(NEXT_TICK_TOKEN, '*')
113+
counter = (counter + 1) % 2
114+
textNode.data = String(counter)
88115
}
89116
} else {
90-
timerFunc = (typeof global !== 'undefined' && global.setImmediate) || setTimeout
117+
// fallback to setTimeout
118+
/* istanbul ignore next */
119+
timerFunc = setTimeout
91120
}
92121

93122
return function (cb, ctx) {
@@ -103,7 +132,7 @@ export const nextTick = (function () {
103132

104133
let _Set
105134
/* istanbul ignore if */
106-
if (typeof Set !== 'undefined' && Set.toString().match(/native code/)) {
135+
if (typeof Set !== 'undefined' && isNative(Set)) {
107136
// use native Set when available.
108137
_Set = Set
109138
} else {

0 commit comments

Comments
 (0)