dom/promise/PromiseCallback.cpp
author Masayuki Nakano <masayuki@d-toybox.com>
Sat, 12 Sep 2015 01:19:28 +0900
changeset 262062 062647162efbdb0086028f6121b05608923b7a5e
parent 261849 f63ec3ee07ed9c74dfddae5feab6545f4584731a
child 273671 e597563f32ff62d4fb5ae612cebf93a630b20621
permissions -rw-r--r--
Bug 895274 part.220 Rename NS_PRINT_EVENT_START to ePrintEventFirst r=smaug

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "PromiseCallback.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"

#include "jsapi.h"

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION_0(PromiseCallback)

PromiseCallback::PromiseCallback()
{
}

PromiseCallback::~PromiseCallback()
{
}

// ResolvePromiseCallback

NS_IMPL_CYCLE_COLLECTION_CLASS(ResolvePromiseCallback)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ResolvePromiseCallback,
                                                PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
  tmp->mGlobal = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ResolvePromiseCallback,
                                                  PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ResolvePromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ResolvePromiseCallback)
NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)

NS_IMPL_ADDREF_INHERITED(ResolvePromiseCallback, PromiseCallback)
NS_IMPL_RELEASE_INHERITED(ResolvePromiseCallback, PromiseCallback)

ResolvePromiseCallback::ResolvePromiseCallback(Promise* aPromise,
                                               JS::Handle<JSObject*> aGlobal)
  : mPromise(aPromise)
  , mGlobal(aGlobal)
{
  MOZ_ASSERT(aPromise);
  MOZ_ASSERT(aGlobal);
  HoldJSObjects(this);
}

ResolvePromiseCallback::~ResolvePromiseCallback()
{
  DropJSObjects(this);
}

nsresult
ResolvePromiseCallback::Call(JSContext* aCx,
                             JS::Handle<JS::Value> aValue)
{
  // Run resolver's algorithm with value and the synchronous flag set.

  JS::ExposeObjectToActiveJS(mGlobal);
  JS::ExposeValueToActiveJS(aValue);

  JSAutoCompartment ac(aCx, mGlobal);
  JS::Rooted<JS::Value> value(aCx, aValue);
  if (!JS_WrapValue(aCx, &value)) {
    NS_WARNING("Failed to wrap value into the right compartment.");
    return NS_ERROR_FAILURE;
  }

  mPromise->ResolveInternal(aCx, value);
  return NS_OK;
}

// RejectPromiseCallback

NS_IMPL_CYCLE_COLLECTION_CLASS(RejectPromiseCallback)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RejectPromiseCallback,
                                                PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
  tmp->mGlobal = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RejectPromiseCallback,
                                                  PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(RejectPromiseCallback)
NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(RejectPromiseCallback,
                                               PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_ADDREF_INHERITED(RejectPromiseCallback, PromiseCallback)
NS_IMPL_RELEASE_INHERITED(RejectPromiseCallback, PromiseCallback)

RejectPromiseCallback::RejectPromiseCallback(Promise* aPromise,
                                             JS::Handle<JSObject*> aGlobal)
  : mPromise(aPromise)
  , mGlobal(aGlobal)
{
  MOZ_ASSERT(aPromise);
  MOZ_ASSERT(mGlobal);
  HoldJSObjects(this);
}

RejectPromiseCallback::~RejectPromiseCallback()
{
  DropJSObjects(this);
}

nsresult
RejectPromiseCallback::Call(JSContext* aCx,
                            JS::Handle<JS::Value> aValue)
{
  // Run resolver's algorithm with value and the synchronous flag set.

  JS::ExposeObjectToActiveJS(mGlobal);
  JS::ExposeValueToActiveJS(aValue);

  JSAutoCompartment ac(aCx, mGlobal);
  JS::Rooted<JS::Value> value(aCx, aValue);
  if (!JS_WrapValue(aCx, &value)) {
    NS_WARNING("Failed to wrap value into the right compartment.");
    return NS_ERROR_FAILURE;
  }


  mPromise->RejectInternal(aCx, value);
  return NS_OK;
}

// WrapperPromiseCallback
NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WrapperPromiseCallback,
                                                PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextPromise)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
  tmp->mGlobal = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WrapperPromiseCallback,
                                                  PromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextPromise)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WrapperPromiseCallback)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback)
NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)

NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback)
NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback)

WrapperPromiseCallback::WrapperPromiseCallback(Promise* aNextPromise,
                                               JS::Handle<JSObject*> aGlobal,
                                               AnyCallback* aCallback)
  : mNextPromise(aNextPromise)
  , mGlobal(aGlobal)
  , mCallback(aCallback)
{
  MOZ_ASSERT(aNextPromise);
  MOZ_ASSERT(aGlobal);
  HoldJSObjects(this);
}

WrapperPromiseCallback::~WrapperPromiseCallback()
{
  DropJSObjects(this);
}

