266 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // 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.dom.TagName');
 | |
| 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);
 | |
|   // 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. See useSetImmediate_
 | |
|   // for details.
 | |
|   if (goog.isFunction(goog.global.setImmediate) &&
 | |
|       (opt_useSetImmediate || goog.async.nextTick.useSetImmediate_())) {
 | |
|     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);
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Returns whether should use setImmediate implementation currently on window.
 | |
|  *
 | |
|  * 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 non-native 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
 | |
|  *
 | |
|  * @return {boolean} Whether to use the implementation of setImmediate defined
 | |
|  *     on Window.
 | |
|  * @private
 | |
|  */
 | |
| goog.async.nextTick.useSetImmediate_ = function() {
 | |
|   // Not a browser environment.
 | |
|   if (!goog.global.Window || !goog.global.Window.prototype) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // MS Edge has window.setImmediate natively, but it's not on Window.prototype.
 | |
|   // Also, there's no clean way to detect if the goog.global.setImmediate has
 | |
|   // been replaced by mockClock as its replacement also shows up as "[native
 | |
|   // code]" when using toString. Therefore, just always use
 | |
|   // goog.global.setImmediate for Edge. It's unclear if it suffers the same
 | |
|   // issues as IE10/11, but based on
 | |
|   // https://dev.modern.ie/testdrive/demos/setimmediatesorting/
 | |
|   // it seems they've been working to ensure it's WAI.
 | |
|   if (goog.labs.userAgent.browser.isEdge() ||
 | |
|       goog.global.Window.prototype.setImmediate != goog.global.setImmediate) {
 | |
|     // Something redefined setImmediate in which case we decide to use it (This
 | |
|     // is so that we use the mockClock setImmediate).
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|   /** @type {!Function|undefined} */
 | |
|   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 = /** @type {!HTMLIFrameElement} */ (
 | |
|           document.createElement(String(goog.dom.TagName.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 '*'.
 | |
|       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 to IE10: Script elements fire an asynchronous
 | |
|   // onreadystatechange event when inserted into the DOM.
 | |
|   if (typeof document !== 'undefined' &&
 | |
|       'onreadystatechange' in
 | |
|           document.createElement(String(goog.dom.TagName.SCRIPT))) {
 | |
|     return function(cb) {
 | |
|       var script = document.createElement(String(goog.dom.TagName.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.
 | |
|   // NOTE(user): This fallback is used for IE11.
 | |
|   return function(cb) {
 | |
|     goog.global.setTimeout(/** @type {function()} */ (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; });
 |