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. 13module.exports = rawAsap; 14function 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 23var queue = []; 24// Once a flush has been requested, no further calls to `requestFlush` are 25// necessary until the next `flush` completes. 26var 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. 30var 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. 34var 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. 38var 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. 46function 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 */ 83var scope = typeof global !== "undefined" ? global : self; 84var 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 97if (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. 136rawAsap.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". 140function 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 191function 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. 216rawAsap.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 224