|
1 | 1 | /* @flow */
|
2 |
| -/* globals MutationObserver */ |
| 2 | +/* globals MessageChannel */ |
3 | 3 |
|
4 |
| -import { noop } from 'shared/util' |
5 | 4 | import { handleError } from './error'
|
6 | 5 |
|
7 | 6 | // can we use __proto__?
|
@@ -80,41 +79,29 @@ export const nextTick = (function () {
|
80 | 79 | }
|
81 | 80 | }
|
82 | 81 |
|
83 |
| - // the nextTick behavior leverages the microtask queue, which can be accessed |
84 |
| - // via either native Promise.then or MutationObserver. |
85 |
| - // MutationObserver has wider support, however it is seriously bugged in |
86 |
| - // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It |
87 |
| - // completely stops working after triggering a few times... so, if native |
88 |
| - // Promise is available, we will use it: |
89 |
| - /* istanbul ignore if */ // $flow-disable-line |
90 |
| - if (typeof Promise !== 'undefined' && isNative(Promise)) { |
91 |
| - var p = Promise.resolve() |
92 |
| - var logError = err => { handleError(err, null, 'nextTick') } |
| 82 | + // An asynchronous deferring mechanism. |
| 83 | + // In pre 2.4, we used to use microtasks (Promise/MutationObserver) |
| 84 | + // but microtasks actually has too high a priority and fires in between |
| 85 | + // supposedly sequential events (e.g. #4521, #6690) or even between |
| 86 | + // bubbling of the same event (#6566). Technically setImmediate should be |
| 87 | + // the ideal choice, but it's not available everywhere; and the only polyfill |
| 88 | + // that consistently queues the callback after all DOM events triggered in the |
| 89 | + // same loop is by using MessageChannel. |
| 90 | + /* istanbul ignore if */ |
| 91 | + if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { |
93 | 92 | timerFunc = () => {
|
94 |
| - p.then(nextTickHandler).catch(logError) |
95 |
| - // in problematic UIWebViews, Promise.then doesn't completely break, but |
96 |
| - // it can get stuck in a weird state where callbacks are pushed into the |
97 |
| - // microtask queue but the queue isn't being flushed, until the browser |
98 |
| - // needs to do some other work, e.g. handle a timer. Therefore we can |
99 |
| - // "force" the microtask queue to be flushed by adding an empty timer. |
100 |
| - if (isIOS) setTimeout(noop) |
| 93 | + setImmediate(nextTickHandler) |
101 | 94 | }
|
102 |
| - } else if (!isIE && typeof MutationObserver !== 'undefined' && ( |
103 |
| - isNative(MutationObserver) || |
104 |
| - // PhantomJS and iOS 7.x |
105 |
| - MutationObserver.toString() === '[object MutationObserverConstructor]' |
| 95 | + } else if (typeof MessageChannel !== 'undefined' && ( |
| 96 | + isNative(MessageChannel) || |
| 97 | + // PhantomJS |
| 98 | + MessageChannel.toString() === '[object MessageChannelConstructor]' |
106 | 99 | )) {
|
107 |
| - // use MutationObserver where native Promise is not available, |
108 |
| - // e.g. PhantomJS, iOS7, Android 4.4 |
109 |
| - var counter = 1 |
110 |
| - var observer = new MutationObserver(nextTickHandler) |
111 |
| - var textNode = document.createTextNode(String(counter)) |
112 |
| - observer.observe(textNode, { |
113 |
| - characterData: true |
114 |
| - }) |
| 100 | + const channel = new MessageChannel() |
| 101 | + const port = channel.port2 |
| 102 | + channel.port1.onmessage = nextTickHandler |
115 | 103 | timerFunc = () => {
|
116 |
| - counter = (counter + 1) % 2 |
117 |
| - textNode.data = String(counter) |
| 104 | + port.postMessage(1) |
118 | 105 | }
|
119 | 106 | } else {
|
120 | 107 | // fallback to setTimeout
|
|
0 commit comments