blob: 3b301358868a68d1f89a87fe4e932094f5b44887 [file] [log] [blame]
/*
* Copyright 2014 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_JAVASCRIPTCORE_CONVERSION_HELPERS_H_
#define COBALT_SCRIPT_JAVASCRIPTCORE_CONVERSION_HELPERS_H_
#include <limits>
#include <string>
#include "base/logging.h"
#include "base/optional.h"
#include "cobalt/base/enable_if.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/base/token.h"
#include "cobalt/script/javascriptcore/jsc_callback_function.h"
#include "cobalt/script/javascriptcore/jsc_callback_function_holder.h"
#include "cobalt/script/javascriptcore/jsc_callback_interface.h"
#include "cobalt/script/javascriptcore/jsc_callback_interface_holder.h"
#include "cobalt/script/javascriptcore/jsc_exception_state.h"
#include "cobalt/script/javascriptcore/jsc_global_object.h"
#include "cobalt/script/javascriptcore/jsc_object_handle_holder.h"
#include "cobalt/script/javascriptcore/union_type_conversion_forward.h"
#include "cobalt/script/javascriptcore/wrapper_base.h"
#include "third_party/WebKit/Source/JavaScriptCore/config.h"
#include "third_party/WebKit/Source/JavaScriptCore/runtime/Error.h"
#include "third_party/WebKit/Source/JavaScriptCore/runtime/JSFunction.h"
#include "third_party/WebKit/Source/JavaScriptCore/runtime/JSGlobalData.h"
#include "third_party/WebKit/Source/JavaScriptCore/runtime/JSValue.h"
#include "third_party/WebKit/Source/WTF/wtf/text/WTFString.h"
namespace cobalt {
namespace script {
namespace javascriptcore {
// 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 callback functions.
kConversionFlagsCallbackFunction = kConversionFlagNullable,
// Valid conversion flags for callback interfaces.
kConversionFlagsCallbackInterface = kConversionFlagNullable,
// Valid conversion flags for objects.
kConversionFlagsObject = kConversionFlagNullable,
};
// Convert std::string in utf8 encoding to WTFString.
WTF::String ToWTFString(const std::string& utf8_string);
// Convert WTFString to std::string in utf8.
std::string FromWTFString(const WTF::String& wtf_string);
// Convert std::string in utf8 encoding to a JSValue representing the string.
JSC::JSValue StringToJSValue(JSC::JSGlobalData* global_data,
const std::string& utf8_string);
// Convert a JSValue to a std::string in utf8 encoding.
void JSValueToString(JSC::ExecState* exec_state, JSC::JSValue value,
std::string* out_string);
// Convert base::Token in utf8 encoding to a JSValue representing the string.
JSC::JSValue TokenToJSValue(JSC::JSGlobalData* global_data,
base::Token utf8_string);
// For a given JSObject* get a pointer to the corresponding Cobalt
// implementation.
template <class T>
inline T* JSObjectToWrappable(JSC::ExecState* exec_state,
JSC::JSObject* js_object,
ExceptionState* out_exception) {
// Assumes we've already checked that null values are acceptable.
if (!js_object) {
return NULL;
}
JSCGlobalObject* global_object =
JSC::jsCast<JSCGlobalObject*>(exec_state->lexicalGlobalObject());
Wrappable* wrappable = NULL;
const JSC::ClassInfo* class_info = NULL;
if (js_object == global_object) {
// This is the global object, so get the pointer to the global interface
// from the JSCGlobalObject.
wrappable = global_object->global_interface().get();
class_info = JSCGlobalObject::s_classinfo();
} else if (global_object->wrapper_factory()->IsWrapper(js_object)) {
// This is a wrapper object, so get the Wrappable from the appropriate
// base class.
if (js_object->isErrorInstance()) {
wrappable = ExceptionBase::GetWrappable(js_object).get();
} else {
wrappable = InterfaceBase::GetWrappable(js_object).get();
}
class_info =
global_object->wrapper_factory()->GetClassInfo(base::GetTypeId<T>());
} else {
// This is not a platform object. Return a type error.
out_exception->SetSimpleException(kDoesNotImplementInterface);
return NULL;
}
// Check that the js_object is the expected class.
if (js_object->inherits(class_info)) {
return base::polymorphic_downcast<T*>(wrappable);
} else {
out_exception->SetSimpleException(kDoesNotImplementInterface);
return NULL;
}
}
// GetWrappableOrSetException functions will set an exception if the value is
// not a Wrapper object that corresponds to the Wrappable type T.
// Additionally, an exception will be set if object is NULL.
template <class T>
T* GetWrappableOrSetException(JSC::ExecState* exec_state,
JSC::JSObject* object) {
if (!object) {
JSC::throwTypeError(exec_state);
return NULL;
}
JSCGlobalObject* global_object =
JSC::jsCast<JSCGlobalObject*>(exec_state->lexicalGlobalObject());
JSCExceptionState exception_state(global_object);
T* impl = JSObjectToWrappable<T>(exec_state, object, &exception_state);
if (exception_state.is_exception_set()) {
JSC::throwError(exec_state, exception_state.exception_object());
return NULL;
}
return impl;
}
template <class T>
T* GetWrappableOrSetException(JSC::ExecState* exec_state, JSC::JSCell* cell) {
// getObject() returns NULL if the cell is not an object.
JSC::JSObject* object = cell ? cell->getObject() : NULL;
return GetWrappableOrSetException<T>(exec_state, object);
}
template <class T>
T* GetWrappableOrSetException(JSC::ExecState* exec_state, JSC::JSValue value) {
// getObject() returns NULL if the value is not an object.
JSC::JSObject* object = value.getObject();
return GetWrappableOrSetException<T>(exec_state, object);
}
// Overloads of ToJSValue convert different Cobalt types to JSValue.
//
// bool -> JSValue
inline JSC::JSValue ToJSValue(JSCGlobalObject*, bool in_boolean) {
return JSC::jsBoolean(in_boolean);
}
// numeric types -> JSValue
template <class T>
inline JSC::JSValue ToJSValue(
JSCGlobalObject*, T in_number,
typename base::enable_if<std::numeric_limits<T>::is_specialized>::type* =
NULL) {
JSC::JSValue number_value = JSC::jsNumber(in_number);
DCHECK(number_value.isNumber());
return number_value;
}
// std::string -> JSValue
inline JSC::JSValue ToJSValue(JSCGlobalObject* global_object,
const std::string& in_string) {
return StringToJSValue(&(global_object->globalData()), in_string);
}
// base::Token -> JSValue
inline JSC::JSValue ToJSValue(JSCGlobalObject* global_object,
base::Token in_token) {
return TokenToJSValue(&(global_object->globalData()), in_token);
}
// object -> JSValue
template <class T>
inline JSC::JSValue ToJSValue(JSCGlobalObject* global_object,
const scoped_refptr<T>& in_object) {
if (!in_object) {
return JSC::jsNull();
}
JSC::JSObject* wrapper =
global_object->wrapper_factory()->GetWrapper(global_object, in_object);
DCHECK(wrapper);
return JSC::JSValue(wrapper);
}
// optional<T> -> JSValue
template <class T>
inline JSC::JSValue ToJSValue(JSCGlobalObject* global_object,
base::optional<T> in_optional) {
if (!in_optional) {
return JSC::jsNull();
}
return ToJSValue(global_object, in_optional.value());
}
// CallbackFunction -> JSValue
template <class T>
inline JSC::JSValue ToJSValue(
JSCGlobalObject* global_object,
const ScriptObject<CallbackFunction<T> >* callback_function) {
if (!callback_function) {
return JSC::jsNull();
}
// Downcast to JSCCallbackFunctionHolder, which has public members that
// we can use to dig in to get the JS object.
const JSCCallbackFunctionHolder<CallbackFunction<T> >*
jsc_callback_function_holder = base::polymorphic_downcast<
const JSCCallbackFunctionHolder<CallbackFunction<T> >*>(
callback_function);
// Get the base CallbackFunction<T> class from the JSCCallbackFunctionHolder.
const CallbackFunction<T>* callback =
jsc_callback_function_holder->GetScriptObject();
// Shouldn't be NULL. If the callback was NULL then NULL should have been
// passed as an argument into this function.
DCHECK(callback);
// Downcast to the JSCCallbackFunction type, from which we can get the
// JSFunction.
const JSCCallbackFunction<T>* jsc_callback =
base::polymorphic_downcast<const JSCCallbackFunction<T>*>(callback);
return JSC::JSValue(jsc_callback->callable());
}
// CallbackInterface -> JSValue
template <class T>
inline JSC::JSValue ToJSValue(JSCGlobalObject* global_object,
const ScriptObject<T>* callback_interface) {
if (!callback_interface) {
return JSC::jsNull();
}
typedef typename CallbackInterfaceTraits<T>::JSCCallbackInterfaceClass
JSCCallbackInterfaceClass;
// Downcast to JSCCallbackInterfaceHolder
const JSCCallbackInterfaceHolder<T>* jsc_callback_interface_holder =
base::polymorphic_downcast<const JSCCallbackInterfaceHolder<T>*>(
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 JSCCallbackInterface type, from which we can
// get the implementing object.
const JSCCallbackInterfaceClass* jsc_callback_interface =
base::polymorphic_downcast<const JSCCallbackInterfaceClass*>(
jsc_callback_interface_holder->GetScriptObject());
DCHECK(jsc_callback_interface);
// Return the user object implementing this interface.
return JSC::JSValue(jsc_callback_interface->implementing_object());
}
// OpaqueHandle -> JSValue
inline JSC::JSValue ToJSValue(JSCGlobalObject* global_object,
const OpaqueHandleHolder* opaque_handle_holder) {
if (!opaque_handle_holder) {
return JSC::jsNull();
}
// Downcast to JSCObjectHandleHolder, which has public members that
// we can use to dig in to get the JS object.
const JSCObjectHandleHolder* jsc_object_handle_holder =
base::polymorphic_downcast<const JSCObjectHandleHolder*>(
opaque_handle_holder);
JSC::JSObject* js_object = jsc_object_handle_holder->js_object();
if (js_object) {
return JSC::JSValue(js_object);
} else {
return JSC::jsNull();
}
}
// Overloads of FromJSValue retrieve the Cobalt value from a JSValue.
//
// JSValue -> bool
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
bool* out_bool) {
DCHECK_EQ(conversion_flags, kNoConversionFlags)
<< "No conversion flags supported.";
*out_bool = jsvalue.toBoolean(exec_state);
}
// JSValue -> signed integers <= 4 bytes
template <class T>
inline void FromJSValue(
JSC::ExecState* exec_state, JSC::JSValue jsvalue, int conversion_flags,
ExceptionState* out_exception, 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.";
int32_t int32_value = jsvalue.toInt32(exec_state);
*out_number = static_cast<T>(int32_value);
}
// JSValue -> unsigned integers <= 4 bytes
template <class T>
inline void FromJSValue(
JSC::ExecState* exec_state, JSC::JSValue jsvalue, int conversion_flags,
ExceptionState* out_exception, 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.";
uint32_t uint32_value = jsvalue.toUInt32(exec_state);
*out_number = static_cast<T>(uint32_value);
}
// JSValue -> signed integers > 4 bytes
template <class T>
inline void FromJSValue(
JSC::ExecState* exec_state, JSC::JSValue jsvalue, int conversion_flags,
ExceptionState* out_exception, 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) {
NOTIMPLEMENTED();
}
// JSValue -> unsigned integers > 4 bytes
template <class T>
inline void FromJSValue(
JSC::ExecState* exec_state, JSC::JSValue jsvalue, int conversion_flags,
ExceptionState* out_exception, 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) {
NOTIMPLEMENTED();
}
// JSValue -> double
template <class T>
inline void FromJSValue(
JSC::ExecState* exec_state, JSC::JSValue jsvalue, int conversion_flags,
ExceptionState* out_exception, 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.";
double double_value = jsvalue.toNumber(exec_state);
if (!isfinite(double_value) &&
(conversion_flags & kConversionFlagRestricted)) {
out_exception->SetSimpleException(kNotFinite);
return;
}
*out_number = double_value;
}
// JSValue -> std::string
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
std::string* out_string) {
DCHECK_EQ(conversion_flags & ~kConversionFlagsString, 0)
<< "Unexpected conversion flags found: ";
if (jsvalue.isNull() &&
conversion_flags & kConversionFlagTreatNullAsEmptyString) {
*out_string = "";
} else if (jsvalue.isUndefined() &&
conversion_flags & kConversionFlagTreatUndefinedAsEmptyString) {
*out_string = "";
} else {
JSValueToString(exec_state, jsvalue, out_string);
}
}
// JSValue -> object
template <class T>
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
scoped_refptr<T>* out_object) {
DCHECK_EQ(conversion_flags & ~kConversionFlagsObject, 0)
<< "Unexpected conversion flags found.";
JSC::JSObject* js_object = NULL;
if (jsvalue.isNull()) {
if (!(conversion_flags & kConversionFlagNullable)) {
out_exception->SetSimpleException(kNotNullableType);
return;
}
} else {
// Returns NULL if jsvalue is not an object.
js_object = jsvalue.getObject();
if (!js_object) {
out_exception->SetSimpleException(kNotObjectType);
return;
}
}
*out_object = JSObjectToWrappable<T>(exec_state, js_object, out_exception);
}
// JSValue -> optional<T>
template <class T>
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
base::optional<T>* out_optional) {
if (jsvalue.isNull()) {
*out_optional = base::nullopt;
} else if (jsvalue.isUndefined()) {
*out_optional = base::nullopt;
} else {
*out_optional = T();
FromJSValue(exec_state, jsvalue,
conversion_flags & ~kConversionFlagNullable, out_exception,
&(out_optional->value()));
}
}
// JSValue -> optional<T>
template <>
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
base::optional<std::string>* out_optional) {
if (jsvalue.isNull()) {
*out_optional = base::nullopt;
} else if (jsvalue.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(exec_state, jsvalue,
conversion_flags & ~kConversionFlagNullable, out_exception,
&(out_optional->value()));
}
}
// JSValue -> CallbackFunction
template <class T>
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
JSCCallbackFunctionHolder<T>* out_callback) {
DCHECK_EQ(conversion_flags & ~kConversionFlagsCallbackFunction, 0)
<< "No conversion flags supported.";
if (jsvalue.isNull()) {
if (!(conversion_flags & kConversionFlagNullable)) {
out_exception->SetSimpleException(kNotNullableType);
}
// If it is a nullable type, just return.
return;
}
JSCGlobalObject* global_object =
JSC::jsCast<JSCGlobalObject*>(exec_state->lexicalGlobalObject());
// https://www.w3.org/TR/WebIDL/#es-callback-function
// 1. If V is not a Function object, throw a TypeError
if (!jsvalue.isFunction()) {
out_exception->SetSimpleException(kNotFunctionValue);
return;
}
JSC::JSFunction* js_function =
JSC::jsCast<JSC::JSFunction*>(jsvalue.asCell());
DCHECK(js_function);
JSCCallbackFunction<typename T::Signature> callback_function(js_function);
*out_callback = JSCCallbackFunctionHolder<T>(
callback_function, global_object->script_object_registry());
}
// JSValue -> CallbackInterface
template <class T>
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
JSCCallbackInterfaceHolder<T>* out_callback_interface) {
typedef typename CallbackInterfaceTraits<T>::JSCCallbackInterfaceClass
JSCCallbackInterfaceClass;
DCHECK_EQ(conversion_flags & ~kConversionFlagsCallbackFunction, 0)
<< "No conversion flags supported.";
if (jsvalue.isNull()) {
if (!(conversion_flags & kConversionFlagNullable)) {
out_exception->SetSimpleException(kNotNullableType);
}
// If it is a nullable type, just return.
return;
}
JSCGlobalObject* global_object =
JSC::jsCast<JSCGlobalObject*>(exec_state->lexicalGlobalObject());
// 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 (!jsvalue.isFunction() && !jsvalue.isObject()) {
out_exception->SetSimpleException(kNotObjectOrFunction);
return;
}
JSC::JSObject* js_object = jsvalue.getObject();
DCHECK(js_object);
JSCCallbackInterfaceClass callback_interface(js_object);
*out_callback_interface = JSCCallbackInterfaceHolder<T>(
callback_interface, global_object->script_object_registry());
}
// JSValue -> OpaqueHandle
inline void FromJSValue(JSC::ExecState* exec_state, JSC::JSValue jsvalue,
int conversion_flags, ExceptionState* out_exception,
JSCObjectHandleHolder* out_handle) {
DCHECK_EQ(conversion_flags & ~kConversionFlagsObject, 0)
<< "Unexpected conversion flags found.";
JSC::JSObject* js_object = NULL;
if (jsvalue.isNull()) {
if (!(conversion_flags & kConversionFlagNullable)) {
out_exception->SetSimpleException(kNotNullableType);
}
// Return here whether an exception was set or not.
return;
} else {
// https://www.w3.org/TR/WebIDL/#es-object
// 1. If Type(V) is not Object, throw a TypeError
js_object = jsvalue.getObject();
if (!js_object) {
out_exception->SetSimpleException(kNotObjectType);
return;
}
}
// Null cases should be handled above.
DCHECK(js_object);
JSCGlobalObject* global_object =
JSC::jsCast<JSCGlobalObject*>(exec_state->lexicalGlobalObject());
JSCObjectHandle jsc_object_handle(js_object);
*out_handle = JSCObjectHandleHolder(jsc_object_handle,
global_object->script_object_registry());
}
} // namespace javascriptcore
} // namespace script
} // namespace cobalt
#include "cobalt/script/javascriptcore/jsc_callback_function_impl.h"
// Union type conversion is generated by a pump script.
#include "cobalt/script/javascriptcore/union_type_conversion_impl.h"
#endif // COBALT_SCRIPT_JAVASCRIPTCORE_CONVERSION_HELPERS_H_