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 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/**
16 * @fileoverview Provides a function to schedule running a function as soon
17 * as possible after the current JS execution stops and yields to the event
18 * loop.
19 *
20 */
21
22goog.provide('goog.async.nextTick');
23goog.provide('goog.async.throwException');
24
25goog.require('goog.debug.entryPointRegistry');
26goog.require('goog.functions');
27goog.require('goog.labs.userAgent.browser');
28
29
30/**
31 * Throw an item without interrupting the current execution context. For
32 * example, if processing a group of items in a loop, sometimes it is useful
33 * to report an error while still allowing the rest of the batch to be
34 * processed.
35 * @param {*} exception
36 */
37goog.async.throwException = function(exception) {
38 // Each throw needs to be in its own context.
39 goog.global.setTimeout(function() { throw exception; }, 0);
40};
41
42
43/**
44 * Fires the provided callbacks as soon as possible after the current JS
45 * execution context. setTimeout(…, 0) takes at least 4ms when called from
46 * within another setTimeout(…, 0) for legacy reasons.
47 *
48 * This will not schedule the callback as a microtask (i.e. a task that can
49 * preempt user input or networking callbacks). It is meant to emulate what
50 * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
51 * behavior, use {@see goog.Promise} instead.
52 *
53 * @param {function(this:SCOPE)} callback Callback function to fire as soon as
54 * possible.
55 * @param {SCOPE=} opt_context Object in whose scope to call the listener.
56 * @template SCOPE
57 */
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 that
66 // setImmediate is faster than setTimeout nor that setImmediate N is before
67 // setImmediate N+1. That is why we do not use the native version if
68 // available. We do, however, call setImmediate if it is a normal function
69 // because that indicates that it has been replaced by goog.testing.MockClock
70 // which we do want to support.
71 // See
72 // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
73 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};
85
86
87/**
88 * Cache for the setImmediate implementation.
89 * @type {function(function())}
90 * @private
91 */
92goog.async.nextTick.setImmediate_;
93
94
95/**
96 * Determines the best possible implementation to run a function as soon as
97 * the JS event loop is idle.
98 * @return {function(function())} The "setImmediate" implementation.
99 * @private
100 */
101goog.async.nextTick.getSetImmediateEmulator_ = function() {
102 // Create a private message channel and use it to postMessage empty messages
103 // to ourselves.
104 var Channel = goog.global['MessageChannel'];
105 // If MessageChannel is not available and we are in a browser, implement
106 // an iframe based polyfill in browsers that have postMessage and
107 // document.addEventListener. The latter excludes IE8 because it has a
108 // 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 for
124 // pages with file: origin could allow that information to be modified or
125 // intercepted.
126 var message = 'callImmediate' + Math.random();
127 // The same origin policy rejects attempts to postMessage from file: urls
128 // 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 was
134 // 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 to
151 // 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 able
154 // 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: cb
169 };
170 tail = tail.next;
171 channel['port2'].postMessage(0);
172 };
173 }
174 // Implementation for IE6+: Script elements fire an asynchronous
175 // onreadystatechange event when inserted into the DOM.
176 if (typeof document !== 'undefined' && 'onreadystatechange' in
177 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 5ms
192 // or more.
193 return function(cb) {
194 goog.global.setTimeout(cb, 0);
195 };
196};
197
198
199/**
200 * Helper function that is overrided to protect callbacks with entry point
201 * 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 * @private
205 */
206goog.async.nextTick.wrapCallback_ = goog.functions.identity;
207
208
209// Register the callback function as an entry point, so that it can be
210// monitored for exception handling, etc. This has to be done in this file
211// since it requires special code to handle all browsers.
212goog.debug.entryPointRegistry.register(
213 /**
214 * @param {function(!Function): !Function} transformer The transforming
215 * function.
216 */
217 function(transformer) {
218 goog.async.nextTick.wrapCallback_ = transformer;
219 });