| 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 | }); |