# 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 |
49 | goog.provide('webdriver.promise');
50 | goog.provide('webdriver.promise.ControlFlow');
51 | goog.provide('webdriver.promise.Deferred');
52 | goog.provide('webdriver.promise.Promise');
53 | goog.provide('webdriver.promise.Thenable');
54 |
55 | goog.require('goog.array');
56 | goog.require('goog.async.run');
57 | goog.require('goog.async.throwException');
58 | goog.require('goog.debug.Error');
59 | goog.require('goog.object');
60 | goog.require('webdriver.EventEmitter');
61 | goog.require('webdriver.stacktrace.Snapshot');
62 | goog.require('webdriver.stacktrace');
63 |
64 |
65 |
66 | /**
67 | * @define {boolean} Whether to append traces of {@code then} to rejection
68 | * errors.
69 | */
70 | goog.define('webdriver.promise.LONG_STACK_TRACES', false);
71 |
72 | goog.scope(function() {
73 | var 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 | */
83 | promise.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 | */
107 | promise.CancellationError = function(opt_msg) {
108 | goog.debug.Error.call(this, opt_msg);
109 |
110 | /** @override */
111 | this.name = 'CancellationError';
112 | };
113 | goog.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 | */
124 | promise.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 | */
151 | promise.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 | */
161 | promise.Thenable.prototype.cancel = function(opt_reason) {};
162 |
163 |
164 | /** @return {boolean} Whether this promise's value is still being computed. */
165 | promise.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 | */
181 | promise.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 | */
207 | promise.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 | */
246 | promise.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 | */
255 | promise.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 | */
266 | promise.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 | */
289 | promise.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 | */
323 | promise.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 | };
364 | promise.Thenable.addImplementation(promise.Promise);
365 |
366 |
367 | /** @override */
368 | promise.Promise.prototype.toString = function() {
369 | return 'Promise::' + goog.getUid(this) +
370 | ' {[[PromiseStatus]]: "' + this.state_ + '"}';
371 | };
372 |
373 |
374 | /**
375 | * @enum {string}
376 | * @private
377 | */
378 | promise.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 | */
394 | promise.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 | */
457 | promise.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 | */
492 | promise.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 | */
503 | promise.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 | */
539 | promise.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 | */
562 | promise.Promise.prototype.notify_ = function(callback) {
563 | callback.notify(this.state_, this.value_);
564 | };
565 |
566 |
567 | /** @override */
568 | promise.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 */
584 | promise.Promise.prototype.isPending = function() {
585 | return this.state_ === promise.Promise.State_.PENDING;
586 | };
587 |
588 |
589 | /** @override */
590 | promise.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 */
597 | promise.Promise.prototype.thenCatch = function(errback) {
598 | return this.addCallback_(
599 | null, errback, 'thenCatch', promise.Promise.prototype.thenCatch);
600 | };
601 |
602 |
603 | /** @override */
604 | promise.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 | */
635 | promise.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 | */
675 | promise.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 | };
711 | promise.Thenable.addImplementation(promise.Deferred);
712 |
713 |
714 | /** @override */
715 | promise.Deferred.prototype.isPending = function() {
716 | return this.promise.isPending();
717 | };
718 |
719 |
720 | /** @override */
721 | promise.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 | */
730 | promise.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 | */
739 | promise.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 | */
748 | promise.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 | */
760 | promise.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 | */
777 | promise.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 | */
793 | promise.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 | */
813 | promise.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 | */
824 | promise.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 | */
841 | promise.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 | */
862 | promise.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 | */
888 | promise.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 | */
909 | promise.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 | */
939 | promise.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 | */
987 | promise.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 | */
1042 | promise.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 | */
1096 | promise.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 | */
1111 | promise.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 | */
1150 | promise.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 | */
1235 | promise.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 | };
1312 | goog.inherits(promise.ControlFlow, webdriver.EventEmitter);
1313 |
1314 |
1315 | /**
1316 | * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.
1317 | * @enum {string}
1318 | */
1319 | promise.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 | */
1346 | promise.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 | */
1354 | promise.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 | */
1373 | promise.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 | */
1382 | promise.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 | */
1393 | promise.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 | */
1407 | promise.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 | */
1467 | promise.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 | */
1482 | promise.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 | */
1502 | promise.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 | */
1533 | promise.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 | */
1573 | promise.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 | */
1654 | promise.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 | */
1665 | promise.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 | */
1677 | promise.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 | */
1689 | promise.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 | */
1699 | promise.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 | */
1714 | promise.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 | */
1766 | promise.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 | */
1793 | promise.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 | */
1821 | promise.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 | */
1858 | promise.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 | */
1971 | promise.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 */
1986 | promise.ControlFlow.prototype.cancelHold_ = function() {
1987 | if (this.hold_) {
1988 | clearInterval(this.hold_);
1989 | this.hold_ = null;
1990 | }
1991 | };
1992 |
1993 |
1994 | /** @private */
1995 | promise.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 | */
2006 | promise.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 | */
2023 | promise.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 | */
2045 | promise.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 | */
2085 | promise.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 | */
2339 | promise.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 | */
2417 | promise.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 | */
2483 | promise.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 | */
2493 | promise.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 | */
2501 | promise.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 | */
2512 | promise.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 | */
2523 | promise.pushFlow_ = function(flow) {
2524 | promise.activeFlows_.push(flow);
2525 | };
2526 |
2527 |
2528 | /** @private */
2529 | promise.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 | */
2543 | promise.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 | */
2556 | promise.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 | */
2599 | promise.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