#include "config.h"
#include "JSCContext.h"
#include "JSCClassPrivate.h"
#include "JSCContextPrivate.h"
#include "JSCExceptionPrivate.h"
#include "JSCInlines.h"
#include "JSCValuePrivate.h"
#include "JSCVirtualMachinePrivate.h"
#include "JSCWrapperMap.h"
#include "JSRetainPtr.h"
#include "JSWithScope.h"
#include "OpaqueJSString.h"
#include "Parser.h"
#include <wtf/glib/GUniquePtr.h>
#include <wtf/glib/WTFGType.h>
enum {
PROP_0,
PROP_VIRTUAL_MACHINE,
};
struct JSCContextExceptionHandler {
JSCContextExceptionHandler(JSCExceptionHandler handler, void* userData = nullptr, GDestroyNotify destroyNotifyFunction = nullptr)
: handler(handler)
, userData(userData)
, destroyNotifyFunction(destroyNotifyFunction)
{
}
~JSCContextExceptionHandler()
{
if (destroyNotifyFunction)
destroyNotifyFunction(userData);
}
JSCContextExceptionHandler(JSCContextExceptionHandler&& other)
{
std::swap(handler, other.handler);
std::swap(userData, other.userData);
std::swap(destroyNotifyFunction, other.destroyNotifyFunction);
}
JSCContextExceptionHandler(const JSCContextExceptionHandler&) = delete;
JSCContextExceptionHandler& operator=(const JSCContextExceptionHandler&) = delete;
JSCExceptionHandler handler { nullptr };
void* userData { nullptr };
GDestroyNotify destroyNotifyFunction { nullptr };
};
struct _JSCContextPrivate {
GRefPtr<JSCVirtualMachine> vm;
JSRetainPtr<JSGlobalContextRef> jsContext;
GRefPtr<JSCException> exception;
Vector<JSCContextExceptionHandler> exceptionHandlers;
};
WEBKIT_DEFINE_TYPE(JSCContext, jsc_context, G_TYPE_OBJECT)
static void jscContextSetVirtualMachine(JSCContext* context, GRefPtr<JSCVirtualMachine>&& vm)
{
JSCContextPrivate* priv = context->priv;
if (vm) {
ASSERT(!priv->vm);
priv->vm = WTFMove(vm);
ASSERT(!priv->jsContext);
GUniquePtr<char> name(g_strdup_printf("%p-jsContext", &Thread::current()));
if (auto* data = g_object_get_data(G_OBJECT(priv->vm.get()), name.get())) {
priv->jsContext = static_cast<JSGlobalContextRef>(data);
g_object_set_data(G_OBJECT(priv->vm.get()), name.get(), nullptr);
} else
priv->jsContext = JSRetainPtr<JSGlobalContextRef>(Adopt, JSGlobalContextCreateInGroup(jscVirtualMachineGetContextGroup(priv->vm.get()), nullptr));
auto* globalObject = toJSGlobalObject(priv->jsContext.get());
if (!globalObject->wrapperMap())
globalObject->setWrapperMap(makeUnique<JSC::WrapperMap>(priv->jsContext.get()));
jscVirtualMachineAddContext(priv->vm.get(), context);
} else if (priv->vm) {
ASSERT(priv->jsContext);
jscVirtualMachineRemoveContext(priv->vm.get(), context);
priv->jsContext = nullptr;
priv->vm = nullptr;
}
}
static void jscContextGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec)
{
JSCContextPrivate* priv = JSC_CONTEXT(object)->priv;
switch (propID) {
case PROP_VIRTUAL_MACHINE:
g_value_set_object(value, priv->vm.get());
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
}
}
static void jscContextSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec)
{
JSCContext* context = JSC_CONTEXT(object);
switch (propID) {
case PROP_VIRTUAL_MACHINE:
if (gpointer vm = g_value_get_object(value))
jscContextSetVirtualMachine(context, GRefPtr<JSCVirtualMachine>(JSC_VIRTUAL_MACHINE(vm)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
}
}
static void jscContextConstructed(GObject* object)
{
G_OBJECT_CLASS(jsc_context_parent_class)->constructed(object);
JSCContext* context = JSC_CONTEXT(object);
if (!context->priv->vm)
jscContextSetVirtualMachine(context, adoptGRef(jsc_virtual_machine_new()));
context->priv->exceptionHandlers.append(JSCContextExceptionHandler([](JSCContext* context, JSCException* exception, gpointer) {
jsc_context_throw_exception(context, exception);
}));
}
static void jscContextDispose(GObject* object)
{
JSCContext* context = JSC_CONTEXT(object);
jscContextSetVirtualMachine(context, nullptr);
G_OBJECT_CLASS(jsc_context_parent_class)->dispose(object);
}
static void jsc_context_class_init(JSCContextClass* klass)
{
GObjectClass* objClass = G_OBJECT_CLASS(klass);
objClass->get_property = jscContextGetProperty;
objClass->set_property = jscContextSetProperty;
objClass->constructed = jscContextConstructed;
objClass->dispose = jscContextDispose;
g_object_class_install_property(objClass,
PROP_VIRTUAL_MACHINE,
g_param_spec_object(
"virtual-machine",
"JSCVirtualMachine",
"JSC Virtual Machine",
JSC_TYPE_VIRTUAL_MACHINE,
static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
}
GRefPtr<JSCContext> jscContextGetOrCreate(JSGlobalContextRef jsContext)
{
auto vm = jscVirtualMachineGetOrCreate(toRef(&toJS(jsContext)->vm()));
if (GRefPtr<JSCContext> context = jscVirtualMachineGetContext(vm.get(), jsContext))
return context;
GUniquePtr<char> name(g_strdup_printf("%p-jsContext", &Thread::current()));
g_object_set_data(G_OBJECT(vm.get()), name.get(), jsContext);
return adoptGRef(jsc_context_new_with_virtual_machine(vm.get()));
}
JSGlobalContextRef jscContextGetJSContext(JSCContext* context)
{
ASSERT(JSC_IS_CONTEXT(context));
JSCContextPrivate* priv = context->priv;
return priv->jsContext.get();
}
static JSC::WrapperMap& wrapperMap(JSCContext* context)
{
auto* map = toJSGlobalObject(context->priv->jsContext.get())->wrapperMap();
ASSERT(map);
return *map;
}
GRefPtr<JSCValue> jscContextGetOrCreateValue(JSCContext* context, JSValueRef jsValue)
{
return wrapperMap(context).gobjectWrapper(context, jsValue);
}
void jscContextValueDestroyed(JSCContext* context, JSValueRef jsValue)
{
wrapperMap(context).unwrap(jsValue);
}
JSC::JSObject* jscContextGetJSWrapper(JSCContext* context, gpointer wrappedObject)
{
return wrapperMap(context).jsWrapper(wrappedObject);
}
JSC::JSObject* jscContextGetOrCreateJSWrapper(JSCContext* context, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
{
if (auto* jsWrapper = jscContextGetJSWrapper(context, wrappedObject))
return jsWrapper;
return wrapperMap(context).createJSWrappper(context->priv->jsContext.get(), jsClass, prototype, wrappedObject, destroyFunction);
}
JSGlobalContextRef jscContextCreateContextWithJSWrapper(JSCContext* context, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
{
return wrapperMap(context).createContextWithJSWrappper(jscVirtualMachineGetContextGroup(context->priv->vm.get()), jsClass, prototype, wrappedObject, destroyFunction);
}
gpointer jscContextWrappedObject(JSCContext* context, JSObjectRef jsObject)
{
return wrapperMap(context).wrappedObject(context->priv->jsContext.get(), jsObject);
}
JSCClass* jscContextGetRegisteredClass(JSCContext* context, JSClassRef jsClass)
{
return wrapperMap(context).registeredClass(jsClass);
}
CallbackData jscContextPushCallback(JSCContext* context, JSValueRef calleeValue, JSValueRef thisValue, size_t argumentCount, const JSValueRef* arguments)
{
Thread& thread = Thread::current();
auto* previousStack = static_cast<CallbackData*>(thread.m_apiData);
CallbackData data = { context, WTFMove(context->priv->exception), calleeValue, thisValue, argumentCount, arguments, previousStack };
thread.m_apiData = &data;
return data;
}
void jscContextPopCallback(JSCContext* context, CallbackData&& data)
{
Thread& thread = Thread::current();
context->priv->exception = WTFMove(data.preservedException);
thread.m_apiData = data.next;
}
JSValueRef jscContextGArrayToJSArray(JSCContext* context, GPtrArray* gArray, JSValueRef* exception)
{
JSCContextPrivate* priv = context->priv;
JSC::JSGlobalObject* globalObject = toJS(priv->jsContext.get());
JSC::JSLockHolder locker(globalObject);
auto* jsArray = JSObjectMakeArray(priv->jsContext.get(), 0, nullptr, exception);
if (*exception)
return JSValueMakeUndefined(priv->jsContext.get());
if (!gArray)
return jsArray;
auto* jsArrayObject = JSValueToObject(priv->jsContext.get(), jsArray, exception);
if (*exception)
return JSValueMakeUndefined(priv->jsContext.get());
for (unsigned i = 0; i < gArray->len; ++i) {
gpointer item = g_ptr_array_index(gArray, i);
if (!item)
JSObjectSetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, JSValueMakeNull(priv->jsContext.get()), exception);
else if (JSC_IS_VALUE(item))
JSObjectSetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, jscValueGetJSValue(JSC_VALUE(item)), exception);
else
*exception = toRef(JSC::createTypeError(globalObject, makeString("invalid item type in GPtrArray")));
if (*exception)
return JSValueMakeUndefined(priv->jsContext.get());
}
return jsArray;
}
static GRefPtr<GPtrArray> jscContextJSArrayToGArray(JSCContext* context, JSValueRef jsArray, JSValueRef* exception)
{
JSCContextPrivate* priv = context->priv;
JSC::JSGlobalObject* globalObject = toJS(priv->jsContext.get());
JSC::JSLockHolder locker(globalObject);
if (JSValueIsNull(priv->jsContext.get(), jsArray))
return nullptr;
if (!JSValueIsArray(priv->jsContext.get(), jsArray)) {
*exception = toRef(JSC::createTypeError(globalObject, makeString("invalid js type for GPtrArray")));
return nullptr;
}
auto* jsArrayObject = JSValueToObject(priv->jsContext.get(), jsArray, exception);
if (*exception)
return nullptr;
JSRetainPtr<JSStringRef> lengthString(Adopt, JSStringCreateWithUTF8CString("length"));
auto* jsLength = JSObjectGetProperty(priv->jsContext.get(), jsArrayObject, lengthString.get(), exception);
if (*exception)
return nullptr;
auto length = JSC::toUInt32(JSValueToNumber(priv->jsContext.get(), jsLength, exception));
if (*exception)
return nullptr;
GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_with_free_func(g_object_unref));
for (unsigned i = 0; i < length; ++i) {
auto* jsItem = JSObjectGetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, exception);
if (*exception)
return nullptr;
g_ptr_array_add(gArray.get(), jsItem ? jscContextGetOrCreateValue(context, jsItem).leakRef() : nullptr);
}
return gArray;
}
GUniquePtr<char*> jscContextJSArrayToGStrv(JSCContext* context, JSValueRef jsArray, JSValueRef* exception)
{
JSCContextPrivate* priv = context->priv;
JSC::JSGlobalObject* globalObject = toJS(priv->jsContext.get());
JSC::JSLockHolder locker(globalObject);
if (JSValueIsNull(priv->jsContext.get(), jsArray))
return nullptr;
if (!JSValueIsArray(priv->jsContext.get(), jsArray)) {
*exception = toRef(JSC::createTypeError(globalObject, makeString("invalid js type for GStrv")));
return nullptr;
}
auto* jsArrayObject = JSValueToObject(priv->jsContext.get(), jsArray, exception);
if (*exception)
return nullptr;
JSRetainPtr<JSStringRef> lengthString(Adopt, JSStringCreateWithUTF8CString("length"));
auto* jsLength = JSObjectGetProperty(priv->jsContext.get(), jsArrayObject, lengthString.get(), exception);
if (*exception)
return nullptr;
auto length = JSC::toUInt32(JSValueToNumber(priv->jsContext.get(), jsLength, exception));
if (*exception)
return nullptr;
GUniquePtr<char*> strv(static_cast<char**>(g_new0(char*, length + 1)));
for (unsigned i = 0; i < length; ++i) {
auto* jsItem = JSObjectGetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, exception);
if (*exception)
return nullptr;
auto jsValueItem = jscContextGetOrCreateValue(context, jsItem);
if (!jsc_value_is_string(jsValueItem.get())) {
*exception = toRef(JSC::createTypeError(globalObject, makeString("invalid js type for GStrv: item ", String::number(i), " is not a string")));
return nullptr;
}
strv.get()[i] = jsc_value_to_string(jsValueItem.get());
}
return strv;
}
JSValueRef jscContextGValueToJSValue(JSCContext* context, const GValue* value, JSValueRef* exception)
{
JSCContextPrivate* priv = context->priv;
JSC::JSGlobalObject* globalObject = toJS(priv->jsContext.get());
JSC::JSLockHolder locker(globalObject);
switch (g_type_fundamental(G_VALUE_TYPE(value))) {
case G_TYPE_BOOLEAN:
return JSValueMakeBoolean(priv->jsContext.get(), g_value_get_boolean(value));
case G_TYPE_CHAR:
case G_TYPE_INT:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_int);
case G_TYPE_ENUM:
return JSValueMakeNumber(priv->jsContext.get(), g_value_get_enum(value));
case G_TYPE_FLAGS:
return JSValueMakeNumber(priv->jsContext.get(), g_value_get_flags(value));
case G_TYPE_UCHAR:
case G_TYPE_UINT:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_uint);
case G_TYPE_FLOAT:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_float);
case G_TYPE_DOUBLE:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_double);
case G_TYPE_LONG:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_long);
case G_TYPE_ULONG:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_ulong);
case G_TYPE_INT64:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_int64);
case G_TYPE_UINT64:
return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_uint64);
case G_TYPE_STRING:
if (const char* stringValue = g_value_get_string(value)) {
JSRetainPtr<JSStringRef> jsString(Adopt, JSStringCreateWithUTF8CString(stringValue));
return JSValueMakeString(priv->jsContext.get(), jsString.get());
}
return JSValueMakeNull(priv->jsContext.get());
case G_TYPE_POINTER:
case G_TYPE_OBJECT:
case G_TYPE_BOXED:
if (auto* ptr = value->data[0].v_pointer) {
if (auto* jsWrapper = jscContextGetJSWrapper(context, ptr))
return toRef(jsWrapper);
if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_VALUE))
return jscValueGetJSValue(JSC_VALUE(ptr));
if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_EXCEPTION))
return jscExceptionGetJSValue(JSC_EXCEPTION(ptr));
if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_PTR_ARRAY))
return jscContextGArrayToJSArray(context, static_cast<GPtrArray*>(ptr), exception);
if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_STRV)) {
auto** strv = static_cast<char**>(ptr);
auto strvLength = g_strv_length(strv);
GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_full(strvLength, g_object_unref));
for (unsigned i = 0; i < strvLength; i++)
g_ptr_array_add(gArray.get(), jsc_value_new_string(context, strv[i]));
return jscContextGArrayToJSArray(context, gArray.get(), exception);
}
} else
return JSValueMakeNull(priv->jsContext.get());
break;
case G_TYPE_PARAM:
case G_TYPE_INTERFACE:
case G_TYPE_VARIANT:
default:
break;
}
*exception = toRef(JSC::createTypeError(globalObject, makeString("unsupported type ", g_type_name(G_VALUE_TYPE(value)))));
return JSValueMakeUndefined(priv->jsContext.get());
}
void jscContextJSValueToGValue(JSCContext* context, JSValueRef jsValue, GType type, GValue* value, JSValueRef* exception)
{
JSCContextPrivate* priv = context->priv;
JSC::JSGlobalObject* globalObject = toJS(priv->jsContext.get());
JSC::JSLockHolder locker(globalObject);
g_value_init(value, type);
auto fundamentalType = g_type_fundamental(G_VALUE_TYPE(value));
switch (fundamentalType) {
case G_TYPE_INT:
g_value_set_int(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_FLOAT:
g_value_set_float(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_DOUBLE:
g_value_set_double(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_BOOLEAN:
g_value_set_boolean(value, JSValueToBoolean(priv->jsContext.get(), jsValue));
break;
case G_TYPE_STRING:
if (!JSValueIsNull(priv->jsContext.get(), jsValue)) {
JSRetainPtr<JSStringRef> jsString(Adopt, JSValueToStringCopy(priv->jsContext.get(), jsValue, exception));
if (*exception)
return;
size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString.get());
auto* string = static_cast<char*>(g_malloc(maxSize));
JSStringGetUTF8CString(jsString.get(), string, maxSize);
g_value_take_string(value, string);
} else
g_value_set_string(value, nullptr);
break;
case G_TYPE_CHAR:
g_value_set_schar(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_UCHAR:
g_value_set_uchar(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_UINT:
g_value_set_uint(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_POINTER:
case G_TYPE_OBJECT:
case G_TYPE_BOXED: {
gpointer wrappedObject = nullptr;
if (!JSValueIsNull(priv->jsContext.get(), jsValue)) {
auto jsObject = JSValueToObject(priv->jsContext.get(), jsValue, exception);
if (*exception)
return;
wrappedObject = jscContextWrappedObject(context, jsObject);
if (!wrappedObject) {
if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_VALUE)) {
auto jscValue = jscContextGetOrCreateValue(context, jsValue);
g_value_set_object(value, jscValue.get());
return;
}
if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_EXCEPTION)) {
auto exception = jscExceptionCreate(context, jsValue);
g_value_set_object(value, exception.get());
return;
}
if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_PTR_ARRAY)) {
auto gArray = jscContextJSArrayToGArray(context, jsValue, exception);
if (!*exception)
g_value_take_boxed(value, gArray.leakRef());
return;
}
if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_STRV)) {
auto strv = jscContextJSArrayToGStrv(context, jsValue, exception);
if (!*exception)
g_value_take_boxed(value, strv.release());
return;
}
*exception = toRef(JSC::createTypeError(globalObject, "invalid pointer type"_s));
return;
}
}
if (fundamentalType == G_TYPE_POINTER)
g_value_set_pointer(value, wrappedObject);
else if (fundamentalType == G_TYPE_BOXED)
g_value_set_boxed(value, wrappedObject);
else if (G_IS_OBJECT(wrappedObject))
g_value_set_object(value, wrappedObject);
else
*exception = toRef(JSC::createTypeError(globalObject, "wrapped object is not a GObject"_s));
break;
}
case G_TYPE_LONG:
g_value_set_long(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_ULONG:
g_value_set_ulong(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_INT64:
g_value_set_int64(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_UINT64:
g_value_set_uint64(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_ENUM:
g_value_set_enum(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_FLAGS:
g_value_set_flags(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
break;
case G_TYPE_PARAM:
case G_TYPE_INTERFACE:
case G_TYPE_VARIANT:
default:
*exception = toRef(JSC::createTypeError(globalObject, makeString("unsupported type ", g_type_name(G_VALUE_TYPE(value)))));
break;
}
}
JSCContext* jsc_context_new()
{
return JSC_CONTEXT(g_object_new(JSC_TYPE_CONTEXT, nullptr));
}
JSCContext* jsc_context_new_with_virtual_machine(JSCVirtualMachine* vm)
{
g_return_val_if_fail(JSC_IS_VIRTUAL_MACHINE(vm), nullptr);
return JSC_CONTEXT(g_object_new(JSC_TYPE_CONTEXT, "virtual-machine", vm, nullptr));
}
JSCVirtualMachine* jsc_context_get_virtual_machine(JSCContext* context)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
return context->priv->vm.get();
}
JSCException* jsc_context_get_exception(JSCContext *context)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
return context->priv->exception.get();
}
void jsc_context_throw(JSCContext* context, const char* errorMessage)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
context->priv->exception = adoptGRef(jsc_exception_new(context, errorMessage));
}
void jsc_context_throw_printf(JSCContext* context, const char* format, ...)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
va_list args;
va_start(args, format);
context->priv->exception = adoptGRef(jsc_exception_new_vprintf(context, format, args));
va_end(args);
}
void jsc_context_throw_with_name(JSCContext* context, const char* errorName, const char* errorMessage)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
g_return_if_fail(errorName);
context->priv->exception = adoptGRef(jsc_exception_new_with_name(context, errorName, errorMessage));
}
void jsc_context_throw_with_name_printf(JSCContext* context, const char* errorName, const char* format, ...)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
va_list args;
va_start(args, format);
context->priv->exception = adoptGRef(jsc_exception_new_with_name_vprintf(context, errorName, format, args));
va_end(args);
}
void jsc_context_throw_exception(JSCContext* context, JSCException* exception)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
g_return_if_fail(JSC_IS_EXCEPTION(exception));
context->priv->exception = exception;
}
void jsc_context_clear_exception(JSCContext* context)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
context->priv->exception = nullptr;
}
void jsc_context_push_exception_handler(JSCContext* context, JSCExceptionHandler handler, gpointer userData, GDestroyNotify destroyNotify)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
g_return_if_fail(handler);
context->priv->exceptionHandlers.append({ handler, userData, destroyNotify });
}
void jsc_context_pop_exception_handler(JSCContext* context)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
g_return_if_fail(context->priv->exceptionHandlers.size() > 1);
context->priv->exceptionHandlers.removeLast();
}
bool jscContextHandleExceptionIfNeeded(JSCContext* context, JSValueRef jsException)
{
if (!jsException)
return false;
auto exception = jscExceptionCreate(context, jsException);
ASSERT(!context->priv->exceptionHandlers.isEmpty());
const auto& exceptionHandler = context->priv->exceptionHandlers.last();
exceptionHandler.handler(context, exception.get(), exceptionHandler.userData);
return true;
}
JSCContext* jsc_context_get_current()
{
auto* data = static_cast<CallbackData*>(Thread::current().m_apiData);
return data ? data->context.get() : nullptr;
}
JSCValue* jsc_context_evaluate(JSCContext* context, const char* code, gssize length)
{
return jsc_context_evaluate_with_source_uri(context, code, length, nullptr, 0);
}
static JSValueRef evaluateScriptInContext(JSGlobalContextRef jsContext, String&& script, const char* uri, unsigned lineNumber, JSValueRef* exception)
{
JSRetainPtr<JSStringRef> scriptJS(Adopt, OpaqueJSString::tryCreate(WTFMove(script)).leakRef());
JSRetainPtr<JSStringRef> sourceURI = uri ? adopt(JSStringCreateWithUTF8CString(uri)) : nullptr;
return JSEvaluateScript(jsContext, scriptJS.get(), nullptr, sourceURI.get(), lineNumber, exception);
}
JSCValue* jsc_context_evaluate_with_source_uri(JSCContext* context, const char* code, gssize length, const char* uri, unsigned lineNumber)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
g_return_val_if_fail(code, nullptr);
JSValueRef exception = nullptr;
JSValueRef result = evaluateScriptInContext(context->priv->jsContext.get(), String::fromUTF8(code, length < 0 ? strlen(code) : length), uri, lineNumber, &exception);
if (jscContextHandleExceptionIfNeeded(context, exception))
return jsc_value_new_undefined(context);
return jscContextGetOrCreateValue(context, result).leakRef();
}
JSCValue* jsc_context_evaluate_in_object(JSCContext* context, const char* code, gssize length, gpointer instance, JSCClass* objectClass, const char* uri, unsigned lineNumber, JSCValue** object)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
g_return_val_if_fail(code, nullptr);
g_return_val_if_fail(!instance || JSC_IS_CLASS(objectClass), nullptr);
g_return_val_if_fail(object && !*object, nullptr);
JSRetainPtr<JSGlobalContextRef> objectContext(Adopt,
instance ? jscClassCreateContextWithJSWrapper(objectClass, context, instance) : JSGlobalContextCreateInGroup(jscVirtualMachineGetContextGroup(context->priv->vm.get()), nullptr));
JSC::JSGlobalObject* globalObject = toJS(objectContext.get());
JSC::VM& vm = globalObject->vm();
JSC::JSLockHolder locker(globalObject);
globalObject->setGlobalScopeExtension(JSC::JSWithScope::create(vm, globalObject, globalObject->globalScope(), toJS(JSContextGetGlobalObject(context->priv->jsContext.get()))));
JSValueRef exception = nullptr;
JSValueRef result = evaluateScriptInContext(objectContext.get(), String::fromUTF8(code, length < 0 ? strlen(code) : length), uri, lineNumber, &exception);
if (jscContextHandleExceptionIfNeeded(context, exception))
return jsc_value_new_undefined(context);
*object = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(objectContext.get())).leakRef();
return jscContextGetOrCreateValue(context, result).leakRef();
}
JSCCheckSyntaxResult jsc_context_check_syntax(JSCContext* context, const char* code, gssize length, JSCCheckSyntaxMode mode, const char* uri, unsigned lineNumber, JSCException **exception)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR);
g_return_val_if_fail(code, JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR);
g_return_val_if_fail(!exception || !*exception, JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR);
lineNumber = std::max<unsigned>(1, lineNumber);
auto* jsContext = context->priv->jsContext.get();
JSC::JSGlobalObject* globalObject = toJS(jsContext);
JSC::VM& vm = globalObject->vm();
JSC::JSLockHolder locker(vm);
URL sourceURL = uri ? URL({ }, uri) : URL();
JSC::SourceCode source = JSC::makeSource(String::fromUTF8(code, length < 0 ? strlen(code) : length), JSC::SourceOrigin { sourceURL },
sourceURL.string() , TextPosition(OrdinalNumber::fromOneBasedInt(lineNumber), OrdinalNumber()));
bool success = false;
JSC::ParserError error;
switch (mode) {
case JSC_CHECK_SYNTAX_MODE_SCRIPT:
success = !!JSC::parse<JSC::ProgramNode>(vm, source, JSC::Identifier(), JSC::JSParserBuiltinMode::NotBuiltin,
JSC::JSParserStrictMode::NotStrict, JSC::JSParserScriptMode::Classic, JSC::SourceParseMode::ProgramMode, JSC::SuperBinding::NotNeeded, error);
break;
case JSC_CHECK_SYNTAX_MODE_MODULE:
success = !!JSC::parse<JSC::ModuleProgramNode>(vm, source, JSC::Identifier(), JSC::JSParserBuiltinMode::NotBuiltin,
JSC::JSParserStrictMode::Strict, JSC::JSParserScriptMode::Module, JSC::SourceParseMode::ModuleAnalyzeMode, JSC::SuperBinding::NotNeeded, error);
break;
}
JSCCheckSyntaxResult result = JSC_CHECK_SYNTAX_RESULT_SUCCESS;
if (success)
return result;
switch (error.type()) {
case JSC::ParserError::ErrorType::SyntaxError: {
switch (error.syntaxErrorType()) {
case JSC::ParserError::SyntaxErrorType::SyntaxErrorIrrecoverable:
result = JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR;
break;
case JSC::ParserError::SyntaxErrorType::SyntaxErrorUnterminatedLiteral:
result = JSC_CHECK_SYNTAX_RESULT_UNTERMINATED_LITERAL_ERROR;
break;
case JSC::ParserError::SyntaxErrorType::SyntaxErrorRecoverable:
result = JSC_CHECK_SYNTAX_RESULT_RECOVERABLE_ERROR;
break;
case JSC::ParserError::SyntaxErrorType::SyntaxErrorNone:
ASSERT_NOT_REACHED();
break;
}
break;
}
case JSC::ParserError::ErrorType::StackOverflow:
result = JSC_CHECK_SYNTAX_RESULT_STACK_OVERFLOW_ERROR;
break;
case JSC::ParserError::ErrorType::OutOfMemory:
result = JSC_CHECK_SYNTAX_RESULT_OUT_OF_MEMORY_ERROR;
break;
case JSC::ParserError::ErrorType::EvalError:
case JSC::ParserError::ErrorType::ErrorNone:
ASSERT_NOT_REACHED();
break;
}
if (exception) {
auto* jsError = error.toErrorObject(globalObject, source);
*exception = jscExceptionCreate(context, toRef(globalObject, jsError)).leakRef();
}
return result;
}
JSCValue* jsc_context_get_global_object(JSCContext* context)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
return jscContextGetOrCreateValue(context, JSContextGetGlobalObject(context->priv->jsContext.get())).leakRef();
}
void jsc_context_set_value(JSCContext* context, const char* name, JSCValue* value)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
g_return_if_fail(name);
g_return_if_fail(JSC_IS_VALUE(value));
auto contextObject = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(context->priv->jsContext.get()));
jsc_value_object_set_property(contextObject.get(), name, value);
}
JSCValue* jsc_context_get_value(JSCContext* context, const char* name)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
g_return_val_if_fail(name, nullptr);
auto contextObject = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(context->priv->jsContext.get()));
return jsc_value_object_get_property(contextObject.get(), name);
}
JSCClass* jsc_context_register_class(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
g_return_val_if_fail(name, nullptr);
g_return_val_if_fail(!parentClass || JSC_IS_CLASS(parentClass), nullptr);
auto jscClass = jscClassCreate(context, name, parentClass, vtable, destroyFunction);
wrapperMap(context).registerClass(jscClass.get());
return jscClass.get();
}