| Index: src/js/promise.js | 
| diff --git a/src/js/promise.js b/src/js/promise.js | 
| index b78f68c4f71c367365a8a149803c3091a9e55f75..25c8beaa9e0aef86dd966faa3af89d8e342c04a3 100644 | 
| --- a/src/js/promise.js | 
| +++ b/src/js/promise.js | 
| @@ -20,6 +20,12 @@ | 
| utils.ImportNow("promise_forwarding_handler_symbol"); | 
| var promiseHasHandlerSymbol = | 
| utils.ImportNow("promise_has_handler_symbol"); | 
| +var promiseRejectReactionsSymbol = | 
| +    utils.ImportNow("promise_reject_reactions_symbol"); | 
| +var promiseFulfillReactionsSymbol = | 
| +    utils.ImportNow("promise_fulfill_reactions_symbol"); | 
| +var promiseDeferredReactionSymbol = | 
| +    utils.ImportNow("promise_deferred_reaction_symbol"); | 
| var promiseHandledHintSymbol = | 
| utils.ImportNow("promise_handled_hint_symbol"); | 
| var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); | 
| @@ -30,7 +36,6 @@ | 
| var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); | 
| var ObjectHasOwnProperty; | 
| var GlobalPromise = global.Promise; | 
| -var PromiseThen = GlobalPromise.prototype.then; | 
|  | 
| utils.Import(function(from) { | 
| ObjectHasOwnProperty = from.ObjectHasOwnProperty; | 
| @@ -40,6 +45,42 @@ | 
| // ------------------------------------------------------------------- | 
|  | 
| // Core functionality. | 
| + | 
| +function PromiseSet(promise, status, value) { | 
| +  SET_PRIVATE(promise, promiseStateSymbol, status); | 
| +  SET_PRIVATE(promise, promiseResultSymbol, value); | 
| + | 
| +  // There are 3 possible states for the resolve, reject symbols when we add | 
| +  // a new callback -- | 
| +  // 1) UNDEFINED -- This is the zero state where there is no callback | 
| +  // registered. When we see this state, we directly attach the callbacks to | 
| +  // the symbol. | 
| +  // 2) !IS_ARRAY -- There is a single callback directly attached to the | 
| +  // symbols. We need to create a new array to store additional callbacks. | 
| +  // 3) IS_ARRAY -- There are multiple callbacks already registered, | 
| +  // therefore we can just push the new callback to the existing array. | 
| +  SET_PRIVATE(promise, promiseFulfillReactionsSymbol, UNDEFINED); | 
| +  SET_PRIVATE(promise, promiseRejectReactionsSymbol, UNDEFINED); | 
| + | 
| +  // This symbol is used only when one deferred needs to be attached. When more | 
| +  // than one deferred need to be attached the promise, we attach them directly | 
| +  // to the promiseFulfillReactionsSymbol and promiseRejectReactionsSymbol and | 
| +  // reset this back to UNDEFINED. | 
| +  SET_PRIVATE(promise, promiseDeferredReactionSymbol, UNDEFINED); | 
| + | 
| +  return promise; | 
| +} | 
| + | 
| +function PromiseCreateAndSet(status, value) { | 
| +  var promise = %promise_internal_constructor(); | 
| +  // If debug is active, notify about the newly created promise first. | 
| +  if (DEBUG_IS_ACTIVE) PromiseSet(promise, kPending, UNDEFINED); | 
| +  return PromiseSet(promise, status, value); | 
| +} | 
| + | 
| +function PromiseInit(promise) { | 
| +  return PromiseSet(promise, kPending, UNDEFINED); | 
| +} | 
|  | 
| function PromiseHandle(value, handler, deferred) { | 
| var debug_is_active = DEBUG_IS_ACTIVE; | 
| @@ -57,6 +98,7 @@ | 
| // Pass false for debugEvent so .then chaining or throwaway promises | 
| // in async functions do not trigger redundant ExceptionEvents. | 
| %PromiseReject(deferred.promise, exception, false); | 
| +        PromiseSet(deferred.promise, kRejected, exception); | 
| } else { | 
| %_Call(deferred.reject, UNDEFINED, exception); | 
| } | 
| @@ -92,6 +134,34 @@ | 
| return [id, name]; | 
| } | 
|  | 
| +function PromiseAttachCallbacks(promise, deferred, onResolve, onReject) { | 
| +  var maybeResolveCallbacks = | 
| +      GET_PRIVATE(promise, promiseFulfillReactionsSymbol); | 
| +  if (IS_UNDEFINED(maybeResolveCallbacks)) { | 
| +    SET_PRIVATE(promise, promiseFulfillReactionsSymbol, onResolve); | 
| +    SET_PRIVATE(promise, promiseRejectReactionsSymbol, onReject); | 
| +    SET_PRIVATE(promise, promiseDeferredReactionSymbol, deferred); | 
| +  } else if (!IS_ARRAY(maybeResolveCallbacks)) { | 
| +    var resolveCallbacks = new InternalArray(); | 
| +    var rejectCallbacks = new InternalArray(); | 
| +    var existingDeferred = GET_PRIVATE(promise, promiseDeferredReactionSymbol); | 
| + | 
| +    resolveCallbacks.push( | 
| +        maybeResolveCallbacks, existingDeferred, onResolve, deferred); | 
| +    rejectCallbacks.push(GET_PRIVATE(promise, promiseRejectReactionsSymbol), | 
| +                         existingDeferred, | 
| +                         onReject, | 
| +                         deferred); | 
| + | 
| +    SET_PRIVATE(promise, promiseFulfillReactionsSymbol, resolveCallbacks); | 
| +    SET_PRIVATE(promise, promiseRejectReactionsSymbol, rejectCallbacks); | 
| +    SET_PRIVATE(promise, promiseDeferredReactionSymbol, UNDEFINED); | 
| +  } else { | 
| +    maybeResolveCallbacks.push(onResolve, deferred); | 
| +    GET_PRIVATE(promise, promiseRejectReactionsSymbol).push(onReject, deferred); | 
| +  } | 
| +} | 
| + | 
| function PromiseIdResolveHandler(x) { return x; } | 
| function PromiseIdRejectHandler(r) { %_ReThrow(r); } | 
| SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true); | 
| @@ -101,9 +171,8 @@ | 
|  | 
| // For bootstrapper. | 
|  | 
| -// This is used by utils and v8-extras. | 
| function PromiseCreate() { | 
| -  return %promise_internal_constructor(); | 
| +  return PromiseInit(%promise_internal_constructor()); | 
| } | 
|  | 
| // ES#sec-promise-resolve-functions | 
| @@ -112,6 +181,7 @@ | 
| if (resolution === promise) { | 
| var exception = %make_type_error(kPromiseCyclic, resolution); | 
| %PromiseReject(promise, exception, true); | 
| +    PromiseSet(promise, kRejected, exception); | 
| return; | 
| } | 
| if (IS_RECEIVER(resolution)) { | 
| @@ -120,6 +190,7 @@ | 
| var then = resolution.then; | 
| } catch (e) { | 
| %PromiseReject(promise, e, true); | 
| +      PromiseSet(promise, kRejected, e); | 
| return; | 
| } | 
|  | 
| @@ -127,16 +198,18 @@ | 
| // rejected, shortcircuit the resolution procedure by directly | 
| // reusing the value from the promise. | 
| if (%is_promise(resolution) && then === PromiseThen) { | 
| -      var thenableState = %PromiseStatus(resolution); | 
| +      var thenableState = GET_PRIVATE(resolution, promiseStateSymbol); | 
| if (thenableState === kFulfilled) { | 
| // This goes inside the if-else to save one symbol lookup in | 
| // the slow path. | 
| -        var thenableValue = %PromiseResult(resolution); | 
| -        %PromiseFulfill(promise, kFulfilled, thenableValue); | 
| +        var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); | 
| +        %PromiseFulfill(promise, kFulfilled, thenableValue, | 
| +                       promiseFulfillReactionsSymbol); | 
| +        PromiseSet(promise, kFulfilled, thenableValue); | 
| SET_PRIVATE(promise, promiseHasHandlerSymbol, true); | 
| return; | 
| } else if (thenableState === kRejected) { | 
| -        var thenableValue = %PromiseResult(resolution); | 
| +        var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); | 
| if (!HAS_DEFINED_PRIVATE(resolution, promiseHasHandlerSymbol)) { | 
| // Promise has already been rejected, but had no handler. | 
| // Revoke previously triggered reject event. | 
| @@ -144,6 +217,7 @@ | 
| } | 
| // Don't cause a debug event as this case is forwarding a rejection | 
| %PromiseReject(promise, thenableValue, false); | 
| +        PromiseSet(promise, kRejected, thenableValue); | 
| SET_PRIVATE(resolution, promiseHasHandlerSymbol, true); | 
| return; | 
| } | 
| @@ -158,17 +232,21 @@ | 
| return; | 
| } | 
| } | 
| -  %PromiseFulfill(promise, kFulfilled, resolution); | 
| +  %PromiseFulfill(promise, kFulfilled, resolution, | 
| +                  promiseFulfillReactionsSymbol); | 
| +  PromiseSet(promise, kFulfilled, resolution); | 
| } | 
|  | 
| // Only used by async-await.js | 
| function RejectPromise(promise, reason, debugEvent) { | 
| %PromiseReject(promise, reason, debugEvent); | 
| +  PromiseSet(promise, kRejected, reason); | 
| } | 
|  | 
| // Export to bindings | 
| function DoRejectPromise(promise, reason) { | 
| %PromiseReject(promise, reason, true); | 
| +  PromiseSet(promise, kRejected, reason); | 
| } | 
|  | 
| // The resultCapability.promise is only ever fulfilled internally, | 
| @@ -176,7 +254,7 @@ | 
| // calling them multiple times. | 
| function CreateInternalPromiseCapability() { | 
| return { | 
| -    promise: %promise_internal_constructor(), | 
| +    promise: PromiseCreate(), | 
| resolve: UNDEFINED, | 
| reject: UNDEFINED | 
| }; | 
| @@ -187,7 +265,7 @@ | 
| function NewPromiseCapability(C, debugEvent) { | 
| if (C === GlobalPromise) { | 
| // Optimized case, avoid extra closure. | 
| -    var promise = %promise_internal_constructor(); | 
| +    var promise = PromiseCreate(); | 
| // TODO(gsathya): Remove container for callbacks when this is | 
| // moved to CPP/TF. | 
| var callbacks = %create_resolving_functions(promise, debugEvent); | 
| @@ -220,7 +298,7 @@ | 
| } | 
| if (this === GlobalPromise) { | 
| // Optimized case, avoid extra closure. | 
| -    var promise = %promise_create_and_set(kRejected, r); | 
| +    var promise = PromiseCreateAndSet(kRejected, r); | 
| // Trigger debug events if the debugger is on, as Promise.reject is | 
| // equivalent to throwing an exception directly. | 
| %PromiseRejectEventFromStack(promise, r); | 
| @@ -232,6 +310,55 @@ | 
| } | 
| } | 
|  | 
| +function PerformPromiseThen(promise, onResolve, onReject, resultCapability) { | 
| +  if (!IS_CALLABLE(onResolve)) onResolve = PromiseIdResolveHandler; | 
| +  if (!IS_CALLABLE(onReject)) onReject = PromiseIdRejectHandler; | 
| + | 
| +  var status = GET_PRIVATE(promise, promiseStateSymbol); | 
| +  switch (status) { | 
| +    case kPending: | 
| +      PromiseAttachCallbacks(promise, resultCapability, onResolve, onReject); | 
| +      break; | 
| +    case kFulfilled: | 
| +      %EnqueuePromiseReactionJob(GET_PRIVATE(promise, promiseResultSymbol), | 
| +                                 onResolve, resultCapability, kFulfilled); | 
| +      break; | 
| +    case kRejected: | 
| +      if (!HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { | 
| +        // Promise has already been rejected, but had no handler. | 
| +        // Revoke previously triggered reject event. | 
| +        %PromiseRevokeReject(promise); | 
| +      } | 
| +      %EnqueuePromiseReactionJob(GET_PRIVATE(promise, promiseResultSymbol), | 
| +                                 onReject, resultCapability, kRejected); | 
| +      break; | 
| +  } | 
| + | 
| +  // Mark this promise as having handler. | 
| +  SET_PRIVATE(promise, promiseHasHandlerSymbol, true); | 
| +  return resultCapability.promise; | 
| +} | 
| + | 
| +// ES#sec-promise.prototype.then | 
| +// Promise.prototype.then ( onFulfilled, onRejected ) | 
| +// Multi-unwrapped chaining with thenable coercion. | 
| +function PromiseThen(onResolve, onReject) { | 
| +  if (!%is_promise(this)) { | 
| +    throw %make_type_error(kNotAPromise, this); | 
| +  } | 
| + | 
| +  var constructor = SpeciesConstructor(this, GlobalPromise); | 
| +  var resultCapability; | 
| +  if (constructor === GlobalPromise) { | 
| +    resultCapability = CreateInternalPromiseCapability(); | 
| +  } else { | 
| +    // Pass false for debugEvent so .then chaining does not trigger | 
| +    // redundant ExceptionEvents. | 
| +    resultCapability = NewPromiseCapability(constructor, false); | 
| +  } | 
| +  return PerformPromiseThen(this, onResolve, onReject, resultCapability); | 
| +} | 
| + | 
| // ES#sec-promise.prototype.catch | 
| // Promise.prototype.catch ( onRejected ) | 
| function PromiseCatch(onReject) { | 
| @@ -250,7 +377,7 @@ | 
|  | 
| // Avoid creating resolving functions. | 
| if (this === GlobalPromise) { | 
| -    var promise = %promise_internal_constructor(); | 
| +    var promise = PromiseCreate(); | 
| ResolvePromise(promise, x); | 
| return promise; | 
| } | 
| @@ -393,10 +520,8 @@ | 
| return true; | 
| } | 
|  | 
| -  if (!%is_promise(promise)) return false; | 
| - | 
| -  var queue = %PromiseRejectReactions(promise); | 
| -  var deferred = %PromiseDeferred(promise); | 
| +  var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol); | 
| +  var deferred = GET_PRIVATE(promise, promiseDeferredReactionSymbol); | 
|  | 
| if (IS_UNDEFINED(queue)) return false; | 
|  | 
| @@ -404,8 +529,8 @@ | 
| return PromiseHasUserDefinedRejectHandlerCheck(queue, deferred); | 
| } | 
|  | 
| -  for (var i = 0; i < queue.length; i++) { | 
| -    if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], deferred[i])) { | 
| +  for (var i = 0; i < queue.length; i += 2) { | 
| +    if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], queue[i + 1])) { | 
| return true; | 
| } | 
| } | 
| @@ -443,6 +568,7 @@ | 
| utils.InstallGetter(GlobalPromise, speciesSymbol, PromiseSpecies); | 
|  | 
| utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ | 
| +  "then", PromiseThen, | 
| "catch", PromiseCatch | 
| ]); | 
|  | 
| @@ -456,11 +582,7 @@ | 
| "promise_resolve", ResolvePromise, | 
| "promise_then", PromiseThen, | 
| "promise_handle", PromiseHandle, | 
| -  "promise_debug_get_info", PromiseDebugGetInfo, | 
| -  "new_promise_capability", NewPromiseCapability, | 
| -  "internal_promise_capability", CreateInternalPromiseCapability, | 
| -  "promise_id_resolve_handler", PromiseIdResolveHandler, | 
| -  "promise_id_reject_handler", PromiseIdRejectHandler | 
| +  "promise_debug_get_info", PromiseDebugGetInfo | 
| ]); | 
|  | 
| // This allows extras to create promises quickly without building extra | 
| @@ -478,6 +600,7 @@ | 
| to.PromiseThen = PromiseThen; | 
|  | 
| to.CreateInternalPromiseCapability = CreateInternalPromiseCapability; | 
| +  to.PerformPromiseThen = PerformPromiseThen; | 
| to.ResolvePromise = ResolvePromise; | 
| to.RejectPromise = RejectPromise; | 
| }); | 
|  |