| // Copyright 2013 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /** |
| * @fileoverview Provides a function to schedule running a function as soon |
| * as possible after the current JS execution stops and yields to the event |
| * loop. |
| * |
| */ |
| |
| goog.provide('goog.async.nextTick'); |
| goog.provide('goog.async.throwException'); |
| |
| goog.require('goog.debug.entryPointRegistry'); |
| goog.require('goog.functions'); |
| goog.require('goog.labs.userAgent.browser'); |
| goog.require('goog.labs.userAgent.engine'); |
| |
| |
| /** |
| * Throw an item without interrupting the current execution context. For |
| * example, if processing a group of items in a loop, sometimes it is useful |
| * to report an error while still allowing the rest of the batch to be |
| * processed. |
| * @param {*} exception |
| */ |
| goog.async.throwException = function(exception) { |
| // Each throw needs to be in its own context. |
| goog.global.setTimeout(function() { throw exception; }, 0); |
| }; |
| |
| |
| /** |
| * Fires the provided callbacks as soon as possible after the current JS |
| * execution context. setTimeout(…, 0) takes at least 4ms when called from |
| * within another setTimeout(…, 0) for legacy reasons. |
| * |
| * This will not schedule the callback as a microtask (i.e. a task that can |
| * preempt user input or networking callbacks). It is meant to emulate what |
| * setTimeout(_, 0) would do if it were not throttled. If you desire microtask |
| * behavior, use {@see goog.Promise} instead. |
| * |
| * @param {function(this:SCOPE)} callback Callback function to fire as soon as |
| * possible. |
| * @param {SCOPE=} opt_context Object in whose scope to call the listener. |
| * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that |
| * ensures correctness at the cost of speed. See comments for details. |
| * @template SCOPE |
| */ |
| goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) { |
| var cb = callback; |
| if (opt_context) { |
| cb = goog.bind(callback, opt_context); |
| } |
| cb = goog.async.nextTick.wrapCallback_(cb); |
| // window.setImmediate was introduced and currently only supported by IE10+, |
| // but due to a bug in the implementation it is not guaranteed that |
| // setImmediate is faster than setTimeout nor that setImmediate N is before |
| // setImmediate N+1. That is why we do not use the native version if |
| // available. We do, however, call setImmediate if it is a normal function |
| // because that indicates that it has been replaced by goog.testing.MockClock |
| // which we do want to support. |
| // See |
| // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10 |
| // |
| // Note we do allow callers to also request setImmediate if they are willing |
| // to accept the possible tradeoffs of incorrectness in exchange for speed. |
| // The IE fallback of readystate change is much slower. |
| if (goog.isFunction(goog.global.setImmediate) && |
| // Opt in. |
| (opt_useSetImmediate || |
| // or it isn't a browser or the environment is weird |
| !goog.global.Window || !goog.global.Window.prototype || |
| // or something redefined setImmediate in which case we (YOLO) decide |
| // to use it (This is so that we use the mockClock setImmediate. sigh). |
| goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) { |
| goog.global.setImmediate(cb); |
| return; |
| } |
| |
| // Look for and cache the custom fallback version of setImmediate. |
| if (!goog.async.nextTick.setImmediate_) { |
| goog.async.nextTick.setImmediate_ = |
| goog.async.nextTick.getSetImmediateEmulator_(); |
| } |
| goog.async.nextTick.setImmediate_(cb); |
| }; |
| |
| |
| /** |
| * Cache for the setImmediate implementation. |
| * @type {function(function())} |
| * @private |
| */ |
| goog.async.nextTick.setImmediate_; |
| |
| |
| /** |
| * Determines the best possible implementation to run a function as soon as |
| * the JS event loop is idle. |
| * @return {function(function())} The "setImmediate" implementation. |
| * @private |
| */ |
| goog.async.nextTick.getSetImmediateEmulator_ = function() { |
| // Create a private message channel and use it to postMessage empty messages |
| // to ourselves. |
| var Channel = goog.global['MessageChannel']; |
| // If MessageChannel is not available and we are in a browser, implement |
| // an iframe based polyfill in browsers that have postMessage and |
| // document.addEventListener. The latter excludes IE8 because it has a |
| // synchronous postMessage implementation. |
| if (typeof Channel === 'undefined' && typeof window !== 'undefined' && |
| window.postMessage && window.addEventListener && |
| // Presto (The old pre-blink Opera engine) has problems with iframes |
| // and contentWindow. |
| !goog.labs.userAgent.engine.isPresto()) { |
| /** @constructor */ |
| Channel = function() { |
| // Make an empty, invisible iframe. |
| var iframe = document.createElement('iframe'); |
| iframe.style.display = 'none'; |
| iframe.src = ''; |
| document.documentElement.appendChild(iframe); |
| var win = iframe.contentWindow; |
| var doc = win.document; |
| doc.open(); |
| doc.write(''); |
| doc.close(); |
| // Do not post anything sensitive over this channel, as the workaround for |
| // pages with file: origin could allow that information to be modified or |
| // intercepted. |
| var message = 'callImmediate' + Math.random(); |
| // The same origin policy rejects attempts to postMessage from file: urls |
| // unless the origin is '*'. |
| // TODO(b/16335441): Use '*' origin for data: and other similar protocols. |
| var origin = win.location.protocol == 'file:' ? |
| '*' : win.location.protocol + '//' + win.location.host; |
| var onmessage = goog.bind(function(e) { |
| // Validate origin and message to make sure that this message was |
| // intended for us. If the origin is set to '*' (see above) only the |
| // message needs to match since, for example, '*' != 'file://'. Allowing |
| // the wildcard is ok, as we are not concerned with security here. |
| if ((origin != '*' && e.origin != origin) || e.data != message) { |
| return; |
| } |
| this['port1'].onmessage(); |
| }, this); |
| win.addEventListener('message', onmessage, false); |
| this['port1'] = {}; |
| this['port2'] = { |
| postMessage: function() { |
| win.postMessage(message, origin); |
| } |
| }; |
| }; |
| } |
| if (typeof Channel !== 'undefined' && |
| (!goog.labs.userAgent.browser.isIE())) { |
| // Exclude all of IE due to |
| // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ |
| // which allows starving postMessage with a busy setTimeout loop. |
| // This currently affects IE10 and IE11 which would otherwise be able |
| // to use the postMessage based fallbacks. |
| var channel = new Channel(); |
| // Use a fifo linked list to call callbacks in the right order. |
| var head = {}; |
| var tail = head; |
| channel['port1'].onmessage = function() { |
| if (goog.isDef(head.next)) { |
| head = head.next; |
| var cb = head.cb; |
| head.cb = null; |
| cb(); |
| } |
| }; |
| return function(cb) { |
| tail.next = { |
| cb: cb |
| }; |
| tail = tail.next; |
| channel['port2'].postMessage(0); |
| }; |
| } |
| // Implementation for IE6+: Script elements fire an asynchronous |
| // onreadystatechange event when inserted into the DOM. |
| if (typeof document !== 'undefined' && 'onreadystatechange' in |
| document.createElement('script')) { |
| return function(cb) { |
| var script = document.createElement('script'); |
| script.onreadystatechange = function() { |
| // Clean up and call the callback. |
| script.onreadystatechange = null; |
| script.parentNode.removeChild(script); |
| script = null; |
| cb(); |
| cb = null; |
| }; |
| document.documentElement.appendChild(script); |
| }; |
| } |
| // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms |
| // or more. |
| return function(cb) { |
| goog.global.setTimeout(cb, 0); |
| }; |
| }; |
| |
| |
| /** |
| * Helper function that is overrided to protect callbacks with entry point |
| * monitor if the application monitors entry points. |
| * @param {function()} callback Callback function to fire as soon as possible. |
| * @return {function()} The wrapped callback. |
| * @private |
| */ |
| goog.async.nextTick.wrapCallback_ = goog.functions.identity; |
| |
| |
| // Register the callback function as an entry point, so that it can be |
| // monitored for exception handling, etc. This has to be done in this file |
| // since it requires special code to handle all browsers. |
| goog.debug.entryPointRegistry.register( |
| /** |
| * @param {function(!Function): !Function} transformer The transforming |
| * function. |
| */ |
| function(transformer) { |
| goog.async.nextTick.wrapCallback_ = transformer; |
| }); |