blob: 6166bfabf77809f91e747b7530420f60ca4fa845 [file] [log] [blame]
/*
* Copyright 2016 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.
*/
#ifndef COBALT_SCRIPT_MOZJS_CONVERSION_HELPERS_H_
#define COBALT_SCRIPT_MOZJS_CONVERSION_HELPERS_H_
#include <limits>
#include <string>
#include "base/logging.h"
#include "base/optional.h"
#include "base/stringprintf.h"
#include "cobalt/base/enable_if.h"
#include "cobalt/base/token.h"
#include "cobalt/script/mozjs/mozjs_callback_interface_holder.h"
#include "cobalt/script/mozjs/mozjs_exception_state.h"
#include "cobalt/script/mozjs/mozjs_global_environment.h"
#include "cobalt/script/mozjs/mozjs_object_handle.h"
#include "cobalt/script/mozjs/mozjs_user_object_holder.h"
#include "cobalt/script/mozjs/type_traits.h"
#include "cobalt/script/mozjs/union_type_conversion_forward.h"
#include "third_party/mozjs/js/src/jsapi.h"
#include "third_party/mozjs/js/src/jsproxy.h"
#include "third_party/mozjs/js/src/jsstr.h"
namespace cobalt {
namespace script {
namespace mozjs {
// Flags that can be used as a bitmask for special conversion behaviour.
enum ConversionFlags {
kNoConversionFlags = 0,
kConversionFlagRestricted = 1 << 0,
kConversionFlagNullable = 1 << 1,
kConversionFlagTreatNullAsEmptyString = 1 << 2,
kConversionFlagTreatUndefinedAsEmptyString = 1 << 3,
// Valid conversion flags for numeric values.
kConversionFlagsNumeric = kConversionFlagRestricted,
// Valid conversion flags for string types.
kConversionFlagsString = kConversionFlagTreatNullAsEmptyString |
kConversionFlagTreatUndefinedAsEmptyString,
// Valid conversion flags for objects.
kConversionFlagsObject = kConversionFlagNullable,
// Valid conversion flags for callback functions.
kConversionFlagsCallbackFunction = kConversionFlagNullable,
// Valid conversion flags for callback interfaces.
kConversionFlagsCallbackInterface = kConversionFlagNullable,
};
// std::string -> JSValue
inline void ToJSValue(JSContext* context, const std::string& in_string,
JS::MutableHandleValue out_value) {
size_t length = in_string.length();
jschar* inflated_buffer =
js::InflateUTF8String(context, in_string.c_str(), &length);
if (!inflated_buffer) {
LOG(ERROR) << "Failed to inflate UTF8 string.";
out_value.setNull();
return;
}
JS::RootedString rooted_string(
context, JS_NewUCStringCopyN(context, inflated_buffer, length));
js_free(inflated_buffer);
out_value.set(JS::StringValue(rooted_string));
}
// JSValue -> std::string
void FromJSValue(JSContext* context, JS::HandleValue value,
int conversion_flags, ExceptionState* exception_state,
std::string* out_string);
// base::Token -> JSValue
inline void ToJSValue(JSContext* context, const base::Token& token,
JS::MutableHandleValue out_value) {
ToJSValue(context, std::string(token.c_str()), out_value);
}
// bool -> JSValue
inline void ToJSValue(JSContext* context, bool in_boolean,
JS::MutableHandleValue out_value) {
out_value.set(JS::BooleanValue(in_boolean));
}
// JSValue -> bool
inline void FromJSValue(JSContext* context, JS::HandleValue value,
int conversion_flags, ExceptionState* exception_state,
bool* out_boolean) {
DCHECK_EQ(conversion_flags, kNoConversionFlags)
<< "No conversion flags supported.";
DCHECK(out_boolean);
// ToBoolean implements the ECMAScript ToBoolean operation.
*out_boolean = JS::ToBoolean(value);
}
// signed integers <= 4 bytes -> JSValue
template <typename T>
inline void ToJSValue(
JSContext* context, T in_number, JS::MutableHandleValue out_value,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
std::numeric_limits<T>::is_signed &&
(sizeof(T) <= 4),
T>::type* = NULL) {
out_value.set(INT_TO_JSVAL(in_number));
}
// JSValue -> signed integers <= 4 bytes
template <typename T>
inline void FromJSValue(
JSContext* context, JS::HandleValue value, int conversion_flags,
ExceptionState* exception_state, T* out_number,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
std::numeric_limits<T>::is_signed &&
(sizeof(T) <= 4),
T>::type* = NULL) {
DCHECK_EQ(conversion_flags, kNoConversionFlags)
<< "No conversion flags supported.";
DCHECK(out_number);
int32_t out;
// Convert a JavaScript value to an integer type as specified by the
// ECMAScript standard.
JSBool success = JS_ValueToECMAInt32(context, value, &out);
DCHECK(success);
*out_number = static_cast<T>(out);
}
// JSValue -> signed integers > 4 bytes
template <typename T>
inline void FromJSValue(
JSContext* context, JS::HandleValue value, int conversion_flags,
ExceptionState* exception_state, T* out_number,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
std::numeric_limits<T>::is_signed &&
(sizeof(T) > 4),
T>::type* = NULL) {
double to_number;
JS::ToNumber(context, value, &to_number);
std::string value_str;
FromJSValue(context, value, conversion_flags, exception_state, &value_str);
DCHECK_EQ(conversion_flags, kNoConversionFlags)
<< "No conversion flags supported.";
DCHECK(out_number);
int64_t out;
// This produces an IDL long long.
JSBool success = JS_ValueToInt64(context, value, &out);
DCHECK(success);
if (!success) {
exception_state->SetSimpleException(kNotInt64Type);
return;
}
*out_number = static_cast<T>(out);
}
// signed integers > 4 bytes -> JSValue
template <typename T>
inline void ToJSValue(
JSContext* context, T in_number, JS::MutableHandleValue out_value,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
std::numeric_limits<T>::is_signed &&
(sizeof(T) > 4),
T>::type* = NULL) {
out_value.set(JS_NumberValue(in_number));
}
// unsigned integers <= 4 bytes -> JSValue
template <typename T>
inline void ToJSValue(
JSContext* context, T in_number, JS::MutableHandleValue out_value,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T>::is_signed &&
(sizeof(T) <= 4),
T>::type* = NULL) {
out_value.set(UINT_TO_JSVAL(in_number));
}
// JSValue -> unsigned integers <= 4 bytes
template <typename T>
inline void FromJSValue(
JSContext* context, JS::HandleValue value, int conversion_flags,
ExceptionState* exception_state, T* out_number,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T>::is_signed &&
(sizeof(T) <= 4),
T>::type* = NULL) {
DCHECK_EQ(conversion_flags, kNoConversionFlags)
<< "No conversion flags supported.";
DCHECK(out_number);
uint32_t out;
// Convert a JavaScript value to an integer type as specified by the
// ECMAScript standard.
JSBool success = JS_ValueToECMAUint32(context, value, &out);
DCHECK(success);
*out_number = static_cast<T>(out);
}
// JSValue -> unsigned integers > 4 bytes
template <typename T>
inline void FromJSValue(
JSContext* context, JS::HandleValue value, int conversion_flags,
ExceptionState* exception_state, T* out_number,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T>::is_signed &&
(sizeof(T) > 4),
T>::type* = NULL) {
DCHECK_EQ(conversion_flags, kNoConversionFlags)
<< "No conversion flags supported.";
DCHECK(out_number);
uint64_t out;
// This produces and IDL unsigned long long.
JSBool success = JS_ValueToUint64(context, value, &out);
DCHECK(success);
if (!success) {
exception_state->SetSimpleException(kNotUint64Type);
return;
}
*out_number = static_cast<T>(out);
}
// unsigned integers > 4 bytes -> JSValue
template <typename T>
inline void ToJSValue(
JSContext* context, T in_number, JS::MutableHandleValue out_value,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T>::is_signed &&
(sizeof(T) > 4),
T>::type* = NULL) {
out_value.set(JS_NumberValue(in_number));
}
// double -> JSValue
template <typename T>
inline void ToJSValue(
JSContext* context, T in_number, JS::MutableHandleValue out_value,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
!std::numeric_limits<T>::is_integer,
T>::type* = NULL) {
out_value.set(DOUBLE_TO_JSVAL(in_number));
}
// JSValue -> double
template <typename T>
inline void FromJSValue(
JSContext* context, JS::HandleValue value, int conversion_flags,
ExceptionState* exception_state, T* out_number,
typename base::enable_if<std::numeric_limits<T>::is_specialized &&
!std::numeric_limits<T>::is_integer,
T>::type* = NULL) {
DCHECK_EQ(conversion_flags & ~kConversionFlagsNumeric, 0)
<< "Unexpected conversion flags found.";
DCHECK(out_number);
double double_value;
if (!JS::ToNumber(context, value, &double_value)) {
exception_state->SetSimpleException(kNotNumberType);
return;
}
if (!mozilla::IsFinite(double_value) &&
(conversion_flags & kConversionFlagRestricted)) {
exception_state->SetSimpleException(kNotFinite);
return;
}
*out_number = double_value;
}
// optional<T> -> JSValue
template <typename T>
inline void ToJSValue(JSContext* context, const base::optional<T>& in_optional,
JS::MutableHandleValue out_value) {
if (!in_optional) {
out_value.setNull();
return;
}
ToJSValue(context, in_optional.value(), out_value);
}
// JSValue -> optional<T>
template <typename T>
inline void FromJSValue(JSContext* context, JS::HandleValue value,
int conversion_flags, ExceptionState* exception_state,
base::optional<T>* out_optional) {
if (value.isNull()) {
*out_optional = base::nullopt;
} else if (value.isUndefined()) {
*out_optional = base::nullopt;
} else {
*out_optional = T();
FromJSValue(context, value, conversion_flags & ~kConversionFlagNullable,
exception_state, &(out_optional->value()));
}
}
// JSValue -> optional<std::string>
template <>
inline void FromJSValue(JSContext* context, JS::HandleValue value,
int conversion_flags, ExceptionState* exception_state,
base::optional<std::string>* out_optional) {
if (value.isNull()) {
*out_optional = base::nullopt;
} else if (value.isUndefined() &&
!(conversion_flags & kConversionFlagTreatUndefinedAsEmptyString)) {
// If TreatUndefinedAs=EmptyString is set, skip the default conversion
// of undefined to null.
*out_optional = base::nullopt;
} else {
*out_optional = std::string();
FromJSValue(context, value, conversion_flags & ~kConversionFlagNullable,
exception_state, &(out_optional->value()));
}
}
// OpaqueHandle -> JSValue
void ToJSValue(JSContext* context,
const OpaqueHandleHolder* opaque_handle_holder,
JS::MutableHandleValue out_value);
// JSValue -> OpaqueHandle
void FromJSValue(JSContext* context, JS::HandleValue value,
int conversion_flags, ExceptionState* exception_state,
MozjsObjectHandleHolder* out_holder);
// object -> JSValue
template <typename T>
inline void ToJSValue(JSContext* context, const scoped_refptr<T>& in_object,
JS::MutableHandleValue out_value) {
if (!in_object) {
out_value.setNull();
return;
}
MozjsGlobalEnvironment* global_environment =
static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
JS::RootedObject object(
context,
global_environment->wrapper_factory()->GetWrapperProxy(in_object));
DCHECK(object);
JS::RootedObject proxy_target(context, js::GetProxyTargetObject(object));
if (JS_IsGlobalObject(proxy_target)) {
object = proxy_target;
}
out_value.set(OBJECT_TO_JSVAL(object));
}
// JSValue -> object
template <typename T>
inline void FromJSValue(JSContext* context, JS::HandleValue value,
int conversion_flags, ExceptionState* exception_state,
scoped_refptr<T>* out_object) {
DCHECK_EQ(conversion_flags & ~kConversionFlagsObject, 0)
<< "Unexpected conversion flags found.";
JS::RootedObject js_object(context);
if (value.isNull() || value.isUndefined()) {
if (!(conversion_flags & kConversionFlagNullable)) {
exception_state->SetSimpleException(kNotNullableType);
}
return;
}
if (!JS_ValueToObject(context, value, js_object.address())) {
exception_state->SetSimpleException(kNotObjectType);
return;
}
DCHECK(js_object);
if (js::IsProxy(js_object)) {
JS::RootedObject wrapper(context, js::GetProxyTargetObject(js_object));
MozjsGlobalEnvironment* global_environment =
static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
const WrapperFactory* wrapper_factory =
global_environment->wrapper_factory();
if (wrapper_factory->IsWrapper(wrapper)) {
bool object_implements_interface =
wrapper_factory->DoesObjectImplementInterface(js_object,
base::GetTypeId<T>());
if (!object_implements_interface) {
exception_state->SetSimpleException(kDoesNotImplementInterface);
return;
}
WrapperPrivate* wrapper_private =
WrapperPrivate::GetFromWrapperObject(wrapper);
*out_object = wrapper_private->wrappable<T>();
return;
}
}
// This is not a platform object. Return a type error.
exception_state->SetSimpleException(kDoesNotImplementInterface);
}
// CallbackInterface -> JSValue
template <typename T>
inline void ToJSValue(JSContext* context,
const ScriptObject<T>* callback_interface,
JS::MutableHandleValue out_value) {
if (!callback_interface) {
out_value.set(JS::NullValue());
return;
}
typedef typename CallbackInterfaceTraits<T>::MozjsCallbackInterfaceClass
MozjsCallbackInterfaceClass;
// Downcast to MozjsUserObjectHolder<T> so we can get the underlying JSObject.
typedef MozjsUserObjectHolder<MozjsCallbackInterfaceClass>
MozjsUserObjectHolderClass;
const MozjsUserObjectHolderClass* user_object_holder =
base::polymorphic_downcast<const MozjsUserObjectHolderClass*>(
callback_interface);
// Shouldn't be NULL. If the callback was NULL then NULL should have been
// passed as an argument into this function.
// Downcast to the corresponding MozjsCallbackInterface type, from which we
// can get the implementing object.
const MozjsCallbackInterfaceClass* mozjs_callback_interface =
base::polymorphic_downcast<const MozjsCallbackInterfaceClass*>(
user_object_holder->GetScriptObject());
DCHECK(mozjs_callback_interface);
out_value.set(OBJECT_TO_JSVAL(mozjs_callback_interface->handle()));
}
// JSValue -> CallbackInterface
template <typename T>
inline void FromJSValue(
JSContext* context, JS::HandleValue value, int conversion_flags,
ExceptionState* out_exception,
MozjsCallbackInterfaceHolder<T>* out_callback_interface) {
typedef T MozjsCallbackInterfaceClass;
DCHECK_EQ(conversion_flags & ~kConversionFlagsCallbackFunction, 0)
<< "No conversion flags supported.";
if (value.isNull()) {
if (!(conversion_flags & kConversionFlagNullable)) {
out_exception->SetSimpleException(kNotNullableType);
}
// If it is a nullable type, just return.
return;
}
// https://www.w3.org/TR/WebIDL/#es-user-objects
// Any user object can be considered to implement a user interface. Actually
// checking if the correct properties exist will happen when the operation
// on the callback interface is run.
if (!value.isObject()) {
out_exception->SetSimpleException(kNotObjectType);
return;
}
MozjsGlobalEnvironment* global_environment =
static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context));
JS::RootedObject implementing_object(context, JSVAL_TO_OBJECT(value));
DCHECK(implementing_object);
*out_callback_interface = MozjsCallbackInterfaceHolder<T>(
implementing_object, context, global_environment->wrapper_factory());
}
} // namespace mozjs
} // namespace script
} // namespace cobalt
// Union type conversion is generated by a pump script.
#include "cobalt/script/mozjs/union_type_conversion_impl.h"
#endif // COBALT_SCRIPT_MOZJS_CONVERSION_HELPERS_H_