# 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 |
22 | goog.provide('goog.async.nextTick');
23 | goog.provide('goog.async.throwException');
24 |
25 | goog.require('goog.debug.entryPointRegistry');
26 | goog.require('goog.dom.TagName');
27 | goog.require('goog.functions');
28 | goog.require('goog.labs.userAgent.browser');
29 | goog.require('goog.labs.userAgent.engine');
30 |
31 |
32 | /**
33 | * Throw an item without interrupting the current execution context. For
34 | * example, if processing a group of items in a loop, sometimes it is useful
35 | * to report an error while still allowing the rest of the batch to be
36 | * processed.
37 | * @param {*} exception
38 | */
39 | goog.async.throwException = function(exception) {
40 | // Each throw needs to be in its own context.
41 | goog.global.setTimeout(function() { throw exception; }, 0);
42 | };
43 |
44 |
45 | /**
46 | * Fires the provided callbacks as soon as possible after the current JS
47 | * execution context. setTimeout(…, 0) takes at least 4ms when called from
48 | * within another setTimeout(…, 0) for legacy reasons.
49 | *
50 | * This will not schedule the callback as a microtask (i.e. a task that can
51 | * preempt user input or networking callbacks). It is meant to emulate what
52 | * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
53 | * behavior, use {@see goog.Promise} instead.
54 | *
55 | * @param {function(this:SCOPE)} callback Callback function to fire as soon as
56 | * possible.
57 | * @param {SCOPE=} opt_context Object in whose scope to call the listener.
58 | * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
59 | * ensures correctness at the cost of speed. See comments for details.
60 | * @template SCOPE
61 | */
62 | goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
63 | var cb = callback;
64 | if (opt_context) {
65 | cb = goog.bind(callback, opt_context);
66 | }
67 | cb = goog.async.nextTick.wrapCallback_(cb);
68 | // window.setImmediate was introduced and currently only supported by IE10+,
69 | // but due to a bug in the implementation it is not guaranteed that
70 | // setImmediate is faster than setTimeout nor that setImmediate N is before
71 | // setImmediate N+1. That is why we do not use the native version if
72 | // available. We do, however, call setImmediate if it is a normal function
73 | // because that indicates that it has been replaced by goog.testing.MockClock
74 | // which we do want to support.
75 | // See
76 | // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
77 | //
78 | // Note we do allow callers to also request setImmediate if they are willing
79 | // to accept the possible tradeoffs of incorrectness in exchange for speed.
80 | // The IE fallback of readystate change is much slower.
81 | if (goog.isFunction(goog.global.setImmediate) &&
82 | // Opt in.
83 | (opt_useSetImmediate ||
84 | // or it isn't a browser or the environment is weird
85 | !goog.global.Window || !goog.global.Window.prototype ||
86 | // or something redefined setImmediate in which case we (YOLO) decide
87 | // to use it (This is so that we use the mockClock setImmediate. sigh).
88 | goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) {
89 | goog.global.setImmediate(cb);
90 | return;
91 | }
92 |
93 | // Look for and cache the custom fallback version of setImmediate.
94 | if (!goog.async.nextTick.setImmediate_) {
95 | goog.async.nextTick.setImmediate_ =
96 | goog.async.nextTick.getSetImmediateEmulator_();
97 | }
98 | goog.async.nextTick.setImmediate_(cb);
99 | };
100 |
101 |
102 | /**
103 | * Cache for the setImmediate implementation.
104 | * @type {function(function())}
105 | * @private
106 | */
107 | goog.async.nextTick.setImmediate_;
108 |
109 |
110 | /**
111 | * Determines the best possible implementation to run a function as soon as
112 | * the JS event loop is idle.
113 | * @return {function(function())} The "setImmediate" implementation.
114 | * @private
115 | */
116 | goog.async.nextTick.getSetImmediateEmulator_ = function() {
117 | // Create a private message channel and use it to postMessage empty messages
118 | // to ourselves.
119 | var Channel = goog.global['MessageChannel'];
120 | // If MessageChannel is not available and we are in a browser, implement
121 | // an iframe based polyfill in browsers that have postMessage and
122 | // document.addEventListener. The latter excludes IE8 because it has a
123 | // synchronous postMessage implementation.
124 | if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
125 | window.postMessage && window.addEventListener &&
126 | // Presto (The old pre-blink Opera engine) has problems with iframes
127 | // and contentWindow.
128 | !goog.labs.userAgent.engine.isPresto()) {
129 | /** @constructor */
130 | Channel = function() {
131 | // Make an empty, invisible iframe.
132 | var iframe = document.createElement(goog.dom.TagName.IFRAME);
133 | iframe.style.display = 'none';
134 | iframe.src = '';
135 | document.documentElement.appendChild(iframe);
136 | var win = iframe.contentWindow;
137 | var doc = win.document;
138 | doc.open();
139 | doc.write('');
140 | doc.close();
141 | // Do not post anything sensitive over this channel, as the workaround for
142 | // pages with file: origin could allow that information to be modified or
143 | // intercepted.
144 | var message = 'callImmediate' + Math.random();
145 | // The same origin policy rejects attempts to postMessage from file: urls
146 | // unless the origin is '*'.
147 | // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
148 | var origin = win.location.protocol == 'file:' ?
149 | '*' : win.location.protocol + '//' + win.location.host;
150 | var onmessage = goog.bind(function(e) {
151 | // Validate origin and message to make sure that this message was
152 | // intended for us. If the origin is set to '*' (see above) only the
153 | // message needs to match since, for example, '*' != 'file://'. Allowing
154 | // the wildcard is ok, as we are not concerned with security here.
155 | if ((origin != '*' && e.origin != origin) || e.data != message) {
156 | return;
157 | }
158 | this['port1'].onmessage();
159 | }, this);
160 | win.addEventListener('message', onmessage, false);
161 | this['port1'] = {};
162 | this['port2'] = {
163 | postMessage: function() {
164 | win.postMessage(message, origin);
165 | }
166 | };
167 | };
168 | }
169 | if (typeof Channel !== 'undefined' &&
170 | (!goog.labs.userAgent.browser.isIE())) {
171 | // Exclude all of IE due to
172 | // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
173 | // which allows starving postMessage with a busy setTimeout loop.
174 | // This currently affects IE10 and IE11 which would otherwise be able
175 | // to use the postMessage based fallbacks.
176 | var channel = new Channel();
177 | // Use a fifo linked list to call callbacks in the right order.
178 | var head = {};
179 | var tail = head;
180 | channel['port1'].onmessage = function() {
181 | if (goog.isDef(head.next)) {
182 | head = head.next;
183 | var cb = head.cb;
184 | head.cb = null;
185 | cb();
186 | }
187 | };
188 | return function(cb) {
189 | tail.next = {
190 | cb: cb
191 | };
192 | tail = tail.next;
193 | channel['port2'].postMessage(0);
194 | };
195 | }
196 | // Implementation for IE6+: Script elements fire an asynchronous
197 | // onreadystatechange event when inserted into the DOM.
198 | if (typeof document !== 'undefined' && 'onreadystatechange' in
199 | document.createElement(goog.dom.TagName.SCRIPT)) {
200 | return function(cb) {
201 | var script = document.createElement(goog.dom.TagName.SCRIPT);
202 | script.onreadystatechange = function() {
203 | // Clean up and call the callback.
204 | script.onreadystatechange = null;
205 | script.parentNode.removeChild(script);
206 | script = null;
207 | cb();
208 | cb = null;
209 | };
210 | document.documentElement.appendChild(script);
211 | };
212 | }
213 | // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
214 | // or more.
215 | return function(cb) {
216 | goog.global.setTimeout(cb, 0);
217 | };
218 | };
219 |
220 |
221 | /**
222 | * Helper function that is overrided to protect callbacks with entry point
223 | * monitor if the application monitors entry points.
224 | * @param {function()} callback Callback function to fire as soon as possible.
225 | * @return {function()} The wrapped callback.
226 | * @private
227 | */
228 | goog.async.nextTick.wrapCallback_ = goog.functions.identity;
229 |
230 |
231 | // Register the callback function as an entry point, so that it can be
232 | // monitored for exception handling, etc. This has to be done in this file
233 | // since it requires special code to handle all browsers.
234 | goog.debug.entryPointRegistry.register(
235 | /**
236 | * @param {function(!Function): !Function} transformer The transforming
237 | * function.
238 | */
239 | function(transformer) {
240 | goog.async.nextTick.wrapCallback_ = transformer;
241 | });