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