Path: blob/trunk/third_party/closure/goog/async/nexttick.js
2868 views
// Copyright 2013 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Provides a function to schedule running a function as soon16* as possible after the current JS execution stops and yields to the event17* loop.18*19*/2021goog.provide('goog.async.nextTick');22goog.provide('goog.async.throwException');2324goog.require('goog.debug.entryPointRegistry');25goog.require('goog.dom.TagName');26goog.require('goog.functions');27goog.require('goog.labs.userAgent.browser');28goog.require('goog.labs.userAgent.engine');293031/**32* Throw an item without interrupting the current execution context. For33* example, if processing a group of items in a loop, sometimes it is useful34* to report an error while still allowing the rest of the batch to be35* processed.36* @param {*} exception37*/38goog.async.throwException = function(exception) {39// Each throw needs to be in its own context.40goog.global.setTimeout(function() { throw exception; }, 0);41};424344/**45* Fires the provided callbacks as soon as possible after the current JS46* execution context. setTimeout(…, 0) takes at least 4ms when called from47* within another setTimeout(…, 0) for legacy reasons.48*49* This will not schedule the callback as a microtask (i.e. a task that can50* preempt user input or networking callbacks). It is meant to emulate what51* setTimeout(_, 0) would do if it were not throttled. If you desire microtask52* behavior, use {@see goog.Promise} instead.53*54* @param {function(this:SCOPE)} callback Callback function to fire as soon as55* possible.56* @param {SCOPE=} opt_context Object in whose scope to call the listener.57* @param {boolean=} opt_useSetImmediate Avoid the IE workaround that58* ensures correctness at the cost of speed. See comments for details.59* @template SCOPE60*/61goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {62var cb = callback;63if (opt_context) {64cb = goog.bind(callback, opt_context);65}66cb = goog.async.nextTick.wrapCallback_(cb);67// Note we do allow callers to also request setImmediate if they are willing68// to accept the possible tradeoffs of incorrectness in exchange for speed.69// The IE fallback of readystate change is much slower. See useSetImmediate_70// for details.71if (goog.isFunction(goog.global.setImmediate) &&72(opt_useSetImmediate || goog.async.nextTick.useSetImmediate_())) {73goog.global.setImmediate(cb);74return;75}7677// Look for and cache the custom fallback version of setImmediate.78if (!goog.async.nextTick.setImmediate_) {79goog.async.nextTick.setImmediate_ =80goog.async.nextTick.getSetImmediateEmulator_();81}82goog.async.nextTick.setImmediate_(cb);83};848586/**87* Returns whether should use setImmediate implementation currently on window.88*89* window.setImmediate was introduced and currently only supported by IE10+,90* but due to a bug in the implementation it is not guaranteed that91* setImmediate is faster than setTimeout nor that setImmediate N is before92* setImmediate N+1. That is why we do not use the native version if93* available. We do, however, call setImmediate if it is a non-native function94* because that indicates that it has been replaced by goog.testing.MockClock95* which we do want to support.96* See97* http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie1098*99* @return {boolean} Whether to use the implementation of setImmediate defined100* on Window.101* @private102*/103goog.async.nextTick.useSetImmediate_ = function() {104// Not a browser environment.105if (!goog.global.Window || !goog.global.Window.prototype) {106return true;107}108109// MS Edge has window.setImmediate natively, but it's not on Window.prototype.110// Also, there's no clean way to detect if the goog.global.setImmediate has111// been replaced by mockClock as its replacement also shows up as "[native112// code]" when using toString. Therefore, just always use113// goog.global.setImmediate for Edge. It's unclear if it suffers the same114// issues as IE10/11, but based on115// https://dev.modern.ie/testdrive/demos/setimmediatesorting/116// it seems they've been working to ensure it's WAI.117if (goog.labs.userAgent.browser.isEdge() ||118goog.global.Window.prototype.setImmediate != goog.global.setImmediate) {119// Something redefined setImmediate in which case we decide to use it (This120// is so that we use the mockClock setImmediate).121return true;122}123124return false;125};126127128/**129* Cache for the setImmediate implementation.130* @type {function(function())}131* @private132*/133goog.async.nextTick.setImmediate_;134135136/**137* Determines the best possible implementation to run a function as soon as138* the JS event loop is idle.139* @return {function(function())} The "setImmediate" implementation.140* @private141*/142goog.async.nextTick.getSetImmediateEmulator_ = function() {143// Create a private message channel and use it to postMessage empty messages144// to ourselves.145/** @type {!Function|undefined} */146var Channel = goog.global['MessageChannel'];147// If MessageChannel is not available and we are in a browser, implement148// an iframe based polyfill in browsers that have postMessage and149// document.addEventListener. The latter excludes IE8 because it has a150// synchronous postMessage implementation.151if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&152window.postMessage && window.addEventListener &&153// Presto (The old pre-blink Opera engine) has problems with iframes154// and contentWindow.155!goog.labs.userAgent.engine.isPresto()) {156/** @constructor */157Channel = function() {158// Make an empty, invisible iframe.159var iframe = /** @type {!HTMLIFrameElement} */ (160document.createElement(String(goog.dom.TagName.IFRAME)));161iframe.style.display = 'none';162iframe.src = '';163document.documentElement.appendChild(iframe);164var win = iframe.contentWindow;165var doc = win.document;166doc.open();167doc.write('');168doc.close();169// Do not post anything sensitive over this channel, as the workaround for170// pages with file: origin could allow that information to be modified or171// intercepted.172var message = 'callImmediate' + Math.random();173// The same origin policy rejects attempts to postMessage from file: urls174// unless the origin is '*'.175// TODO(b/16335441): Use '*' origin for data: and other similar protocols.176var origin = win.location.protocol == 'file:' ?177'*' :178win.location.protocol + '//' + win.location.host;179var onmessage = goog.bind(function(e) {180// Validate origin and message to make sure that this message was181// intended for us. If the origin is set to '*' (see above) only the182// message needs to match since, for example, '*' != 'file://'. Allowing183// the wildcard is ok, as we are not concerned with security here.184if ((origin != '*' && e.origin != origin) || e.data != message) {185return;186}187this['port1'].onmessage();188}, this);189win.addEventListener('message', onmessage, false);190this['port1'] = {};191this['port2'] = {192postMessage: function() { win.postMessage(message, origin); }193};194};195}196if (typeof Channel !== 'undefined' && !goog.labs.userAgent.browser.isIE()) {197// Exclude all of IE due to198// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/199// which allows starving postMessage with a busy setTimeout loop.200// This currently affects IE10 and IE11 which would otherwise be able201// to use the postMessage based fallbacks.202var channel = new Channel();203// Use a fifo linked list to call callbacks in the right order.204var head = {};205var tail = head;206channel['port1'].onmessage = function() {207if (goog.isDef(head.next)) {208head = head.next;209var cb = head.cb;210head.cb = null;211cb();212}213};214return function(cb) {215tail.next = {cb: cb};216tail = tail.next;217channel['port2'].postMessage(0);218};219}220// Implementation for IE6 to IE10: Script elements fire an asynchronous221// onreadystatechange event when inserted into the DOM.222if (typeof document !== 'undefined' &&223'onreadystatechange' in224document.createElement(String(goog.dom.TagName.SCRIPT))) {225return function(cb) {226var script = document.createElement(String(goog.dom.TagName.SCRIPT));227script.onreadystatechange = function() {228// Clean up and call the callback.229script.onreadystatechange = null;230script.parentNode.removeChild(script);231script = null;232cb();233cb = null;234};235document.documentElement.appendChild(script);236};237}238// Fall back to setTimeout with 0. In browsers this creates a delay of 5ms239// or more.240// NOTE(user): This fallback is used for IE11.241return function(cb) {242goog.global.setTimeout(/** @type {function()} */ (cb), 0);243};244};245246247/**248* Helper function that is overrided to protect callbacks with entry point249* monitor if the application monitors entry points.250* @param {function()} callback Callback function to fire as soon as possible.251* @return {function()} The wrapped callback.252* @private253*/254goog.async.nextTick.wrapCallback_ = goog.functions.identity;255256257// Register the callback function as an entry point, so that it can be258// monitored for exception handling, etc. This has to be done in this file259// since it requires special code to handle all browsers.260goog.debug.entryPointRegistry.register(261/**262* @param {function(!Function): !Function} transformer The transforming263* function.264*/265function(transformer) { goog.async.nextTick.wrapCallback_ = transformer; });266267268