| 1 | "use strict";
|
|---|
| 2 |
|
|---|
| 3 | // Use the fastest means possible to execute a task in its own turn, with
|
|---|
| 4 | // priority over other events including IO, animation, reflow, and redraw
|
|---|
| 5 | // events in browsers.
|
|---|
| 6 | //
|
|---|
| 7 | // An exception thrown by a task will permanently interrupt the processing of
|
|---|
| 8 | // subsequent tasks. The higher level `asap` function ensures that if an
|
|---|
| 9 | // exception is thrown by a task, that the task queue will continue flushing as
|
|---|
| 10 | // soon as possible, but if you use `rawAsap` directly, you are responsible to
|
|---|
| 11 | // either ensure that no exceptions are thrown from your task, or to manually
|
|---|
| 12 | // call `rawAsap.requestFlush` if an exception is thrown.
|
|---|
| 13 | module.exports = rawAsap;
|
|---|
| 14 | function rawAsap(task) {
|
|---|
| 15 | if (!queue.length) {
|
|---|
| 16 | requestFlush();
|
|---|
| 17 | flushing = true;
|
|---|
| 18 | }
|
|---|
| 19 | // Equivalent to push, but avoids a function call.
|
|---|
| 20 | queue[queue.length] = task;
|
|---|
| 21 | }
|
|---|
| 22 |
|
|---|
| 23 | var queue = [];
|
|---|
| 24 | // Once a flush has been requested, no further calls to `requestFlush` are
|
|---|
| 25 | // necessary until the next `flush` completes.
|
|---|
| 26 | var flushing = false;
|
|---|
| 27 | // `requestFlush` is an implementation-specific method that attempts to kick
|
|---|
| 28 | // off a `flush` event as quickly as possible. `flush` will attempt to exhaust
|
|---|
| 29 | // the event queue before yielding to the browser's own event loop.
|
|---|
| 30 | var requestFlush;
|
|---|
| 31 | // The position of the next task to execute in the task queue. This is
|
|---|
| 32 | // preserved between calls to `flush` so that it can be resumed if
|
|---|
| 33 | // a task throws an exception.
|
|---|
| 34 | var index = 0;
|
|---|
| 35 | // If a task schedules additional tasks recursively, the task queue can grow
|
|---|
| 36 | // unbounded. To prevent memory exhaustion, the task queue will periodically
|
|---|
| 37 | // truncate already-completed tasks.
|
|---|
| 38 | var capacity = 1024;
|
|---|
| 39 |
|
|---|
| 40 | // The flush function processes all tasks that have been scheduled with
|
|---|
| 41 | // `rawAsap` unless and until one of those tasks throws an exception.
|
|---|
| 42 | // If a task throws an exception, `flush` ensures that its state will remain
|
|---|
| 43 | // consistent and will resume where it left off when called again.
|
|---|
| 44 | // However, `flush` does not make any arrangements to be called again if an
|
|---|
| 45 | // exception is thrown.
|
|---|
| 46 | function flush() {
|
|---|
| 47 | while (index < queue.length) {
|
|---|
| 48 | var currentIndex = index;
|
|---|
| 49 | // Advance the index before calling the task. This ensures that we will
|
|---|
| 50 | // begin flushing on the next task the task throws an error.
|
|---|
| 51 | index = index + 1;
|
|---|
| 52 | queue[currentIndex].call();
|
|---|
| 53 | // Prevent leaking memory for long chains of recursive calls to `asap`.
|
|---|
| 54 | // If we call `asap` within tasks scheduled by `asap`, the queue will
|
|---|
| 55 | // grow, but to avoid an O(n) walk for every task we execute, we don't
|
|---|
| 56 | // shift tasks off the queue after they have been executed.
|
|---|
| 57 | // Instead, we periodically shift 1024 tasks off the queue.
|
|---|
| 58 | if (index > capacity) {
|
|---|
| 59 | // Manually shift all values starting at the index back to the
|
|---|
| 60 | // beginning of the queue.
|
|---|
| 61 | for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
|
|---|
| 62 | queue[scan] = queue[scan + index];
|
|---|
| 63 | }
|
|---|
| 64 | queue.length -= index;
|
|---|
| 65 | index = 0;
|
|---|
| 66 | }
|
|---|
| 67 | }
|
|---|
| 68 | queue.length = 0;
|
|---|
| 69 | index = 0;
|
|---|
| 70 | flushing = false;
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | // `requestFlush` is implemented using a strategy based on data collected from
|
|---|
| 74 | // every available SauceLabs Selenium web driver worker at time of writing.
|
|---|
| 75 | // https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593
|
|---|
| 76 |
|
|---|
| 77 | // Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that
|
|---|
| 78 | // have WebKitMutationObserver but not un-prefixed MutationObserver.
|
|---|
| 79 | // Must use `global` or `self` instead of `window` to work in both frames and web
|
|---|
| 80 | // workers. `global` is a provision of Browserify, Mr, Mrs, or Mop.
|
|---|
| 81 |
|
|---|
| 82 | /* globals self */
|
|---|
| 83 | var scope = typeof global !== "undefined" ? global : self;
|
|---|
| 84 | var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
|
|---|
| 85 |
|
|---|
| 86 | // MutationObservers are desirable because they have high priority and work
|
|---|
| 87 | // reliably everywhere they are implemented.
|
|---|
| 88 | // They are implemented in all modern browsers.
|
|---|
| 89 | //
|
|---|
| 90 | // - Android 4-4.3
|
|---|
| 91 | // - Chrome 26-34
|
|---|
| 92 | // - Firefox 14-29
|
|---|
| 93 | // - Internet Explorer 11
|
|---|
| 94 | // - iPad Safari 6-7.1
|
|---|
| 95 | // - iPhone Safari 7-7.1
|
|---|
| 96 | // - Safari 6-7
|
|---|
| 97 | if (typeof BrowserMutationObserver === "function") {
|
|---|
| 98 | requestFlush = makeRequestCallFromMutationObserver(flush);
|
|---|
| 99 |
|
|---|
| 100 | // MessageChannels are desirable because they give direct access to the HTML
|
|---|
| 101 | // task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera
|
|---|
| 102 | // 11-12, and in web workers in many engines.
|
|---|
| 103 | // Although message channels yield to any queued rendering and IO tasks, they
|
|---|
| 104 | // would be better than imposing the 4ms delay of timers.
|
|---|
| 105 | // However, they do not work reliably in Internet Explorer or Safari.
|
|---|
| 106 |
|
|---|
| 107 | // Internet Explorer 10 is the only browser that has setImmediate but does
|
|---|
| 108 | // not have MutationObservers.
|
|---|
| 109 | // Although setImmediate yields to the browser's renderer, it would be
|
|---|
| 110 | // preferrable to falling back to setTimeout since it does not have
|
|---|
| 111 | // the minimum 4ms penalty.
|
|---|
| 112 | // Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and
|
|---|
| 113 | // Desktop to a lesser extent) that renders both setImmediate and
|
|---|
| 114 | // MessageChannel useless for the purposes of ASAP.
|
|---|
| 115 | // https://github.com/kriskowal/q/issues/396
|
|---|
| 116 |
|
|---|
| 117 | // Timers are implemented universally.
|
|---|
| 118 | // We fall back to timers in workers in most engines, and in foreground
|
|---|
| 119 | // contexts in the following browsers.
|
|---|
| 120 | // However, note that even this simple case requires nuances to operate in a
|
|---|
| 121 | // broad spectrum of browsers.
|
|---|
| 122 | //
|
|---|
| 123 | // - Firefox 3-13
|
|---|
| 124 | // - Internet Explorer 6-9
|
|---|
| 125 | // - iPad Safari 4.3
|
|---|
| 126 | // - Lynx 2.8.7
|
|---|
| 127 | } else {
|
|---|
| 128 | requestFlush = makeRequestCallFromTimer(flush);
|
|---|
| 129 | }
|
|---|
| 130 |
|
|---|
| 131 | // `requestFlush` requests that the high priority event queue be flushed as
|
|---|
| 132 | // soon as possible.
|
|---|
| 133 | // This is useful to prevent an error thrown in a task from stalling the event
|
|---|
| 134 | // queue if the exception handled by Node.js’s
|
|---|
| 135 | // `process.on("uncaughtException")` or by a domain.
|
|---|
| 136 | rawAsap.requestFlush = requestFlush;
|
|---|
| 137 |
|
|---|
| 138 | // To request a high priority event, we induce a mutation observer by toggling
|
|---|
| 139 | // the text of a text node between "1" and "-1".
|
|---|
| 140 | function makeRequestCallFromMutationObserver(callback) {
|
|---|
| 141 | var toggle = 1;
|
|---|
| 142 | var observer = new BrowserMutationObserver(callback);
|
|---|
| 143 | var node = document.createTextNode("");
|
|---|
| 144 | observer.observe(node, {characterData: true});
|
|---|
| 145 | return function requestCall() {
|
|---|
| 146 | toggle = -toggle;
|
|---|
| 147 | node.data = toggle;
|
|---|
| 148 | };
|
|---|
| 149 | }
|
|---|
| 150 |
|
|---|
| 151 | // The message channel technique was discovered by Malte Ubl and was the
|
|---|
| 152 | // original foundation for this library.
|
|---|
| 153 | // http://www.nonblocking.io/2011/06/windownexttick.html
|
|---|
| 154 |
|
|---|
| 155 | // Safari 6.0.5 (at least) intermittently fails to create message ports on a
|
|---|
| 156 | // page's first load. Thankfully, this version of Safari supports
|
|---|
| 157 | // MutationObservers, so we don't need to fall back in that case.
|
|---|
| 158 |
|
|---|
| 159 | // function makeRequestCallFromMessageChannel(callback) {
|
|---|
| 160 | // var channel = new MessageChannel();
|
|---|
| 161 | // channel.port1.onmessage = callback;
|
|---|
| 162 | // return function requestCall() {
|
|---|
| 163 | // channel.port2.postMessage(0);
|
|---|
| 164 | // };
|
|---|
| 165 | // }
|
|---|
| 166 |
|
|---|
| 167 | // For reasons explained above, we are also unable to use `setImmediate`
|
|---|
| 168 | // under any circumstances.
|
|---|
| 169 | // Even if we were, there is another bug in Internet Explorer 10.
|
|---|
| 170 | // It is not sufficient to assign `setImmediate` to `requestFlush` because
|
|---|
| 171 | // `setImmediate` must be called *by name* and therefore must be wrapped in a
|
|---|
| 172 | // closure.
|
|---|
| 173 | // Never forget.
|
|---|
| 174 |
|
|---|
| 175 | // function makeRequestCallFromSetImmediate(callback) {
|
|---|
| 176 | // return function requestCall() {
|
|---|
| 177 | // setImmediate(callback);
|
|---|
| 178 | // };
|
|---|
| 179 | // }
|
|---|
| 180 |
|
|---|
| 181 | // Safari 6.0 has a problem where timers will get lost while the user is
|
|---|
| 182 | // scrolling. This problem does not impact ASAP because Safari 6.0 supports
|
|---|
| 183 | // mutation observers, so that implementation is used instead.
|
|---|
| 184 | // However, if we ever elect to use timers in Safari, the prevalent work-around
|
|---|
| 185 | // is to add a scroll event listener that calls for a flush.
|
|---|
| 186 |
|
|---|
| 187 | // `setTimeout` does not call the passed callback if the delay is less than
|
|---|
| 188 | // approximately 7 in web workers in Firefox 8 through 18, and sometimes not
|
|---|
| 189 | // even then.
|
|---|
| 190 |
|
|---|
| 191 | function makeRequestCallFromTimer(callback) {
|
|---|
| 192 | return function requestCall() {
|
|---|
| 193 | // We dispatch a timeout with a specified delay of 0 for engines that
|
|---|
| 194 | // can reliably accommodate that request. This will usually be snapped
|
|---|
| 195 | // to a 4 milisecond delay, but once we're flushing, there's no delay
|
|---|
| 196 | // between events.
|
|---|
| 197 | var timeoutHandle = setTimeout(handleTimer, 0);
|
|---|
| 198 | // However, since this timer gets frequently dropped in Firefox
|
|---|
| 199 | // workers, we enlist an interval handle that will try to fire
|
|---|
| 200 | // an event 20 times per second until it succeeds.
|
|---|
| 201 | var intervalHandle = setInterval(handleTimer, 50);
|
|---|
| 202 |
|
|---|
| 203 | function handleTimer() {
|
|---|
| 204 | // Whichever timer succeeds will cancel both timers and
|
|---|
| 205 | // execute the callback.
|
|---|
| 206 | clearTimeout(timeoutHandle);
|
|---|
| 207 | clearInterval(intervalHandle);
|
|---|
| 208 | callback();
|
|---|
| 209 | }
|
|---|
| 210 | };
|
|---|
| 211 | }
|
|---|
| 212 |
|
|---|
| 213 | // This is for `asap.js` only.
|
|---|
| 214 | // Its name will be periodically randomized to break any code that depends on
|
|---|
| 215 | // its existence.
|
|---|
| 216 | rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer;
|
|---|
| 217 |
|
|---|
| 218 | // ASAP was originally a nextTick shim included in Q. This was factored out
|
|---|
| 219 | // into this ASAP package. It was later adapted to RSVP which made further
|
|---|
| 220 | // amendments. These decisions, particularly to marginalize MessageChannel and
|
|---|
| 221 | // to capture the MutationObserver implementation in a closure, were integrated
|
|---|
| 222 | // back into ASAP proper.
|
|---|
| 223 | // https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js
|
|---|