lib/webdriver/promise.js

1// Licensed to the Software Freedom Conservancy (SFC) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The SFC licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18/**
19 * @license Portions of this code are from the Dojo toolkit, received under the
20 * BSD License:
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions are met:
23 *
24 * * Redistributions of source code must retain the above copyright notice,
25 * this list of conditions and the following disclaimer.
26 * * Redistributions in binary form must reproduce the above copyright notice,
27 * this list of conditions and the following disclaimer in the documentation
28 * and/or other materials provided with the distribution.
29 * * Neither the name of the Dojo Foundation nor the names of its contributors
30 * may be used to endorse or promote products derived from this software
31 * without specific prior written permission.
32 *
33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
34 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
37 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
38 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
39 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
40 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
41 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
43 * POSSIBILITY OF SUCH DAMAGE.
44 */
45
46/**
47 * @fileoverview A promise implementation based on the CommonJS promise/A and
48 * promise/B proposals. For more information, see
49 * http://wiki.commonjs.org/wiki/Promises.
50 */
51
52goog.module('webdriver.promise');
53goog.module.declareLegacyNamespace();
54
55var Arrays = goog.require('goog.array');
56var asserts = goog.require('goog.asserts');
57var asyncRun = goog.require('goog.async.run');
58var throwException = goog.require('goog.async.throwException');
59var DebugError = goog.require('goog.debug.Error');
60var Objects = goog.require('goog.object');
61var EventEmitter = goog.require('webdriver.EventEmitter');
62var stacktrace = goog.require('webdriver.stacktrace');
63
64
65
66/**
67 * @define {boolean} Whether to append traces of {@code then} to rejection
68 * errors.
69 */
70goog.define('webdriver.promise.LONG_STACK_TRACES', false);
71
72/** @const */
73var promise = exports;
74
75
76/**
77 * Generates an error to capture the current stack trace.
78 * @param {string} name Error name for this stack trace.
79 * @param {string} msg Message to record.
80 * @param {!Function} topFn The function that should appear at the top of the
81 * stack; only applicable in V8.
82 * @return {!Error} The generated error.
83 */
84promise.captureStackTrace = function(name, msg, topFn) {
85 var e = Error(msg);
86 e.name = name;
87 if (Error.captureStackTrace) {
88 Error.captureStackTrace(e, topFn);
89 } else {
90 var stack = stacktrace.getStack(e);
91 e.stack = e.toString();
92 if (stack) {
93 e.stack += '\n' + stack;
94 }
95 }
96 return e;
97};
98
99
100/**
101 * Error used when the computation of a promise is cancelled.
102 *
103 * @param {string=} opt_msg The cancellation message.
104 * @constructor
105 * @extends {DebugError}
106 * @final
107 */
108promise.CancellationError = function(opt_msg) {
109 DebugError.call(this, opt_msg);
110
111 /** @override */
112 this.name = 'CancellationError';
113};
114goog.inherits(promise.CancellationError, DebugError);
115
116
117/**
118 * Wraps the given error in a CancellationError. Will trivially return
119 * the error itself if it is an instanceof CancellationError.
120 *
121 * @param {*} error The error to wrap.
122 * @param {string=} opt_msg The prefix message to use.
123 * @return {!promise.CancellationError} A cancellation error.
124 */
125promise.CancellationError.wrap = function(error, opt_msg) {
126 if (error instanceof promise.CancellationError) {
127 return /** @type {!promise.CancellationError} */(error);
128 } else if (opt_msg) {
129 var message = opt_msg;
130 if (error) {
131 message += ': ' + error;
132 }
133 return new promise.CancellationError(message);
134 }
135 var message;
136 if (error) {
137 message = error + '';
138 }
139 return new promise.CancellationError(message);
140};
141
142
143
144/**
145 * Thenable is a promise-like object with a {@code then} method which may be
146 * used to schedule callbacks on a promised value.
147 *
148 * @interface
149 * @extends {IThenable<T>}
150 * @template T
151 */
152promise.Thenable = function() {};
153
154
155/**
156 * Cancels the computation of this promise's value, rejecting the promise in the
157 * process. This method is a no-op if the promise has already been resolved.
158 *
159 * @param {(string|promise.CancellationError)=} opt_reason The reason this
160 * promise is being cancelled.
161 */
162promise.Thenable.prototype.cancel = function(opt_reason) {};
163
164
165/** @return {boolean} Whether this promise's value is still being computed. */
166promise.Thenable.prototype.isPending = function() {};
167
168
169/**
170 * Registers listeners for when this instance is resolved.
171 *
172 * @param {?(function(T): (R|IThenable<R>))=} opt_callback The
173 * function to call if this promise is successfully resolved. The function
174 * should expect a single argument: the promise's resolved value.
175 * @param {?(function(*): (R|IThenable<R>))=} opt_errback
176 * The function to call if this promise is rejected. The function should
177 * expect a single argument: the rejection reason.
178 * @return {!promise.Promise<R>} A new promise which will be
179 * resolved with the result of the invoked callback.
180 * @template R
181 */
182promise.Thenable.prototype.then = function(opt_callback, opt_errback) {};
183
184
185/**
186 * Registers a listener for when this promise is rejected. This is synonymous
187 * with the {@code catch} clause in a synchronous API:
188 *
189 * // Synchronous API:
190 * try {
191 * doSynchronousWork();
192 * } catch (ex) {
193 * console.error(ex);
194 * }
195 *
196 * // Asynchronous promise API:
197 * doAsynchronousWork().thenCatch(function(ex) {
198 * console.error(ex);
199 * });
200 *
201 * @param {function(*): (R|IThenable<R>)} errback The
202 * function to call if this promise is rejected. The function should
203 * expect a single argument: the rejection reason.
204 * @return {!promise.Promise<R>} A new promise which will be
205 * resolved with the result of the invoked callback.
206 * @template R
207 */
208promise.Thenable.prototype.thenCatch = function(errback) {};
209
210
211/**
212 * Registers a listener to invoke when this promise is resolved, regardless
213 * of whether the promise's value was successfully computed. This function
214 * is synonymous with the {@code finally} clause in a synchronous API:
215 *
216 * // Synchronous API:
217 * try {
218 * doSynchronousWork();
219 * } finally {
220 * cleanUp();
221 * }
222 *
223 * // Asynchronous promise API:
224 * doAsynchronousWork().thenFinally(cleanUp);
225 *
226 * __Note:__ similar to the {@code finally} clause, if the registered
227 * callback returns a rejected promise or throws an error, it will silently
228 * replace the rejection error (if any) from this promise:
229 *
230 * try {
231 * throw Error('one');
232 * } finally {
233 * throw Error('two'); // Hides Error: one
234 * }
235 *
236 * promise.rejected(Error('one'))
237 * .thenFinally(function() {
238 * throw Error('two'); // Hides Error: one
239 * });
240 *
241 * @param {function(): (R|IThenable<R>)} callback The function
242 * to call when this promise is resolved.
243 * @return {!promise.Promise<R>} A promise that will be fulfilled
244 * with the callback result.
245 * @template R
246 */
247promise.Thenable.prototype.thenFinally = function(callback) {};
248
249
250/**
251 * Property used to flag constructor's as implementing the Thenable interface
252 * for runtime type checking.
253 * @type {string}
254 * @const
255 */
256var IMPLEMENTED_BY_PROP = '$webdriver_Thenable';
257
258
259/**
260 * Adds a property to a class prototype to allow runtime checks of whether
261 * instances of that class implement the Thenable interface. This function will
262 * also ensure the prototype's {@code then} function is exported from compiled
263 * code.
264 * @param {function(new: promise.Thenable, ...?)} ctor The
265 * constructor whose prototype to modify.
266 */
267promise.Thenable.addImplementation = function(ctor) {
268 // Based on goog.promise.Thenable.isImplementation.
269 ctor.prototype['then'] = ctor.prototype.then;
270 try {
271 // Old IE7 does not support defineProperty; IE8 only supports it for
272 // DOM elements.
273 Object.defineProperty(
274 ctor.prototype,
275 IMPLEMENTED_BY_PROP,
276 {'value': true, 'enumerable': false});
277 } catch (ex) {
278 ctor.prototype[IMPLEMENTED_BY_PROP] = true;
279 }
280};
281
282
283/**
284 * Checks if an object has been tagged for implementing the Thenable interface
285 * as defined by {@link webdriver.promise.Thenable.addImplementation}.
286 * @param {*} object The object to test.
287 * @return {boolean} Whether the object is an implementation of the Thenable
288 * interface.
289 */
290promise.Thenable.isImplementation = function(object) {
291 // Based on goog.promise.Thenable.isImplementation.
292 if (!object) {
293 return false;
294 }
295 try {
296 return !!object[IMPLEMENTED_BY_PROP];
297 } catch (e) {
298 return false; // Property access seems to be forbidden.
299 }
300};
301
302
303
304/**
305 * @enum {string}
306 */
307var PromiseState = {
308 PENDING: 'pending',
309 BLOCKED: 'blocked',
310 REJECTED: 'rejected',
311 FULFILLED: 'fulfilled'
312};
313
314
315
316/**
317 * Represents the eventual value of a completed operation. Each promise may be
318 * in one of three states: pending, fulfilled, or rejected. Each promise starts
319 * in the pending state and may make a single transition to either a
320 * fulfilled or rejected state, at which point the promise is considered
321 * resolved.
322 *
323 * @param {function(
324 * function((T|IThenable<T>|Thenable)=),
325 * function(*=))} resolver
326 * Function that is invoked immediately to begin computation of this
327 * promise's value. The function should accept a pair of callback functions,
328 * one for fulfilling the promise and another for rejecting it.
329 * @param {promise.ControlFlow=} opt_flow The control flow
330 * this instance was created under. Defaults to the currently active flow.
331 * @constructor
332 * @implements {promise.Thenable<T>}
333 * @template T
334 * @see http://promises-aplus.github.io/promises-spec/
335 */
336promise.Promise = function(resolver, opt_flow) {
337 goog.getUid(this);
338
339 /** @private {!promise.ControlFlow} */
340 this.flow_ = opt_flow || promise.controlFlow();
341
342 /** @private {Error} */
343 this.stack_ = null;
344 if (promise.LONG_STACK_TRACES) {
345 this.stack_ = promise.captureStackTrace('Promise', 'new', promise.Promise);
346 }
347
348 /** @private {promise.Promise<?>} */
349 this.parent_ = null;
350
351 /** @private {Array<!Callback>} */
352 this.callbacks_ = null;
353
354 /** @private {PromiseState} */
355 this.state_ = PromiseState.PENDING;
356
357 /** @private {boolean} */
358 this.handled_ = false;
359
360 /** @private {boolean} */
361 this.pendingNotifications_ = false;
362
363 /** @private {*} */
364 this.value_ = undefined;
365
366 try {
367 var self = this;
368 resolver(function(value) {
369 self.resolve_(PromiseState.FULFILLED, value);
370 }, function(reason) {
371 self.resolve_(PromiseState.REJECTED, reason);
372 });
373 } catch (ex) {
374 this.resolve_(PromiseState.REJECTED, ex);
375 }
376};
377promise.Thenable.addImplementation(promise.Promise);
378
379
380/** @override */
381promise.Promise.prototype.toString = function() {
382 return 'Promise::' + goog.getUid(this) +
383 ' {[[PromiseStatus]]: "' + this.state_ + '"}';
384};
385
386
387/**
388 * Resolves this promise. If the new value is itself a promise, this function
389 * will wait for it to be resolved before notifying the registered listeners.
390 * @param {PromiseState} newState The promise's new state.
391 * @param {*} newValue The promise's new value.
392 * @throws {TypeError} If {@code newValue === this}.
393 * @private
394 */
395promise.Promise.prototype.resolve_ = function(newState, newValue) {
396 if (PromiseState.PENDING !== this.state_) {
397 return;
398 }
399
400 if (newValue === this) {
401 // See promise a+, 2.3.1
402 // http://promises-aplus.github.io/promises-spec/#point-48
403 throw new TypeError('A promise may not resolve to itself');
404 }
405
406 this.parent_ = null;
407 this.state_ = PromiseState.BLOCKED;
408
409 if (promise.Thenable.isImplementation(newValue)) {
410 // 2.3.2
411 newValue = /** @type {!promise.Thenable} */(newValue);
412 newValue.then(
413 this.unblockAndResolve_.bind(this, PromiseState.FULFILLED),
414 this.unblockAndResolve_.bind(this, PromiseState.REJECTED));
415 return;
416
417 } else if (goog.isObject(newValue)) {
418 // 2.3.3
419
420 try {
421 // 2.3.3.1
422 var then = newValue['then'];
423 } catch (e) {
424 // 2.3.3.2
425 this.state_ = PromiseState.REJECTED;
426 this.value_ = e;
427 this.scheduleNotifications_();
428 return;
429 }
430
431 // NB: goog.isFunction is loose and will accept instanceof Function.
432 if (typeof then === 'function') {
433 // 2.3.3.3
434 this.invokeThen_(newValue, then);
435 return;
436 }
437 }
438
439 if (newState === PromiseState.REJECTED &&
440 isError(newValue) && newValue.stack && this.stack_) {
441 newValue.stack += '\nFrom: ' + (this.stack_.stack || this.stack_);
442 }
443
444 // 2.3.3.4 and 2.3.4
445 this.state_ = newState;
446 this.value_ = newValue;
447 this.scheduleNotifications_();
448};
449
450
451/**
452 * Invokes a thenable's "then" method according to 2.3.3.3 of the promise
453 * A+ spec.
454 * @param {!Object} x The thenable object.
455 * @param {!Function} then The "then" function to invoke.
456 * @private
457 */
458promise.Promise.prototype.invokeThen_ = function(x, then) {
459 var called = false;
460 var self = this;
461
462 var resolvePromise = function(value) {
463 if (!called) { // 2.3.3.3.3
464 called = true;
465 // 2.3.3.3.1
466 self.unblockAndResolve_(PromiseState.FULFILLED, value);
467 }
468 };
469
470 var rejectPromise = function(reason) {
471 if (!called) { // 2.3.3.3.3
472 called = true;
473 // 2.3.3.3.2
474 self.unblockAndResolve_(PromiseState.REJECTED, reason);
475 }
476 };
477
478 try {
479 // 2.3.3.3
480 then.call(x, resolvePromise, rejectPromise);
481 } catch (e) {
482 // 2.3.3.3.4.2
483 rejectPromise(e);
484 }
485};
486
487
488/**
489 * @param {PromiseState} newState The promise's new state.
490 * @param {*} newValue The promise's new value.
491 * @private
492 */
493promise.Promise.prototype.unblockAndResolve_ = function(newState, newValue) {
494 if (this.state_ === PromiseState.BLOCKED) {
495 this.state_ = PromiseState.PENDING;
496 this.resolve_(newState, newValue);
497 }
498};
499
500
501/**
502 * @private
503 */
504promise.Promise.prototype.scheduleNotifications_ = function() {
505 if (!this.pendingNotifications_) {
506 this.pendingNotifications_ = true;
507 this.flow_.suspend_();
508
509 var activeFrame;
510
511 if (!this.handled_ &&
512 this.state_ === PromiseState.REJECTED &&
513 !(this.value_ instanceof promise.CancellationError)) {
514 activeFrame = this.flow_.getActiveFrame_();
515 activeFrame.pendingRejection = true;
516 }
517
518 if (this.callbacks_ && this.callbacks_.length) {
519 activeFrame = this.flow_.getRunningFrame_();
520 var self = this;
521 this.callbacks_.forEach(function(callback) {
522 if (!callback.frame_.getParent()) {
523 activeFrame.addChild(callback.frame_);
524 }
525 });
526 }
527
528 asyncRun(goog.bind(this.notifyAll_, this, activeFrame));
529 }
530};
531
532
533/**
534 * Notifies all of the listeners registered with this promise that its state
535 * has changed.
536 * @param {Frame} frame The active frame from when this round of
537 * notifications were scheduled.
538 * @private
539 */
540promise.Promise.prototype.notifyAll_ = function(frame) {
541 this.flow_.resume_();
542 this.pendingNotifications_ = false;
543
544 if (!this.handled_ &&
545 this.state_ === PromiseState.REJECTED &&
546 !(this.value_ instanceof promise.CancellationError)) {
547 this.flow_.abortFrame_(this.value_, frame);
548 }
549
550 if (this.callbacks_) {
551 var callbacks = this.callbacks_;
552 this.callbacks_ = null;
553 callbacks.forEach(this.notify_, this);
554 }
555};
556
557
558/**
559 * Notifies a single callback of this promise's change ins tate.
560 * @param {Callback} callback The callback to notify.
561 * @private
562 */
563promise.Promise.prototype.notify_ = function(callback) {
564 callback.notify(this.state_, this.value_);
565};
566
567
568/** @override */
569promise.Promise.prototype.cancel = function(opt_reason) {
570 if (!this.isPending()) {
571 return;
572 }
573
574 if (this.parent_) {
575 this.parent_.cancel(opt_reason);
576 } else {
577 this.resolve_(
578 PromiseState.REJECTED,
579 promise.CancellationError.wrap(opt_reason));
580 }
581};
582
583
584/** @override */
585promise.Promise.prototype.isPending = function() {
586 return this.state_ === PromiseState.PENDING;
587};
588
589
590/** @override */
591promise.Promise.prototype.then = function(opt_callback, opt_errback) {
592 return this.addCallback_(
593 opt_callback, opt_errback, 'then', promise.Promise.prototype.then);
594};
595
596
597/** @override */
598promise.Promise.prototype.thenCatch = function(errback) {
599 return this.addCallback_(
600 null, errback, 'thenCatch', promise.Promise.prototype.thenCatch);
601};
602
603
604/** @override */
605promise.Promise.prototype.thenFinally = function(callback) {
606 var error;
607 var mustThrow = false;
608 return this.then(function() {
609 return callback();
610 }, function(err) {
611 error = err;
612 mustThrow = true;
613 return callback();
614 }).then(function() {
615 if (mustThrow) {
616 throw error;
617 }
618 });
619};
620
621
622/**
623 * Registers a new callback with this promise
624 * @param {(function(T): (R|IThenable<R>)|null|undefined)} callback The
625 * fulfillment callback.
626 * @param {(function(*): (R|IThenable<R>)|null|undefined)} errback The
627 * rejection callback.
628 * @param {string} name The callback name.
629 * @param {!Function} fn The function to use as the top of the stack when
630 * recording the callback's creation point.
631 * @return {!promise.Promise<R>} A new promise which will be resolved with the
632 * esult of the invoked callback.
633 * @template R
634 * @private
635 */
636promise.Promise.prototype.addCallback_ = function(callback, errback, name, fn) {
637 if (!goog.isFunction(callback) && !goog.isFunction(errback)) {
638 return this;
639 }
640
641 this.handled_ = true;
642 var cb = new Callback(this, callback, errback, name, fn);
643
644 if (!this.callbacks_) {
645 this.callbacks_ = [];
646 }
647 this.callbacks_.push(cb);
648
649 if (this.state_ !== PromiseState.PENDING &&
650 this.state_ !== PromiseState.BLOCKED) {
651 this.flow_.getSchedulingFrame_().addChild(cb.frame_);
652 this.scheduleNotifications_();
653 }
654 return cb.promise;
655};
656
657
658/**
659 * Represents a value that will be resolved at some point in the future. This
660 * class represents the protected "producer" half of a Promise - each Deferred
661 * has a {@code promise} property that may be returned to consumers for
662 * registering callbacks, reserving the ability to resolve the deferred to the
663 * producer.
664 *
665 * If this Deferred is rejected and there are no listeners registered before
666 * the next turn of the event loop, the rejection will be passed to the
667 * {@link webdriver.promise.ControlFlow} as an unhandled failure.
668 *
669 * @param {promise.ControlFlow=} opt_flow The control flow
670 * this instance was created under. This should only be provided during
671 * unit tests.
672 * @constructor
673 * @implements {promise.Thenable<T>}
674 * @template T
675 */
676promise.Deferred = function(opt_flow) {
677 var fulfill, reject;
678
679 /** @type {!promise.Promise<T>} */
680 this.promise = new promise.Promise(function(f, r) {
681 fulfill = f;
682 reject = r;
683 }, opt_flow);
684
685 var self = this;
686 var checkNotSelf = function(value) {
687 if (value === self) {
688 throw new TypeError('May not resolve a Deferred with itself');
689 }
690 };
691
692 /**
693 * Resolves this deferred with the given value. It is safe to call this as a
694 * normal function (with no bound "this").
695 * @param {(T|IThenable<T>|Thenable)=} opt_value The fulfilled value.
696 */
697 this.fulfill = function(opt_value) {
698 checkNotSelf(opt_value);
699 fulfill(opt_value);
700 };
701
702 /**
703 * Rejects this promise with the given reason. It is safe to call this as a
704 * normal function (with no bound "this").
705 * @param {*=} opt_reason The rejection reason.
706 */
707 this.reject = function(opt_reason) {
708 checkNotSelf(opt_reason);
709 reject(opt_reason);
710 };
711};
712promise.Thenable.addImplementation(promise.Deferred);
713
714
715/** @override */
716promise.Deferred.prototype.isPending = function() {
717 return this.promise.isPending();
718};
719
720
721/** @override */
722promise.Deferred.prototype.cancel = function(opt_reason) {
723 this.promise.cancel(opt_reason);
724};
725
726
727/**
728 * @override
729 * @deprecated Use {@code then} from the promise property directly.
730 */
731promise.Deferred.prototype.then = function(opt_cb, opt_eb) {
732 return this.promise.then(opt_cb, opt_eb);
733};
734
735
736/**
737 * @override
738 * @deprecated Use {@code thenCatch} from the promise property directly.
739 */
740promise.Deferred.prototype.thenCatch = function(opt_eb) {
741 return this.promise.thenCatch(opt_eb);
742};
743
744
745/**
746 * @override
747 * @deprecated Use {@code thenFinally} from the promise property directly.
748 */
749promise.Deferred.prototype.thenFinally = function(opt_cb) {
750 return this.promise.thenFinally(opt_cb);
751};
752
753
754/**
755 * Tests if a value is an Error-like object. This is more than an straight
756 * instanceof check since the value may originate from another context.
757 * @param {*} value The value to test.
758 * @return {boolean} Whether the value is an error.
759 */
760function isError(value) {
761 return value instanceof Error ||
762 goog.isObject(value) &&
763 (goog.isString(value.message) ||
764 // A special test for goog.testing.JsUnitException.
765 value.isJsUnitException);
766
767};
768
769
770/**
771 * Determines whether a {@code value} should be treated as a promise.
772 * Any object whose "then" property is a function will be considered a promise.
773 *
774 * @param {*} value The value to test.
775 * @return {boolean} Whether the value is a promise.
776 */
777promise.isPromise = function(value) {
778 return !!value && goog.isObject(value) &&
779 // Use array notation so the Closure compiler does not obfuscate away our
780 // contract. Use typeof rather than goog.isFunction because
781 // goog.isFunction accepts instanceof Function, which the promise spec
782 // does not.
783 typeof value['then'] === 'function';
784};
785
786
787/**
788 * Creates a promise that will be resolved at a set time in the future.
789 * @param {number} ms The amount of time, in milliseconds, to wait before
790 * resolving the promise.
791 * @return {!promise.Promise} The promise.
792 */
793promise.delayed = function(ms) {
794 var key;
795 return new promise.Promise(function(fulfill) {
796 key = setTimeout(function() {
797 key = null;
798 fulfill();
799 }, ms);
800 }).thenCatch(function(e) {
801 clearTimeout(key);
802 key = null;
803 throw e;
804 });
805};
806
807
808/**
809 * Creates a new deferred object.
810 * @return {!promise.Deferred<T>} The new deferred object.
811 * @template T
812 */
813promise.defer = function() {
814 return new promise.Deferred();
815};
816
817
818/**
819 * Creates a promise that has been resolved with the given value.
820 * @param {T=} opt_value The resolved value.
821 * @return {!promise.Promise<T>} The resolved promise.
822 * @template T
823 */
824promise.fulfilled = function(opt_value) {
825 if (opt_value instanceof promise.Promise) {
826 return opt_value;
827 }
828 return new promise.Promise(function(fulfill) {
829 fulfill(opt_value);
830 });
831};
832
833
834/**
835 * Creates a promise that has been rejected with the given reason.
836 * @param {*=} opt_reason The rejection reason; may be any value, but is
837 * usually an Error or a string.
838 * @return {!promise.Promise<T>} The rejected promise.
839 * @template T
840 */
841promise.rejected = function(opt_reason) {
842 if (opt_reason instanceof promise.Promise) {
843 return opt_reason;
844 }
845 return new promise.Promise(function(_, reject) {
846 reject(opt_reason);
847 });
848};
849
850
851/**
852 * Wraps a function that expects a node-style callback as its final
853 * argument. This callback expects two arguments: an error value (which will be
854 * null if the call succeeded), and the success value as the second argument.
855 * The callback will the resolve or reject the returned promise, based on its arguments.
856 * @param {!Function} fn The function to wrap.
857 * @param {...?} var_args The arguments to apply to the function, excluding the
858 * final callback.
859 * @return {!promise.Promise} A promise that will be resolved with the
860 * result of the provided function's callback.
861 */
862promise.checkedNodeCall = function(fn, var_args) {
863 var args = Arrays.slice(arguments, 1);
864 return new promise.Promise(function(fulfill, reject) {
865 try {
866 args.push(function(error, value) {
867 error ? reject(error) : fulfill(value);
868 });
869 fn.apply(undefined, args);
870 } catch (ex) {
871 reject(ex);
872 }
873 });
874};
875
876
877/**
878 * Registers an observer on a promised {@code value}, returning a new promise
879 * that will be resolved when the value is. If {@code value} is not a promise,
880 * then the return promise will be immediately resolved.
881 * @param {*} value The value to observe.
882 * @param {Function=} opt_callback The function to call when the value is
883 * resolved successfully.
884 * @param {Function=} opt_errback The function to call when the value is
885 * rejected.
886 * @return {!promise.Promise} A new promise.
887 */
888promise.when = function(value, opt_callback, opt_errback) {
889 if (promise.Thenable.isImplementation(value)) {
890 return value.then(opt_callback, opt_errback);
891 }
892
893 return new promise.Promise(function(fulfill, reject) {
894 promise.asap(value, fulfill, reject);
895 }).then(opt_callback, opt_errback);
896};
897
898
899/**
900 * Invokes the appropriate callback function as soon as a promised
901 * {@code value} is resolved. This function is similar to
902 * {@link webdriver.promise.when}, except it does not return a new promise.
903 * @param {*} value The value to observe.
904 * @param {Function} callback The function to call when the value is
905 * resolved successfully.
906 * @param {Function=} opt_errback The function to call when the value is
907 * rejected.
908 */
909promise.asap = function(value, callback, opt_errback) {
910 if (promise.isPromise(value)) {
911 value.then(callback, opt_errback);
912
913 // Maybe a Dojo-like deferred object?
914 } else if (!!value && goog.isObject(value) &&
915 goog.isFunction(value.addCallbacks)) {
916 value.addCallbacks(callback, opt_errback);
917
918 // A raw value, return a resolved promise.
919 } else if (callback) {
920 callback(value);
921 }
922};
923
924
925/**
926 * Given an array of promises, will return a promise that will be fulfilled
927 * with the fulfillment values of the input array's values. If any of the
928 * input array's promises are rejected, the returned promise will be rejected
929 * with the same reason.
930 *
931 * @param {!Array<(T|!promise.Promise<T>)>} arr An array of
932 * promises to wait on.
933 * @return {!promise.Promise<!Array<T>>} A promise that is
934 * fulfilled with an array containing the fulfilled values of the
935 * input array, or rejected with the same reason as the first
936 * rejected value.
937 * @template T
938 */
939promise.all = function(arr) {
940 return new promise.Promise(function(fulfill, reject) {
941 var n = arr.length;
942 var values = [];
943
944 if (!n) {
945 fulfill(values);
946 return;
947 }
948
949 var toFulfill = n;
950 var onFulfilled = function(index, value) {
951 values[index] = value;
952 toFulfill--;
953 if (toFulfill == 0) {
954 fulfill(values);
955 }
956 };
957
958 for (var i = 0; i < n; ++i) {
959 promise.asap(arr[i], goog.partial(onFulfilled, i), reject);
960 }
961 });
962};
963
964
965/**
966 * Calls a function for each element in an array and inserts the result into a
967 * new array, which is used as the fulfillment value of the promise returned
968 * by this function.
969 *
970 * If the return value of the mapping function is a promise, this function
971 * will wait for it to be fulfilled before inserting it into the new array.
972 *
973 * If the mapping function throws or returns a rejected promise, the
974 * promise returned by this function will be rejected with the same reason.
975 * Only the first failure will be reported; all subsequent errors will be
976 * silently ignored.
977 *
978 * @param {!(Array<TYPE>|promise.Promise<!Array<TYPE>>)} arr The
979 * array to iterator over, or a promise that will resolve to said array.
980 * @param {function(this: SELF, TYPE, number, !Array<TYPE>): ?} fn The
981 * function to call for each element in the array. This function should
982 * expect three arguments (the element, the index, and the array itself.
983 * @param {SELF=} opt_self The object to be used as the value of 'this' within
984 * {@code fn}.
985 * @template TYPE, SELF
986 */
987promise.map = function(arr, fn, opt_self) {
988 return promise.fulfilled(arr).then(function(arr) {
989 goog.asserts.assertNumber(arr.length, 'not an array like value');
990 return new promise.Promise(function(fulfill, reject) {
991 var n = arr.length;
992 var values = new Array(n);
993 (function processNext(i) {
994 for (; i < n; i++) {
995 if (i in arr) {
996 break;
997 }
998 }
999 if (i >= n) {
1000 fulfill(values);
1001 return;
1002 }
1003 try {
1004 promise.asap(
1005 fn.call(opt_self, arr[i], i, /** @type {!Array} */(arr)),
1006 function(value) {
1007 values[i] = value;
1008 processNext(i + 1);
1009 },
1010 reject);
1011 } catch (ex) {
1012 reject(ex);
1013 }
1014 })(0);
1015 });
1016 });
1017};
1018
1019
1020/**
1021 * Calls a function for each element in an array, and if the function returns
1022 * true adds the element to a new array.
1023 *
1024 * If the return value of the filter function is a promise, this function
1025 * will wait for it to be fulfilled before determining whether to insert the
1026 * element into the new array.
1027 *
1028 * If the filter function throws or returns a rejected promise, the promise
1029 * returned by this function will be rejected with the same reason. Only the
1030 * first failure will be reported; all subsequent errors will be silently
1031 * ignored.
1032 *
1033 * @param {!(Array<TYPE>|promise.Promise<!Array<TYPE>>)} arr The
1034 * array to iterator over, or a promise that will resolve to said array.
1035 * @param {function(this: SELF, TYPE, number, !Array<TYPE>): (
1036 * boolean|promise.Promise<boolean>)} fn The function
1037 * to call for each element in the array.
1038 * @param {SELF=} opt_self The object to be used as the value of 'this' within
1039 * {@code fn}.
1040 * @template TYPE, SELF
1041 */
1042promise.filter = function(arr, fn, opt_self) {
1043 return promise.fulfilled(arr).then(function(arr) {
1044 goog.asserts.assertNumber(arr.length, 'not an array like value');
1045 return new promise.Promise(function(fulfill, reject) {
1046 var n = arr.length;
1047 var values = [];
1048 var valuesLength = 0;
1049 (function processNext(i) {
1050 for (; i < n; i++) {
1051 if (i in arr) {
1052 break;
1053 }
1054 }
1055 if (i >= n) {
1056 fulfill(values);
1057 return;
1058 }
1059 try {
1060 var value = arr[i];
1061 var include = fn.call(opt_self, value, i, /** @type {!Array} */(arr));
1062 promise.asap(include, function(include) {
1063 if (include) {
1064 values[valuesLength++] = value;
1065 }
1066 processNext(i + 1);
1067 }, reject);
1068 } catch (ex) {
1069 reject(ex);
1070 }
1071 })(0);
1072 });
1073 });
1074};
1075
1076
1077/**
1078 * Returns a promise that will be resolved with the input value in a
1079 * fully-resolved state. If the value is an array, each element will be fully
1080 * resolved. Likewise, if the value is an object, all keys will be fully
1081 * resolved. In both cases, all nested arrays and objects will also be
1082 * fully resolved. All fields are resolved in place; the returned promise will
1083 * resolve on {@code value} and not a copy.
1084 *
1085 * Warning: This function makes no checks against objects that contain
1086 * cyclical references:
1087 *
1088 * var value = {};
1089 * value['self'] = value;
1090 * promise.fullyResolved(value); // Stack overflow.
1091 *
1092 * @param {*} value The value to fully resolve.
1093 * @return {!promise.Promise} A promise for a fully resolved version
1094 * of the input value.
1095 */
1096promise.fullyResolved = function(value) {
1097 if (promise.isPromise(value)) {
1098 return promise.when(value, fullyResolveValue);
1099 }
1100 return fullyResolveValue(value);
1101};
1102
1103
1104/**
1105 * @param {*} value The value to fully resolve. If a promise, assumed to
1106 * already be resolved.
1107 * @return {!promise.Promise} A promise for a fully resolved version
1108 * of the input value.
1109 */
1110 function fullyResolveValue(value) {
1111 switch (goog.typeOf(value)) {
1112 case 'array':
1113 return fullyResolveKeys(/** @type {!Array} */ (value));
1114
1115 case 'object':
1116 if (promise.isPromise(value)) {
1117 // We get here when the original input value is a promise that
1118 // resolves to itself. When the user provides us with such a promise,
1119 // trust that it counts as a "fully resolved" value and return it.
1120 // Of course, since it's already a promise, we can just return it
1121 // to the user instead of wrapping it in another promise.
1122 return /** @type {!promise.Promise} */ (value);
1123 }
1124
1125 if (goog.isNumber(value.nodeType) &&
1126 goog.isObject(value.ownerDocument) &&
1127 goog.isNumber(value.ownerDocument.nodeType)) {
1128 // DOM node; return early to avoid infinite recursion. Should we
1129 // only support objects with a certain level of nesting?
1130 return promise.fulfilled(value);
1131 }
1132
1133 return fullyResolveKeys(/** @type {!Object} */ (value));
1134
1135 default: // boolean, function, null, number, string, undefined
1136 return promise.fulfilled(value);
1137 }
1138};
1139
1140
1141/**
1142 * @param {!(Array|Object)} obj the object to resolve.
1143 * @return {!promise.Promise} A promise that will be resolved with the
1144 * input object once all of its values have been fully resolved.
1145 */
1146 function fullyResolveKeys(obj) {
1147 var isArray = goog.isArray(obj);
1148 var numKeys = isArray ? obj.length : Objects.getCount(obj);
1149 if (!numKeys) {
1150 return promise.fulfilled(obj);
1151 }
1152
1153 var numResolved = 0;
1154 return new promise.Promise(function(fulfill, reject) {
1155 // In pre-IE9, goog.array.forEach will not iterate properly over arrays
1156 // containing undefined values because "index in array" returns false
1157 // when array[index] === undefined (even for x = [undefined, 1]). To get
1158 // around this, we need to use our own forEach implementation.
1159 // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be
1160 // reproduced in IE9 by changing the browser/document modes, it requires an
1161 // actual pre-IE9 browser. Yay, IE!
1162 var forEachKey = !isArray ? Objects.forEach : function(arr, fn) {
1163 var n = arr.length;
1164 for (var i = 0; i < n; ++i) {
1165 fn.call(null, arr[i], i, arr);
1166 }
1167 };
1168
1169 forEachKey(obj, function(partialValue, key) {
1170 var type = goog.typeOf(partialValue);
1171 if (type != 'array' && type != 'object') {
1172 maybeResolveValue();
1173 return;
1174 }
1175
1176 promise.fullyResolved(partialValue).then(
1177 function(resolvedValue) {
1178 obj[key] = resolvedValue;
1179 maybeResolveValue();
1180 },
1181 reject);
1182 });
1183
1184 function maybeResolveValue() {
1185 if (++numResolved == numKeys) {
1186 fulfill(obj);
1187 }
1188 }
1189 });
1190};
1191
1192
1193//////////////////////////////////////////////////////////////////////////////
1194//
1195// promise.ControlFlow
1196//
1197//////////////////////////////////////////////////////////////////////////////
1198
1199
1200
1201/**
1202 * Handles the execution of scheduled tasks, each of which may be an
1203 * asynchronous operation. The control flow will ensure tasks are executed in
1204 * the ordered scheduled, starting each task only once those before it have
1205 * completed.
1206 *
1207 * Each task scheduled within this flow may return a
1208 * {@link webdriver.promise.Promise} to indicate it is an asynchronous
1209 * operation. The ControlFlow will wait for such promises to be resolved before
1210 * marking the task as completed.
1211 *
1212 * Tasks and each callback registered on a {@link webdriver.promise.Promise}
1213 * will be run in their own ControlFlow frame. Any tasks scheduled within a
1214 * frame will take priority over previously scheduled tasks. Furthermore, if any
1215 * of the tasks in the frame fail, the remainder of the tasks in that frame will
1216 * be discarded and the failure will be propagated to the user through the
1217 * callback/task's promised result.
1218 *
1219 * Each time a ControlFlow empties its task queue, it will fire an
1220 * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event. Conversely,
1221 * whenever the flow terminates due to an unhandled error, it will remove all
1222 * remaining tasks in its queue and fire an
1223 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION
1224 * UNCAUGHT_EXCEPTION} event. If there are no listeners registered with the
1225 * flow, the error will be rethrown to the global error handler.
1226 *
1227 * @constructor
1228 * @extends {EventEmitter}
1229 * @final
1230 */
1231promise.ControlFlow = function() {
1232 EventEmitter.call(this);
1233 goog.getUid(this);
1234
1235 /**
1236 * Tracks the active execution frame for this instance. Lazily initialized
1237 * when the first task is scheduled.
1238 * @private {Frame}
1239 */
1240 this.activeFrame_ = null;
1241
1242 /**
1243 * A reference to the frame which is currently top of the stack in
1244 * {@link #runInFrame_}. The {@link #activeFrame_} will always be an ancestor
1245 * of the {@link #runningFrame_}, but the two will often not be the same. The
1246 * active frame represents which frame is currently executing a task while the
1247 * running frame represents either the task itself or a promise callback which
1248 * has fired asynchronously.
1249 * @private {Frame}
1250 */
1251 this.runningFrame_ = null;
1252
1253 /**
1254 * A reference to the frame in which new tasks should be scheduled. If
1255 * {@code null}, tasks will be scheduled within the active frame. When forcing
1256 * a function to run in the context of a new frame, this pointer is used to
1257 * ensure tasks are scheduled within the newly created frame, even though it
1258 * won't be active yet.
1259 * @private {Frame}
1260 * @see {#runInFrame_}
1261 */
1262 this.schedulingFrame_ = null;
1263
1264 /**
1265 * Micro task that controls shutting down the control flow. Upon shut down,
1266 * the flow will emit an {@link webdriver.promise.ControlFlow.EventType.IDLE}
1267 * event. Idle events always follow a brief timeout in order to catch latent
1268 * errors from the last completed task. If this task had a callback
1269 * registered, but no errback, and the task fails, the unhandled failure would
1270 * not be reported by the promise system until the next turn of the event
1271 * loop:
1272 *
1273 * // Schedule 1 task that fails.
1274 * var result = promise.controlFlow().schedule('example',
1275 * function() { return promise.rejected('failed'); });
1276 * // Set a callback on the result. This delays reporting the unhandled
1277 * // failure for 1 turn of the event loop.
1278 * result.then(goog.nullFunction);
1279 *
1280 * @private {MicroTask}
1281 */
1282 this.shutdownTask_ = null;
1283
1284 /**
1285 * Micro task used to trigger execution of this instance's event loop.
1286 * @private {MicroTask}
1287 */
1288 this.eventLoopTask_ = null;
1289
1290 /**
1291 * ID for a long running interval used to keep a Node.js process running
1292 * while a control flow's event loop has yielded. This is a cheap hack
1293 * required since the {@link #runEventLoop_} is only scheduled to run when
1294 * there is _actually_ something to run. When a control flow is waiting on
1295 * a task, there will be nothing in the JS event loop and the process would
1296 * terminate without this.
1297 *
1298 * An alternative solution would be to change {@link #runEventLoop_} to run
1299 * as an interval rather than as on-demand micro-tasks. While this approach
1300 * (which was previously used) requires fewer micro-task allocations, it
1301 * results in many unnecessary invocations of {@link #runEventLoop_}.
1302 *
1303 * @private {?number}
1304 */
1305 this.hold_ = null;
1306
1307 /**
1308 * The number of holds placed on this flow. These represent points where the
1309 * flow must not execute any further actions so an asynchronous action may
1310 * run first. One such example are notifications fired by a
1311 * {@link webdriver.promise.Promise}: the Promise spec requires that callbacks
1312 * are invoked in a turn of the event loop after they are scheduled. To ensure
1313 * tasks within a callback are scheduled in the correct frame, a promise will
1314 * make the parent flow yield before its notifications are fired.
1315 * @private {number}
1316 */
1317 this.yieldCount_ = 0;
1318};
1319goog.inherits(promise.ControlFlow, EventEmitter);
1320
1321
1322/**
1323 * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.
1324 * @enum {string}
1325 */
1326promise.ControlFlow.EventType = {
1327
1328 /** Emitted when all tasks have been successfully executed. */
1329 IDLE: 'idle',
1330
1331 /** Emitted when a ControlFlow has been reset. */
1332 RESET: 'reset',
1333
1334 /** Emitted whenever a new task has been scheduled. */
1335 SCHEDULE_TASK: 'scheduleTask',
1336
1337 /**
1338 * Emitted whenever a control flow aborts due to an unhandled promise
1339 * rejection. This event will be emitted along with the offending rejection
1340 * reason. Upon emitting this event, the control flow will empty its task
1341 * queue and revert to its initial state.
1342 */
1343 UNCAUGHT_EXCEPTION: 'uncaughtException'
1344};
1345
1346
1347/**
1348 * Returns a string representation of this control flow, which is its current
1349 * {@link #getSchedule() schedule}, sans task stack traces.
1350 * @return {string} The string representation of this contorl flow.
1351 * @override
1352 */
1353promise.ControlFlow.prototype.toString = function() {
1354 return this.getSchedule();
1355};
1356
1357
1358/**
1359 * Resets this instance, clearing its queue and removing all event listeners.
1360 */
1361promise.ControlFlow.prototype.reset = function() {
1362 this.activeFrame_ = null;
1363 this.schedulingFrame_ = null;
1364 this.emit(promise.ControlFlow.EventType.RESET);
1365 this.removeAllListeners();
1366 this.cancelShutdown_();
1367 this.cancelEventLoop_();
1368};
1369
1370
1371/**
1372 * Generates an annotated string describing the internal state of this control
1373 * flow, including the currently executing as well as pending tasks. If
1374 * {@code opt_includeStackTraces === true}, the string will include the
1375 * stack trace from when each task was scheduled.
1376 * @param {string=} opt_includeStackTraces Whether to include the stack traces
1377 * from when each task was scheduled. Defaults to false.
1378 * @return {string} String representation of this flow's internal state.
1379 */
1380promise.ControlFlow.prototype.getSchedule = function(opt_includeStackTraces) {
1381 var ret = 'ControlFlow::' + goog.getUid(this);
1382 var activeFrame = this.activeFrame_;
1383 var runningFrame = this.runningFrame_;
1384 if (!activeFrame) {
1385 return ret;
1386 }
1387 var childIndent = '| ';
1388 return ret + '\n' + toStringHelper(activeFrame.getRoot(), childIndent);
1389
1390 /**
1391 * @param {!(Frame|Task)} node .
1392 * @param {string} indent .
1393 * @param {boolean=} opt_isPending .
1394 * @return {string} .
1395 */
1396 function toStringHelper(node, indent, opt_isPending) {
1397 var ret = node.toString();
1398 if (opt_isPending) {
1399 ret = '(pending) ' + ret;
1400 }
1401 if (node === activeFrame) {
1402 ret = '(active) ' + ret;
1403 }
1404 if (node === runningFrame) {
1405 ret = '(running) ' + ret;
1406 }
1407 if (node instanceof Frame) {
1408 if (node.getPendingTask()) {
1409 ret += '\n' + toStringHelper(
1410 /** @type {!Task} */(node.getPendingTask()),
1411 childIndent,
1412 true);
1413 }
1414 if (node.children_) {
1415 node.children_.forEach(function(child) {
1416 if (!node.getPendingTask() ||
1417 node.getPendingTask().getFrame() !== child) {
1418 ret += '\n' + toStringHelper(child, childIndent);
1419 }
1420 });
1421 }
1422 } else {
1423 var task = /** @type {!Task} */(node);
1424 if (opt_includeStackTraces && task.promise.stack_) {
1425 ret += '\n' + childIndent +
1426 (task.promise.stack_.stack || task.promise.stack_).
1427 replace(/\n/g, '\n' + childIndent);
1428 }
1429 if (task.getFrame()) {
1430 ret += '\n' + toStringHelper(
1431 /** @type {!Frame} */(task.getFrame()),
1432 childIndent);
1433 }
1434 }
1435 return indent + ret.replace(/\n/g, '\n' + indent);
1436 }
1437};
1438
1439
1440/**
1441 * @return {!Frame} The active frame for this flow.
1442 * @private
1443 */
1444promise.ControlFlow.prototype.getActiveFrame_ = function() {
1445 this.cancelShutdown_();
1446 if (!this.activeFrame_) {
1447 this.activeFrame_ = new Frame(this);
1448 this.activeFrame_.once(Frame.ERROR_EVENT, this.abortNow_, this);
1449 this.scheduleEventLoopStart_();
1450 }
1451 return this.activeFrame_;
1452};
1453
1454
1455/**
1456 * @return {!Frame} The frame that new items should be added to.
1457 * @private
1458 */
1459promise.ControlFlow.prototype.getSchedulingFrame_ = function() {
1460 return this.schedulingFrame_ || this.getActiveFrame_();
1461};
1462
1463
1464/**
1465 * @return {!Frame} The frame that is current executing.
1466 * @private
1467 */
1468promise.ControlFlow.prototype.getRunningFrame_ = function() {
1469 return this.runningFrame_ || this.getActiveFrame_();
1470};
1471
1472
1473/**
1474 * Schedules a task for execution. If there is nothing currently in the
1475 * queue, the task will be executed in the next turn of the event loop. If
1476 * the task function is a generator, the task will be executed using
1477 * {@link webdriver.promise.consume}.
1478 *
1479 * @param {function(): (T|promise.Promise<T>)} fn The function to
1480 * call to start the task. If the function returns a
1481 * {@link webdriver.promise.Promise}, this instance will wait for it to be
1482 * resolved before starting the next task.
1483 * @param {string=} opt_description A description of the task.
1484 * @return {!promise.Promise<T>} A promise that will be resolved
1485 * with the result of the action.
1486 * @template T
1487 */
1488promise.ControlFlow.prototype.execute = function(fn, opt_description) {
1489 if (promise.isGenerator(fn)) {
1490 fn = goog.partial(promise.consume, fn);
1491 }
1492
1493 if (!this.hold_) {
1494 var holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js
1495 this.hold_ = setInterval(goog.nullFunction, holdIntervalMs);
1496 }
1497
1498 var description = opt_description || '<anonymous>';
1499 var task = new Task(this, fn, description);
1500 task.promise.stack_ = promise.captureStackTrace('Task', description,
1501 promise.ControlFlow.prototype.execute);
1502
1503 this.getSchedulingFrame_().addChild(task);
1504 this.emit(promise.ControlFlow.EventType.SCHEDULE_TASK, opt_description);
1505 this.scheduleEventLoopStart_();
1506 return task.promise;
1507};
1508
1509
1510/**
1511 * Inserts a {@code setTimeout} into the command queue. This is equivalent to
1512 * a thread sleep in a synchronous programming language.
1513 *
1514 * @param {number} ms The timeout delay, in milliseconds.
1515 * @param {string=} opt_description A description to accompany the timeout.
1516 * @return {!promise.Promise} A promise that will be resolved with
1517 * the result of the action.
1518 */
1519promise.ControlFlow.prototype.timeout = function(ms, opt_description) {
1520 return this.execute(function() {
1521 return promise.delayed(ms);
1522 }, opt_description);
1523};
1524
1525
1526/**
1527 * Schedules a task that shall wait for a condition to hold. Each condition
1528 * function may return any value, but it will always be evaluated as a boolean.
1529 *
1530 * Condition functions may schedule sub-tasks with this instance, however,
1531 * their execution time will be factored into whether a wait has timed out.
1532 *
1533 * In the event a condition returns a Promise, the polling loop will wait for
1534 * it to be resolved before evaluating whether the condition has been satisfied.
1535 * The resolution time for a promise is factored into whether a wait has timed
1536 * out.
1537 *
1538 * If the condition function throws, or returns a rejected promise, the
1539 * wait task will fail.
1540 *
1541 * If the condition is defined as a promise, the flow will wait for it to
1542 * settle. If the timeout expires before the promise settles, the promise
1543 * returned by this function will be rejected.
1544 *
1545 * If this function is invoked with `timeout === 0`, or the timeout is omitted,
1546 * the flow will wait indefinitely for the condition to be satisfied.
1547 *
1548 * @param {(!promise.Promise<T>|function())} condition The condition to poll,
1549 * or a promise to wait on.
1550 * @param {number=} opt_timeout How long to wait, in milliseconds, for the
1551 * condition to hold before timing out. If omitted, the flow will wait
1552 * indefinitely.
1553 * @param {string=} opt_message An optional error message to include if the
1554 * wait times out; defaults to the empty string.
1555 * @return {!promise.Promise<T>} A promise that will be fulfilled
1556 * when the condition has been satisified. The promise shall be rejected if
1557 * the wait times out waiting for the condition.
1558 * @throws {TypeError} If condition is not a function or promise or if timeout
1559 * is not a number >= 0.
1560 * @template T
1561 */
1562promise.ControlFlow.prototype.wait = function(
1563 condition, opt_timeout, opt_message) {
1564 var timeout = opt_timeout || 0;
1565 if (!goog.isNumber(timeout) || timeout < 0) {
1566 throw TypeError('timeout must be a number >= 0: ' + timeout);
1567 }
1568
1569 if (promise.isPromise(condition)) {
1570 return this.execute(function() {
1571 if (!timeout) {
1572 return condition;
1573 }
1574 return new promise.Promise(function(fulfill, reject) {
1575 var start = goog.now();
1576 var timer = setTimeout(function() {
1577 timer = null;
1578 reject(Error((opt_message ? opt_message + '\n' : '') +
1579 'Timed out waiting for promise to resolve after ' +
1580 (goog.now() - start) + 'ms'));
1581 }, timeout);
1582
1583 /** @type {Thenable} */(condition).then(
1584 function(value) {
1585 timer && clearTimeout(timer);
1586 fulfill(value);
1587 },
1588 function(error) {
1589 timer && clearTimeout(timer);
1590 reject(error);
1591 });
1592 });
1593 }, opt_message || '<anonymous wait: promise resolution>');
1594 }
1595
1596 if (!goog.isFunction(condition)) {
1597 throw TypeError('Invalid condition; must be a function or promise: ' +
1598 goog.typeOf(condition));
1599 }
1600
1601 if (promise.isGenerator(condition)) {
1602 condition = goog.partial(promise.consume, condition);
1603 }
1604
1605 var self = this;
1606 return this.execute(function() {
1607 var startTime = goog.now();
1608 return new promise.Promise(function(fulfill, reject) {
1609 self.suspend_();
1610 pollCondition();
1611
1612 function pollCondition() {
1613 self.resume_();
1614 self.execute(/**@type {function()}*/(condition)).then(function(value) {
1615 var elapsed = goog.now() - startTime;
1616 if (!!value) {
1617 fulfill(value);
1618 } else if (timeout && elapsed >= timeout) {
1619 reject(new Error((opt_message ? opt_message + '\n' : '') +
1620 'Wait timed out after ' + elapsed + 'ms'));
1621 } else {
1622 self.suspend_();
1623 // Do not use asyncRun here because we need a non-micro yield
1624 // here so the UI thread is given a chance when running in a
1625 // browser.
1626 setTimeout(pollCondition, 0);
1627 }
1628 }, reject);
1629 }
1630 });
1631 }, opt_message || '<anonymous wait>');
1632};
1633
1634
1635/**
1636 * Schedules the interval for this instance's event loop, if necessary.
1637 * @private
1638 */
1639promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() {
1640 if (!this.eventLoopTask_ && !this.yieldCount_ && this.activeFrame_ &&
1641 !this.activeFrame_.getPendingTask()) {
1642 this.eventLoopTask_ = new MicroTask(this.runEventLoop_, this);
1643 }
1644};
1645
1646
1647/**
1648 * Cancels the event loop, if necessary.
1649 * @private
1650 */
1651promise.ControlFlow.prototype.cancelEventLoop_ = function() {
1652 if (this.eventLoopTask_) {
1653 this.eventLoopTask_.cancel();
1654 this.eventLoopTask_ = null;
1655 }
1656};
1657
1658
1659/**
1660 * Suspends this control flow, preventing it from executing any more tasks.
1661 * @private
1662 */
1663promise.ControlFlow.prototype.suspend_ = function() {
1664 this.yieldCount_ += 1;
1665 this.cancelEventLoop_();
1666};
1667
1668
1669/**
1670 * Resumes execution of tasks scheduled within this control flow.
1671 * @private
1672 */
1673promise.ControlFlow.prototype.resume_ = function() {
1674 this.yieldCount_ -= 1;
1675 if (!this.yieldCount_ && this.activeFrame_) {
1676 this.scheduleEventLoopStart_();
1677 }
1678};
1679
1680
1681/**
1682 * Executes the next task for the current frame. If the current frame has no
1683 * more tasks, the frame's result will be resolved, returning control to the
1684 * frame's creator. This will terminate the flow if the completed frame was at
1685 * the top of the stack.
1686 * @private
1687 */
1688promise.ControlFlow.prototype.runEventLoop_ = function() {
1689 this.eventLoopTask_ = null;
1690
1691 if (this.yieldCount_) {
1692 return;
1693 }
1694
1695 if (!this.activeFrame_) {
1696 this.commenceShutdown_();
1697 return;
1698 }
1699
1700 if (this.activeFrame_.getPendingTask()) {
1701 return;
1702 }
1703
1704 var task = this.getNextTask_();
1705 if (!task) {
1706 return;
1707 }
1708
1709 var activeFrame = this.activeFrame_;
1710 var scheduleEventLoop = goog.bind(this.scheduleEventLoopStart_, this);
1711
1712 var onSuccess = function(value) {
1713 activeFrame.setPendingTask(null);
1714 task.setFrame(null);
1715 task.fulfill(value);
1716 scheduleEventLoop();
1717 };
1718
1719 var onFailure = function(reason) {
1720 activeFrame.setPendingTask(null);
1721 task.setFrame(null);
1722 task.reject(reason);
1723 scheduleEventLoop();
1724 };
1725
1726 activeFrame.setPendingTask(task);
1727 var frame = new Frame(this);
1728 task.setFrame(frame);
1729 this.runInFrame_(frame, task.execute, function(result) {
1730 promise.asap(result, onSuccess, onFailure);
1731 }, onFailure, true);
1732};
1733
1734
1735/**
1736 * @return {Task} The next task to execute, or
1737 * {@code null} if a frame was resolved.
1738 * @private
1739 */
1740promise.ControlFlow.prototype.getNextTask_ = function() {
1741 var frame = this.activeFrame_;
1742 var firstChild = frame.getFirstChild();
1743 if (!firstChild) {
1744 if (!frame.pendingCallback && !frame.isBlocked_) {
1745 this.resolveFrame_(frame);
1746 }
1747 return null;
1748 }
1749
1750 if (firstChild instanceof Frame) {
1751 this.activeFrame_ = firstChild;
1752 return this.getNextTask_();
1753 }
1754
1755 frame.removeChild(firstChild);
1756 if (!firstChild.isPending()) {
1757 return this.getNextTask_();
1758 }
1759 return firstChild;
1760};
1761
1762
1763/**
1764 * @param {!Frame} frame The frame to resolve.
1765 * @private
1766 */
1767promise.ControlFlow.prototype.resolveFrame_ = function(frame) {
1768 if (this.activeFrame_ === frame) {
1769 this.activeFrame_ = frame.getParent();
1770 }
1771
1772 if (frame.getParent()) {
1773 frame.getParent().removeChild(frame);
1774 }
1775 frame.emit(Frame.CLOSE_EVENT);
1776
1777 if (!this.activeFrame_) {
1778 this.commenceShutdown_();
1779 } else {
1780 this.scheduleEventLoopStart_();
1781 }
1782};
1783
1784
1785/**
1786 * Aborts the current frame. The frame, and all of the tasks scheduled within it
1787 * will be discarded. If this instance does not have an active frame, it will
1788 * immediately terminate all execution.
1789 * @param {*} error The reason the frame is being aborted; typically either
1790 * an Error or string.
1791 * @param {Frame=} opt_frame The frame to abort; will use the
1792 * currently active frame if not specified.
1793 * @private
1794 */
1795promise.ControlFlow.prototype.abortFrame_ = function(error, opt_frame) {
1796 if (!this.activeFrame_) {
1797 this.abortNow_(error);
1798 return;
1799 }
1800
1801 // Frame parent is always another frame, but the compiler is not smart
1802 // enough to recognize this.
1803 var parent = /** @type {Frame} */ (
1804 this.activeFrame_.getParent());
1805 if (parent) {
1806 parent.removeChild(this.activeFrame_);
1807 }
1808
1809 var frame = this.activeFrame_;
1810 this.activeFrame_ = parent;
1811 frame.abort(error);
1812};
1813
1814
1815/**
1816 * Executes a function within a specific frame. If the function does not
1817 * schedule any new tasks, the frame will be discarded and the function's result
1818 * returned passed to the given callback immediately. Otherwise, the callback
1819 * will be invoked once all of the tasks scheduled within the function have been
1820 * completed. If the frame is aborted, the `errback` will be invoked with the
1821 * offending error.
1822 *
1823 * @param {!Frame} newFrame The frame to use.
1824 * @param {!Function} fn The function to execute.
1825 * @param {function(T)} callback The function to call with a successful result.
1826 * @param {function(*)} errback The function to call if there is an error.
1827 * @param {boolean=} opt_isTask Whether the function is a task and the frame
1828 * should be immediately activated to capture subtasks and errors.
1829 * @throws {Error} If this function is invoked while another call to this
1830 * function is present on the stack.
1831 * @template T
1832 * @private
1833 */
1834promise.ControlFlow.prototype.runInFrame_ = function(
1835 newFrame, fn, callback, errback, opt_isTask) {
1836 asserts.assert(
1837 !this.runningFrame_, 'unexpected recursive call to runInFrame');
1838
1839 var self = this,
1840 oldFrame = this.activeFrame_;
1841
1842 try {
1843 if (this.activeFrame_ !== newFrame && !newFrame.getParent()) {
1844 this.activeFrame_.addChild(newFrame);
1845 }
1846
1847 // Activate the new frame to force tasks to be treated as sub-tasks of
1848 // the parent frame.
1849 if (opt_isTask) {
1850 this.activeFrame_ = newFrame;
1851 }
1852
1853 try {
1854 this.runningFrame_ = newFrame;
1855 this.schedulingFrame_ = newFrame;
1856 activeFlows.push(this);
1857 var result = fn();
1858 } finally {
1859 activeFlows.pop();
1860 this.schedulingFrame_ = null;
1861
1862 // `newFrame` should only be considered the running frame when it is
1863 // actually executing. After it finishes, set top of stack to its parent
1864 // so any failures/interrupts that occur while processing newFrame's
1865 // result are handled there.
1866 this.runningFrame_ = newFrame.parent_;
1867 }
1868 newFrame.isLocked_ = true;
1869
1870 // If there was nothing scheduled in the new frame we can discard the
1871 // frame and return immediately.
1872 if (isCloseable(newFrame) && (!opt_isTask || !promise.isPromise(result))) {
1873 removeNewFrame();
1874 callback(result);
1875 return;
1876 }
1877
1878 // If the executed function returned a promise, wait for it to resolve. If
1879 // there is nothing scheduled in the frame, go ahead and discard it.
1880 // Otherwise, we wait for the frame to be closed out by the event loop.
1881 var shortCircuitTask;
1882 if (promise.isPromise(result)) {
1883 newFrame.isBlocked_ = true;
1884 var onResolve = function() {
1885 newFrame.isBlocked_ = false;
1886 shortCircuitTask = new MicroTask(function() {
1887 if (isCloseable(newFrame)) {
1888 removeNewFrame();
1889 callback(result);
1890 }
1891 });
1892 };
1893 /** @type {Thenable} */(result).then(onResolve, onResolve);
1894
1895 // If the result is a thenable, attach a listener to silence any unhandled
1896 // rejection warnings. This is safe because we *will* handle it once the
1897 // frame has completed.
1898 } else if (promise.Thenable.isImplementation(result)) {
1899 /** @type {!promise.Thenable} */(result).thenCatch(goog.nullFunction);
1900 }
1901
1902 newFrame.once(Frame.CLOSE_EVENT, function() {
1903 shortCircuitTask && shortCircuitTask.cancel();
1904 if (isCloseable(newFrame)) {
1905 removeNewFrame();
1906 }
1907 callback(result);
1908 }).once(Frame.ERROR_EVENT, function(reason) {
1909 shortCircuitTask && shortCircuitTask.cancel();
1910 if (promise.Thenable.isImplementation(result) && result.isPending()) {
1911 result.cancel(reason);
1912 }
1913 errback(reason);
1914 });
1915 } catch (ex) {
1916 removeNewFrame(ex);
1917 errback(ex);
1918 } finally {
1919 // No longer running anything, clear the reference.
1920 this.runningFrame_ = null;
1921 }
1922
1923 function isCloseable(frame) {
1924 return (!frame.children_ || !frame.children_.length)
1925 && !frame.pendingRejection;
1926 }
1927
1928 /**
1929 * @param {*=} opt_err If provided, the reason that the frame was removed.
1930 */
1931 function removeNewFrame(opt_err) {
1932 var parent = newFrame.getParent();
1933 if (parent) {
1934 parent.removeChild(newFrame);
1935 asyncRun(function() {
1936 if (isCloseable(parent) && parent !== self.activeFrame_) {
1937 parent.emit(Frame.CLOSE_EVENT);
1938 }
1939 });
1940 self.scheduleEventLoopStart_();
1941 }
1942
1943 if (opt_err) {
1944 newFrame.cancelRemainingTasks(promise.CancellationError.wrap(
1945 opt_err, 'Tasks cancelled due to uncaught error'));
1946 }
1947 self.activeFrame_ = oldFrame;
1948 }
1949};
1950
1951
1952/**
1953 * Commences the shutdown sequence for this instance. After one turn of the
1954 * event loop, this object will emit the
1955 * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event to signal
1956 * listeners that it has completed. During this wait, if another task is
1957 * scheduled, the shutdown will be aborted.
1958 * @private
1959 */
1960promise.ControlFlow.prototype.commenceShutdown_ = function() {
1961 if (!this.shutdownTask_) {
1962 // Go ahead and stop the event loop now. If we're in here, then there are
1963 // no more frames with tasks to execute. If we waited to cancel the event
1964 // loop in our timeout below, the event loop could trigger *before* the
1965 // timeout, generating an error from there being no frames.
1966 // If #execute is called before the timeout below fires, it will cancel
1967 // the timeout and restart the event loop.
1968 this.cancelEventLoop_();
1969 this.shutdownTask_ = new MicroTask(this.shutdown_, this);
1970 }
1971};
1972
1973
1974/** @private */
1975promise.ControlFlow.prototype.cancelHold_ = function() {
1976 if (this.hold_) {
1977 clearInterval(this.hold_);
1978 this.hold_ = null;
1979 }
1980};
1981
1982
1983/** @private */
1984promise.ControlFlow.prototype.shutdown_ = function() {
1985 this.cancelHold_();
1986 this.shutdownTask_ = null;
1987 this.emit(promise.ControlFlow.EventType.IDLE);
1988};
1989
1990
1991/**
1992 * Cancels the shutdown sequence if it is currently scheduled.
1993 * @private
1994 */
1995promise.ControlFlow.prototype.cancelShutdown_ = function() {
1996 if (this.shutdownTask_) {
1997 this.shutdownTask_.cancel();
1998 this.shutdownTask_ = null;
1999 }
2000};
2001
2002
2003/**
2004 * Aborts this flow, abandoning all remaining tasks. If there are
2005 * listeners registered, an {@code UNCAUGHT_EXCEPTION} will be emitted with the
2006 * offending {@code error}, otherwise, the {@code error} will be rethrown to the
2007 * global error handler.
2008 * @param {*} error Object describing the error that caused the flow to
2009 * abort; usually either an Error or string value.
2010 * @private
2011 */
2012promise.ControlFlow.prototype.abortNow_ = function(error) {
2013 this.activeFrame_ = null;
2014 this.cancelShutdown_();
2015 this.cancelEventLoop_();
2016 this.cancelHold_();
2017
2018 var listeners = this.listeners(
2019 promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
2020 if (!listeners.length) {
2021 throwException(error);
2022 } else {
2023 this.emit(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, error);
2024 }
2025};
2026
2027
2028/**
2029 * Wraps a function to execute as a cancellable micro task.
2030 * @final
2031 */
2032var MicroTask = goog.defineClass(null, {
2033 /**
2034 * @param {function(this: THIS)} fn The function to run as a micro task.
2035 * @param {THIS=} opt_scope The scope to run the function in.
2036 * @template THIS
2037 */
2038 constructor: function(fn, opt_scope) {
2039 /** @private {boolean} */
2040 this.cancelled_ = false;
2041 asyncRun(function() {
2042 if (!this.cancelled_) {
2043 fn.call(opt_scope);
2044 }
2045 }, this);
2046 },
2047
2048 /**
2049 * Cancels the execution of this task. Note: this will not prevent the task
2050 * timer from firing, just the invocation of the wrapped function.
2051 */
2052 cancel: function() {
2053 this.cancelled_ = true;
2054 }
2055});
2056
2057
2058/**
2059 * An execution frame within a {@link webdriver.promise.ControlFlow}. Each
2060 * frame represents the execution context for either a
2061 * {@link webdriver.Task} or a callback on a
2062 * {@link webdriver.promise.Promise}.
2063 *
2064 * Each frame may contain sub-frames. If child N is a sub-frame, then the
2065 * items queued within it are given priority over child N+1.
2066 *
2067 * @unrestricted
2068 * @final
2069 * @private
2070 */
2071var Frame = goog.defineClass(EventEmitter, {
2072 /**
2073 * @param {!promise.ControlFlow} flow The flow this instance belongs to.
2074 */
2075 constructor: function(flow) {
2076 EventEmitter.call(this);
2077 goog.getUid(this);
2078
2079 /** @private {!promise.ControlFlow} */
2080 this.flow_ = flow;
2081
2082 /** @private {Frame} */
2083 this.parent_ = null;
2084
2085 /** @private {Array<!(Frame|Task)>} */
2086 this.children_ = null;
2087
2088 /** @private {(Frame|Task)} */
2089 this.lastInsertedChild_ = null;
2090
2091 /**
2092 * The task currently being executed within this frame.
2093 * @private {Task}
2094 */
2095 this.pendingTask_ = null;
2096
2097 /**
2098 * Whether this frame is currently locked. A locked frame represents an
2099 * executed function that has scheduled all of its tasks.
2100 *
2101 * Once a frame becomes locked, any new frames which are added as children
2102 * represent interrupts (such as a {@link webdriver.promise.Promise}
2103 * callback) whose tasks must be given priority over those already scheduled
2104 * within this frame. For example:
2105 *
2106 * var flow = promise.controlFlow();
2107 * flow.execute('start here', goog.nullFunction).then(function() {
2108 * flow.execute('this should execute 2nd', goog.nullFunction);
2109 * });
2110 * flow.execute('this should execute last', goog.nullFunction);
2111 *
2112 * @private {boolean}
2113 */
2114 this.isLocked_ = false;
2115
2116 /**
2117 * Whether this frame's completion is blocked on the resolution of a promise
2118 * returned by its main function.
2119 * @private
2120 */
2121 this.isBlocked_ = false;
2122
2123 /**
2124 * Whether this frame represents a pending callback attached to a
2125 * {@link webdriver.promise.Promise}.
2126 * @private {boolean}
2127 */
2128 this.pendingCallback = false;
2129
2130 /**
2131 * Whether there are pending unhandled rejections detected within this frame.
2132 * @private {boolean}
2133 */
2134 this.pendingRejection = false;
2135
2136 /** @private {promise.CancellationError} */
2137 this.cancellationError_ = null;
2138 },
2139
2140 statics: {
2141 /** @const */
2142 CLOSE_EVENT: 'close',
2143
2144 /** @const */
2145 ERROR_EVENT: 'error',
2146
2147 /**
2148 * @param {!promise.CancellationError} error The cancellation error.
2149 * @param {!(Frame|Task)} child The child to cancel.
2150 * @private
2151 */
2152 cancelChild_: function(error, child) {
2153 if (child instanceof Frame) {
2154 child.cancelRemainingTasks(error);
2155 } else {
2156 child.promise.callbacks_ = null;
2157 child.cancel(error);
2158 }
2159 }
2160 },
2161
2162 /** @return {Frame} This frame's parent, if any. */
2163 getParent: function() {
2164 return this.parent_;
2165 },
2166
2167 /** @param {Frame} parent This frame's new parent. */
2168 setParent: function(parent) {
2169 this.parent_ = parent;
2170 },
2171
2172 /** @return {!Frame} The root of this frame's tree. */
2173 getRoot: function() {
2174 var root = this;
2175 while (root.parent_) {
2176 root = root.parent_;
2177 }
2178 return root;
2179 },
2180
2181 /**
2182 * Aborts the execution of this frame, cancelling all outstanding tasks
2183 * scheduled within this frame.
2184 *
2185 * @param {*} error The error that triggered this abortion.
2186 */
2187 abort: function(error) {
2188 this.cancellationError_ = promise.CancellationError.wrap(
2189 error, 'Task discarded due to a previous task failure');
2190 this.cancelRemainingTasks(this.cancellationError_);
2191 if (!this.pendingCallback) {
2192 this.emit(Frame.ERROR_EVENT, error);
2193 }
2194 },
2195
2196 /**
2197 * Marks all of the tasks that are descendants of this frame in the execution
2198 * tree as cancelled. This is necessary for callbacks scheduled asynchronous.
2199 * For example:
2200 *
2201 * var someResult;
2202 * promise.createFlow(function(flow) {
2203 * someResult = flow.execute(function() {});
2204 * throw Error();
2205 * }).thenCatch(function(err) {
2206 * console.log('flow failed: ' + err);
2207 * someResult.then(function() {
2208 * console.log('task succeeded!');
2209 * }, function(err) {
2210 * console.log('task failed! ' + err);
2211 * });
2212 * });
2213 * // flow failed: Error: boom
2214 * // task failed! CancelledTaskError: Task discarded due to a previous
2215 * // task failure: Error: boom
2216 *
2217 * @param {!promise.CancellationError} reason The cancellation reason.
2218 */
2219 cancelRemainingTasks: function(reason) {
2220 if (this.children_) {
2221 this.children_.forEach(function(child) {
2222 Frame.cancelChild_(reason, child);
2223 });
2224 }
2225 },
2226
2227 /**
2228 * @return {Task} The task currently executing
2229 * within this frame, if any.
2230 */
2231 getPendingTask: function() {
2232 return this.pendingTask_;
2233 },
2234
2235 /**
2236 * @param {Task} task The task currently
2237 * executing within this frame, if any.
2238 */
2239 setPendingTask: function(task) {
2240 this.pendingTask_ = task;
2241 },
2242
2243 /**
2244 * @return {boolean} Whether this frame is empty (has no scheduled tasks or
2245 * pending callback frames).
2246 */
2247 isEmpty: function() {
2248 return !this.children_ || !this.children_.length;
2249 },
2250
2251 /**
2252 * Adds a new node to this frame.
2253 * @param {!(Frame|Task)} node The node to insert.
2254 */
2255 addChild: function(node) {
2256 if (this.cancellationError_) {
2257 Frame.cancelChild_(this.cancellationError_, node);
2258 return; // Child will never run, no point keeping a reference.
2259 }
2260
2261 if (!this.children_) {
2262 this.children_ = [];
2263 }
2264
2265 node.setParent(this);
2266 if (this.isLocked_ && node instanceof Frame) {
2267 var index = 0;
2268 if (this.lastInsertedChild_ instanceof Frame) {
2269 index = this.children_.indexOf(this.lastInsertedChild_);
2270 // If the last inserted child into a locked frame is a pending callback,
2271 // it is an interrupt and the new interrupt must come after it. Otherwise,
2272 // we have our first interrupt for this frame and it shoudl go before the
2273 // last inserted child.
2274 index += (this.lastInsertedChild_.pendingCallback) ? 1 : -1;
2275 }
2276 this.children_.splice(Math.max(index, 0), 0, node);
2277 this.lastInsertedChild_ = node;
2278 return;
2279 }
2280
2281 this.lastInsertedChild_ = node;
2282 this.children_.push(node);
2283 },
2284
2285 /**
2286 * @return {(Frame|Task)} This frame's fist child.
2287 */
2288 getFirstChild: function() {
2289 this.isLocked_ = true;
2290 return this.children_ && this.children_[0];
2291 },
2292
2293 /**
2294 * Removes a child from this frame.
2295 * @param {!(Frame|Task)} child The child to remove.
2296 */
2297 removeChild: function(child) {
2298 goog.asserts.assert(child.parent_ === this, 'not a child of this frame');
2299 goog.asserts.assert(this.children_ !== null, 'frame has no children!');
2300 var index = this.children_.indexOf(child);
2301 child.setParent(null);
2302 this.children_.splice(index, 1);
2303 if (this.lastInsertedChild_ === child) {
2304 this.lastInsertedChild_ = this.children_[index - 1] || null;
2305 }
2306 if (!this.children_.length) {
2307 this.children_ = null;
2308 }
2309 },
2310
2311 /** @override */
2312 toString: function() {
2313 return 'Frame::' + goog.getUid(this);
2314 }
2315});
2316
2317
2318/**
2319 * A task to be executed by a {@link webdriver.promise.ControlFlow}.
2320 *
2321 * @unrestricted
2322 * @final
2323 */
2324var Task = goog.defineClass(promise.Deferred, {
2325 /**
2326 * @param {!promise.ControlFlow} flow The flow this instances belongs
2327 * to.
2328 * @param {function(): (T|!promise.Promise<T>)} fn The function to
2329 * call when the task executes. If it returns a
2330 * {@link webdriver.promise.Promise}, the flow will wait for it to be
2331 * resolved before starting the next task.
2332 * @param {string} description A description of the task for debugging.
2333 * @constructor
2334 * @extends {promise.Deferred<T>}
2335 * @template T
2336 */
2337 constructor: function(flow, fn, description) {
2338 Task.base(this, 'constructor', flow);
2339 goog.getUid(this);
2340
2341 /**
2342 * @type {function(): (T|!promise.Promise<T>)}
2343 */
2344 this.execute = fn;
2345
2346 /** @private {string} */
2347 this.description_ = description;
2348
2349 /** @private {Frame} */
2350 this.parent_ = null;
2351
2352 /** @private {Frame} */
2353 this.frame_ = null;
2354 },
2355
2356 /**
2357 * @return {Frame} frame The frame used to run this task's
2358 * {@link #execute} method.
2359 */
2360 getFrame: function() {
2361 return this.frame_;
2362 },
2363
2364 /**
2365 * @param {Frame} frame The frame used to run this task's
2366 * {@link #execute} method.
2367 */
2368 setFrame: function(frame) {
2369 this.frame_ = frame;
2370 },
2371
2372 /**
2373 * @param {Frame} frame The frame this task is scheduled in.
2374 */
2375 setParent: function(frame) {
2376 goog.asserts.assert(goog.isNull(this.parent_) || goog.isNull(frame),
2377 'parent already set');
2378 this.parent_ = frame;
2379 },
2380
2381 /** @return {string} This task's description. */
2382 getDescription: function() {
2383 return this.description_;
2384 },
2385
2386 /** @override */
2387 toString: function() {
2388 return 'Task::' + goog.getUid(this) + '<' + this.description_ + '>';
2389 }
2390});
2391
2392
2393/**
2394 * Manages a callback attached to a {@link webdriver.promise.Promise}. When the
2395 * promise is resolved, this callback will invoke the appropriate callback
2396 * function based on the promise's resolved value.
2397 *
2398 * @unrestricted
2399 * @final
2400 */
2401var Callback = goog.defineClass(promise.Deferred, {
2402 /**
2403 * @param {!promise.Promise} parent The promise this callback is attached to.
2404 * @param {(function(T): (IThenable<R>|R)|null|undefined)} callback
2405 * The fulfillment callback.
2406 * @param {(function(*): (IThenable<R>|R)|null|undefined)} errback
2407 * The rejection callback.
2408 * @param {string} name The callback name.
2409 * @param {!Function} fn The function to use as the top of the stack when
2410 * recording the callback's creation point.
2411 * @extends {promise.Deferred<R>}
2412 * @template T, R
2413 */
2414 constructor: function(parent, callback, errback, name, fn) {
2415 Callback.base(this, 'constructor', parent.flow_);
2416
2417 /** @private {(function(T): (IThenable<R>|R)|null|undefined)} */
2418 this.callback_ = callback;
2419
2420 /** @private {(function(*): (IThenable<R>|R)|null|undefined)} */
2421 this.errback_ = errback;
2422
2423 /** @private {!Frame} */
2424 this.frame_ = new Frame(parent.flow_);
2425 this.frame_.pendingCallback = true;
2426
2427 this.promise.parent_ = parent;
2428 if (promise.LONG_STACK_TRACES) {
2429 this.promise.stack_ = promise.captureStackTrace('Promise', name, fn);
2430 }
2431 },
2432
2433 /**
2434 * Called by the parent promise when it has been resolved.
2435 * @param {!PromiseState} state The parent's new state.
2436 * @param {*} value The parent's new value.
2437 */
2438 notify: function(state, value) {
2439 var callback = this.callback_;
2440 var fallback = this.fulfill;
2441 if (state === PromiseState.REJECTED) {
2442 callback = this.errback_;
2443 fallback = this.reject;
2444 }
2445
2446 this.frame_.pendingCallback = false;
2447 if (goog.isFunction(callback)) {
2448 this.frame_.flow_.runInFrame_(
2449 this.frame_,
2450 goog.bind(callback, undefined, value),
2451 this.fulfill, this.reject);
2452 } else {
2453 if (this.frame_.getParent()) {
2454 this.frame_.getParent().removeChild(this.frame_);
2455 }
2456 fallback(value);
2457 }
2458 }
2459});
2460
2461
2462
2463/**
2464 * The default flow to use if no others are active.
2465 * @type {!promise.ControlFlow}
2466 */
2467var defaultFlow = new promise.ControlFlow();
2468
2469
2470/**
2471 * A stack of active control flows, with the top of the stack used to schedule
2472 * commands. When there are multiple flows on the stack, the flow at index N
2473 * represents a callback triggered within a task owned by the flow at index
2474 * N-1.
2475 * @type {!Array<!promise.ControlFlow>}
2476 */
2477var activeFlows = [];
2478
2479
2480/**
2481 * Changes the default flow to use when no others are active.
2482 * @param {!promise.ControlFlow} flow The new default flow.
2483 * @throws {Error} If the default flow is not currently active.
2484 */
2485promise.setDefaultFlow = function(flow) {
2486 if (activeFlows.length) {
2487 throw Error('You may only change the default flow while it is active');
2488 }
2489 defaultFlow = flow;
2490};
2491
2492
2493/**
2494 * @return {!promise.ControlFlow} The currently active control flow.
2495 */
2496promise.controlFlow = function() {
2497 return /** @type {!promise.ControlFlow} */ (
2498 Arrays.peek(activeFlows) || defaultFlow);
2499};
2500
2501
2502/**
2503 * Creates a new control flow. The provided callback will be invoked as the
2504 * first task within the new flow, with the flow as its sole argument. Returns
2505 * a promise that resolves to the callback result.
2506 * @param {function(!promise.ControlFlow)} callback The entry point
2507 * to the newly created flow.
2508 * @return {!promise.Promise} A promise that resolves to the callback
2509 * result.
2510 */
2511promise.createFlow = function(callback) {
2512 var flow = new promise.ControlFlow;
2513 return flow.execute(function() {
2514 return callback(flow);
2515 });
2516};
2517
2518
2519/**
2520 * Tests is a function is a generator.
2521 * @param {!Function} fn The function to test.
2522 * @return {boolean} Whether the function is a generator.
2523 */
2524promise.isGenerator = function(fn) {
2525 return fn.constructor.name === 'GeneratorFunction';
2526};
2527
2528
2529/**
2530 * Consumes a {@code GeneratorFunction}. Each time the generator yields a
2531 * promise, this function will wait for it to be fulfilled before feeding the
2532 * fulfilled value back into {@code next}. Likewise, if a yielded promise is
2533 * rejected, the rejection error will be passed to {@code throw}.
2534 *
2535 * __Example 1:__ the Fibonacci Sequence.
2536 *
2537 * promise.consume(function* fibonacci() {
2538 * var n1 = 1, n2 = 1;
2539 * for (var i = 0; i < 4; ++i) {
2540 * var tmp = yield n1 + n2;
2541 * n1 = n2;
2542 * n2 = tmp;
2543 * }
2544 * return n1 + n2;
2545 * }).then(function(result) {
2546 * console.log(result); // 13
2547 * });
2548 *
2549 * __Example 2:__ a generator that throws.
2550 *
2551 * promise.consume(function* () {
2552 * yield promise.delayed(250).then(function() {
2553 * throw Error('boom');
2554 * });
2555 * }).thenCatch(function(e) {
2556 * console.log(e.toString()); // Error: boom
2557 * });
2558 *
2559 * @param {!Function} generatorFn The generator function to execute.
2560 * @param {Object=} opt_self The object to use as "this" when invoking the
2561 * initial generator.
2562 * @param {...*} var_args Any arguments to pass to the initial generator.
2563 * @return {!promise.Promise<?>} A promise that will resolve to the
2564 * generator's final result.
2565 * @throws {TypeError} If the given function is not a generator.
2566 */
2567promise.consume = function(generatorFn, opt_self, var_args) {
2568 if (!promise.isGenerator(generatorFn)) {
2569 throw new TypeError('Input is not a GeneratorFunction: ' +
2570 generatorFn.constructor.name);
2571 }
2572
2573 var deferred = promise.defer();
2574 var generator = generatorFn.apply(opt_self, Arrays.slice(arguments, 2));
2575 callNext();
2576 return deferred.promise;
2577
2578 /** @param {*=} opt_value . */
2579 function callNext(opt_value) {
2580 pump(generator.next, opt_value);
2581 }
2582
2583 /** @param {*=} opt_error . */
2584 function callThrow(opt_error) {
2585 // Dictionary lookup required because Closure compiler's built-in
2586 // externs does not include GeneratorFunction.prototype.throw.
2587 pump(generator['throw'], opt_error);
2588 }
2589
2590 function pump(fn, opt_arg) {
2591 if (!deferred.isPending()) {
2592 return; // Defererd was cancelled; silently abort.
2593 }
2594
2595 try {
2596 var result = fn.call(generator, opt_arg);
2597 } catch (ex) {
2598 deferred.reject(ex);
2599 return;
2600 }
2601
2602 if (result.done) {
2603 deferred.fulfill(result.value);
2604 return;
2605 }
2606
2607 promise.asap(result.value, callNext, callThrow);
2608 }
2609};