nexttick.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // Copyright 2013 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Provides a function to schedule running a function as soon
  16. * as possible after the current JS execution stops and yields to the event
  17. * loop.
  18. *
  19. */
  20. goog.provide('goog.async.nextTick');
  21. goog.provide('goog.async.throwException');
  22. goog.require('goog.debug.entryPointRegistry');
  23. goog.require('goog.dom.TagName');
  24. goog.require('goog.functions');
  25. goog.require('goog.labs.userAgent.browser');
  26. goog.require('goog.labs.userAgent.engine');
  27. /**
  28. * Throw an item without interrupting the current execution context. For
  29. * example, if processing a group of items in a loop, sometimes it is useful
  30. * to report an error while still allowing the rest of the batch to be
  31. * processed.
  32. * @param {*} exception
  33. */
  34. goog.async.throwException = function(exception) {
  35. // Each throw needs to be in its own context.
  36. goog.global.setTimeout(function() { throw exception; }, 0);
  37. };
  38. /**
  39. * Fires the provided callbacks as soon as possible after the current JS
  40. * execution context. setTimeout(…, 0) takes at least 4ms when called from
  41. * within another setTimeout(…, 0) for legacy reasons.
  42. *
  43. * This will not schedule the callback as a microtask (i.e. a task that can
  44. * preempt user input or networking callbacks). It is meant to emulate what
  45. * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
  46. * behavior, use {@see goog.Promise} instead.
  47. *
  48. * @param {function(this:SCOPE)} callback Callback function to fire as soon as
  49. * possible.
  50. * @param {SCOPE=} opt_context Object in whose scope to call the listener.
  51. * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
  52. * ensures correctness at the cost of speed. See comments for details.
  53. * @template SCOPE
  54. */
  55. goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
  56. var cb = callback;
  57. if (opt_context) {
  58. cb = goog.bind(callback, opt_context);
  59. }
  60. cb = goog.async.nextTick.wrapCallback_(cb);
  61. // Note we do allow callers to also request setImmediate if they are willing
  62. // to accept the possible tradeoffs of incorrectness in exchange for speed.
  63. // The IE fallback of readystate change is much slower. See useSetImmediate_
  64. // for details.
  65. if (goog.isFunction(goog.global.setImmediate) &&
  66. (opt_useSetImmediate || goog.async.nextTick.useSetImmediate_())) {
  67. goog.global.setImmediate(cb);
  68. return;
  69. }
  70. // Look for and cache the custom fallback version of setImmediate.
  71. if (!goog.async.nextTick.setImmediate_) {
  72. goog.async.nextTick.setImmediate_ =
  73. goog.async.nextTick.getSetImmediateEmulator_();
  74. }
  75. goog.async.nextTick.setImmediate_(cb);
  76. };
  77. /**
  78. * Returns whether should use setImmediate implementation currently on window.
  79. *
  80. * window.setImmediate was introduced and currently only supported by IE10+,
  81. * but due to a bug in the implementation it is not guaranteed that
  82. * setImmediate is faster than setTimeout nor that setImmediate N is before
  83. * setImmediate N+1. That is why we do not use the native version if
  84. * available. We do, however, call setImmediate if it is a non-native function
  85. * because that indicates that it has been replaced by goog.testing.MockClock
  86. * which we do want to support.
  87. * See
  88. * http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
  89. *
  90. * @return {boolean} Whether to use the implementation of setImmediate defined
  91. * on Window.
  92. * @private
  93. */
  94. goog.async.nextTick.useSetImmediate_ = function() {
  95. // Not a browser environment.
  96. if (!goog.global.Window || !goog.global.Window.prototype) {
  97. return true;
  98. }
  99. // MS Edge has window.setImmediate natively, but it's not on Window.prototype.
  100. // Also, there's no clean way to detect if the goog.global.setImmediate has
  101. // been replaced by mockClock as its replacement also shows up as "[native
  102. // code]" when using toString. Therefore, just always use
  103. // goog.global.setImmediate for Edge. It's unclear if it suffers the same
  104. // issues as IE10/11, but based on
  105. // https://dev.modern.ie/testdrive/demos/setimmediatesorting/
  106. // it seems they've been working to ensure it's WAI.
  107. if (goog.labs.userAgent.browser.isEdge() ||
  108. goog.global.Window.prototype.setImmediate != goog.global.setImmediate) {
  109. // Something redefined setImmediate in which case we decide to use it (This
  110. // is so that we use the mockClock setImmediate).
  111. return true;
  112. }
  113. return false;
  114. };
  115. /**
  116. * Cache for the setImmediate implementation.
  117. * @type {function(function())}
  118. * @private
  119. */
  120. goog.async.nextTick.setImmediate_;
  121. /**
  122. * Determines the best possible implementation to run a function as soon as
  123. * the JS event loop is idle.
  124. * @return {function(function())} The "setImmediate" implementation.
  125. * @private
  126. */
  127. goog.async.nextTick.getSetImmediateEmulator_ = function() {
  128. // Create a private message channel and use it to postMessage empty messages
  129. // to ourselves.
  130. /** @type {!Function|undefined} */
  131. var Channel = goog.global['MessageChannel'];
  132. // If MessageChannel is not available and we are in a browser, implement
  133. // an iframe based polyfill in browsers that have postMessage and
  134. // document.addEventListener. The latter excludes IE8 because it has a
  135. // synchronous postMessage implementation.
  136. if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
  137. window.postMessage && window.addEventListener &&
  138. // Presto (The old pre-blink Opera engine) has problems with iframes
  139. // and contentWindow.
  140. !goog.labs.userAgent.engine.isPresto()) {
  141. /** @constructor */
  142. Channel = function() {
  143. // Make an empty, invisible iframe.
  144. var iframe = /** @type {!HTMLIFrameElement} */ (
  145. document.createElement(String(goog.dom.TagName.IFRAME)));
  146. iframe.style.display = 'none';
  147. iframe.src = '';
  148. document.documentElement.appendChild(iframe);
  149. var win = iframe.contentWindow;
  150. var doc = win.document;
  151. doc.open();
  152. doc.write('');
  153. doc.close();
  154. // Do not post anything sensitive over this channel, as the workaround for
  155. // pages with file: origin could allow that information to be modified or
  156. // intercepted.
  157. var message = 'callImmediate' + Math.random();
  158. // The same origin policy rejects attempts to postMessage from file: urls
  159. // unless the origin is '*'.
  160. // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
  161. var origin = win.location.protocol == 'file:' ?
  162. '*' :
  163. win.location.protocol + '//' + win.location.host;
  164. var onmessage = goog.bind(function(e) {
  165. // Validate origin and message to make sure that this message was
  166. // intended for us. If the origin is set to '*' (see above) only the
  167. // message needs to match since, for example, '*' != 'file://'. Allowing
  168. // the wildcard is ok, as we are not concerned with security here.
  169. if ((origin != '*' && e.origin != origin) || e.data != message) {
  170. return;
  171. }
  172. this['port1'].onmessage();
  173. }, this);
  174. win.addEventListener('message', onmessage, false);
  175. this['port1'] = {};
  176. this['port2'] = {
  177. postMessage: function() { win.postMessage(message, origin); }
  178. };
  179. };
  180. }
  181. if (typeof Channel !== 'undefined' && !goog.labs.userAgent.browser.isIE()) {
  182. // Exclude all of IE due to
  183. // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
  184. // which allows starving postMessage with a busy setTimeout loop.
  185. // This currently affects IE10 and IE11 which would otherwise be able
  186. // to use the postMessage based fallbacks.
  187. var channel = new Channel();
  188. // Use a fifo linked list to call callbacks in the right order.
  189. var head = {};
  190. var tail = head;
  191. channel['port1'].onmessage = function() {
  192. if (goog.isDef(head.next)) {
  193. head = head.next;
  194. var cb = head.cb;
  195. head.cb = null;
  196. cb();
  197. }
  198. };
  199. return function(cb) {
  200. tail.next = {cb: cb};
  201. tail = tail.next;
  202. channel['port2'].postMessage(0);
  203. };
  204. }
  205. // Implementation for IE6 to IE10: Script elements fire an asynchronous
  206. // onreadystatechange event when inserted into the DOM.
  207. if (typeof document !== 'undefined' &&
  208. 'onreadystatechange' in
  209. document.createElement(String(goog.dom.TagName.SCRIPT))) {
  210. return function(cb) {
  211. var script = document.createElement(String(goog.dom.TagName.SCRIPT));
  212. script.onreadystatechange = function() {
  213. // Clean up and call the callback.
  214. script.onreadystatechange = null;
  215. script.parentNode.removeChild(script);
  216. script = null;
  217. cb();
  218. cb = null;
  219. };
  220. document.documentElement.appendChild(script);
  221. };
  222. }
  223. // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
  224. // or more.
  225. // NOTE(user): This fallback is used for IE11.
  226. return function(cb) {
  227. goog.global.setTimeout(/** @type {function()} */ (cb), 0);
  228. };
  229. };
  230. /**
  231. * Helper function that is overrided to protect callbacks with entry point
  232. * monitor if the application monitors entry points.
  233. * @param {function()} callback Callback function to fire as soon as possible.
  234. * @return {function()} The wrapped callback.
  235. * @private
  236. */
  237. goog.async.nextTick.wrapCallback_ = goog.functions.identity;
  238. // Register the callback function as an entry point, so that it can be
  239. // monitored for exception handling, etc. This has to be done in this file
  240. // since it requires special code to handle all browsers.
  241. goog.debug.entryPointRegistry.register(
  242. /**
  243. * @param {function(!Function): !Function} transformer The transforming
  244. * function.
  245. */
  246. function(transformer) { goog.async.nextTick.wrapCallback_ = transformer; });