# promise.js

# 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 * @template T74 * @see http://wiki.commonjs.org/wiki/Promises/A75 */76webdriver.promise.Promise = function() {77};787980/**81 * Cancels the computation of this promise's value, rejecting the promise in the82 * process.83 * @param {*} reason The reason this promise is being cancelled. If not an84 *     {@code Error}, one will be created using the value's string85 *     representation.86 */87webdriver.promise.Promise.prototype.cancel = function(reason) {88  throw new TypeError('Unimplemented function: "cancel"');89};909192/** @return {boolean} Whether this promise's value is still being computed. */93webdriver.promise.Promise.prototype.isPending = function() {94  throw new TypeError('Unimplemented function: "isPending"');95};969798/**99 * Registers listeners for when this instance is resolved. This function most100 * overridden by subtypes.101 *102 * @param {?(function(T): (R|webdriver.promise.Promise.<R>))=} opt_callback The103 *     function to call if this promise is successfully resolved. The function104 *     should expect a single argument: the promise's resolved value.105 * @param {?(function(*): (R|webdriver.promise.Promise.<R>))=} opt_errback The106 *     function to call if this promise is rejected. The function should expect107 *     a single argument: the rejection reason.108 * @return {!webdriver.promise.Promise.<R>} A new promise which will be109 *     resolved with the result of the invoked callback.110 * @template R111 */112webdriver.promise.Promise.prototype.then = function(113    opt_callback, opt_errback) {114  throw new TypeError('Unimplemented function: "then"');115};116117118/**119 * Registers a listener for when this promise is rejected. This is synonymous120 * with the {@code catch} clause in a synchronous API:121 * <pre><code>122 *   // Synchronous API:123 *   try {124 *     doSynchronousWork();125 *   } catch (ex) {126 *     console.error(ex);127 *   }128 *129 *   // Asynchronous promise API:130 *   doAsynchronousWork().thenCatch(function(ex) {131 *     console.error(ex);132 *   });133 * </code></pre>134 *135 * @param {function(*): (R|webdriver.promise.Promise.<R>)} errback The function136 *     to call if this promise is rejected. The function should expect a single137 *     argument: the rejection reason.138 * @return {!webdriver.promise.Promise.<R>} A new promise which will be139 *     resolved with the result of the invoked callback.140 * @template R141 */142webdriver.promise.Promise.prototype.thenCatch = function(errback) {143  return this.then(null, errback);144};145146147/**148 * Registers a listener to invoke when this promise is resolved, regardless149 * of whether the promise's value was successfully computed. This function150 * is synonymous with the {@code finally} clause in a synchronous API:151 * <pre><code>152 *   // Synchronous API:153 *   try {154 *     doSynchronousWork();155 *   } finally {156 *     cleanUp();157 *   }158 *159 *   // Asynchronous promise API:160 *   doAsynchronousWork().thenFinally(cleanUp);161 * </code></pre>162 *163 * <b>Note:</b> similar to the {@code finally} clause, if the registered164 * callback returns a rejected promise or throws an error, it will silently165 * replace the rejection error (if any) from this promise:166 * <pre><code>167 *   try {168 *     throw Error('one');169 *   } finally {170 *     throw Error('two');  // Hides Error: one171 *   }172 *173 *   webdriver.promise.rejected(Error('one'))174 *       .thenFinally(function() {175 *         throw Error('two');  // Hides Error: one176 *       });177 * </code></pre>178 *179 *180 * @param {function(): (R|webdriver.promise.Promise.<R>)} callback The function181 *     to call when this promise is resolved.182 * @return {!webdriver.promise.Promise.<R>} A promise that will be fulfilled183 *     with the callback result.184 * @template R185 */186webdriver.promise.Promise.prototype.thenFinally = function(callback) {187  return this.then(callback, function(err) {188    var value = callback();189    if (webdriver.promise.isPromise(value)) {190      return value.then(function() {191        throw err;192      });193    }194    throw err;195  });196};197198199200/**201 * Represents a value that will be resolved at some point in the future. This202 * class represents the protected "producer" half of a Promise - each Deferred203 * has a {@code promise} property that may be returned to consumers for204 * registering callbacks, reserving the ability to resolve the deferred to the205 * producer.206 *207 * <p>If this Deferred is rejected and there are no listeners registered before208 * the next turn of the event loop, the rejection will be passed to the209 * {@link webdriver.promise.ControlFlow} as an unhandled failure.210 *211 * <p>If this Deferred is cancelled, the cancellation reason will be forward to212 * the Deferred's canceller function (if provided). The canceller may return a213 * truth-y value to override the reason provided for rejection.214 *215 * @param {Function=} opt_canceller Function to call when cancelling the216 *     computation of this instance's value.217 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow218 *     this instance was created under. This should only be provided during219 *     unit tests.220 * @constructor221 * @extends {webdriver.promise.Promise.<T>}222 * @template T223 */224webdriver.promise.Deferred = function(opt_canceller, opt_flow) {225  /* NOTE: This class's implementation diverges from the prototypical style226   * used in the rest of the atoms library. This was done intentionally to227   * protect the internal Deferred state from consumers, as outlined by228   *     http://wiki.commonjs.org/wiki/Promises229   */230  goog.base(this);231232  var flow = opt_flow || webdriver.promise.controlFlow();233234  /**235   * The listeners registered with this Deferred. Each element in the list will236   * be a 3-tuple of the callback function, errback function, and the237   * corresponding deferred object.238   * @type {!Array.<!webdriver.promise.Deferred.Listener_>}239   */240  var listeners = [];241242  /**243   * Whether this Deferred's resolution was ever handled by a listener.244   * If the Deferred is rejected and its value is not handled by a listener245   * before the next turn of the event loop, the error will be passed to the246   * global error handler.247   * @type {boolean}248   */249  var handled = false;250251  /**252   * Key for the timeout used to delay reproting an unhandled rejection to the253   * parent {@link webdriver.promise.ControlFlow}.254   * @type {?number}255   */256  var pendingRejectionKey = null;257258  /**259   * This Deferred's current state.260   * @type {!webdriver.promise.Deferred.State_}261   */262  var state = webdriver.promise.Deferred.State_.PENDING;263264  /**265   * This Deferred's resolved value; set when the state transitions from266   * {@code webdriver.promise.Deferred.State_.PENDING}.267   * @type {*}268   */269  var value;270271  /** @return {boolean} Whether this promise's value is still pending. */272  function isPending() {273    return state == webdriver.promise.Deferred.State_.PENDING;274  }275276  /**277   * Removes all of the listeners previously registered on this deferred.278   * @throws {Error} If this deferred has already been resolved.279   */280  function removeAll() {281    listeners = [];282  }283284  /**285   * Resolves this deferred. If the new value is a promise, this function will286   * wait for it to be resolved before notifying the registered listeners.287   * @param {!webdriver.promise.Deferred.State_} newState The deferred's new288   *     state.289   * @param {*} newValue The deferred's new value.290   */291  function resolve(newState, newValue) {292    if (webdriver.promise.Deferred.State_.PENDING !== state) {293      return;294    }295296    state = webdriver.promise.Deferred.State_.BLOCKED;297298    if (webdriver.promise.isPromise(newValue) && newValue !== self) {299      var onFulfill = goog.partial(notifyAll, newState);300      var onReject = goog.partial(301          notifyAll, webdriver.promise.Deferred.State_.REJECTED);302      if (newValue instanceof webdriver.promise.Deferred) {303        newValue.then(onFulfill, onReject);304      } else {305        webdriver.promise.asap(newValue, onFulfill, onReject);306      }307308    } else {309      notifyAll(newState, newValue);310    }311  }312313  /**314   * Notifies all of the listeners registered with this Deferred that its state315   * has changed.316   * @param {!webdriver.promise.Deferred.State_} newState The deferred's new317   *     state.318   * @param {*} newValue The deferred's new value.319   */320  function notifyAll(newState, newValue) {321    if (newState === webdriver.promise.Deferred.State_.REJECTED &&322        // We cannot check instanceof Error since the object may have been323        // created in a different JS context.324        goog.isObject(newValue) && goog.isString(newValue.message)) {325      newValue = flow.annotateError(/** @type {!Error} */(newValue));326    }327328    state = newState;329    value = newValue;330    while (listeners.length) {331      notify(listeners.shift());332    }333334    if (!handled && state == webdriver.promise.Deferred.State_.REJECTED) {335      pendingRejectionKey = propagateError(value);336    }337  }338339  /**340   * Propagates an unhandled rejection to the parent ControlFlow in a341   * future turn of the JavaScript event loop.342   * @param {*} error The error value to report.343   * @return {number} The key for the registered timeout.344   */345  function propagateError(error) {346    flow.pendingRejections_ += 1;347    return flow.timer.setTimeout(function() {348      flow.pendingRejections_ -= 1;349      flow.abortFrame_(error);350    }, 0);351  }352353  /**354   * Notifies a single listener of this Deferred's change in state.355   * @param {!webdriver.promise.Deferred.Listener_} listener The listener to356   *     notify.357   */358  function notify(listener) {359    var func = state == webdriver.promise.Deferred.State_.RESOLVED ?360        listener.callback : listener.errback;361    if (func) {362      flow.runInNewFrame_(goog.partial(func, value),363          listener.fulfill, listener.reject);364    } else if (state == webdriver.promise.Deferred.State_.REJECTED) {365      listener.reject(value);366    } else {367      listener.fulfill(value);368    }369  }370371  /**372   * The consumer promise for this instance. Provides protected access to the373   * callback registering functions.374   * @type {!webdriver.promise.Promise.<T>}375   */376  var promise = new webdriver.promise.Promise();377378  /**379   * Registers a callback on this Deferred.380   *381   * @param {?(function(T): (R|webdriver.promise.Promise.<R>))=} opt_callback .382   * @param {?(function(*): (R|webdriver.promise.Promise.<R>))=} opt_errback .383   * @return {!webdriver.promise.Promise.<R>} A new promise representing the384   *     result of the callback.385   * @template R386   * @see webdriver.promise.Promise#then387   */388  function then(opt_callback, opt_errback) {389    // Avoid unnecessary allocations if we weren't given any callback functions.390    if (!opt_callback && !opt_errback) {391      return promise;392    }393394    // The moment a listener is registered, we consider this deferred to be395    // handled; the callback must handle any rejection errors.396    handled = true;397    if (pendingRejectionKey) {398      flow.pendingRejections_ -= 1;399      flow.timer.clearTimeout(pendingRejectionKey);400    }401402    var deferred = new webdriver.promise.Deferred(cancel, flow);403    var listener = {404      callback: opt_callback,405      errback: opt_errback,406      fulfill: deferred.fulfill,407      reject: deferred.reject408    };409410    if (state == webdriver.promise.Deferred.State_.PENDING ||411        state == webdriver.promise.Deferred.State_.BLOCKED) {412      listeners.push(listener);413    } else {414      notify(listener);415    }416417    return deferred.promise;418  }419420  var self = this;421422  /**423   * Resolves this promise with the given value. If the value is itself a424   * promise and not a reference to this deferred, this instance will wait for425   * it before resolving.426   * @param {T=} opt_value The fulfilled value.427   */428  function fulfill(opt_value) {429    resolve(webdriver.promise.Deferred.State_.RESOLVED, opt_value);430  }431432  /**433   * Rejects this promise. If the error is itself a promise, this instance will434   * be chained to it and be rejected with the error's resolved value.435   * @param {*=} opt_error The rejection reason, typically either a436   *     {@code Error} or a {@code string}.437   */438  function reject(opt_error) {439    resolve(webdriver.promise.Deferred.State_.REJECTED, opt_error);440  }441442  /**443   * Attempts to cancel the computation of this instance's value. This attempt444   * will silently fail if this instance has already resolved.445   * @param {*=} opt_reason The reason for cancelling this promise.446   */447  function cancel(opt_reason) {448    if (!isPending()) {449      return;450    }451452    if (opt_canceller) {453      opt_reason = opt_canceller(opt_reason) || opt_reason;454    }455456    reject(opt_reason);457  }458459  this.promise = promise;460  this.promise.then = this.then = then;461  this.promise.cancel = this.cancel = cancel;462  this.promise.isPending = this.isPending = isPending;463  this.fulfill = fulfill;464  this.reject = this.errback = reject;465466  // Only expose this function to our internal classes.467  // TODO: find a cleaner way of handling this.468  if (this instanceof webdriver.promise.Task_) {469    this.removeAll = removeAll;470  }471472  // Export symbols necessary for the contract on this object to work in473  // compiled mode.474  goog.exportProperty(this, 'then', this.then);475  goog.exportProperty(this, 'cancel', cancel);476  goog.exportProperty(this, 'fulfill', fulfill);477  goog.exportProperty(this, 'reject', reject);478  goog.exportProperty(this, 'isPending', isPending);479  goog.exportProperty(this, 'promise', this.promise);480  goog.exportProperty(this.promise, 'then', this.then);481  goog.exportProperty(this.promise, 'cancel', cancel);482  goog.exportProperty(this.promise, 'isPending', isPending);483};484goog.inherits(webdriver.promise.Deferred, webdriver.promise.Promise);485486487/**488 * Type definition for a listener registered on a Deferred object.489 * @typedef {{callback:(Function|undefined),490 *            errback:(Function|undefined),491 *            fulfill: function(*), reject: function(*)}}492 * @private493 */494webdriver.promise.Deferred.Listener_;495496497/**498 * The three states a {@link webdriver.promise.Deferred} object may be in.499 * @enum {number}500 * @private501 */502webdriver.promise.Deferred.State_ = {503  REJECTED: -1,504  PENDING: 0,505  BLOCKED: 1,506  RESOLVED: 2507};508509510/**511 * Tests if a value is an Error-like object. This is more than an straight512 * instanceof check since the value may originate from another context.513 * @param {*} value The value to test.514 * @return {boolean} Whether the value is an error.515 * @private516 */517webdriver.promise.isError_ = function(value) {518  return value instanceof Error ||519      goog.isObject(value) &&520      (Object.prototype.toString.call(value) === '[object Error]' ||521       // A special test for goog.testing.JsUnitException.522       value.isJsUnitException);523524};525526527/**528 * Determines whether a {@code value} should be treated as a promise.529 * Any object whose "then" property is a function will be considered a promise.530 *531 * @param {*} value The value to test.532 * @return {boolean} Whether the value is a promise.533 */534webdriver.promise.isPromise = function(value) {535  return !!value && goog.isObject(value) &&536      // Use array notation so the Closure compiler does not obfuscate away our537      // contract.538      goog.isFunction(value['then']);539};540541542/**543 * Creates a promise that will be resolved at a set time in the future.544 * @param {number} ms The amount of time, in milliseconds, to wait before545 *     resolving the promise.546 * @return {!webdriver.promise.Promise} The promise.547 */548webdriver.promise.delayed = function(ms) {549  var timer = webdriver.promise.controlFlow().timer;550  var key;551  var deferred = new webdriver.promise.Deferred(function() {552    timer.clearTimeout(key);553  });554  key = timer.setTimeout(deferred.fulfill, ms);555  return deferred.promise;556};557558559/**560 * Creates a new deferred object.561 * @param {Function=} opt_canceller Function to call when cancelling the562 *     computation of this instance's value.563 * @return {!webdriver.promise.Deferred.<T>} The new deferred object.564 * @template T565 */566webdriver.promise.defer = function(opt_canceller) {567  return new webdriver.promise.Deferred(opt_canceller);568};569570571/**572 * Creates a promise that has been resolved with the given value.573 * @param {T=} opt_value The resolved value.574 * @return {!webdriver.promise.Promise.<T>} The resolved promise.575 * @template T576 */577webdriver.promise.fulfilled = function(opt_value) {578  if (opt_value instanceof webdriver.promise.Promise) {579    return opt_value;580  }581  var deferred = new webdriver.promise.Deferred();582  deferred.fulfill(opt_value);583  return deferred.promise;584};585586587/**588 * Creates a promise that has been rejected with the given reason.589 * @param {*=} opt_reason The rejection reason; may be any value, but is590 *     usually an Error or a string.591 * @return {!webdriver.promise.Promise.<T>} The rejected promise.592 * @template T593 */594webdriver.promise.rejected = function(opt_reason) {595  var deferred = new webdriver.promise.Deferred();596  deferred.reject(opt_reason);597  return deferred.promise;598};599600601/**602 * Wraps a function that is assumed to be a node-style callback as its final603 * argument. This callback takes two arguments: an error value (which will be604 * null if the call succeeded), and the success value as the second argument.605 * If the call fails, the returned promise will be rejected, otherwise it will606 * be resolved with the result.607 * @param {!Function} fn The function to wrap.608 * @return {!webdriver.promise.Promise} A promise that will be resolved with the609 *     result of the provided function's callback.610 */611webdriver.promise.checkedNodeCall = function(fn) {612  var deferred = new webdriver.promise.Deferred(function() {613    throw Error('This Deferred may not be cancelled');614  });615  try {616    fn(function(error, value) {617      error ? deferred.reject(error) : deferred.fulfill(value);618    });619  } catch (ex) {620    deferred.reject(ex);621  }622  return deferred.promise;623};624625626/**627 * Registers an observer on a promised {@code value}, returning a new promise628 * that will be resolved when the value is. If {@code value} is not a promise,629 * then the return promise will be immediately resolved.630 * @param {*} value The value to observe.631 * @param {Function=} opt_callback The function to call when the value is632 *     resolved successfully.633 * @param {Function=} opt_errback The function to call when the value is634 *     rejected.635 * @return {!webdriver.promise.Promise} A new promise.636 */637webdriver.promise.when = function(value, opt_callback, opt_errback) {638  if (value instanceof webdriver.promise.Promise) {639    return value.then(opt_callback, opt_errback);640  }641642  var deferred = new webdriver.promise.Deferred();643644  webdriver.promise.asap(value, deferred.fulfill, deferred.reject);645646  return deferred.then(opt_callback, opt_errback);647};648649650/**651 * Invokes the appropriate callback function as soon as a promised652 * {@code value} is resolved. This function is similar to653 * {@link webdriver.promise.when}, except it does not return a new promise.654 * @param {*} value The value to observe.655 * @param {Function} callback The function to call when the value is656 *     resolved successfully.657 * @param {Function=} opt_errback The function to call when the value is658 *     rejected.659 */660webdriver.promise.asap = function(value, callback, opt_errback) {661  if (webdriver.promise.isPromise(value)) {662    value.then(callback, opt_errback);663664  // Maybe a Dojo-like deferred object?665  } else if (!!value && goog.isObject(value) &&666      goog.isFunction(value.addCallbacks)) {667    value.addCallbacks(callback, opt_errback);668669  // A raw value, return a resolved promise.670  } else if (callback) {671    callback(value);672  }673};674675676/**677 * Given an array of promises, will return a promise that will be fulfilled678 * with the fulfillment values of the input array's values. If any of the679 * input array's promises are rejected, the returned promise will be rejected680 * with the same reason.681 *682 * @param {!Array.<(T|!webdriver.promise.Promise.<T>)>} arr An array of683 *     promises to wait on.684 * @return {!webdriver.promise.Promise.<!Array.<T>>} A promise that is685 *     fulfilled with an array containing the fulfilled values of the686 *     input array, or rejected with the same reason as the first687 *     rejected value.688 * @template T689 */690webdriver.promise.all = function(arr) {691  var n = arr.length;692  if (!n) {693    return webdriver.promise.fulfilled([]);694  }695696  var toFulfill = n;697  var result = webdriver.promise.defer();698  var values = [];699700  var onFulfill = function(index, value) {701    values[index] = value;702    toFulfill--;703    if (toFulfill == 0) {704      result.fulfill(values);705    }706  };707708  for (var i = 0; i < n; ++i) {709    webdriver.promise.asap(710        arr[i], goog.partial(onFulfill, i), result.reject);711  }712713  return result.promise;714};715716717/**718 * Calls a function for each element in an array and inserts the result into a719 * new array, which is used as the fulfillment value of the promise returned720 * by this function.721 *722 * <p>If the return value of the mapping function is a promise, this function723 * will wait for it to be fulfilled before inserting it into the new array.724 *725 * <p>If the mapping function throws or returns a rejected promise, the726 * promise returned by this function will be rejected with the same reason.727 * Only the first failure will be reported; all subsequent errors will be728 * silently ignored.729 *730 * @param {!(Array.<TYPE>|webdriver.promise.Promise.<!Array.<TYPE>>)} arr The731 *     array to iterator over, or a promise that will resolve to said array.732 * @param {function(this: SELF, TYPE, number, !Array.<TYPE>): ?} fn The733 *     function to call for each element in the array. This function should734 *     expect three arguments (the element, the index, and the array itself.735 * @param {SELF=} opt_self The object to be used as the value of 'this' within736 *     {@code fn}.737 * @template TYPE, SELF738 */739webdriver.promise.map = function(arr, fn, opt_self) {740  return webdriver.promise.when(arr, function(arr) {741    var result = goog.array.map(arr, fn, opt_self);742    return webdriver.promise.all(result);743  });744};745746747/**748 * Calls a function for each element in an array, and if the function returns749 * true adds the element to a new array.750 *751 * <p>If the return value of the filter function is a promise, this function752 * will wait for it to be fulfilled before determining whether to insert the753 * element into the new array.754 *755 * <p>If the filter function throws or returns a rejected promise, the promise756 * returned by this function will be rejected with the same reason. Only the757 * first failure will be reported; all subsequent errors will be silently758 * ignored.759 *760 * @param {!(Array.<TYPE>|webdriver.promise.Promise.<!Array.<TYPE>>)} arr The761 *     array to iterator over, or a promise that will resolve to said array.762 * @param {function(this: SELF, TYPE, number, !Array.<TYPE>): (763 *             boolean|webdriver.promise.Promise.<boolean>)} fn The function764 *     to call for each element in the array.765 * @param {SELF=} opt_self The object to be used as the value of 'this' within766 *     {@code fn}.767 * @template TYPE, SELF768 */769webdriver.promise.filter = function(arr, fn, opt_self) {770  return webdriver.promise.when(arr, function(arr) {771    var originalValues = goog.array.clone(arr);772    return webdriver.promise.map(arr, fn, opt_self).then(function(include) {773      return goog.array.filter(originalValues, function(value, index) {774        return include[index];775      });776    });777  });778};779780781/**782 * Returns a promise that will be resolved with the input value in a783 * fully-resolved state. If the value is an array, each element will be fully784 * resolved. Likewise, if the value is an object, all keys will be fully785 * resolved. In both cases, all nested arrays and objects will also be786 * fully resolved.  All fields are resolved in place; the returned promise will787 * resolve on {@code value} and not a copy.788 *789 * Warning: This function makes no checks against objects that contain790 * cyclical references:791 * <pre><code>792 *   var value = {};793 *   value['self'] = value;794 *   webdriver.promise.fullyResolved(value);  // Stack overflow.795 * </code></pre>796 *797 * @param {*} value The value to fully resolve.798 * @return {!webdriver.promise.Promise} A promise for a fully resolved version799 *     of the input value.800 */801webdriver.promise.fullyResolved = function(value) {802  if (webdriver.promise.isPromise(value)) {803    return webdriver.promise.when(value, webdriver.promise.fullyResolveValue_);804  }805  return webdriver.promise.fullyResolveValue_(value);806};807808809/**810 * @param {*} value The value to fully resolve. If a promise, assumed to811 *     already be resolved.812 * @return {!webdriver.promise.Promise} A promise for a fully resolved version813 *     of the input value.814 * @private815 */816webdriver.promise.fullyResolveValue_ = function(value) {817  switch (goog.typeOf(value)) {818    case 'array':819      return webdriver.promise.fullyResolveKeys_(820          /** @type {!Array} */ (value));821822    case 'object':823      if (webdriver.promise.isPromise(value)) {824        // We get here when the original input value is a promise that825        // resolves to itself. When the user provides us with such a promise,826        // trust that it counts as a "fully resolved" value and return it.827        // Of course, since it's already a promise, we can just return it828        // to the user instead of wrapping it in another promise.829        return /** @type {!webdriver.promise.Promise} */ (value);830      }831832      if (goog.isNumber(value.nodeType) &&833          goog.isObject(value.ownerDocument) &&834          goog.isNumber(value.ownerDocument.nodeType)) {835        // DOM node; return early to avoid infinite recursion. Should we836        // only support objects with a certain level of nesting?837        return webdriver.promise.fulfilled(value);838      }839840      return webdriver.promise.fullyResolveKeys_(841          /** @type {!Object} */ (value));842843    default:  // boolean, function, null, number, string, undefined844      return webdriver.promise.fulfilled(value);845  }846};847848849/**850 * @param {!(Array|Object)} obj the object to resolve.851 * @return {!webdriver.promise.Promise} A promise that will be resolved with the852 *     input object once all of its values have been fully resolved.853 * @private854 */855webdriver.promise.fullyResolveKeys_ = function(obj) {856  var isArray = goog.isArray(obj);857  var numKeys = isArray ? obj.length : goog.object.getCount(obj);858  if (!numKeys) {859    return webdriver.promise.fulfilled(obj);860  }861862  var numResolved = 0;863  var deferred = new webdriver.promise.Deferred();864865  // In pre-IE9, goog.array.forEach will not iterate properly over arrays866  // containing undefined values because "index in array" returns false867  // when array[index] === undefined (even for x = [undefined, 1]). To get868  // around this, we need to use our own forEach implementation.869  // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be870  // reproduced in IE9 by changing the browser/document modes, it requires an871  // actual pre-IE9 browser.  Yay, IE!872  var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) {873    var n = arr.length;874    for (var i = 0; i < n; ++i) {875      fn.call(null, arr[i], i, arr);876    }877  };878879  forEachKey(obj, function(partialValue, key) {880    var type = goog.typeOf(partialValue);881    if (type != 'array' && type != 'object') {882      maybeResolveValue();883      return;884    }885886    webdriver.promise.fullyResolved(partialValue).then(887        function(resolvedValue) {888          obj[key] = resolvedValue;889          maybeResolveValue();890        },891        deferred.reject);892  });893894  return deferred.promise;895896  function maybeResolveValue() {897    if (++numResolved == numKeys) {898      deferred.fulfill(obj);899    }900  }901};902903904//////////////////////////////////////////////////////////////////////////////905//906//  webdriver.promise.ControlFlow907//908//////////////////////////////////////////////////////////////////////////////909910911912/**913 * Handles the execution of scheduled tasks, each of which may be an914 * asynchronous operation. The control flow will ensure tasks are executed in915 * the ordered scheduled, starting each task only once those before it have916 * completed.917 *918 * <p>Each task scheduled within this flow may return a919 * {@link webdriver.promise.Promise} to indicate it is an asynchronous920 * operation. The ControlFlow will wait for such promises to be resolved before921 * marking the task as completed.922 *923 * <p>Tasks and each callback registered on a {@link webdriver.promise.Deferred}924 * will be run in their own ControlFlow frame.  Any tasks scheduled within a925 * frame will have priority over previously scheduled tasks. Furthermore, if926 * any of the tasks in the frame fails, the remainder of the tasks in that frame927 * will be discarded and the failure will be propagated to the user through the928 * callback/task's promised result.929 *930 * <p>Each time a ControlFlow empties its task queue, it will fire an931 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Conversely,932 * whenever the flow terminates due to an unhandled error, it will remove all933 * remaining tasks in its queue and fire an934 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION} event. If935 * there are no listeners registered with the flow, the error will be936 * rethrown to the global error handler.937 *938 * @param {webdriver.promise.ControlFlow.Timer=} opt_timer The timer object939 *     to use. Should only be set for testing.940 * @constructor941 * @extends {webdriver.EventEmitter}942 */943webdriver.promise.ControlFlow = function(opt_timer) {944  webdriver.EventEmitter.call(this);945946  /**947   * The timer used by this instance.948   * @type {webdriver.promise.ControlFlow.Timer}949   */950  this.timer = opt_timer || webdriver.promise.ControlFlow.defaultTimer;951952  /**953   * A list of recent tasks. Each time a new task is started, or a frame is954   * completed, the previously recorded task is removed from this list. If955   * there are multiple tasks, task N+1 is considered a sub-task of task956   * N.957   * @private {!Array.<!webdriver.promise.Task_>}958   */959  this.history_ = [];960};961goog.inherits(webdriver.promise.ControlFlow, webdriver.EventEmitter);962963964/**965 * @typedef {{clearInterval: function(number),966 *            clearTimeout: function(number),967 *            setInterval: function(!Function, number): number,968 *            setTimeout: function(!Function, number): number}}969 */970webdriver.promise.ControlFlow.Timer;971972973/**974 * The default timer object, which uses the global timer functions.975 * @type {webdriver.promise.ControlFlow.Timer}976 */977webdriver.promise.ControlFlow.defaultTimer = (function() {978  // The default timer functions may be defined as free variables for the979  // current context, so do not reference them using "window" or980  // "goog.global".  Also, we must invoke them in a closure, and not using981  // bind(), so we do not get "TypeError: Illegal invocation" (WebKit) or982  // "Invalid calling object" (IE) errors.983  return {984    clearInterval: wrap(clearInterval),985    clearTimeout: wrap(clearTimeout),986    setInterval: wrap(setInterval),987    setTimeout: wrap(setTimeout)988  };989990  function wrap(fn) {991    return function() {992      // Cannot use .call() or .apply() since we do not know which variable993      // the function is bound to, and using the wrong one will generate994      // an error.995      return fn(arguments[0], arguments[1]);996    };997  }998})();99910001001/**1002 * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.1003 * @enum {string}1004 */1005webdriver.promise.ControlFlow.EventType = {10061007  /** Emitted when all tasks have been successfully executed. */1008  IDLE: 'idle',10091010  /** Emitted whenever a new task has been scheduled. */1011  SCHEDULE_TASK: 'scheduleTask',10121013  /**1014   * Emitted whenever a control flow aborts due to an unhandled promise1015   * rejection. This event will be emitted along with the offending rejection1016   * reason. Upon emitting this event, the control flow will empty its task1017   * queue and revert to its initial state.1018   */1019  UNCAUGHT_EXCEPTION: 'uncaughtException'1020};102110221023/**1024 * How often, in milliseconds, the event loop should run.1025 * @type {number}1026 * @const1027 */1028webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY = 10;102910301031/**1032 * Tracks the active execution frame for this instance. Lazily initialized1033 * when the first task is scheduled.1034 * @private {webdriver.promise.Frame_}1035 */1036webdriver.promise.ControlFlow.prototype.activeFrame_ = null;103710381039/**1040 * A reference to the frame in which new tasks should be scheduled. If1041 * {@code null}, tasks will be scheduled within the active frame. When forcing1042 * a function to run in the context of a new frame, this pointer is used to1043 * ensure tasks are scheduled within the newly created frame, even though it1044 * won't be active yet.1045 * @private {webdriver.promise.Frame_}1046 * @see {#runInNewFrame_}1047 */1048webdriver.promise.ControlFlow.prototype.schedulingFrame_ = null;104910501051/**1052 * Timeout ID set when the flow is about to shutdown without any errors1053 * being detected. Upon shutting down, the flow will emit an1054 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Idle events1055 * always follow a brief timeout in order to catch latent errors from the last1056 * completed task. If this task had a callback registered, but no errback, and1057 * the task fails, the unhandled failure would not be reported by the promise1058 * system until the next turn of the event loop:1059 *1060 *   // Schedule 1 task that fails.1061 *   var result = webriver.promise.controlFlow().schedule('example',1062 *       function() { return webdriver.promise.rejected('failed'); });1063 *   // Set a callback on the result. This delays reporting the unhandled1064 *   // failure for 1 turn of the event loop.1065 *   result.then(goog.nullFunction);1066 *1067 * @private {?number}1068 */1069webdriver.promise.ControlFlow.prototype.shutdownId_ = null;107010711072/**1073 * Interval ID for this instance's event loop.1074 * @private {?number}1075 */1076webdriver.promise.ControlFlow.prototype.eventLoopId_ = null;107710781079/**1080 * The number of "pending" promise rejections.1081 *1082 * <p>Each time a promise is rejected and is not handled by a listener, it will1083 * schedule a 0-based timeout to check if it is still unrejected in the next1084 * turn of the JS-event loop. This allows listeners to attach to, and handle,1085 * the rejected promise at any point in same turn of the event loop that the1086 * promise was rejected.1087 *1088 * <p>When this flow's own event loop triggers, it will not run if there1089 * are any outstanding promise rejections. This allows unhandled promises to1090 * be reported before a new task is started, ensuring the error is reported to1091 * the current task queue.1092 *1093 * @private {number}1094 */1095webdriver.promise.ControlFlow.prototype.pendingRejections_ = 0;109610971098/**1099 * The number of aborted frames since the last time a task was executed or a1100 * frame completed successfully.1101 * @private {number}1102 */1103webdriver.promise.ControlFlow.prototype.numAbortedFrames_ = 0;110411051106/**1107 * Resets this instance, clearing its queue and removing all event listeners.1108 */1109webdriver.promise.ControlFlow.prototype.reset = function() {1110  this.activeFrame_ = null;1111  this.clearHistory();1112  this.removeAllListeners();1113  this.cancelShutdown_();1114  this.cancelEventLoop_();1115};111611171118/**1119 * Returns a summary of the recent task activity for this instance. This1120 * includes the most recently completed task, as well as any parent tasks. In1121 * the returned summary, the task at index N is considered a sub-task of the1122 * task at index N+1.1123 * @return {!Array.<string>} A summary of this instance's recent task1124 *     activity.1125 */1126webdriver.promise.ControlFlow.prototype.getHistory = function() {1127  var pendingTasks = [];1128  var currentFrame = this.activeFrame_;1129  while (currentFrame) {1130    var task = currentFrame.getPendingTask();1131    if (task) {1132      pendingTasks.push(task);1133    }1134    // A frame's parent node will always be another frame.1135    currentFrame =1136        /** @type {webdriver.promise.Frame_} */ (currentFrame.getParent());1137  }11381139  var fullHistory = goog.array.concat(this.history_, pendingTasks);1140  return goog.array.map(fullHistory, function(task) {1141    return task.toString();1142  });1143};114411451146/** Clears this instance's task history. */1147webdriver.promise.ControlFlow.prototype.clearHistory = function() {1148  this.history_ = [];1149};115011511152/**1153 * Removes a completed task from this instance's history record. If any1154 * tasks remain from aborted frames, those will be removed as well.1155 * @private1156 */1157webdriver.promise.ControlFlow.prototype.trimHistory_ = function() {1158  if (this.numAbortedFrames_) {1159    goog.array.splice(this.history_,1160        this.history_.length - this.numAbortedFrames_,1161        this.numAbortedFrames_);1162    this.numAbortedFrames_ = 0;1163  }1164  this.history_.pop();1165};116611671168/**1169 * Property used to track whether an error has been annotated by1170 * {@link webdriver.promise.ControlFlow#annotateError}.1171 * @private {string}1172 * @const1173 */1174webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_ =1175    'webdriver_promise_error_';117611771178/**1179 * Appends a summary of this instance's recent task history to the given1180 * error's stack trace. This function will also ensure the error's stack trace1181 * is in canonical form.1182 * @param {!(Error|goog.testing.JsUnitException)} e The error to annotate.1183 * @return {!(Error|goog.testing.JsUnitException)} The annotated error.1184 */1185webdriver.promise.ControlFlow.prototype.annotateError = function(e) {1186  if (!!e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_]) {1187    return e;1188  }11891190  var history = this.getHistory();1191  if (history.length) {1192    e = webdriver.stacktrace.format(e);11931194    /** @type {!Error} */(e).stack += [1195      '\n==== async task ====\n',1196      history.join('\n==== async task ====\n')1197    ].join('');11981199    e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_] = true;1200  }12011202  return e;1203};120412051206/**1207 * @return {string} The scheduled tasks still pending with this instance.1208 */1209webdriver.promise.ControlFlow.prototype.getSchedule = function() {1210  return this.activeFrame_ ? this.activeFrame_.getRoot().toString() : '[]';1211};121212131214/**1215 * Schedules a task for execution. If there is nothing currently in the1216 * queue, the task will be executed in the next turn of the event loop.1217 *1218 * @param {function(): (T|webdriver.promise.Promise.<T>)} fn The function to1219 *     call to start the task. If the function returns a1220 *     {@link webdriver.promise.Promise}, this instance will wait for it to be1221 *     resolved before starting the next task.1222 * @param {string=} opt_description A description of the task.1223 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved1224 *     with the result of the action.1225 * @template T1226 */1227webdriver.promise.ControlFlow.prototype.execute = function(1228    fn, opt_description) {1229  this.cancelShutdown_();12301231  if (!this.activeFrame_) {1232    this.activeFrame_ = new webdriver.promise.Frame_(this);1233  }12341235  // Trim an extra frame off the generated stack trace for the call to this1236  // function.1237  var snapshot = new webdriver.stacktrace.Snapshot(1);1238  var task = new webdriver.promise.Task_(1239      this, fn, opt_description || '', snapshot);1240  var scheduleIn = this.schedulingFrame_ || this.activeFrame_;1241  scheduleIn.addChild(task);12421243  this.emit(webdriver.promise.ControlFlow.EventType.SCHEDULE_TASK, opt_description);12441245  this.scheduleEventLoopStart_();1246  return task.promise;1247};124812491250/**1251 * Inserts a {@code setTimeout} into the command queue. This is equivalent to1252 * a thread sleep in a synchronous programming language.1253 *1254 * @param {number} ms The timeout delay, in milliseconds.1255 * @param {string=} opt_description A description to accompany the timeout.1256 * @return {!webdriver.promise.Promise} A promise that will be resolved with1257 *     the result of the action.1258 */1259webdriver.promise.ControlFlow.prototype.timeout = function(1260    ms, opt_description) {1261  return this.execute(function() {1262    return webdriver.promise.delayed(ms);1263  }, opt_description);1264};126512661267/**1268 * Schedules a task that shall wait for a condition to hold. Each condition1269 * function may return any value, but it will always be evaluated as a boolean.1270 *1271 * <p>Condition functions may schedule sub-tasks with this instance, however,1272 * their execution time will be factored into whether a wait has timed out.1273 *1274 * <p>In the event a condition returns a Promise, the polling loop will wait for1275 * it to be resolved before evaluating whether the condition has been satisfied.1276 * The resolution time for a promise is factored into whether a wait has timed1277 * out.1278 *1279 * <p>If the condition function throws, or returns a rejected promise, the1280 * wait task will fail.1281 *1282 * @param {!Function} condition The condition function to poll.1283 * @param {number} timeout How long to wait, in milliseconds, for the condition1284 *     to hold before timing out.1285 * @param {string=} opt_message An optional error message to include if the1286 *     wait times out; defaults to the empty string.1287 * @return {!webdriver.promise.Promise} A promise that will be resolved when the1288 *     condition has been satisified. The promise shall be rejected if the wait1289 *     times out waiting for the condition.1290 */1291webdriver.promise.ControlFlow.prototype.wait = function(1292    condition, timeout, opt_message) {1293  var sleep = Math.min(timeout, 100);1294  var self = this;12951296  return this.execute(function() {1297    var startTime = goog.now();1298    var waitResult = new webdriver.promise.Deferred();1299    var waitFrame = self.activeFrame_;1300    waitFrame.isWaiting = true;1301    pollCondition();1302    return waitResult.promise;13031304    function pollCondition() {1305      self.runInNewFrame_(condition, function(value) {1306        var elapsed = goog.now() - startTime;1307        if (!!value) {1308          waitFrame.isWaiting = false;1309          waitResult.fulfill(value);1310        } else if (elapsed >= timeout) {1311          waitResult.reject(new Error((opt_message ? opt_message + '\n' : '') +1312              'Wait timed out after ' + elapsed + 'ms'));1313        } else {1314          self.timer.setTimeout(pollCondition, sleep);1315        }1316      }, waitResult.reject, true);1317    }1318  }, opt_message);1319};132013211322/**1323 * Schedules a task that will wait for another promise to resolve.  The resolved1324 * promise's value will be returned as the task result.1325 * @param {!webdriver.promise.Promise} promise The promise to wait on.1326 * @return {!webdriver.promise.Promise} A promise that will resolve when the1327 *     task has completed.1328 */1329webdriver.promise.ControlFlow.prototype.await = function(promise) {1330  return this.execute(function() {1331    return promise;1332  });1333};133413351336/**1337 * Schedules the interval for this instance's event loop, if necessary.1338 * @private1339 */1340webdriver.promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() {1341  if (!this.eventLoopId_) {1342    this.eventLoopId_ = this.timer.setInterval(1343        goog.bind(this.runEventLoop_, this),1344        webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY);1345  }1346};134713481349/**1350 * Cancels the event loop, if necessary.1351 * @private1352 */1353webdriver.promise.ControlFlow.prototype.cancelEventLoop_ = function() {1354  if (this.eventLoopId_) {1355    this.timer.clearInterval(this.eventLoopId_);1356    this.eventLoopId_ = null;1357  }1358};135913601361/**1362 * Executes the next task for the current frame. If the current frame has no1363 * more tasks, the frame's result will be resolved, returning control to the1364 * frame's creator. This will terminate the flow if the completed frame was at1365 * the top of the stack.1366 * @private1367 */1368webdriver.promise.ControlFlow.prototype.runEventLoop_ = function() {1369  // If we get here and there are pending promise rejections, then those1370  // promises are queued up to run as soon as this (JS) event loop terminates.1371  // Short-circuit our loop to give those promises a chance to run. Otherwise,1372  // we might start a new task only to have it fail because of one of these1373  // pending rejections.1374  if (this.pendingRejections_) {1375    return;1376  }13771378  // If the flow aborts due to an unhandled exception after we've scheduled1379  // another turn of the execution loop, we can end up in here with no tasks1380  // left. This is OK, just quietly return.1381  if (!this.activeFrame_) {1382    this.commenceShutdown_();1383    return;1384  }13851386  var task;1387  if (this.activeFrame_.getPendingTask() || !(task = this.getNextTask_())) {1388    // Either the current frame is blocked on a pending task, or we don't have1389    // a task to finish because we've completed a frame. When completing a1390    // frame, we must abort the event loop to allow the frame's promise's1391    // callbacks to execute.1392    return;1393  }13941395  var activeFrame = this.activeFrame_;1396  activeFrame.setPendingTask(task);1397  var markTaskComplete = goog.bind(function() {1398    this.history_.push(/** @type {!webdriver.promise.Task_} */ (task));1399    activeFrame.setPendingTask(null);1400  }, this);14011402  this.trimHistory_();1403  var self = this;1404  this.runInNewFrame_(task.execute, function(result) {1405    markTaskComplete();1406    task.fulfill(result);1407  }, function(error) {1408    markTaskComplete();14091410    if (!webdriver.promise.isError_(error) &&1411        !webdriver.promise.isPromise(error)) {1412      error = Error(error);1413    }14141415    task.reject(self.annotateError(/** @type {!Error} */ (error)));1416  }, true);1417};141814191420/**1421 * @return {webdriver.promise.Task_} The next task to execute, or1422 *     {@code null} if a frame was resolved.1423 * @private1424 */1425webdriver.promise.ControlFlow.prototype.getNextTask_ = function() {1426  var firstChild = this.activeFrame_.getFirstChild();1427  if (!firstChild) {1428    if (!this.activeFrame_.isWaiting) {1429      this.resolveFrame_(this.activeFrame_);1430    }1431    return null;1432  }14331434  if (firstChild instanceof webdriver.promise.Frame_) {1435    this.activeFrame_ = firstChild;1436    return this.getNextTask_();1437  }14381439  firstChild.getParent().removeChild(firstChild);1440  return firstChild;1441};144214431444/**1445 * @param {!webdriver.promise.Frame_} frame The frame to resolve.1446 * @private1447 */1448webdriver.promise.ControlFlow.prototype.resolveFrame_ = function(frame) {1449  if (this.activeFrame_ === frame) {1450    // Frame parent is always another frame, but the compiler is not smart1451    // enough to recognize this.1452    this.activeFrame_ =1453        /** @type {webdriver.promise.Frame_} */ (frame.getParent());1454  }14551456  if (frame.getParent()) {1457    frame.getParent().removeChild(frame);1458  }1459  this.trimHistory_();1460  frame.fulfill();14611462  if (!this.activeFrame_) {1463    this.commenceShutdown_();1464  }1465};146614671468/**1469 * Aborts the current frame. The frame, and all of the tasks scheduled within it1470 * will be discarded. If this instance does not have an active frame, it will1471 * immediately terminate all execution.1472 * @param {*} error The reason the frame is being aborted; typically either1473 *     an Error or string.1474 * @private1475 */1476webdriver.promise.ControlFlow.prototype.abortFrame_ = function(error) {1477  // Annotate the error value if it is Error-like.1478  if (webdriver.promise.isError_(error)) {1479    this.annotateError(/** @type {!Error} */ (error));1480  }1481  this.numAbortedFrames_++;14821483  if (!this.activeFrame_) {1484    this.abortNow_(error);1485    return;1486  }14871488  // Frame parent is always another frame, but the compiler is not smart1489  // enough to recognize this.1490  var parent = /** @type {webdriver.promise.Frame_} */ (1491      this.activeFrame_.getParent());1492  if (parent) {1493    parent.removeChild(this.activeFrame_);1494  }14951496  var frame = this.activeFrame_;1497  this.activeFrame_ = parent;1498  frame.reject(error);1499};150015011502/**1503 * Executes a function in a new frame. If the function does not schedule any new1504 * tasks, the frame will be discarded and the function's result returned1505 * immediately. Otherwise, a promise will be returned. This promise will be1506 * resolved with the function's result once all of the tasks scheduled within1507 * the function have been completed. If the function's frame is aborted, the1508 * returned promise will be rejected.1509 *1510 * @param {!Function} fn The function to execute.1511 * @param {function(*)} callback The function to call with a successful result.1512 * @param {function(*)} errback The function to call if there is an error.1513 * @param {boolean=} opt_activate Whether the active frame should be updated to1514 *     the newly created frame so tasks are treated as sub-tasks.1515 * @private1516 */1517webdriver.promise.ControlFlow.prototype.runInNewFrame_ = function(1518    fn, callback, errback, opt_activate) {1519  var newFrame = new webdriver.promise.Frame_(this),1520      self = this,1521      oldFrame = this.activeFrame_;15221523  try {1524    if (!this.activeFrame_) {1525      this.activeFrame_ = newFrame;1526    } else {1527      this.activeFrame_.addChild(newFrame);1528    }15291530    // Activate the new frame to force tasks to be treated as sub-tasks of1531    // the parent frame.1532    if (opt_activate) {1533      this.activeFrame_ = newFrame;1534    }15351536    try {1537      this.schedulingFrame_ = newFrame;1538      webdriver.promise.pushFlow_(this);1539      var result = fn();1540    } finally {1541      webdriver.promise.popFlow_();1542      this.schedulingFrame_ = null;1543    }1544    newFrame.lockFrame();15451546    // If there was nothing scheduled in the new frame we can discard the1547    // frame and return immediately.1548    if (!newFrame.children_.length) {1549      removeNewFrame();1550      webdriver.promise.asap(result, callback, errback);1551      return;1552    }15531554    newFrame.then(function() {1555      webdriver.promise.asap(result, callback, errback);1556    }, function(e) {1557      if (result instanceof webdriver.promise.Promise && result.isPending()) {1558        result.cancel(e);1559        e = result;1560      }1561      errback(e);1562    });1563  } catch (ex) {1564    removeNewFrame(new webdriver.promise.CanceledTaskError_(ex));1565    errback(ex);1566  }15671568  /**1569   * @param {webdriver.promise.CanceledTaskError_=} opt_err If provided, the1570   *     error that triggered the removal of this frame.1571   */1572  function removeNewFrame(opt_err) {1573    var parent = newFrame.getParent();1574    if (parent) {1575      parent.removeChild(newFrame);1576    }15771578    if (opt_err) {1579      newFrame.cancelRemainingTasks(opt_err);1580    }1581    self.activeFrame_ = oldFrame;1582  }1583};158415851586/**1587 * Commences the shutdown sequence for this instance. After one turn of the1588 * event loop, this object will emit the1589 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event to signal1590 * listeners that it has completed. During this wait, if another task is1591 * scheduled, the shutdown will be aborted.1592 * @private1593 */1594webdriver.promise.ControlFlow.prototype.commenceShutdown_ = function() {1595  if (!this.shutdownId_) {1596    // Go ahead and stop the event loop now.  If we're in here, then there are1597    // no more frames with tasks to execute. If we waited to cancel the event1598    // loop in our timeout below, the event loop could trigger *before* the1599    // timeout, generating an error from there being no frames.1600    // If #execute is called before the timeout below fires, it will cancel1601    // the timeout and restart the event loop.1602    this.cancelEventLoop_();16031604    var self = this;1605    self.shutdownId_ = self.timer.setTimeout(function() {1606      self.shutdownId_ = null;1607      self.emit(webdriver.promise.ControlFlow.EventType.IDLE);1608    }, 0);1609  }1610};161116121613/**1614 * Cancels the shutdown sequence if it is currently scheduled.1615 * @private1616 */1617webdriver.promise.ControlFlow.prototype.cancelShutdown_ = function() {1618  if (this.shutdownId_) {1619    this.timer.clearTimeout(this.shutdownId_);1620    this.shutdownId_ = null;1621  }1622};162316241625/**1626 * Aborts this flow, abandoning all remaining tasks. If there are1627 * listeners registered, an {@code UNCAUGHT_EXCEPTION} will be emitted with the1628 * offending {@code error}, otherwise, the {@code error} will be rethrown to the1629 * global error handler.1630 * @param {*} error Object describing the error that caused the flow to1631 *     abort; usually either an Error or string value.1632 * @private1633 */1634webdriver.promise.ControlFlow.prototype.abortNow_ = function(error) {1635  this.activeFrame_ = null;1636  this.cancelShutdown_();1637  this.cancelEventLoop_();16381639  var listeners = this.listeners(1640      webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);1641  if (!listeners.length) {1642    this.timer.setTimeout(function() {1643      throw error;1644    }, 0);1645  } else {1646    this.emit(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,1647        error);1648  }1649};1650165116521653/**1654 * A single node in an {@link webdriver.promise.ControlFlow}'s task tree.1655 * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs1656 *     to.1657 * @constructor1658 * @extends {webdriver.promise.Deferred}1659 * @private1660 */1661webdriver.promise.Node_ = function(flow) {1662  webdriver.promise.Deferred.call(this, null, flow);1663};1664goog.inherits(webdriver.promise.Node_, webdriver.promise.Deferred);166516661667/**1668 * This node's parent.1669 * @private {webdriver.promise.Node_}1670 */1671webdriver.promise.Node_.prototype.parent_ = null;167216731674/** @return {webdriver.promise.Node_} This node's parent. */1675webdriver.promise.Node_.prototype.getParent = function() {1676  return this.parent_;1677};167816791680/**1681 * @param {webdriver.promise.Node_} parent This node's new parent.1682 */1683webdriver.promise.Node_.prototype.setParent = function(parent) {1684  this.parent_ = parent;1685};168616871688/**1689 * @return {!webdriver.promise.Node_} The root of this node's tree.1690 */1691webdriver.promise.Node_.prototype.getRoot = function() {1692  var root = this;1693  while (root.parent_) {1694    root = root.parent_;1695  }1696  return root;1697};1698169917001701/**1702 * An execution frame within a {@link webdriver.promise.ControlFlow}.  Each1703 * frame represents the execution context for either a1704 * {@link webdriver.promise.Task_} or a callback on a1705 * {@link webdriver.promise.Deferred}.1706 *1707 * <p>Each frame may contain sub-frames.  If child N is a sub-frame, then the1708 * items queued within it are given priority over child N+1.1709 *1710 * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs1711 *     to.1712 * @constructor1713 * @extends {webdriver.promise.Node_}1714 * @private1715 */1716webdriver.promise.Frame_ = function(flow) {1717  webdriver.promise.Node_.call(this, flow);17181719  var reject = goog.bind(this.reject, this);1720  var cancelRemainingTasks = goog.bind(this.cancelRemainingTasks, this);17211722  /** @override */1723  this.reject = function(e) {1724    cancelRemainingTasks(new webdriver.promise.CanceledTaskError_(e));1725    reject(e);1726  };17271728  /**1729   * @private {!Array.<!(webdriver.promise.Frame_|webdriver.promise.Task_)>}1730   */1731  this.children_ = [];1732};1733goog.inherits(webdriver.promise.Frame_, webdriver.promise.Node_);173417351736/**1737 * The task currently being executed within this frame.1738 * @private {webdriver.promise.Task_}1739 */1740webdriver.promise.Frame_.prototype.pendingTask_ = null;174117421743/**1744 * Whether this frame is active. A frame is considered active once one of its1745 * descendants has been removed for execution.1746 *1747 * Adding a sub-frame as a child to an active frame is an indication that1748 * a callback to a {@link webdriver.promise.Deferred} is being invoked and any1749 * tasks scheduled within it should have priority over previously scheduled1750 * tasks:1751 * <code><pre>1752 *   var flow = webdriver.promise.controlFlow();1753 *   flow.execute('start here', goog.nullFunction).then(function() {1754 *     flow.execute('this should execute 2nd', goog.nullFunction);1755 *   });1756 *   flow.execute('this should execute last', goog.nullFunction);1757 * </pre></code>1758 *1759 * @private {boolean}1760 */1761webdriver.promise.Frame_.prototype.isActive_ = false;176217631764/**1765 * Whether this frame is currently locked. A locked frame represents a callback1766 * or task function which has run to completion and scheduled all of its tasks.1767 *1768 * <p>Once a frame becomes {@link #isActive_ active}, any new frames which are1769 * added represent callbacks on a {@link webdriver.promise.Deferred}, whose1770 * tasks must be given priority over previously scheduled tasks.1771 *1772 * @private {boolean}1773 */1774webdriver.promise.Frame_.prototype.isLocked_ = false;177517761777/**1778 * A reference to the last node inserted in this frame.1779 * @private {webdriver.promise.Node_}1780 */1781webdriver.promise.Frame_.prototype.lastInsertedChild_ = null;178217831784/**1785 * Marks all of the tasks that are descendants of this frame in the execution1786 * tree as cancelled. This is necessary for callbacks scheduled asynchronous.1787 * For example:1788 *1789 *     var someResult;1790 *     webdriver.promise.createFlow(function(flow) {1791 *       someResult = flow.execute(function() {});1792 *       throw Error();1793 *     }).addErrback(function(err) {1794 *       console.log('flow failed: ' + err);1795 *       someResult.then(function() {1796 *         console.log('task succeeded!');1797 *       }, function(err) {1798 *         console.log('task failed! ' + err);1799 *       });1800 *     });1801 *     // flow failed: Error: boom1802 *     // task failed! CanceledTaskError: Task discarded due to a previous1803 *     // task failure: Error: boom1804 *1805 * @param {!webdriver.promise.CanceledTaskError_} error The cancellation1806 *     error.1807 */1808webdriver.promise.Frame_.prototype.cancelRemainingTasks = function(error) {1809  goog.array.forEach(this.children_, function(child) {1810    if (child instanceof webdriver.promise.Frame_) {1811      child.cancelRemainingTasks(error);1812    } else {1813      // None of the previously registered listeners should be notified that1814      // the task is being canceled, however, we need at least one errback1815      // to prevent the cancellation from bubbling up.1816      child.removeAll();1817      child.thenCatch(goog.nullFunction);1818      child.cancel(error);1819    }1820  });1821};182218231824/**1825 * @return {webdriver.promise.Task_} The task currently executing1826 *     within this frame, if any.1827 */1828webdriver.promise.Frame_.prototype.getPendingTask = function() {1829  return this.pendingTask_;1830};183118321833/**1834 * @param {webdriver.promise.Task_} task The task currently1835 *     executing within this frame, if any.1836 */1837webdriver.promise.Frame_.prototype.setPendingTask = function(task) {1838  this.pendingTask_ = task;1839};184018411842/** Locks this frame. */1843webdriver.promise.Frame_.prototype.lockFrame = function() {1844  this.isLocked_ = true;1845};184618471848/**1849 * Adds a new node to this frame.1850 * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} node1851 *     The node to insert.1852 */1853webdriver.promise.Frame_.prototype.addChild = function(node) {1854  if (this.lastInsertedChild_ &&1855      this.lastInsertedChild_ instanceof webdriver.promise.Frame_ &&1856      !this.lastInsertedChild_.isLocked_) {1857    this.lastInsertedChild_.addChild(node);1858    return;1859  }18601861  node.setParent(this);18621863  if (this.isActive_ && node instanceof webdriver.promise.Frame_) {1864    var index = 0;1865    if (this.lastInsertedChild_ instanceof1866        webdriver.promise.Frame_) {1867      index = goog.array.indexOf(this.children_, this.lastInsertedChild_) + 1;1868    }1869    goog.array.insertAt(this.children_, node, index);1870    this.lastInsertedChild_ = node;1871    return;1872  }18731874  this.lastInsertedChild_ = node;1875  this.children_.push(node);1876};187718781879/**1880 * @return {(webdriver.promise.Frame_|webdriver.promise.Task_)} This frame's1881 *     fist child.1882 */1883webdriver.promise.Frame_.prototype.getFirstChild = function() {1884  this.isActive_ = true;1885  this.lastInsertedChild_ = null;1886  return this.children_[0];1887};188818891890/**1891 * Removes a child from this frame.1892 * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} child1893 *     The child to remove.1894 */1895webdriver.promise.Frame_.prototype.removeChild = function(child) {1896  var index = goog.array.indexOf(this.children_, child);1897  child.setParent(null);1898  goog.array.removeAt(this.children_, index);1899  if (this.lastInsertedChild_ === child) {1900    this.lastInsertedChild_ = null;1901  }1902};190319041905/** @override */1906webdriver.promise.Frame_.prototype.toString = function() {1907  return '[' + goog.array.map(this.children_, function(child) {1908    return child.toString();1909  }).join(', ') + ']';1910};1911191219131914/**1915 * A task to be executed by a {@link webdriver.promise.ControlFlow}.1916 *1917 * @param {!webdriver.promise.ControlFlow} flow The flow this instances belongs1918 *     to.1919 * @param {!Function} fn The function to call when the task executes. If it1920 *     returns a {@code webdriver.promise.Promise}, the flow will wait1921 *     for it to be resolved before starting the next task.1922 * @param {string} description A description of the task for debugging.1923 * @param {!webdriver.stacktrace.Snapshot} snapshot A snapshot of the stack1924 *     when this task was scheduled.1925 * @constructor1926 * @extends {webdriver.promise.Node_}1927 * @private1928 */1929webdriver.promise.Task_ = function(flow, fn, description, snapshot) {1930  webdriver.promise.Node_.call(this, flow);19311932  /**1933   * Executes this task.1934   * @type {!Function}1935   */1936  this.execute = fn;19371938  /** @private {string} */1939  this.description_ = description;19401941  /** @private {!webdriver.stacktrace.Snapshot} */1942  this.snapshot_ = snapshot;1943};1944goog.inherits(webdriver.promise.Task_, webdriver.promise.Node_);194519461947/** @return {string} This task's description. */1948webdriver.promise.Task_.prototype.getDescription = function() {1949  return this.description_;1950};195119521953/** @override */1954webdriver.promise.Task_.prototype.toString = function() {1955  var stack = this.snapshot_.getStacktrace();1956  var ret = this.description_;1957  if (stack.length) {1958    if (this.description_) {1959      ret += '\n';1960    }1961    ret += stack.join('\n');1962  }1963  return ret;1964};1965196619671968/**1969 * Special error used to signal when a task is canceled because a previous1970 * task in the same frame failed.1971 * @param {*} err The error that caused the task cancellation.1972 * @constructor1973 * @extends {goog.debug.Error}1974 * @private1975 */1976webdriver.promise.CanceledTaskError_ = function(err) {1977  goog.base(this, 'Task discarded due to a previous task failure: ' + err);1978};1979goog.inherits(webdriver.promise.CanceledTaskError_, goog.debug.Error);198019811982/** @override */1983webdriver.promise.CanceledTaskError_.prototype.name = 'CanceledTaskError';1984198519861987/**1988 * The default flow to use if no others are active.1989 * @private {!webdriver.promise.ControlFlow}1990 */1991webdriver.promise.defaultFlow_ = new webdriver.promise.ControlFlow();199219931994/**1995 * A stack of active control flows, with the top of the stack used to schedule1996 * commands. When there are multiple flows on the stack, the flow at index N1997 * represents a callback triggered within a task owned by the flow at index1998 * N-1.1999 * @private {!Array.<!webdriver.promise.ControlFlow>}2000 */2001webdriver.promise.activeFlows_ = [];200220032004/**2005 * Changes the default flow to use when no others are active.2006 * @param {!webdriver.promise.ControlFlow} flow The new default flow.2007 * @throws {Error} If the default flow is not currently active.2008 */2009webdriver.promise.setDefaultFlow = function(flow) {2010  if (webdriver.promise.activeFlows_.length) {2011    throw Error('You may only change the default flow while it is active');2012  }2013  webdriver.promise.defaultFlow_ = flow;2014};201520162017/**2018 * @return {!webdriver.promise.ControlFlow} The currently active control flow.2019 */2020webdriver.promise.controlFlow = function() {2021  return /** @type {!webdriver.promise.ControlFlow} */ (2022      goog.array.peek(webdriver.promise.activeFlows_) ||2023      webdriver.promise.defaultFlow_);2024};202520262027/**2028 * @param {!webdriver.promise.ControlFlow} flow The new flow.2029 * @private2030 */2031webdriver.promise.pushFlow_ = function(flow) {2032  webdriver.promise.activeFlows_.push(flow);2033};203420352036/** @private */2037webdriver.promise.popFlow_ = function() {2038  webdriver.promise.activeFlows_.pop();2039};204020412042/**2043 * Creates a new control flow. The provided callback will be invoked as the2044 * first task within the new flow, with the flow as its sole argument. Returns2045 * a promise that resolves to the callback result.2046 * @param {function(!webdriver.promise.ControlFlow)} callback The entry point2047 *     to the newly created flow.2048 * @return {!webdriver.promise.Promise} A promise that resolves to the callback2049 *     result.2050 */2051webdriver.promise.createFlow = function(callback) {2052  var flow = new webdriver.promise.ControlFlow(2053      webdriver.promise.defaultFlow_.timer);2054  return flow.execute(function() {2055    return callback(flow);2056  });2057};