# lib/goog/async/nexttick.js

    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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @fileoverview Provides a function to schedule running a function as soon17 * as possible after the current JS execution stops and yields to the event18 * loop.19 *20 */2122goog.provide('goog.async.nextTick');23goog.provide('goog.async.throwException');2425goog.require('goog.debug.entryPointRegistry');26goog.require('goog.functions');27goog.require('goog.labs.userAgent.browser');282930/**31 * Throw an item without interrupting the current execution context.  For32 * example, if processing a group of items in a loop, sometimes it is useful33 * to report an error while still allowing the rest of the batch to be34 * processed.35 * @param {*} exception36 */37goog.async.throwException = function(exception) {38  // Each throw needs to be in its own context.39  goog.global.setTimeout(function() { throw exception; }, 0);40};414243/**44 * Fires the provided callbacks as soon as possible after the current JS45 * execution context. setTimeout(…, 0) takes at least 4ms when called from46 * within another setTimeout(…, 0) for legacy reasons.47 *48 * This will not schedule the callback as a microtask (i.e. a task that can49 * preempt user input or networking callbacks). It is meant to emulate what50 * setTimeout(_, 0) would do if it were not throttled. If you desire microtask51 * behavior, use {@see goog.Promise} instead.52 *53 * @param {function(this:SCOPE)} callback Callback function to fire as soon as54 *     possible.55 * @param {SCOPE=} opt_context Object in whose scope to call the listener.56 * @template SCOPE57 */58goog.async.nextTick = function(callback, opt_context) {59  var cb = callback;60  if (opt_context) {61    cb = goog.bind(callback, opt_context);62  }63  cb = goog.async.nextTick.wrapCallback_(cb);64  // window.setImmediate was introduced and currently only supported by IE10+,65  // but due to a bug in the implementation it is not guaranteed that66  // setImmediate is faster than setTimeout nor that setImmediate N is before67  // setImmediate N+1. That is why we do not use the native version if68  // available. We do, however, call setImmediate if it is a normal function69  // because that indicates that it has been replaced by goog.testing.MockClock70  // which we do want to support.71  // See72  // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie1073  if (goog.isFunction(goog.global.setImmediate) && (!goog.global.Window ||74      goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) {75    goog.global.setImmediate(cb);76    return;77  }78  // Look for and cache the custom fallback version of setImmediate.79  if (!goog.async.nextTick.setImmediate_) {80    goog.async.nextTick.setImmediate_ =81        goog.async.nextTick.getSetImmediateEmulator_();82  }83  goog.async.nextTick.setImmediate_(cb);84};858687/**88 * Cache for the setImmediate implementation.89 * @type {function(function())}90 * @private91 */92goog.async.nextTick.setImmediate_;939495/**96 * Determines the best possible implementation to run a function as soon as97 * the JS event loop is idle.98 * @return {function(function())} The "setImmediate" implementation.99 * @private100 */101goog.async.nextTick.getSetImmediateEmulator_ = function() {102  // Create a private message channel and use it to postMessage empty messages103  // to ourselves.104  var Channel = goog.global['MessageChannel'];105  // If MessageChannel is not available and we are in a browser, implement106  // an iframe based polyfill in browsers that have postMessage and107  // document.addEventListener. The latter excludes IE8 because it has a108  // synchronous postMessage implementation.109  if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&110      window.postMessage && window.addEventListener) {111    /** @constructor */112    Channel = function() {113      // Make an empty, invisible iframe.114      var iframe = document.createElement('iframe');115      iframe.style.display = 'none';116      iframe.src = '';117      document.documentElement.appendChild(iframe);118      var win = iframe.contentWindow;119      var doc = win.document;120      doc.open();121      doc.write('');122      doc.close();123      // Do not post anything sensitive over this channel, as the workaround for124      // pages with file: origin could allow that information to be modified or125      // intercepted.126      var message = 'callImmediate' + Math.random();127      // The same origin policy rejects attempts to postMessage from file: urls128      // unless the origin is '*'.129      // TODO(b/16335441): Use '*' origin for data: and other similar protocols.130      var origin = win.location.protocol == 'file:' ?131          '*' : win.location.protocol + '//' + win.location.host;132      var onmessage = goog.bind(function(e) {133        // Validate origin and message to make sure that this message was134        // intended for us.135        if (e.origin != origin && e.data != message) {136          return;137        }138        this['port1'].onmessage();139      }, this);140      win.addEventListener('message', onmessage, false);141      this['port1'] = {};142      this['port2'] = {143        postMessage: function() {144          win.postMessage(message, origin);145        }146      };147    };148  }149  if (typeof Channel !== 'undefined' &&150      // Exclude all of IE due to151      // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/152      // which allows starving postMessage with a busy setTimeout loop.153      // This currently affects IE10 and IE11 which would otherwise be able154      // to use the postMessage based fallbacks.155      !goog.labs.userAgent.browser.isIE()) {156    var channel = new Channel();157    // Use a fifo linked list to call callbacks in the right order.158    var head = {};159    var tail = head;160    channel['port1'].onmessage = function() {161      head = head.next;162      var cb = head.cb;163      head.cb = null;164      cb();165    };166    return function(cb) {167      tail.next = {168        cb: cb169      };170      tail = tail.next;171      channel['port2'].postMessage(0);172    };173  }174  // Implementation for IE6+: Script elements fire an asynchronous175  // onreadystatechange event when inserted into the DOM.176  if (typeof document !== 'undefined' && 'onreadystatechange' in177      document.createElement('script')) {178    return function(cb) {179      var script = document.createElement('script');180      script.onreadystatechange = function() {181        // Clean up and call the callback.182        script.onreadystatechange = null;183        script.parentNode.removeChild(script);184        script = null;185        cb();186        cb = null;187      };188      document.documentElement.appendChild(script);189    };190  }191  // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms192  // or more.193  return function(cb) {194    goog.global.setTimeout(cb, 0);195  };196};197198199/**200 * Helper function that is overrided to protect callbacks with entry point201 * monitor if the application monitors entry points.202 * @param {function()} callback Callback function to fire as soon as possible.203 * @return {function()} The wrapped callback.204 * @private205 */206goog.async.nextTick.wrapCallback_ = goog.functions.identity;207208209// Register the callback function as an entry point, so that it can be210// monitored for exception handling, etc. This has to be done in this file211// since it requires special code to handle all browsers.212goog.debug.entryPointRegistry.register(213    /**214     * @param {function(!Function): !Function} transformer The transforming215     *     function.216     */217    function(transformer) {218      goog.async.nextTick.wrapCallback_ = transformer;219    });