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.functions'); |
27 | goog.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 | */ |
37 | goog.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 | */ |
58 | goog.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 | */ |
92 | goog.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 | */ |
101 | goog.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 | */ |
206 | goog.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. |
212 | goog.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 | }); |