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