blob: dc34136ae2083732aa953115a29fdc0588d3f8e5 [file] [log] [blame]
/*
* Copyright (C) 2018 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#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>
/**
* SECTION: JSCContext
* @short_description: JavaScript execution context
* @title: JSCContext
*
* JSCContext represents a JavaScript execution context, where all operations
* take place and where the values will be associated.
*
* When a new context is created, a global object is allocated and the built-in JavaScript
* objects (Object, Function, String, Array) are populated. You can execute JavaScript in
* the context by using jsc_context_evaluate() or jsc_context_evaluate_with_source_uri().
* It's also possible to register custom objects in the context with jsc_context_register_class().
*/
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;
/**
* JSCContext:virtual-machine:
*
* The #JSCVirtualMachine in which the context was created.
*/
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;
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(toJS(priv->jsContext.get()), 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;
if (JSValueIsNull(priv->jsContext.get(), jsArray))
return nullptr;
if (!JSValueIsArray(priv->jsContext.get(), jsArray)) {
*exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), 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;
if (JSValueIsNull(priv->jsContext.get(), jsArray))
return nullptr;
if (!JSValueIsArray(priv->jsContext.get(), jsArray)) {
*exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), 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(toJS(priv->jsContext.get()), 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;
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(toJS(priv->jsContext.get()), 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;
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(toJS(priv->jsContext.get()), "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(toJS(priv->jsContext.get()), "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(toJS(priv->jsContext.get()), makeString("unsupported type ", g_type_name(G_VALUE_TYPE(value)))));
break;
}
}
/**
* jsc_context_new:
*
* Create a new #JSCContext. The context is created in a new #JSCVirtualMachine.
* Use jsc_context_new_with_virtual_machine() to create a new #JSCContext in an
* existing #JSCVirtualMachine.
*
* Returns: (transfer full): the newly created #JSCContext.
*/
JSCContext* jsc_context_new()
{
return JSC_CONTEXT(g_object_new(JSC_TYPE_CONTEXT, nullptr));
}
/**
* jsc_context_new_with_virtual_machine:
* @vm: a #JSCVirtualMachine
*
* Create a new #JSCContext in @virtual_machine.
*
* Returns: (transfer full): the newly created #JSCContext.
*/
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));
}
/**
* jsc_context_get_virtual_machine:
* @context: a #JSCContext
*
* Get the #JSCVirtualMachine where @context was created.
*
* Returns: (transfer none): the #JSCVirtualMachine where the #JSCContext was created.
*/
JSCVirtualMachine* jsc_context_get_virtual_machine(JSCContext* context)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
return context->priv->vm.get();
}
/**
* jsc_context_get_exception:
* @context: a #JSCContext
*
* Get the last unhandled exception thrown in @context by API functions calls.
*
* Returns: (transfer none) (nullable): a #JSCException or %NULL if there isn't any
* unhandled exception in the #JSCContext.
*/
JSCException* jsc_context_get_exception(JSCContext *context)
{
g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
return context->priv->exception.get();
}
/**
* jsc_context_throw:
* @context: a #JSCContext
* @error_message: an error message
*
* Throw an exception to @context using the given error message. The created #JSCException
* can be retrieved with jsc_context_get_exception().
*/
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));
}
/**
* jsc_context_throw_printf:
* @context: a #JSCContext
* @format: the string format
* @...: the parameters to insert into the format string
*
* Throw an exception to @context using the given formatted string as error message.
* The created #JSCException can be retrieved with jsc_context_get_exception().
*/
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);
}
/**
* jsc_context_throw_with_name:
* @context: a #JSCContext
* @error_name: the error name
* @error_message: an error message
*
* Throw an exception to @context using the given error name and message. The created #JSCException
* can be retrieved with jsc_context_get_exception().
*/
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));
}
/**
* jsc_context_throw_with_name_printf:
* @context: a #JSCContext
* @error_name: the error name
* @format: the string format
* @...: the parameters to insert into the format string
*
* Throw an exception to @context using the given error name and the formatted string as error message.
* The created #JSCException can be retrieved with jsc_context_get_exception().
*/
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);
}
/**
* jsc_context_throw_exception:
* @context: a #JSCContext
* @exception: a #JSCException
*
* Throw @exception to @context.
*/
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;
}
/**
* jsc_context_clear_exception:
* @context: a #JSCContext
*
* Clear the uncaught exception in @context if any.
*/
void jsc_context_clear_exception(JSCContext* context)
{
g_return_if_fail(JSC_IS_CONTEXT(context));
context->priv->exception = nullptr;
}
/**
* JSCExceptionHandler:
* @context: a #JSCContext
* @exception: a #JSCException
* @user_data: user data
*
* Function used to handle JavaScript exceptions in a #JSCContext.
*/
/**
* jsc_context_push_exception_handler:
* @context: a #JSCContext
* @handler: a #JSCExceptionHandler
* @user_data: (closure): user data to pass to @handler
* @destroy_notify: (nullable): destroy notifier for @user_data
*
* Push an exception handler in @context. Whenever a JavaScript exception happens in
* the #JSCContext, the given @handler will be called. The default #JSCExceptionHandler
* simply calls jsc_context_throw_exception() to throw the exception to the #JSCContext.
* If you don't want to catch the exception, but only get notified about it, call
* jsc_context_throw_exception() in @handler like the default one does.
* The last exception handler pushed is the only one used by the #JSCContext, use
* jsc_context_pop_exception_handler() to remove it and set the previous one. When @handler
* is removed from the context, @destroy_notify i called with @user_data as parameter.
*/
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 });
}
/**
* jsc_context_pop_exception_handler:
* @context: a #JSCContext
*
* Remove the last #JSCExceptionHandler previously pushed to @context with
* jsc_context_push_exception_handler().
*/
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;
}
/**
* jsc_context_get_current:
*
* Get the #JSCContext that is currently executing a function. This should only be
* called within a function or method callback, otherwise %NULL will be returned.
*
* Returns: (transfer none) (nullable): the #JSCContext that is currently executing.
*/
JSCContext* jsc_context_get_current()
{
auto* data = static_cast<CallbackData*>(Thread::current().m_apiData);
return data ? data->context.get() : nullptr;
}
/**
* jsc_context_evaluate:
* @context: a #JSCContext
* @code: a JavaScript script to evaluate
* @length: length of @code, or -1 if @code is a nul-terminated string
*
* Evaluate @code in @context.
*
* Returns: (transfer full): a #JSCValue representing the last value generated by the script.
*/
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);
}
/**
* jsc_context_evaluate_with_source_uri:
* @context: a #JSCContext
* @code: a JavaScript script to evaluate
* @length: length of @code, or -1 if @code is a nul-terminated string
* @uri: the source URI
* @line_number: the starting line number
*
* Evaluate @code in @context using @uri as the source URI. The @line_number is the starting line number
* in @uri; the value is one-based so the first line is 1. @uri and @line_number will be shown in exceptions and
* they don't affect the behavior of the script.
*
* Returns: (transfer full): a #JSCValue representing the last value generated by the script.
*/
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();
}
/**
* jsc_context_evaluate_in_object:
* @context: a #JSCContext
* @code: a JavaScript script to evaluate
* @length: length of @code, or -1 if @code is a nul-terminated string
* @object_instance: (nullable): an object instance
* @object_class: (nullable): a #JSCClass or %NULL to use the default
* @uri: the source URI
* @line_number: the starting line number
* @object: (out) (transfer full): return location for a #JSCValue.
*
* Evaluate @code and create an new object where symbols defined in @code will be added as properties,
* instead of being added to @context global object. The new object is returned as @object parameter.
* Similar to how jsc_value_new_object() works, if @object_instance is not %NULL @object_class must be provided too.
* The @line_number is the starting line number in @uri; the value is one-based so the first line is 1.
* @uri and @line_number will be shown in exceptions and they don't affect the behavior of the script.
*
* Returns: (transfer full): a #JSCValue representing the last value generated by the script.
*/
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::ExecState* exec = toJS(objectContext.get());
JSC::VM& vm = exec->vm();
auto* jsObject = vm.vmEntryGlobalObject(exec);
jsObject->setGlobalScopeExtension(JSC::JSWithScope::create(vm, jsObject, jsObject->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();
}
/**
* JSCCheckSyntaxMode:
* @JSC_CHECK_SYNTAX_MODE_SCRIPT: mode to check syntax of a script
* @JSC_CHECK_SYNTAX_MODE_MODULE: mode to check syntax of a module
*
* Enum values to specify a mode to check for syntax errors in jsc_context_check_syntax().
*/
/**
* JSCCheckSyntaxResult:
* @JSC_CHECK_SYNTAX_RESULT_SUCCESS: no errors
* @JSC_CHECK_SYNTAX_RESULT_RECOVERABLE_ERROR: recoverable syntax error
* @JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR: irrecoverable syntax error
* @JSC_CHECK_SYNTAX_RESULT_UNTERMINATED_LITERAL_ERROR: unterminated literal error
* @JSC_CHECK_SYNTAX_RESULT_OUT_OF_MEMORY_ERROR: out of memory error
* @JSC_CHECK_SYNTAX_RESULT_STACK_OVERFLOW_ERROR: stack overflow error
*
* Enum values to specify the result of jsc_context_check_syntax().
*/
/**
* jsc_context_check_syntax:
* @context: a #JSCContext
* @code: a JavaScript script to check
* @length: length of @code, or -1 if @code is a nul-terminated string
* @mode: a #JSCCheckSyntaxMode
* @uri: the source URI
* @line_number: the starting line number
* @exception: (out) (optional) (transfer full): return location for a #JSCException, or %NULL to ignore
*
* Check the given @code in @context for syntax errors. The @line_number is the starting line number in @uri;
* the value is one-based so the first line is 1. @uri and @line_number are only used to fill the @exception.
* In case of errors @exception will be set to a new #JSCException with the details. You can pass %NULL to
* @exception to ignore the error details.
*
* Returns: a #JSCCheckSyntaxResult
*/
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::ExecState* exec = toJS(jsContext);
JSC::VM& vm = exec->vm();
JSC::JSLockHolder locker(vm);
String sourceURLString = uri ? String::fromUTF8(uri) : String();
JSC::SourceCode source = JSC::makeSource(String::fromUTF8(code, length < 0 ? strlen(code) : length), JSC::SourceOrigin { sourceURLString },
URL({ }, sourceURLString), 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(exec->lexicalGlobalObject(), source);
*exception = jscExceptionCreate(context, toRef(exec, jsError)).leakRef();
}
return result;
}
/**
* jsc_context_get_global_object:
* @context: a #JSCContext
*
* Get a #JSCValue referencing the @context global object
*
* Returns: (transfer full): a #JSCValue
*/
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();
}
/**
* jsc_context_set_value:
* @context: a #JSCContext
* @name: the value name
* @value: a #JSCValue
*
* Set a property of @context global object with @name and @value.
*/
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);
}
/**
* jsc_context_get_value:
* @context: a #JSCContext
* @name: the value name
*
* Get a property of @context global object with @name.
*
* Returns: (transfer full): a #JSCValue
*/
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);
}
/**
* jsc_context_register_class:
* @context: a #JSCContext
* @name: the class name
* @parent_class: (nullable): a #JSCClass or %NULL
* @vtable: (nullable): an optional #JSCClassVTable or %NULL
* @destroy_notify: (nullable): a destroy notifier for class instances
*
* Register a custom class in @context using the given @name. If the new class inherits from
* another #JSCClass, the parent should be passed as @parent_class, otherwise %NULL should be
* used. The optional @vtable parameter allows to provide a custom implementation for handling
* the class, for example, to handle external properties not added to the prototype.
* When an instance of the #JSCClass is cleared in the context, @destroy_notify is called with
* the instance as parameter.
*
* Returns: (transfer none): a #JSCClass
*/
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();
}