blob: 36689cbd156eb5b6545f613160dcda9a8352fd8d [file] [log] [blame]
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobalt/script/mozjs/promise_wrapper.h"
#include "base/logging.h"
#include "third_party/mozjs/js/src/jsfun.h"
namespace cobalt {
namespace script {
namespace mozjs {
namespace {
enum ReservedSlots {
kResolveFunction,
kRejectFunction,
kPromiseObject,
kNumReservedSlots,
};
JSClass native_promise_class = {
"NativePromise", // name
JSCLASS_HAS_RESERVED_SLOTS(kNumReservedSlots), // flags
JS_PropertyStub, // addProperty
JS_DeletePropertyStub, // delProperty
JS_PropertyStub, // getProperty
JS_StrictPropertyStub, // setProperty
JS_EnumerateStub, // enumerate
JS_ResolveStub, // resolve
JS_ConvertStub, // convert
NULL, // finalize
NULL, // trace
};
JSBool NativeExecutor(JSContext* context, unsigned argc, JS::Value* vp) {
// Get the resolve/reject functions from the call args.
JS::CallArgs call_args = CallArgsFromVp(argc, vp);
DCHECK_EQ(call_args.length(), 2);
// Get the this object. Should be the native_promise object.
JS::RootedValue this_value(context, call_args.computeThis(context));
DCHECK(this_value.isObject());
JS::RootedObject this_object(context, JSVAL_TO_OBJECT(this_value));
DCHECK_EQ(JS_GetClass(this_object), &native_promise_class);
// First argument is the resolve function. Second is the reject function.
// Stash these in the reserved slots. Reserved slots get visited so there is
// no need to define a special trace function.
JS::RootedValue resolve_function_value(context, call_args.get(0));
JS::RootedValue reject_function_value(context, call_args.get(1));
DCHECK(resolve_function_value.isObject());
DCHECK(JS_ObjectIsFunction(context, JSVAL_TO_OBJECT(resolve_function_value)));
DCHECK(reject_function_value.isObject());
DCHECK(JS_ObjectIsFunction(context, JSVAL_TO_OBJECT(reject_function_value)));
JS_SetReservedSlot(this_object, kResolveFunction, resolve_function_value);
JS_SetReservedSlot(this_object, kRejectFunction, reject_function_value);
return true;
}
// Creates a new NativePromise object and initializes its reserved slots.
JSObject* CreateNativePromise(JSContext* context) {
JS::RootedObject native_promise(
context, JS_NewObject(context, &native_promise_class, NULL, NULL));
DCHECK(native_promise);
for (uint32_t i = 0; i < kNumReservedSlots; ++i) {
JS_SetReservedSlot(native_promise, i, JS::NullHandleValue);
}
return native_promise;
}
// Create a new native function with the |native_promise| bound as |this|.
JSObject* CreateExecutorArgument(JSContext* context,
JS::HandleObject native_promise) {
JS::RootedObject executor_function(context);
executor_function =
JS_NewFunction(context, &NativeExecutor, 2, 0, NULL, NULL);
DCHECK(executor_function);
JS::RootedObject bound_executor(context);
bound_executor = JS_BindCallable(context, executor_function, native_promise);
DCHECK(bound_executor);
return bound_executor;
}
// Get the Promise constructor from the global object.
JSObject* GetPromiseConstructor(JSContext* context,
JS::HandleObject global_object) {
JS::RootedValue promise_constructor_property(context);
JSBool result = JS_GetProperty(context, global_object, "Promise",
promise_constructor_property.address());
DCHECK(result);
if (!promise_constructor_property.isObject() ||
!JS_ObjectIsFunction(context,
JSVAL_TO_OBJECT(promise_constructor_property))) {
DLOG(ERROR) << "\"Promise\" property is not a function.";
return NULL;
}
return JSVAL_TO_OBJECT(promise_constructor_property);
}
void Settle(JSContext* context, JS::HandleValue result,
JS::HandleObject resolver, ReservedSlots slot) {
JS::RootedValue slot_value(context, JS_GetReservedSlot(resolver, slot));
DCHECK(slot_value.isObject());
DCHECK(JS_ObjectIsFunction(context, JSVAL_TO_OBJECT(slot_value)));
JS::RootedValue return_value(context);
const size_t kNumArguments = result.isUndefined() ? 0 : 1;
JS::Value args[1] = {result};
JSBool call_result =
JS_CallFunctionValue(context, resolver, slot_value, kNumArguments, args,
return_value.address());
if (!call_result) {
DLOG(ERROR) << "Exception calling Promise function.";
JS_ClearPendingException(context);
}
}
} // namespace
JSObject* PromiseWrapper::Create(JSContext* context,
JS::HandleObject global_object) {
// Get the Promise constructor.
JS::RootedObject constructor(context,
GetPromiseConstructor(context, global_object));
if (!constructor) {
DLOG(ERROR) << "Failed to find Promise constructor.";
return NULL;
}
// Create a new NativePromise JS object, and bind it to the NativeExecutor
// function.
JS::RootedObject promise_wrapper(context, CreateNativePromise(context));
DCHECK(promise_wrapper);
JS::RootedObject executor(context,
CreateExecutorArgument(context, promise_wrapper));
DCHECK(executor);
// Invoke the Promise constructor with the native executor function.
const size_t kNumArguments = 1;
JS::Value args[kNumArguments] = {OBJECT_TO_JSVAL(executor)};
JS::RootedObject promise_object(context);
promise_object = JS_New(context, constructor, kNumArguments, args);
if (!promise_object) {
DLOG(ERROR) << "Failed to create a new Promise.";
return NULL;
}
// Maintain a handle to the promise object on the NativePromise.
JS_SetReservedSlot(promise_wrapper, kPromiseObject,
OBJECT_TO_JSVAL(promise_object));
return promise_wrapper;
}
JSObject* PromiseWrapper::GetPromise() const {
JS::RootedObject promise(context_);
JS::RootedObject promise_wrapper(context_, weak_promise_wrapper_.GetObject());
if (promise_wrapper) {
JS::RootedValue slot_value(
context_, JS_GetReservedSlot(promise_wrapper, kPromiseObject));
DCHECK(slot_value.isObject());
promise = JSVAL_TO_OBJECT(slot_value);
}
return promise;
}
void PromiseWrapper::Resolve(JS::HandleValue value) const {
JS::RootedObject promise_wrapper(context_, weak_promise_wrapper_.GetObject());
if (promise_wrapper) {
Settle(context_, value, promise_wrapper, kResolveFunction);
}
}
void PromiseWrapper::Reject(JS::HandleValue value) const {
JS::RootedObject promise_wrapper(context_, weak_promise_wrapper_.GetObject());
if (promise_wrapper) {
Settle(context_, value, promise_wrapper, kRejectFunction);
}
}
PromiseWrapper::PromiseWrapper(JSContext* context,
JS::HandleObject promise_wrapper)
: context_(context), weak_promise_wrapper_(context, promise_wrapper) {
DCHECK_EQ(JS_GetClass(promise_wrapper), &native_promise_class);
}
} // namespace mozjs
} // namespace script
} // namespace cobalt