nsresult
WrapperPromiseCallback::Call(JSContext* aCx,
                             JS::Handle<JS::Value> aValue)
{
  JS::ExposeObjectToActiveJS(mGlobal);
  JS::ExposeValueToActiveJS(aValue);

  JSAutoCompartment ac(aCx, mGlobal);
  JS::Rooted<JS::Value> value(aCx, aValue);
  if (!JS_WrapValue(aCx, &value)) {
    NS_WARNING("Failed to wrap value into the right compartment.");
    return NS_ERROR_FAILURE;
  }

  ErrorResult rv;

  // PromiseReactionTask step 6
  JS::Rooted<JS::Value> retValue(aCx);
  mCallback->Call(value, &retValue, rv, "promise callback",
                  CallbackObject::eRethrowExceptions,
                  mNextPromise->Compartment());

  rv.WouldReportJSException();

  // PromiseReactionTask step 7
  if (rv.Failed()) {
    JS::Rooted<JS::Value> value(aCx);
    if (rv.IsJSException()) {
      rv.StealJSException(aCx, &value);

      if (!JS_WrapValue(aCx, &value)) {
        NS_WARNING("Failed to wrap value into the right compartment.");
        return NS_ERROR_FAILURE;
      }
    } else {
      // Convert the ErrorResult to a JS exception object that we can reject
      // ourselves with.  This will be exactly the exception that would get
      // thrown from a binding method whose ErrorResult ended up with whatever
      // is on "rv" right now.
      JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject());
      DebugOnly<bool> conversionResult = ToJSValue(aCx, rv, &value);
      MOZ_ASSERT(conversionResult);
    }

    mNextPromise->RejectInternal(aCx, value);
    return NS_OK;
  }

  // If the return value is the same as the promise itself, throw TypeError.
  if (retValue.isObject()) {
    JS::Rooted<JSObject*> valueObj(aCx, &retValue.toObject());
    Promise* returnedPromise;
    nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise);

    if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) {
      const char* fileName = nullptr;
      uint32_t lineNumber = 0;

      // Try to get some information about the callback to report a sane error,
      // but don't try too hard (only deals with scripted functions).
      JS::Rooted<JSObject*> unwrapped(aCx,
        js::CheckedUnwrap(mCallback->Callback()));

      if (unwrapped) {
        JSAutoCompartment ac(aCx, unwrapped);
        if (JS_ObjectIsFunction(aCx, unwrapped)) {
          JS::Rooted<JS::Value> asValue(aCx, JS::ObjectValue(*unwrapped));
          JS::Rooted<JSFunction*> func(aCx, JS_ValueToFunction(aCx, asValue));

          MOZ_ASSERT(func);
          JSScript* script = JS_GetFunctionScript(aCx, func);
          if (script) {
            fileName = JS_GetScriptFilename(script);
            lineNumber = JS_GetScriptBaseLineNumber(aCx, script);
          }
        }
      }

      // We're back in aValue's compartment here.
      JS::Rooted<JSString*> fn(aCx, JS_NewStringCopyZ(aCx, fileName));
      if (!fn) {
        // Out of memory. Promise will stay unresolved.
        JS_ClearPendingException(aCx);
        return NS_ERROR_OUT_OF_MEMORY;
      }

      JS::Rooted<JSString*> message(aCx,
        JS_NewStringCopyZ(aCx,
          "then() cannot return same Promise that it resolves."));
      if (!message) {
        // Out of memory. Promise will stay unresolved.
        JS_ClearPendingException(aCx);
        return NS_ERROR_OUT_OF_MEMORY;
      }

      JS::Rooted<JS::Value> typeError(aCx);
      if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, fn, lineNumber, 0,
                           nullptr, message, &typeError)) {
        // Out of memory. Promise will stay unresolved.
        JS_ClearPendingException(aCx);
        return NS_ERROR_OUT_OF_MEMORY;
      }

      mNextPromise->RejectInternal(aCx, typeError);
      return NS_OK;
    }
  }

  // Otherwise, run resolver's resolve with value.
  if (!JS_WrapValue(aCx, &retValue)) {
    NS_WARNING("Failed to wrap value into the right compartment.");
    return NS_ERROR_FAILURE;
  }

  mNextPromise->ResolveInternal(aCx, retValue);
  return NS_OK;
}

// NativePromiseCallback

NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback,
                                   PromiseCallback, mHandler)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback)
NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)

NS_IMPL_ADDREF_INHERITED(NativePromiseCallback, PromiseCallback)
NS_IMPL_RELEASE_INHERITED(NativePromiseCallback, PromiseCallback)

NativePromiseCallback::NativePromiseCallback(PromiseNativeHandler* aHandler,
                                             Promise::PromiseState aState)
  : mHandler(aHandler)
  , mState(aState)
{
  MOZ_ASSERT(aHandler);
}

NativePromiseCallback::~NativePromiseCallback()
{
}

nsresult
NativePromiseCallback::Call(JSContext* aCx,
                            JS::Handle<JS::Value> aValue)
{
  JS::ExposeValueToActiveJS(aValue);

  if (mState == Promise::Resolved) {
    mHandler->ResolvedCallback(aCx, aValue);
    return NS_OK;
  }

  if (mState == Promise::Rejected) {
    mHandler->RejectedCallback(aCx, aValue);
    return NS_OK;
  }

  NS_NOTREACHED("huh?");
  return NS_ERROR_FAILURE;
}

/* static */ PromiseCallback*
PromiseCallback::Factory(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal,
                         AnyCallback* aCallback, Task aTask)
{
  MOZ_ASSERT(aNextPromise);

  // If we have a callback and a next resolver, we have to exec the callback and
  // then propagate the return value to the next resolver->resolve().
  if (aCallback) {
    return new WrapperPromiseCallback(aNextPromise, aGlobal, aCallback);
  }

  if (aTask == Resolve) {
    return new ResolvePromiseCallback(aNextPromise, aGlobal);
  }

  if (aTask == Reject) {
    return new RejectPromiseCallback(aNextPromise, aGlobal);
  }

  MOZ_ASSERT(false, "This should not happen");
  return nullptr;
}

} // namespace dom
} // namespace mozilla