# lib/webdriver/promise.js

    1// Copyright 2011 Software Freedom Conservancy. 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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @license Portions of this code are from the Dojo toolkit, received under the17 * BSD License:18 * Redistribution and use in source and binary forms, with or without19 * modification, are permitted provided that the following conditions are met:20 *21 *   * Redistributions of source code must retain the above copyright notice,22 *     this list of conditions and the following disclaimer.23 *   * Redistributions in binary form must reproduce the above copyright notice,24 *     this list of conditions and the following disclaimer in the documentation25 *     and/or other materials provided with the distribution.26 *   * Neither the name of the Dojo Foundation nor the names of its contributors27 *     may be used to endorse or promote products derived from this software28 *     without specific prior written permission.29 *30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"31 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE33 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE34 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR35 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF36 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS37 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN38 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)39 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE40 * POSSIBILITY OF SUCH DAMAGE.41 */4243/**44 * @fileoverview A promise implementation based on the CommonJS promise/A and45 * promise/B proposals. For more information, see46 * http://wiki.commonjs.org/wiki/Promises.47 */4849goog.provide('webdriver.promise');50goog.provide('webdriver.promise.ControlFlow');51goog.provide('webdriver.promise.ControlFlow.Timer');52goog.provide('webdriver.promise.Deferred');53goog.provide('webdriver.promise.Promise');5455goog.require('goog.array');56goog.require('goog.debug.Error');57goog.require('goog.object');58goog.require('webdriver.EventEmitter');59goog.require('webdriver.stacktrace.Snapshot');60616263/**64 * Represents the eventual value of a completed operation. Each promise may be65 * in one of three states: pending, resolved, or rejected. Each promise starts66 * in the pending state and may make a single transition to either a67 * fulfilled or failed state.68 *69 * <p/>This class is based on the Promise/A proposal from CommonJS. Additional70 * functions are provided for API compatibility with Dojo Deferred objects.71 *72 * @constructor73 * @see http://wiki.commonjs.org/wiki/Promises/A74 */75webdriver.promise.Promise = function() {76};777879/**80 * Cancels the computation of this promise's value, rejecting the promise in the81 * process.82 * @param {*} reason The reason this promise is being cancelled. If not an83 *     {@code Error}, one will be created using the value's string84 *     representation.85 */86webdriver.promise.Promise.prototype.cancel = function(reason) {87  throw new TypeError('Unimplemented function: "cancel"');88};899091/** @return {boolean} Whether this promise's value is still being computed. */92webdriver.promise.Promise.prototype.isPending = function() {93  throw new TypeError('Unimplemented function: "isPending"');94};959697/**98 * Registers listeners for when this instance is resolved. This function most99 * overridden by subtypes.100 *101 * @param {Function=} opt_callback The function to call if this promise is102 *     successfully resolved. The function should expect a single argument: the103 *     promise's resolved value.104 * @param {Function=} opt_errback The function to call if this promise is105 *     rejected. The function should expect a single argument: the rejection106 *     reason.107 * @return {!webdriver.promise.Promise} A new promise which will be resolved108 *     with the result of the invoked callback.109 */110webdriver.promise.Promise.prototype.then = function(111    opt_callback, opt_errback) {112  throw new TypeError('Unimplemented function: "then"');113};114115116/**117 * Registers a listener for when this promise is rejected. This is synonymous118 * with the {@code catch} clause in a synchronous API:119 * <pre><code>120 *   // Synchronous API:121 *   try {122 *     doSynchronousWork();123 *   } catch (ex) {124 *     console.error(ex);125 *   }126 *127 *   // Asynchronous promise API:128 *   doAsynchronousWork().thenCatch(function(ex) {129 *     console.error(ex);130 *   });131 * </code></pre>132 *133 * @param {!Function} errback The function to call if this promise is134 *     rejected. The function should expect a single argument: the rejection135 *     reason.136 * @return {!webdriver.promise.Promise} A new promise which will be resolved137 *     with the result of the invoked callback.138 */139webdriver.promise.Promise.prototype.thenCatch = function(errback) {140  return this.then(null, errback);141};142143144/**145 * Registers a listener to invoke when this promise is resolved, regardless146 * of whether the promise's value was successfully computed. This function147 * is synonymous with the {@code finally} clause in a synchronous API:148 * <pre><code>149 *   // Synchronous API:150 *   try {151 *     doSynchronousWork();152 *   } finally {153 *     cleanUp();154 *   }155 *156 *   // Asynchronous promise API:157 *   doAsynchronousWork().thenFinally(cleanUp);158 * </code></pre>159 *160 * <b>Note:</b> similar to the {@code finally} clause, if the registered161 * callback returns a rejected promise or throws an error, it will silently162 * replace the rejection error (if any) from this promise:163 * <pre><code>164 *   try {165 *     throw Error('one');166 *   } finally {167 *     throw Error('two');  // Hides Error: one168 *   }169 *170 *   webdriver.promise.rejected(Error('one'))171 *       .thenFinally(function() {172 *         throw Error('two');  // Hides Error: one173 *       });174 * </code></pre>175 *176 *177 * @param callback178 * @returns {!webdriver.promise.Promise}179 */180webdriver.promise.Promise.prototype.thenFinally = function(callback) {181  return this.then(callback, callback);182};183184185/**186 * Registers a function to be invoked when this promise is successfully187 * resolved. This function is provided for backwards compatibility with the188 * Dojo Deferred API.189 *190 * @param {Function} callback The function to call if this promise is191 *     successfully resolved. The function should expect a single argument: the192 *     promise's resolved value.193 * @param {!Object=} opt_self The object which |this| should refer to when the194 *     function is invoked.195 * @return {!webdriver.promise.Promise} A new promise which will be resolved196 *     with the result of the invoked callback.197 * @deprecated Use {@link #then()} instead.198 */199webdriver.promise.Promise.prototype.addCallback = function(callback, opt_self) {200  return this.then(goog.bind(callback, opt_self));201};202203204/**205 * Registers a function to be invoked when this promise is rejected.206 * This function is provided for backwards compatibility with the207 * Dojo Deferred API.208 *209 * @param {Function} errback The function to call if this promise is210 *     rejected. The function should expect a single argument: the rejection211 *     reason.212 * @param {!Object=} opt_self The object which |this| should refer to when the213 *     function is invoked.214 * @return {!webdriver.promise.Promise} A new promise which will be resolved215 *     with the result of the invoked callback.216 * @deprecated Use {@link #thenCatch()} instead.217 */218webdriver.promise.Promise.prototype.addErrback = function(errback, opt_self) {219  return this.thenCatch(goog.bind(errback, opt_self));220};221222223/**224 * Registers a function to be invoked when this promise is either rejected or225 * resolved. This function is provided for backwards compatibility with the226 * Dojo Deferred API.227 *228 * @param {Function} callback The function to call when this promise is229 *     either resolved or rejected. The function should expect a single230 *     argument: the resolved value or rejection error.231 * @param {!Object=} opt_self The object which |this| should refer to when the232 *     function is invoked.233 * @return {!webdriver.promise.Promise} A new promise which will be resolved234 *     with the result of the invoked callback.235 * @deprecated Use {@link #thenFinally()} instead.236 */237webdriver.promise.Promise.prototype.addBoth = function(callback, opt_self) {238  return this.thenFinally(goog.bind(callback, opt_self));239};240241242/**243 * An alias for {@code webdriver.promise.Promise.prototype.then} that permits244 * the scope of the invoked function to be specified. This function is provided245 * for backwards compatibility with the Dojo Deferred API.246 *247 * @param {Function} callback The function to call if this promise is248 *     successfully resolved. The function should expect a single argument: the249 *     promise's resolved value.250 * @param {Function} errback The function to call if this promise is251 *     rejected. The function should expect a single argument: the rejection252 *     reason.253 * @param {!Object=} opt_self The object which |this| should refer to when the254 *     function is invoked.255 * @return {!webdriver.promise.Promise} A new promise which will be resolved256 *     with the result of the invoked callback.257 * @deprecated Use {@link #then()} instead.258 */259webdriver.promise.Promise.prototype.addCallbacks = function(260    callback, errback, opt_self) {261  return this.then(goog.bind(callback, opt_self),262      goog.bind(errback, opt_self));263};264265266267/**268 * Represents a value that will be resolved at some point in the future. This269 * class represents the protected "producer" half of a Promise - each Deferred270 * has a {@code promise} property that may be returned to consumers for271 * registering callbacks, reserving the ability to resolve the deferred to the272 * producer.273 *274 * <p>If this Deferred is rejected and there are no listeners registered before275 * the next turn of the event loop, the rejection will be passed to the276 * {@link webdriver.promise.ControlFlow} as an unhandled failure.277 *278 * <p>If this Deferred is cancelled, the cancellation reason will be forward to279 * the Deferred's canceller function (if provided). The canceller may return a280 * truth-y value to override the reason provided for rejection.281 *282 * @param {Function=} opt_canceller Function to call when cancelling the283 *     computation of this instance's value.284 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow285 *     this instance was created under. This should only be provided during286 *     unit tests.287 * @constructor288 * @extends {webdriver.promise.Promise}289 */290webdriver.promise.Deferred = function(opt_canceller, opt_flow) {291  /* NOTE: This class's implementation diverges from the prototypical style292   * used in the rest of the atoms library. This was done intentionally to293   * protect the internal Deferred state from consumers, as outlined by294   *     http://wiki.commonjs.org/wiki/Promises295   */296  goog.base(this);297298  var flow = opt_flow || webdriver.promise.controlFlow();299300  /**301   * The listeners registered with this Deferred. Each element in the list will302   * be a 3-tuple of the callback function, errback function, and the303   * corresponding deferred object.304   * @type {!Array.<!webdriver.promise.Deferred.Listener_>}305   */306  var listeners = [];307308  /**309   * Whether this Deferred's resolution was ever handled by a listener.310   * If the Deferred is rejected and its value is not handled by a listener311   * before the next turn of the event loop, the error will be passed to the312   * global error handler.313   * @type {boolean}314   */315  var handled = false;316317  /**318   * Key for the timeout used to delay reproting an unhandled rejection to the319   * parent {@link webdriver.promise.ControlFlow}.320   * @type {?number}321   */322  var pendingRejectionKey = null;323324  /**325   * This Deferred's current state.326   * @type {!webdriver.promise.Deferred.State_}327   */328  var state = webdriver.promise.Deferred.State_.PENDING;329330  /**331   * This Deferred's resolved value; set when the state transitions from332   * {@code webdriver.promise.Deferred.State_.PENDING}.333   * @type {*}334   */335  var value;336337  /** @return {boolean} Whether this promise's value is still pending. */338  function isPending() {339    return state == webdriver.promise.Deferred.State_.PENDING;340  }341342  /**343   * Removes all of the listeners previously registered on this deferred.344   * @throws {Error} If this deferred has already been resolved.345   */346  function removeAll() {347    listeners = [];348  }349350  /**351   * Resolves this deferred. If the new value is a promise, this function will352   * wait for it to be resolved before notifying the registered listeners.353   * @param {!webdriver.promise.Deferred.State_} newState The deferred's new354   *     state.355   * @param {*} newValue The deferred's new value.356   */357  function resolve(newState, newValue) {358    if (webdriver.promise.Deferred.State_.PENDING !== state) {359      return;360    }361362    state = webdriver.promise.Deferred.State_.BLOCKED;363364    if (webdriver.promise.isPromise(newValue) && newValue !== self) {365      var onFulfill = goog.partial(notifyAll, newState);366      var onReject = goog.partial(367          notifyAll, webdriver.promise.Deferred.State_.REJECTED);368      if (newValue instanceof webdriver.promise.Deferred) {369        newValue.then(onFulfill, onReject);370      } else {371        webdriver.promise.asap(newValue, onFulfill, onReject);372      }373374    } else {375      notifyAll(newState, newValue);376    }377  }378379  /**380   * Notifies all of the listeners registered with this Deferred that its state381   * has changed.382   * @param {!webdriver.promise.Deferred.State_} newState The deferred's new383   *     state.384   * @param {*} newValue The deferred's new value.385   */386  function notifyAll(newState, newValue) {387    if (newState === webdriver.promise.Deferred.State_.REJECTED &&388        // We cannot check instanceof Error since the object may have been389        // created in a different JS context.390        goog.isObject(newValue) && goog.isString(newValue.message)) {391      newValue = flow.annotateError(/** @type {!Error} */(newValue));392    }393394    state = newState;395    value = newValue;396    while (listeners.length) {397      notify(listeners.shift());398    }399400    if (!handled && state == webdriver.promise.Deferred.State_.REJECTED) {401      pendingRejectionKey = propagateError(value);402    }403  }404405  /**406   * Propagates an unhandled rejection to the parent ControlFlow in a407   * future turn of the JavaScript event loop.408   * @param {*} error The error value to report.409   * @return {number} The key for the registered timeout.410   */411  function propagateError(error) {412    flow.pendingRejections_ += 1;413    return flow.timer.setTimeout(function() {414      flow.pendingRejections_ -= 1;415      flow.abortFrame_(error);416    }, 0);417  }418419  /**420   * Notifies a single listener of this Deferred's change in state.421   * @param {!webdriver.promise.Deferred.Listener_} listener The listener to422   *     notify.423   */424  function notify(listener) {425    var func = state == webdriver.promise.Deferred.State_.RESOLVED ?426        listener.callback : listener.errback;427    if (func) {428      flow.runInNewFrame_(goog.partial(func, value),429          listener.fulfill, listener.reject);430    } else if (state == webdriver.promise.Deferred.State_.REJECTED) {431      listener.reject(value);432    } else {433      listener.fulfill(value);434    }435  }436437  /**438   * The consumer promise for this instance. Provides protected access to the439   * callback registering functions.440   * @type {!webdriver.promise.Promise}441   */442  var promise = new webdriver.promise.Promise();443444  /**445   * Registers a callback on this Deferred.446   * @param {Function=} opt_callback The callback.447   * @param {Function=} opt_errback The errback.448   * @return {!webdriver.promise.Promise} A new promise representing the result449   *     of the callback.450   * @see webdriver.promise.Promise#then451   */452  function then(opt_callback, opt_errback) {453    // Avoid unnecessary allocations if we weren't given any callback functions.454    if (!opt_callback && !opt_errback) {455      return promise;456    }457458    // The moment a listener is registered, we consider this deferred to be459    // handled; the callback must handle any rejection errors.460    handled = true;461    if (pendingRejectionKey) {462      flow.pendingRejections_ -= 1;463      flow.timer.clearTimeout(pendingRejectionKey);464    }465466    var deferred = new webdriver.promise.Deferred(cancel, flow);467    var listener = {468      callback: opt_callback,469      errback: opt_errback,470      fulfill: deferred.fulfill,471      reject: deferred.reject472    };473474    if (state == webdriver.promise.Deferred.State_.PENDING ||475        state == webdriver.promise.Deferred.State_.BLOCKED) {476      listeners.push(listener);477    } else {478      notify(listener);479    }480481    return deferred.promise;482  }483484  var self = this;485486  /**487   * Resolves this promise with the given value. If the value is itself a488   * promise and not a reference to this deferred, this instance will wait for489   * it before resolving.490   * @param {*=} opt_value The resolved value.491   */492  function fulfill(opt_value) {493    resolve(webdriver.promise.Deferred.State_.RESOLVED, opt_value);494  }495496  /**497   * Rejects this promise. If the error is itself a promise, this instance will498   * be chained to it and be rejected with the error's resolved value.499   * @param {*=} opt_error The rejection reason, typically either a500   *     {@code Error} or a {@code string}.501   */502  function reject(opt_error) {503    resolve(webdriver.promise.Deferred.State_.REJECTED, opt_error);504  }505506  /**507   * Attempts to cancel the computation of this instance's value. This attempt508   * will silently fail if this instance has already resolved.509   * @param {*=} opt_reason The reason for cancelling this promise.510   */511  function cancel(opt_reason) {512    if (!isPending()) {513      return;514    }515516    if (opt_canceller) {517      opt_reason = opt_canceller(opt_reason) || opt_reason;518    }519520    reject(opt_reason);521  }522523  this.promise = promise;524  this.promise.then = this.then = then;525  this.promise.cancel = this.cancel = cancel;526  this.promise.isPending = this.isPending = isPending;527  this.fulfill = fulfill;528  this.reject = this.errback = reject;529530  // Only expose this function to our internal classes.531  // TODO: find a cleaner way of handling this.532  if (this instanceof webdriver.promise.Task_) {533    this.removeAll = removeAll;534  }535536  // Export symbols necessary for the contract on this object to work in537  // compiled mode.538  goog.exportProperty(this, 'then', this.then);539  goog.exportProperty(this, 'cancel', cancel);540  goog.exportProperty(this, 'fulfill', fulfill);541  goog.exportProperty(this, 'reject', reject);542  goog.exportProperty(this, 'isPending', isPending);543  goog.exportProperty(this, 'promise', this.promise);544  goog.exportProperty(this.promise, 'then', this.then);545  goog.exportProperty(this.promise, 'cancel', cancel);546  goog.exportProperty(this.promise, 'isPending', isPending);547};548goog.inherits(webdriver.promise.Deferred, webdriver.promise.Promise);549550551/**552 * Type definition for a listener registered on a Deferred object.553 * @typedef {{callback:(Function|undefined),554 *            errback:(Function|undefined),555 *            fulfill: function(*), reject: function(*)}}556 * @private557 */558webdriver.promise.Deferred.Listener_;559560561/**562 * The three states a {@link webdriver.promise.Deferred} object may be in.563 * @enum {number}564 * @private565 */566webdriver.promise.Deferred.State_ = {567  REJECTED: -1,568  PENDING: 0,569  BLOCKED: 1,570  RESOLVED: 2571};572573574/**575 * Tests if a value is an Error-like object. This is more than an straight576 * instanceof check since the value may originate from another context.577 * @param {*} value The value to test.578 * @return {boolean} Whether the value is an error.579 * @private580 */581webdriver.promise.isError_ = function(value) {582  return value instanceof Error ||583      goog.isObject(value) &&584      (Object.prototype.toString.call(value) === '[object Error]' ||585       // A special test for goog.testing.JsUnitException.586       value.isJsUnitException);587588};589590591/**592 * Determines whether a {@code value} should be treated as a promise.593 * Any object whose "then" property is a function will be considered a promise.594 *595 * @param {*} value The value to test.596 * @return {boolean} Whether the value is a promise.597 */598webdriver.promise.isPromise = function(value) {599  return !!value && goog.isObject(value) &&600      // Use array notation so the Closure compiler does not obfuscate away our601      // contract.602      goog.isFunction(value['then']);603};604605606/**607 * Creates a promise that will be resolved at a set time in the future.608 * @param {number} ms The amount of time, in milliseconds, to wait before609 *     resolving the promise.610 * @return {!webdriver.promise.Promise} The promise.611 */612webdriver.promise.delayed = function(ms) {613  var timer = webdriver.promise.controlFlow().timer;614  var key;615  var deferred = new webdriver.promise.Deferred(function() {616    timer.clearTimeout(key);617  });618  key = timer.setTimeout(deferred.fulfill, ms);619  return deferred.promise;620};621622623/**624 * Creates a new deferred object.625 * @param {Function=} opt_canceller Function to call when cancelling the626 *     computation of this instance's value.627 * @return {!webdriver.promise.Deferred} The new deferred object.628 */629webdriver.promise.defer = function(opt_canceller) {630  return new webdriver.promise.Deferred(opt_canceller);631};632633634/**635 * Creates a promise that has been resolved with the given value.636 * @param {*=} opt_value The resolved value.637 * @return {!webdriver.promise.Promise} The resolved promise.638 */639webdriver.promise.fulfilled = function(opt_value) {640  if (opt_value instanceof webdriver.promise.Promise) {641    return opt_value;642  }643  var deferred = new webdriver.promise.Deferred();644  deferred.fulfill(opt_value);645  return deferred.promise;646};647648649/**650 * Creates a promise that has been rejected with the given reason.651 * @param {*=} opt_reason The rejection reason; may be any value, but is652 *     usually an Error or a string.653 * @return {!webdriver.promise.Promise} The rejected promise.654 */655webdriver.promise.rejected = function(opt_reason) {656  var deferred = new webdriver.promise.Deferred();657  deferred.reject(opt_reason);658  return deferred.promise;659};660661662/**663 * Wraps a function that is assumed to be a node-style callback as its final664 * argument. This callback takes two arguments: an error value (which will be665 * null if the call succeeded), and the success value as the second argument.666 * If the call fails, the returned promise will be rejected, otherwise it will667 * be resolved with the result.668 * @param {!Function} fn The function to wrap.669 * @return {!webdriver.promise.Promise} A promise that will be resolved with the670 *     result of the provided function's callback.671 */672webdriver.promise.checkedNodeCall = function(fn) {673  var deferred = new webdriver.promise.Deferred(function() {674    throw Error('This Deferred may not be cancelled');675  });676  try {677    fn(function(error, value) {678      error ? deferred.reject(error) : deferred.fulfill(value);679    });680  } catch (ex) {681    deferred.reject(ex);682  }683  return deferred.promise;684};685686687/**688 * Registers an observer on a promised {@code value}, returning a new promise689 * that will be resolved when the value is. If {@code value} is not a promise,690 * then the return promise will be immediately resolved.691 * @param {*} value The value to observe.692 * @param {Function=} opt_callback The function to call when the value is693 *     resolved successfully.694 * @param {Function=} opt_errback The function to call when the value is695 *     rejected.696 * @return {!webdriver.promise.Promise} A new promise.697 */698webdriver.promise.when = function(value, opt_callback, opt_errback) {699  if (value instanceof webdriver.promise.Promise) {700    return value.then(opt_callback, opt_errback);701  }702703  var deferred = new webdriver.promise.Deferred();704705  webdriver.promise.asap(value, deferred.fulfill, deferred.reject);706707  return deferred.then(opt_callback, opt_errback);708};709710711/**712 * Invokes the appropriate callback function as soon as a promised713 * {@code value} is resolved. This function is similar to714 * {@link webdriver.promise.when}, except it does not return a new promise.715 * @param {*} value The value to observe.716 * @param {Function} callback The function to call when the value is717 *     resolved successfully.718 * @param {Function=} opt_errback The function to call when the value is719 *     rejected.720 */721webdriver.promise.asap = function(value, callback, opt_errback) {722  if (webdriver.promise.isPromise(value)) {723    value.then(callback, opt_errback);724725  // Maybe a Dojo-like deferred object?726  } else if (!!value && goog.isObject(value) &&727      goog.isFunction(value.addCallbacks)) {728    value.addCallbacks(callback, opt_errback);729730  // A raw value, return a resolved promise.731  } else if (callback) {732    callback(value);733  }734};735736737/**738 * Given an array of promises, will return a promise that will be fulfilled739 * with the fulfillment values of the input array's values. If any of the740 * input array's promises are rejected, the returned promise will be rejected741 * with the same reason.742 *743 * @param {!Array.<(T|!webdriver.promise.Promise.<T>)>} arr An array of744 *     promises to wait on.745 * @return {!webdriver.promise.Promise.<!Array.<T>>} A promise that is746 *     fulfilled with an array containing the fulfilled values of the747 *     input array, or rejected with the same reason as the first748 *     rejected value.749 * @template T750 */751webdriver.promise.all = function(arr) {752  var n = arr.length;753  if (!n) {754    return webdriver.promise.fulfilled([]);755  }756757  var toFulfill = n;758  var result = webdriver.promise.defer();759  var values = [];760761  var onFulfill = function(index, value) {762    values[index] = value;763    toFulfill--;764    if (toFulfill == 0) {765      result.fulfill(values);766    }767  };768769  for (var i = 0; i < n; ++i) {770    webdriver.promise.asap(771        arr[i], goog.partial(onFulfill, i), result.reject);772  }773774  return result.promise;775};776777778/**779 * Calls a function for each element in an array and inserts the result into a780 * new array, which is used as the fulfillment value of the promise returned781 * by this function.782 *783 * <p>If the return value of the mapping function is a promise, this function784 * will wait for it to be fulfilled before inserting it into the new array.785 *786 * <p>If the mapping function throws or returns a rejected promise, the787 * promise returned by this function will be rejected with the same reason.788 * Only the first failure will be reported; all subsequent errors will be789 * silently ignored.790 *791 * @param {!(Array.<TYPE>|webdriver.promise.Promise.<!Array.<TYPE>>)} arr The792 *     array to iterator over, or a promise that will resolve to said array.793 * @param {function(this: SELF, TYPE, number, !Array.<TYPE>): ?} fn The794 *     function to call for each element in the array. This function should795 *     expect three arguments (the element, the index, and the array itself.796 * @param {SELF=} opt_self The object to be used as the value of 'this' within797 *     {@code fn}.798 * @template TYPE, SELF799 */800webdriver.promise.map = function(arr, fn, opt_self) {801  return webdriver.promise.when(arr, function(arr) {802    var result = goog.array.map(arr, fn, opt_self);803    return webdriver.promise.all(result);804  });805};806807808/**809 * Calls a function for each element in an array, and if the function returns810 * true adds the element to a new array.811 *812 * <p>If the return value of the filter function is a promise, this function813 * will wait for it to be fulfilled before determining whether to insert the814 * element into the new array.815 *816 * <p>If the filter function throws or returns a rejected promise, the promise817 * returned by this function will be rejected with the same reason. Only the818 * first failure will be reported; all subsequent errors will be silently819 * ignored.820 *821 * @param {!(Array.<TYPE>|webdriver.promise.Promise.<!Array.<TYPE>>)} arr The822 *     array to iterator over, or a promise that will resolve to said array.823 * @param {function(this: SELF, TYPE, number, !Array.<TYPE>): (824 *             boolean|webdriver.promise.Promise.<boolean>)} fn The function825 *     to call for each element in the array.826 * @param {SELF=} opt_self The object to be used as the value of 'this' within827 *     {@code fn}.828 * @template TYPE, SELF829 */830webdriver.promise.filter = function(arr, fn, opt_self) {831  return webdriver.promise.when(arr, function(arr) {832    var originalValues = goog.array.clone(arr);833    return webdriver.promise.map(arr, fn, opt_self).then(function(include) {834      return goog.array.filter(originalValues, function(value, index) {835        return include[index];836      });837    });838  });839};840841842/**843 * Returns a promise that will be resolved with the input value in a844 * fully-resolved state. If the value is an array, each element will be fully845 * resolved. Likewise, if the value is an object, all keys will be fully846 * resolved. In both cases, all nested arrays and objects will also be847 * fully resolved.  All fields are resolved in place; the returned promise will848 * resolve on {@code value} and not a copy.849 *850 * Warning: This function makes no checks against objects that contain851 * cyclical references:852 * <pre><code>853 *   var value = {};854 *   value['self'] = value;855 *   webdriver.promise.fullyResolved(value);  // Stack overflow.856 * </code></pre>857 *858 * @param {*} value The value to fully resolve.859 * @return {!webdriver.promise.Promise} A promise for a fully resolved version860 *     of the input value.861 */862webdriver.promise.fullyResolved = function(value) {863  if (webdriver.promise.isPromise(value)) {864    return webdriver.promise.when(value, webdriver.promise.fullyResolveValue_);865  }866  return webdriver.promise.fullyResolveValue_(value);867};868869870/**871 * @param {*} value The value to fully resolve. If a promise, assumed to872 *     already be resolved.873 * @return {!webdriver.promise.Promise} A promise for a fully resolved version874 *     of the input value.875 * @private876 */877webdriver.promise.fullyResolveValue_ = function(value) {878  switch (goog.typeOf(value)) {879    case 'array':880      return webdriver.promise.fullyResolveKeys_(881          /** @type {!Array} */ (value));882883    case 'object':884      if (webdriver.promise.isPromise(value)) {885        // We get here when the original input value is a promise that886        // resolves to itself. When the user provides us with such a promise,887        // trust that it counts as a "fully resolved" value and return it.888        // Of course, since it's already a promise, we can just return it889        // to the user instead of wrapping it in another promise.890        return /** @type {!webdriver.promise.Promise} */ (value);891      }892893      if (goog.isNumber(value.nodeType) &&894          goog.isObject(value.ownerDocument) &&895          goog.isNumber(value.ownerDocument.nodeType)) {896        // DOM node; return early to avoid infinite recursion. Should we897        // only support objects with a certain level of nesting?898        return webdriver.promise.fulfilled(value);899      }900901      return webdriver.promise.fullyResolveKeys_(902          /** @type {!Object} */ (value));903904    default:  // boolean, function, null, number, string, undefined905      return webdriver.promise.fulfilled(value);906  }907};908909910/**911 * @param {!(Array|Object)} obj the object to resolve.912 * @return {!webdriver.promise.Promise} A promise that will be resolved with the913 *     input object once all of its values have been fully resolved.914 * @private915 */916webdriver.promise.fullyResolveKeys_ = function(obj) {917  var isArray = goog.isArray(obj);918  var numKeys = isArray ? obj.length : goog.object.getCount(obj);919  if (!numKeys) {920    return webdriver.promise.fulfilled(obj);921  }922923  var numResolved = 0;924  var deferred = new webdriver.promise.Deferred();925926  // In pre-IE9, goog.array.forEach will not iterate properly over arrays927  // containing undefined values because "index in array" returns false928  // when array[index] === undefined (even for x = [undefined, 1]). To get929  // around this, we need to use our own forEach implementation.930  // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be931  // reproduced in IE9 by changing the browser/document modes, it requires an932  // actual pre-IE9 browser.  Yay, IE!933  var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) {934    var n = arr.length;935    for (var i = 0; i < n; ++i) {936      fn.call(null, arr[i], i, arr);937    }938  };939940  forEachKey(obj, function(partialValue, key) {941    var type = goog.typeOf(partialValue);942    if (type != 'array' && type != 'object') {943      maybeResolveValue();944      return;945    }946947    webdriver.promise.fullyResolved(partialValue).then(948        function(resolvedValue) {949          obj[key] = resolvedValue;950          maybeResolveValue();951        },952        deferred.reject);953  });954955  return deferred.promise;956957  function maybeResolveValue() {958    if (++numResolved == numKeys) {959      deferred.fulfill(obj);960    }961  }962};963964965//////////////////////////////////////////////////////////////////////////////966//967//  webdriver.promise.ControlFlow968//969//////////////////////////////////////////////////////////////////////////////970971972973/**974 * Handles the execution of scheduled tasks, each of which may be an975 * asynchronous operation. The control flow will ensure tasks are executed in976 * the ordered scheduled, starting each task only once those before it have977 * completed.978 *979 * <p>Each task scheduled within this flow may return a980 * {@link webdriver.promise.Promise} to indicate it is an asynchronous981 * operation. The ControlFlow will wait for such promises to be resolved before982 * marking the task as completed.983 *984 * <p>Tasks and each callback registered on a {@link webdriver.promise.Deferred}985 * will be run in their own ControlFlow frame.  Any tasks scheduled within a986 * frame will have priority over previously scheduled tasks. Furthermore, if987 * any of the tasks in the frame fails, the remainder of the tasks in that frame988 * will be discarded and the failure will be propagated to the user through the989 * callback/task's promised result.990 *991 * <p>Each time a ControlFlow empties its task queue, it will fire an992 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Conversely,993 * whenever the flow terminates due to an unhandled error, it will remove all994 * remaining tasks in its queue and fire an995 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION} event. If996 * there are no listeners registered with the flow, the error will be997 * rethrown to the global error handler.998 *999 * @param {webdriver.promise.ControlFlow.Timer=} opt_timer The timer object1000 *     to use. Should only be set for testing.1001 * @constructor1002 * @extends {webdriver.EventEmitter}1003 */1004webdriver.promise.ControlFlow = function(opt_timer) {1005  webdriver.EventEmitter.call(this);10061007  /**1008   * The timer used by this instance.1009   * @type {webdriver.promise.ControlFlow.Timer}1010   */1011  this.timer = opt_timer || webdriver.promise.ControlFlow.defaultTimer;10121013  /**1014   * A list of recent tasks. Each time a new task is started, or a frame is1015   * completed, the previously recorded task is removed from this list. If1016   * there are multiple tasks, task N+1 is considered a sub-task of task1017   * N.1018   * @private {!Array.<!webdriver.promise.Task_>}1019   */1020  this.history_ = [];1021};1022goog.inherits(webdriver.promise.ControlFlow, webdriver.EventEmitter);102310241025/**1026 * @typedef {{clearInterval: function(number),1027 *            clearTimeout: function(number),1028 *            setInterval: function(!Function, number): number,1029 *            setTimeout: function(!Function, number): number}}1030 */1031webdriver.promise.ControlFlow.Timer;103210331034/**1035 * The default timer object, which uses the global timer functions.1036 * @type {webdriver.promise.ControlFlow.Timer}1037 */1038webdriver.promise.ControlFlow.defaultTimer = (function() {1039  // The default timer functions may be defined as free variables for the1040  // current context, so do not reference them using "window" or1041  // "goog.global".  Also, we must invoke them in a closure, and not using1042  // bind(), so we do not get "TypeError: Illegal invocation" (WebKit) or1043  // "Invalid calling object" (IE) errors.1044  return {1045    clearInterval: wrap(clearInterval),1046    clearTimeout: wrap(clearTimeout),1047    setInterval: wrap(setInterval),1048    setTimeout: wrap(setTimeout)1049  };10501051  function wrap(fn) {1052    return function() {1053      // Cannot use .call() or .apply() since we do not know which variable1054      // the function is bound to, and using the wrong one will generate1055      // an error.1056      return fn(arguments[0], arguments[1]);1057    };1058  }1059})();106010611062/**1063 * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.1064 * @enum {string}1065 */1066webdriver.promise.ControlFlow.EventType = {10671068  /** Emitted when all tasks have been successfully executed. */1069  IDLE: 'idle',10701071  /** Emitted whenever a new task has been scheduled. */1072  SCHEDULE_TASK: 'scheduleTask',10731074  /**1075   * Emitted whenever a control flow aborts due to an unhandled promise1076   * rejection. This event will be emitted along with the offending rejection1077   * reason. Upon emitting this event, the control flow will empty its task1078   * queue and revert to its initial state.1079   */1080  UNCAUGHT_EXCEPTION: 'uncaughtException'1081};108210831084/**1085 * How often, in milliseconds, the event loop should run.1086 * @type {number}1087 * @const1088 */1089webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY = 10;109010911092/**1093 * Tracks the active execution frame for this instance. Lazily initialized1094 * when the first task is scheduled.1095 * @private {webdriver.promise.Frame_}1096 */1097webdriver.promise.ControlFlow.prototype.activeFrame_ = null;109810991100/**1101 * A reference to the frame in which new tasks should be scheduled. If1102 * {@code null}, tasks will be scheduled within the active frame. When forcing1103 * a function to run in the context of a new frame, this pointer is used to1104 * ensure tasks are scheduled within the newly created frame, even though it1105 * won't be active yet.1106 * @private {webdriver.promise.Frame_}1107 * @see {#runInNewFrame_}1108 */1109webdriver.promise.ControlFlow.prototype.schedulingFrame_ = null;111011111112/**1113 * Timeout ID set when the flow is about to shutdown without any errors1114 * being detected. Upon shutting down, the flow will emit an1115 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Idle events1116 * always follow a brief timeout in order to catch latent errors from the last1117 * completed task. If this task had a callback registered, but no errback, and1118 * the task fails, the unhandled failure would not be reported by the promise1119 * system until the next turn of the event loop:1120 *1121 *   // Schedule 1 task that fails.1122 *   var result = webriver.promise.controlFlow().schedule('example',1123 *       function() { return webdriver.promise.rejected('failed'); });1124 *   // Set a callback on the result. This delays reporting the unhandled1125 *   // failure for 1 turn of the event loop.1126 *   result.then(goog.nullFunction);1127 *1128 * @private {?number}1129 */1130webdriver.promise.ControlFlow.prototype.shutdownId_ = null;113111321133/**1134 * Interval ID for this instance's event loop.1135 * @private {?number}1136 */1137webdriver.promise.ControlFlow.prototype.eventLoopId_ = null;113811391140/**1141 * The number of "pending" promise rejections.1142 *1143 * <p>Each time a promise is rejected and is not handled by a listener, it will1144 * schedule a 0-based timeout to check if it is still unrejected in the next1145 * turn of the JS-event loop. This allows listeners to attach to, and handle,1146 * the rejected promise at any point in same turn of the event loop that the1147 * promise was rejected.1148 *1149 * <p>When this flow's own event loop triggers, it will not run if there1150 * are any outstanding promise rejections. This allows unhandled promises to1151 * be reported before a new task is started, ensuring the error is reported to1152 * the current task queue.1153 *1154 * @private {number}1155 */1156webdriver.promise.ControlFlow.prototype.pendingRejections_ = 0;115711581159/**1160 * The number of aborted frames since the last time a task was executed or a1161 * frame completed successfully.1162 * @private {number}1163 */1164webdriver.promise.ControlFlow.prototype.numAbortedFrames_ = 0;116511661167/**1168 * Resets this instance, clearing its queue and removing all event listeners.1169 */1170webdriver.promise.ControlFlow.prototype.reset = function() {1171  this.activeFrame_ = null;1172  this.clearHistory();1173  this.removeAllListeners();1174  this.cancelShutdown_();1175  this.cancelEventLoop_();1176};117711781179/**1180 * Returns a summary of the recent task activity for this instance. This1181 * includes the most recently completed task, as well as any parent tasks. In1182 * the returned summary, the task at index N is considered a sub-task of the1183 * task at index N+1.1184 * @return {!Array.<string>} A summary of this instance's recent task1185 *     activity.1186 */1187webdriver.promise.ControlFlow.prototype.getHistory = function() {1188  var pendingTasks = [];1189  var currentFrame = this.activeFrame_;1190  while (currentFrame) {1191    var task = currentFrame.getPendingTask();1192    if (task) {1193      pendingTasks.push(task);1194    }1195    // A frame's parent node will always be another frame.1196    currentFrame =1197        /** @type {webdriver.promise.Frame_} */ (currentFrame.getParent());1198  }11991200  var fullHistory = goog.array.concat(this.history_, pendingTasks);1201  return goog.array.map(fullHistory, function(task) {1202    return task.toString();1203  });1204};120512061207/** Clears this instance's task history. */1208webdriver.promise.ControlFlow.prototype.clearHistory = function() {1209  this.history_ = [];1210};121112121213/**1214 * Removes a completed task from this instance's history record. If any1215 * tasks remain from aborted frames, those will be removed as well.1216 * @private1217 */1218webdriver.promise.ControlFlow.prototype.trimHistory_ = function() {1219  if (this.numAbortedFrames_) {1220    goog.array.splice(this.history_,1221        this.history_.length - this.numAbortedFrames_,1222        this.numAbortedFrames_);1223    this.numAbortedFrames_ = 0;1224  }1225  this.history_.pop();1226};122712281229/**1230 * Property used to track whether an error has been annotated by1231 * {@link webdriver.promise.ControlFlow#annotateError}.1232 * @private {string}1233 * @const1234 */1235webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_ =1236    'webdriver_promise_error_';123712381239/**1240 * Appends a summary of this instance's recent task history to the given1241 * error's stack trace. This function will also ensure the error's stack trace1242 * is in canonical form.1243 * @param {!(Error|goog.testing.JsUnitException)} e The error to annotate.1244 * @return {!(Error|goog.testing.JsUnitException)} The annotated error.1245 */1246webdriver.promise.ControlFlow.prototype.annotateError = function(e) {1247  if (!!e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_]) {1248    return e;1249  }12501251  var history = this.getHistory();1252  if (history.length) {1253    e = webdriver.stacktrace.format(e);12541255    /** @type {!Error} */(e).stack += [1256      '\n==== async task ====\n',1257      history.join('\n==== async task ====\n')1258    ].join('');12591260    e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_] = true;1261  }12621263  return e;1264};126512661267/**1268 * @return {string} The scheduled tasks still pending with this instance.1269 */1270webdriver.promise.ControlFlow.prototype.getSchedule = function() {1271  return this.activeFrame_ ? this.activeFrame_.getRoot().toString() : '[]';1272};127312741275/**1276 * Schedules a task for execution. If there is nothing currently in the1277 * queue, the task will be executed in the next turn of the event loop.1278 *1279 * @param {!Function} fn The function to call to start the task. If the1280 *     function returns a {@link webdriver.promise.Promise}, this instance1281 *     will wait for it to be resolved before starting the next task.1282 * @param {string=} opt_description A description of the task.1283 * @return {!webdriver.promise.Promise} A promise that will be resolved with1284 *     the result of the action.1285 */1286webdriver.promise.ControlFlow.prototype.execute = function(1287    fn, opt_description) {1288  this.cancelShutdown_();12891290  if (!this.activeFrame_) {1291    this.activeFrame_ = new webdriver.promise.Frame_(this);1292  }12931294  // Trim an extra frame off the generated stack trace for the call to this1295  // function.1296  var snapshot = new webdriver.stacktrace.Snapshot(1);1297  var task = new webdriver.promise.Task_(1298      this, fn, opt_description || '', snapshot);1299  var scheduleIn = this.schedulingFrame_ || this.activeFrame_;1300  scheduleIn.addChild(task);13011302  this.emit(webdriver.promise.ControlFlow.EventType.SCHEDULE_TASK);13031304  this.scheduleEventLoopStart_();1305  return task.promise;1306};130713081309/**1310 * Inserts a {@code setTimeout} into the command queue. This is equivalent to1311 * a thread sleep in a synchronous programming language.1312 *1313 * @param {number} ms The timeout delay, in milliseconds.1314 * @param {string=} opt_description A description to accompany the timeout.1315 * @return {!webdriver.promise.Promise} A promise that will be resolved with1316 *     the result of the action.1317 */1318webdriver.promise.ControlFlow.prototype.timeout = function(1319    ms, opt_description) {1320  return this.execute(function() {1321    return webdriver.promise.delayed(ms);1322  }, opt_description);1323};132413251326/**1327 * Schedules a task that shall wait for a condition to hold. Each condition1328 * function may return any value, but it will always be evaluated as a boolean.1329 *1330 * <p>Condition functions may schedule sub-tasks with this instance, however,1331 * their execution time will be factored into whether a wait has timed out.1332 *1333 * <p>In the event a condition returns a Promise, the polling loop will wait for1334 * it to be resolved before evaluating whether the condition has been satisfied.1335 * The resolution time for a promise is factored into whether a wait has timed1336 * out.1337 *1338 * <p>If the condition function throws, or returns a rejected promise, the1339 * wait task will fail.1340 *1341 * @param {!Function} condition The condition function to poll.1342 * @param {number} timeout How long to wait, in milliseconds, for the condition1343 *     to hold before timing out.1344 * @param {string=} opt_message An optional error message to include if the1345 *     wait times out; defaults to the empty string.1346 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1347 *     condition has been satisified. The promise shall be rejected if the wait1348 *     times out waiting for the condition.1349 */1350webdriver.promise.ControlFlow.prototype.wait = function(1351    condition, timeout, opt_message) {1352  var sleep = Math.min(timeout, 100);1353  var self = this;13541355  return this.execute(function() {1356    var startTime = goog.now();1357    var waitResult = new webdriver.promise.Deferred();1358    var waitFrame = self.activeFrame_;1359    waitFrame.isWaiting = true;1360    pollCondition();1361    return waitResult.promise;13621363    function pollCondition() {1364      self.runInNewFrame_(condition, function(value) {1365        var elapsed = goog.now() - startTime;1366        if (!!value) {1367          waitFrame.isWaiting = false;1368          waitResult.fulfill(value);1369        } else if (elapsed >= timeout) {1370          waitResult.reject(new Error((opt_message ? opt_message + '\n' : '') +1371              'Wait timed out after ' + elapsed + 'ms'));1372        } else {1373          self.timer.setTimeout(pollCondition, sleep);1374        }1375      }, waitResult.reject, true);1376    }1377  }, opt_message);1378};137913801381/**1382 * Schedules a task that will wait for another promise to resolve.  The resolved1383 * promise's value will be returned as the task result.1384 * @param {!webdriver.promise.Promise} promise The promise to wait on.1385 * @return {!webdriver.promise.Promise} A promise that will resolve when the1386 *     task has completed.1387 */1388webdriver.promise.ControlFlow.prototype.await = function(promise) {1389  return this.execute(function() {1390    return promise;1391  });1392};139313941395/**1396 * Schedules the interval for this instance's event loop, if necessary.1397 * @private1398 */1399webdriver.promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() {1400  if (!this.eventLoopId_) {1401    this.eventLoopId_ = this.timer.setInterval(1402        goog.bind(this.runEventLoop_, this),1403        webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY);1404  }1405};140614071408/**1409 * Cancels the event loop, if necessary.1410 * @private1411 */1412webdriver.promise.ControlFlow.prototype.cancelEventLoop_ = function() {1413  if (this.eventLoopId_) {1414    this.timer.clearInterval(this.eventLoopId_);1415    this.eventLoopId_ = null;1416  }1417};141814191420/**1421 * Executes the next task for the current frame. If the current frame has no1422 * more tasks, the frame's result will be resolved, returning control to the1423 * frame's creator. This will terminate the flow if the completed frame was at1424 * the top of the stack.1425 * @private1426 */1427webdriver.promise.ControlFlow.prototype.runEventLoop_ = function() {1428  // If we get here and there are pending promise rejections, then those1429  // promises are queued up to run as soon as this (JS) event loop terminates.1430  // Short-circuit our loop to give those promises a chance to run. Otherwise,1431  // we might start a new task only to have it fail because of one of these1432  // pending rejections.1433  if (this.pendingRejections_) {1434    return;1435  }14361437  // If the flow aborts due to an unhandled exception after we've scheduled1438  // another turn of the execution loop, we can end up in here with no tasks1439  // left. This is OK, just quietly return.1440  if (!this.activeFrame_) {1441    this.commenceShutdown_();1442    return;1443  }14441445  var task;1446  if (this.activeFrame_.getPendingTask() || !(task = this.getNextTask_())) {1447    // Either the current frame is blocked on a pending task, or we don't have1448    // a task to finish because we've completed a frame. When completing a1449    // frame, we must abort the event loop to allow the frame's promise's1450    // callbacks to execute.1451    return;1452  }14531454  var activeFrame = this.activeFrame_;1455  activeFrame.setPendingTask(task);1456  var markTaskComplete = goog.bind(function() {1457    this.history_.push(/** @type {!webdriver.promise.Task_} */ (task));1458    activeFrame.setPendingTask(null);1459  }, this);14601461  this.trimHistory_();1462  var self = this;1463  this.runInNewFrame_(task.execute, function(result) {1464    markTaskComplete();1465    task.fulfill(result);1466  }, function(error) {1467    markTaskComplete();14681469    if (!webdriver.promise.isError_(error) &&1470        !webdriver.promise.isPromise(error)) {1471      error = Error(error);1472    }14731474    task.reject(self.annotateError(/** @type {!Error} */ (error)));1475  }, true);1476};147714781479/**1480 * @return {webdriver.promise.Task_} The next task to execute, or1481 *     {@code null} if a frame was resolved.1482 * @private1483 */1484webdriver.promise.ControlFlow.prototype.getNextTask_ = function() {1485  var firstChild = this.activeFrame_.getFirstChild();1486  if (!firstChild) {1487    if (!this.activeFrame_.isWaiting) {1488      this.resolveFrame_(this.activeFrame_);1489    }1490    return null;1491  }14921493  if (firstChild instanceof webdriver.promise.Frame_) {1494    this.activeFrame_ = firstChild;1495    return this.getNextTask_();1496  }14971498  firstChild.getParent().removeChild(firstChild);1499  return firstChild;1500};150115021503/**1504 * @param {!webdriver.promise.Frame_} frame The frame to resolve.1505 * @private1506 */1507webdriver.promise.ControlFlow.prototype.resolveFrame_ = function(frame) {1508  if (this.activeFrame_ === frame) {1509    // Frame parent is always another frame, but the compiler is not smart1510    // enough to recognize this.1511    this.activeFrame_ =1512        /** @type {webdriver.promise.Frame_} */ (frame.getParent());1513  }15141515  if (frame.getParent()) {1516    frame.getParent().removeChild(frame);1517  }1518  this.trimHistory_();1519  frame.fulfill();15201521  if (!this.activeFrame_) {1522    this.commenceShutdown_();1523  }1524};152515261527/**1528 * Aborts the current frame. The frame, and all of the tasks scheduled within it1529 * will be discarded. If this instance does not have an active frame, it will1530 * immediately terminate all execution.1531 * @param {*} error The reason the frame is being aborted; typically either1532 *     an Error or string.1533 * @private1534 */1535webdriver.promise.ControlFlow.prototype.abortFrame_ = function(error) {1536  // Annotate the error value if it is Error-like.1537  if (webdriver.promise.isError_(error)) {1538    this.annotateError(/** @type {!Error} */ (error));1539  }1540  this.numAbortedFrames_++;15411542  if (!this.activeFrame_) {1543    this.abortNow_(error);1544    return;1545  }15461547  // Frame parent is always another frame, but the compiler is not smart1548  // enough to recognize this.1549  var parent = /** @type {webdriver.promise.Frame_} */ (1550      this.activeFrame_.getParent());1551  if (parent) {1552    parent.removeChild(this.activeFrame_);1553  }15541555  var frame = this.activeFrame_;1556  this.activeFrame_ = parent;1557  frame.reject(error);1558};155915601561/**1562 * Executes a function in a new frame. If the function does not schedule any new1563 * tasks, the frame will be discarded and the function's result returned1564 * immediately. Otherwise, a promise will be returned. This promise will be1565 * resolved with the function's result once all of the tasks scheduled within1566 * the function have been completed. If the function's frame is aborted, the1567 * returned promise will be rejected.1568 *1569 * @param {!Function} fn The function to execute.1570 * @param {function(*)} callback The function to call with a successful result.1571 * @param {function(*)} errback The function to call if there is an error.1572 * @param {boolean=} opt_activate Whether the active frame should be updated to1573 *     the newly created frame so tasks are treated as sub-tasks.1574 * @private1575 */1576webdriver.promise.ControlFlow.prototype.runInNewFrame_ = function(1577    fn, callback, errback, opt_activate) {1578  var newFrame = new webdriver.promise.Frame_(this),1579      self = this,1580      oldFrame = this.activeFrame_;15811582  try {1583    if (!this.activeFrame_) {1584      this.activeFrame_ = newFrame;1585    } else {1586      this.activeFrame_.addChild(newFrame);1587    }15881589    // Activate the new frame to force tasks to be treated as sub-tasks of1590    // the parent frame.1591    if (opt_activate) {1592      this.activeFrame_ = newFrame;1593    }15941595    try {1596      this.schedulingFrame_ = newFrame;1597      webdriver.promise.pushFlow_(this);1598      var result = fn();1599    } finally {1600      webdriver.promise.popFlow_();1601      this.schedulingFrame_ = null;1602    }1603    newFrame.lockFrame();16041605    // If there was nothing scheduled in the new frame we can discard the1606    // frame and return immediately.1607    if (!newFrame.children_.length) {1608      removeNewFrame();1609      webdriver.promise.asap(result, callback, errback);1610      return;1611    }16121613    newFrame.then(function() {1614      webdriver.promise.asap(result, callback, errback);1615    }, function(e) {1616      if (result instanceof webdriver.promise.Promise && result.isPending()) {1617        result.cancel(e);1618        e = result;1619      }1620      errback(e);1621    });1622  } catch (ex) {1623    removeNewFrame(new webdriver.promise.CanceledTaskError_(ex));1624    errback(ex);1625  }16261627  /**1628   * @param {webdriver.promise.CanceledTaskError_=} opt_err If provided, the1629   *     error that triggered the removal of this frame.1630   */1631  function removeNewFrame(opt_err) {1632    var parent = newFrame.getParent();1633    if (parent) {1634      parent.removeChild(newFrame);1635    }16361637    if (opt_err) {1638      newFrame.cancelRemainingTasks(opt_err);1639    }1640    self.activeFrame_ = oldFrame;1641  }1642};164316441645/**1646 * Commences the shutdown sequence for this instance. After one turn of the1647 * event loop, this object will emit the1648 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event to signal1649 * listeners that it has completed. During this wait, if another task is1650 * scheduled, the shutdown will be aborted.1651 * @private1652 */1653webdriver.promise.ControlFlow.prototype.commenceShutdown_ = function() {1654  if (!this.shutdownId_) {1655    // Go ahead and stop the event loop now.  If we're in here, then there are1656    // no more frames with tasks to execute. If we waited to cancel the event1657    // loop in our timeout below, the event loop could trigger *before* the1658    // timeout, generating an error from there being no frames.1659    // If #execute is called before the timeout below fires, it will cancel1660    // the timeout and restart the event loop.1661    this.cancelEventLoop_();16621663    var self = this;1664    self.shutdownId_ = self.timer.setTimeout(function() {1665      self.shutdownId_ = null;1666      self.emit(webdriver.promise.ControlFlow.EventType.IDLE);1667    }, 0);1668  }1669};167016711672/**1673 * Cancels the shutdown sequence if it is currently scheduled.1674 * @private1675 */1676webdriver.promise.ControlFlow.prototype.cancelShutdown_ = function() {1677  if (this.shutdownId_) {1678    this.timer.clearTimeout(this.shutdownId_);1679    this.shutdownId_ = null;1680  }1681};168216831684/**1685 * Aborts this flow, abandoning all remaining tasks. If there are1686 * listeners registered, an {@code UNCAUGHT_EXCEPTION} will be emitted with the1687 * offending {@code error}, otherwise, the {@code error} will be rethrown to the1688 * global error handler.1689 * @param {*} error Object describing the error that caused the flow to1690 *     abort; usually either an Error or string value.1691 * @private1692 */1693webdriver.promise.ControlFlow.prototype.abortNow_ = function(error) {1694  this.activeFrame_ = null;1695  this.cancelShutdown_();1696  this.cancelEventLoop_();16971698  var listeners = this.listeners(1699      webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);1700  if (!listeners.length) {1701    this.timer.setTimeout(function() {1702      throw error;1703    }, 0);1704  } else {1705    this.emit(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,1706        error);1707  }1708};1709171017111712/**1713 * A single node in an {@link webdriver.promise.ControlFlow}'s task tree.1714 * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs1715 *     to.1716 * @constructor1717 * @extends {webdriver.promise.Deferred}1718 * @private1719 */1720webdriver.promise.Node_ = function(flow) {1721  webdriver.promise.Deferred.call(this, null, flow);1722};1723goog.inherits(webdriver.promise.Node_, webdriver.promise.Deferred);172417251726/**1727 * This node's parent.1728 * @private {webdriver.promise.Node_}1729 */1730webdriver.promise.Node_.prototype.parent_ = null;173117321733/** @return {webdriver.promise.Node_} This node's parent. */1734webdriver.promise.Node_.prototype.getParent = function() {1735  return this.parent_;1736};173717381739/**1740 * @param {webdriver.promise.Node_} parent This node's new parent.1741 */1742webdriver.promise.Node_.prototype.setParent = function(parent) {1743  this.parent_ = parent;1744};174517461747/**1748 * @return {!webdriver.promise.Node_} The root of this node's tree.1749 */1750webdriver.promise.Node_.prototype.getRoot = function() {1751  var root = this;1752  while (root.parent_) {1753    root = root.parent_;1754  }1755  return root;1756};1757175817591760/**1761 * An execution frame within a {@link webdriver.promise.ControlFlow}.  Each1762 * frame represents the execution context for either a1763 * {@link webdriver.promise.Task_} or a callback on a1764 * {@link webdriver.promise.Deferred}.1765 *1766 * <p>Each frame may contain sub-frames.  If child N is a sub-frame, then the1767 * items queued within it are given priority over child N+1.1768 *1769 * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs1770 *     to.1771 * @constructor1772 * @extends {webdriver.promise.Node_}1773 * @private1774 */1775webdriver.promise.Frame_ = function(flow) {1776  webdriver.promise.Node_.call(this, flow);17771778  var reject = goog.bind(this.reject, this);1779  var cancelRemainingTasks = goog.bind(this.cancelRemainingTasks, this);17801781  /** @override */1782  this.reject = function(e) {1783    cancelRemainingTasks(new webdriver.promise.CanceledTaskError_(e));1784    reject(e);1785  };17861787  /**1788   * @private {!Array.<!(webdriver.promise.Frame_|webdriver.promise.Task_)>}1789   */1790  this.children_ = [];1791};1792goog.inherits(webdriver.promise.Frame_, webdriver.promise.Node_);179317941795/**1796 * The task currently being executed within this frame.1797 * @private {webdriver.promise.Task_}1798 */1799webdriver.promise.Frame_.prototype.pendingTask_ = null;180018011802/**1803 * Whether this frame is active. A frame is considered active once one of its1804 * descendants has been removed for execution.1805 *1806 * Adding a sub-frame as a child to an active frame is an indication that1807 * a callback to a {@link webdriver.promise.Deferred} is being invoked and any1808 * tasks scheduled within it should have priority over previously scheduled1809 * tasks:1810 * <code><pre>1811 *   var flow = webdriver.promise.controlFlow();1812 *   flow.execute('start here', goog.nullFunction).then(function() {1813 *     flow.execute('this should execute 2nd', goog.nullFunction);1814 *   });1815 *   flow.execute('this should execute last', goog.nullFunction);1816 * </pre></code>1817 *1818 * @private {boolean}1819 */1820webdriver.promise.Frame_.prototype.isActive_ = false;182118221823/**1824 * Whether this frame is currently locked. A locked frame represents a callback1825 * or task function which has run to completion and scheduled all of its tasks.1826 *1827 * <p>Once a frame becomes {@link #isActive_ active}, any new frames which are1828 * added represent callbacks on a {@link webdriver.promise.Deferred}, whose1829 * tasks must be given priority over previously scheduled tasks.1830 *1831 * @private {boolean}1832 */1833webdriver.promise.Frame_.prototype.isLocked_ = false;183418351836/**1837 * A reference to the last node inserted in this frame.1838 * @private {webdriver.promise.Node_}1839 */1840webdriver.promise.Frame_.prototype.lastInsertedChild_ = null;184118421843/**1844 * Marks all of the tasks that are descendants of this frame in the execution1845 * tree as cancelled. This is necessary for callbacks scheduled asynchronous.1846 * For example:1847 *1848 *     var someResult;1849 *     webdriver.promise.createFlow(function(flow) {1850 *       someResult = flow.execute(function() {});1851 *       throw Error();1852 *     }).addErrback(function(err) {1853 *       console.log('flow failed: ' + err);1854 *       someResult.then(function() {1855 *         console.log('task succeeded!');1856 *       }, function(err) {1857 *         console.log('task failed! ' + err);1858 *       });1859 *     });1860 *     // flow failed: Error: boom1861 *     // task failed! CanceledTaskError: Task discarded due to a previous1862 *     // task failure: Error: boom1863 *1864 * @param {!webdriver.promise.CanceledTaskError_} error The cancellation1865 *     error.1866 */1867webdriver.promise.Frame_.prototype.cancelRemainingTasks = function(error) {1868  goog.array.forEach(this.children_, function(child) {1869    if (child instanceof webdriver.promise.Frame_) {1870      child.cancelRemainingTasks(error);1871    } else {1872      // None of the previously registered listeners should be notified that1873      // the task is being canceled, however, we need at least one errback1874      // to prevent the cancellation from bubbling up.1875      child.removeAll();1876      child.thenCatch(goog.nullFunction);1877      child.cancel(error);1878    }1879  });1880};188118821883/**1884 * @return {webdriver.promise.Task_} The task currently executing1885 *     within this frame, if any.1886 */1887webdriver.promise.Frame_.prototype.getPendingTask = function() {1888  return this.pendingTask_;1889};189018911892/**1893 * @param {webdriver.promise.Task_} task The task currently1894 *     executing within this frame, if any.1895 */1896webdriver.promise.Frame_.prototype.setPendingTask = function(task) {1897  this.pendingTask_ = task;1898};189919001901/** Locks this frame. */1902webdriver.promise.Frame_.prototype.lockFrame = function() {1903  this.isLocked_ = true;1904};190519061907/**1908 * Adds a new node to this frame.1909 * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} node1910 *     The node to insert.1911 */1912webdriver.promise.Frame_.prototype.addChild = function(node) {1913  if (this.lastInsertedChild_ &&1914      this.lastInsertedChild_ instanceof webdriver.promise.Frame_ &&1915      !this.lastInsertedChild_.isLocked_) {1916    this.lastInsertedChild_.addChild(node);1917    return;1918  }19191920  node.setParent(this);19211922  if (this.isActive_ && node instanceof webdriver.promise.Frame_) {1923    var index = 0;1924    if (this.lastInsertedChild_ instanceof1925        webdriver.promise.Frame_) {1926      index = goog.array.indexOf(this.children_, this.lastInsertedChild_) + 1;1927    }1928    goog.array.insertAt(this.children_, node, index);1929    this.lastInsertedChild_ = node;1930    return;1931  }19321933  this.lastInsertedChild_ = node;1934  this.children_.push(node);1935};193619371938/**1939 * @return {(webdriver.promise.Frame_|webdriver.promise.Task_)} This frame's1940 *     fist child.1941 */1942webdriver.promise.Frame_.prototype.getFirstChild = function() {1943  this.isActive_ = true;1944  this.lastInsertedChild_ = null;1945  return this.children_[0];1946};194719481949/**1950 * Removes a child from this frame.1951 * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} child1952 *     The child to remove.1953 */1954webdriver.promise.Frame_.prototype.removeChild = function(child) {1955  var index = goog.array.indexOf(this.children_, child);1956  child.setParent(null);1957  goog.array.removeAt(this.children_, index);1958  if (this.lastInsertedChild_ === child) {1959    this.lastInsertedChild_ = null;1960  }1961};196219631964/** @override */1965webdriver.promise.Frame_.prototype.toString = function() {1966  return '[' + goog.array.map(this.children_, function(child) {1967    return child.toString();1968  }).join(', ') + ']';1969};1970197119721973/**1974 * A task to be executed by a {@link webdriver.promise.ControlFlow}.1975 *1976 * @param {!webdriver.promise.ControlFlow} flow The flow this instances belongs1977 *     to.1978 * @param {!Function} fn The function to call when the task executes. If it1979 *     returns a {@code webdriver.promise.Promise}, the flow will wait1980 *     for it to be resolved before starting the next task.1981 * @param {string} description A description of the task for debugging.1982 * @param {!webdriver.stacktrace.Snapshot} snapshot A snapshot of the stack1983 *     when this task was scheduled.1984 * @constructor1985 * @extends {webdriver.promise.Node_}1986 * @private1987 */1988webdriver.promise.Task_ = function(flow, fn, description, snapshot) {1989  webdriver.promise.Node_.call(this, flow);19901991  /**1992   * Executes this task.1993   * @type {!Function}1994   */1995  this.execute = fn;19961997  /** @private {string} */1998  this.description_ = description;19992000  /** @private {!webdriver.stacktrace.Snapshot} */2001  this.snapshot_ = snapshot;2002};2003goog.inherits(webdriver.promise.Task_, webdriver.promise.Node_);200420052006/** @return {string} This task's description. */2007webdriver.promise.Task_.prototype.getDescription = function() {2008  return this.description_;2009};201020112012/** @override */2013webdriver.promise.Task_.prototype.toString = function() {2014  var stack = this.snapshot_.getStacktrace();2015  var ret = this.description_;2016  if (stack.length) {2017    if (this.description_) {2018      ret += '\n';2019    }2020    ret += stack.join('\n');2021  }2022  return ret;2023};2024202520262027/**2028 * Special error used to signal when a task is canceled because a previous2029 * task in the same frame failed.2030 * @param {*} err The error that caused the task cancellation.2031 * @constructor2032 * @extends {goog.debug.Error}2033 * @private2034 */2035webdriver.promise.CanceledTaskError_ = function(err) {2036  goog.base(this, 'Task discarded due to a previous task failure: ' + err);2037};2038goog.inherits(webdriver.promise.CanceledTaskError_, goog.debug.Error);203920402041/** @override */2042webdriver.promise.CanceledTaskError_.prototype.name = 'CanceledTaskError';2043204420452046/**2047 * The default flow to use if no others are active.2048 * @private {!webdriver.promise.ControlFlow}2049 */2050webdriver.promise.defaultFlow_ = new webdriver.promise.ControlFlow();205120522053/**2054 * A stack of active control flows, with the top of the stack used to schedule2055 * commands. When there are multiple flows on the stack, the flow at index N2056 * represents a callback triggered within a task owned by the flow at index2057 * N-1.2058 * @private {!Array.<!webdriver.promise.ControlFlow>}2059 */2060webdriver.promise.activeFlows_ = [];206120622063/**2064 * Changes the default flow to use when no others are active.2065 * @param {!webdriver.promise.ControlFlow} flow The new default flow.2066 * @throws {Error} If the default flow is not currently active.2067 */2068webdriver.promise.setDefaultFlow = function(flow) {2069  if (webdriver.promise.activeFlows_.length) {2070    throw Error('You may only change the default flow while it is active');2071  }2072  webdriver.promise.defaultFlow_ = flow;2073};207420752076/**2077 * @return {!webdriver.promise.ControlFlow} The currently active control flow.2078 */2079webdriver.promise.controlFlow = function() {2080  return /** @type {!webdriver.promise.ControlFlow} */ (2081      goog.array.peek(webdriver.promise.activeFlows_) ||2082      webdriver.promise.defaultFlow_);2083};208420852086/**2087 * @param {!webdriver.promise.ControlFlow} flow The new flow.2088 * @private2089 */2090webdriver.promise.pushFlow_ = function(flow) {2091  webdriver.promise.activeFlows_.push(flow);2092};209320942095/** @private */2096webdriver.promise.popFlow_ = function() {2097  webdriver.promise.activeFlows_.pop();2098};209921002101/**2102 * Creates a new control flow. The provided callback will be invoked as the2103 * first task within the new flow, with the flow as its sole argument. Returns2104 * a promise that resolves to the callback result.2105 * @param {function(!webdriver.promise.ControlFlow)} callback The entry point2106 *     to the newly created flow.2107 * @return {!webdriver.promise.Promise} A promise that resolves to the callback2108 *     result.2109 */2110webdriver.promise.createFlow = function(callback) {2111  var flow = new webdriver.promise.ControlFlow(2112      webdriver.promise.defaultFlow_.timer);2113  return flow.execute(function() {2114    return callback(flow);2115  });2116};