browser-raw.js 9.4 KB

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