Skip to content

Commit 43b489b

Browse files
committed
revert nextTick to microtask semantics by using Promise.then (fix #3771)
1 parent 08f2b97 commit 43b489b

File tree

1 file changed

+44
-18
lines changed

1 file changed

+44
-18
lines changed

src/core/util/env.js

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
/* @flow */
2+
/* globals MutationObserver */
3+
4+
import { noop } from 'shared/util'
25

36
// can we use __proto__?
47
export const hasProto = '__proto__' in {}
@@ -13,61 +16,84 @@ export const isIE = UA && /msie|trident/.test(UA)
1316
export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
1417
export const isEdge = UA && UA.indexOf('edge/') > 0
1518
export const isAndroid = UA && UA.indexOf('android') > 0
19+
export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
1620

1721
// detect devtools
1822
export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
1923

24+
function isNative (Ctor: Function): boolean {
25+
return /native code/.test(Ctor.toString())
26+
}
27+
2028
/**
2129
* Defer a task to execute it asynchronously. Ideally this
2230
* should be executed as a microtask, but MutationObserver is unreliable
2331
* in iOS UIWebView so we use a setImmediate shim and fallback to setTimeout.
2432
*/
2533
export const nextTick = (function () {
26-
let callbacks = []
34+
const callbacks = []
2735
let pending = false
2836
let timerFunc
2937

3038
function nextTickHandler () {
3139
pending = false
3240
const copies = callbacks.slice(0)
33-
callbacks = []
41+
callbacks.length = 0
3442
for (let i = 0; i < copies.length; i++) {
3543
copies[i]()
3644
}
3745
}
3846

39-
/* istanbul ignore else */
40-
if (inBrowser && window.postMessage &&
41-
!window.importScripts && // not in WebWorker
42-
!(isAndroid && !window.requestAnimationFrame) // not in Android <= 4.3
43-
) {
44-
const NEXT_TICK_TOKEN = '__vue__nextTick__'
45-
window.addEventListener('message', e => {
46-
if (e.source === window && e.data === NEXT_TICK_TOKEN) {
47-
nextTickHandler()
48-
}
47+
// the nextTick behavior leverages the microtask queue, which can be accessed
48+
// via either native Promise.then or MutationObserver.
49+
// MutationObserver has wider support, however it is seriously bugged in
50+
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
51+
// completely stops working after triggering a few times... so, if native
52+
// Promise is available, we will use it:
53+
if (typeof Promise !== 'undefined' && isNative(Promise)) {
54+
var p = Promise.resolve()
55+
timerFunc = () => {
56+
p.then(nextTickHandler)
57+
// in problematic UIWebViews, Promise.then doesn't completely break, but
58+
// it can get stuck in a weird state where callbacks are pushed into the
59+
// microtask queue but the queue isn't being flushed, until the browser
60+
// needs to do some other work, e.g. handle a timer. Therefore we can
61+
// "force" the microtask queue to be flushed by adding an empty timer.
62+
if (isIOS) setTimeout(noop)
63+
}
64+
} else if (typeof MutationObserver !== 'undefined') {
65+
// use MutationObserver where native Promise is not available,
66+
// e.g. IE11, iOS7, Android 4.4
67+
var counter = 1
68+
var observer = new MutationObserver(nextTickHandler)
69+
var textNode = document.createTextNode(String(counter))
70+
observer.observe(textNode, {
71+
characterData: true
4972
})
5073
timerFunc = () => {
51-
window.postMessage(NEXT_TICK_TOKEN, '*')
74+
counter = (counter + 1) % 2
75+
textNode.data = String(counter)
5276
}
5377
} else {
54-
timerFunc = (typeof global !== 'undefined' && global.setImmediate) || setTimeout
78+
// fallback to setTimeout
79+
timerFunc = setTimeout
5580
}
5681

5782
return function queueNextTick (cb: Function, ctx?: Object) {
5883
const func = ctx
5984
? function () { cb.call(ctx) }
6085
: cb
6186
callbacks.push(func)
62-
if (pending) return
63-
pending = true
64-
timerFunc(nextTickHandler, 0)
87+
if (!pending) {
88+
pending = true
89+
timerFunc(nextTickHandler, 0)
90+
}
6591
}
6692
})()
6793

6894
let _Set
6995
/* istanbul ignore if */
70-
if (typeof Set !== 'undefined' && /native code/.test(Set.toString())) {
96+
if (typeof Set !== 'undefined' && isNative(Set)) {
7197
// use native Set when available.
7298
_Set = Set
7399
} else {

0 commit comments

Comments
 (0)