1/*
2 * Copyright (C) 2018 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "JSCContext.h"
22
23#include "JSCClassPrivate.h"
24#include "JSCContextPrivate.h"
25#include "JSCExceptionPrivate.h"
26#include "JSCInlines.h"
27#include "JSCValuePrivate.h"
28#include "JSCVirtualMachinePrivate.h"
29#include "JSCWrapperMap.h"
30#include "JSRetainPtr.h"
31#include "JSWithScope.h"
32#include "OpaqueJSString.h"
33#include "Parser.h"
34#include <wtf/glib/GUniquePtr.h>
35#include <wtf/glib/WTFGType.h>
36
37/**
38 * SECTION: JSCContext
39 * @short_description: JavaScript execution context
40 * @title: JSCContext
41 *
42 * JSCContext represents a JavaScript execution context, where all operations
43 * take place and where the values will be associated.
44 *
45 * When a new context is created, a global object is allocated and the built-in JavaScript
46 * objects (Object, Function, String, Array) are populated. You can execute JavaScript in
47 * the context by using jsc_context_evaluate() or jsc_context_evaluate_with_source_uri().
48 * It's also possible to register custom objects in the context with jsc_context_register_class().
49 */
50
51enum {
52 PROP_0,
53
54 PROP_VIRTUAL_MACHINE,
55};
56
57struct JSCContextExceptionHandler {
58 JSCContextExceptionHandler(JSCExceptionHandler handler, void* userData = nullptr, GDestroyNotify destroyNotifyFunction = nullptr)
59 : handler(handler)
60 , userData(userData)
61 , destroyNotifyFunction(destroyNotifyFunction)
62 {
63 }
64
65 ~JSCContextExceptionHandler()
66 {
67 if (destroyNotifyFunction)
68 destroyNotifyFunction(userData);
69 }
70
71 JSCContextExceptionHandler(JSCContextExceptionHandler&& other)
72 {
73 std::swap(handler, other.handler);
74 std::swap(userData, other.userData);
75 std::swap(destroyNotifyFunction, other.destroyNotifyFunction);
76 }
77
78 JSCContextExceptionHandler(const JSCContextExceptionHandler&) = delete;
79 JSCContextExceptionHandler& operator=(const JSCContextExceptionHandler&) = delete;
80
81 JSCExceptionHandler handler { nullptr };
82 void* userData { nullptr };
83 GDestroyNotify destroyNotifyFunction { nullptr };
84};
85
86struct _JSCContextPrivate {
87 GRefPtr<JSCVirtualMachine> vm;
88 JSRetainPtr<JSGlobalContextRef> jsContext;
89 GRefPtr<JSCException> exception;
90 Vector<JSCContextExceptionHandler> exceptionHandlers;
91};
92
93WEBKIT_DEFINE_TYPE(JSCContext, jsc_context, G_TYPE_OBJECT)
94
95static void jscContextSetVirtualMachine(JSCContext* context, GRefPtr<JSCVirtualMachine>&& vm)
96{
97 JSCContextPrivate* priv = context->priv;
98 if (vm) {
99 ASSERT(!priv->vm);
100 priv->vm = WTFMove(vm);
101 ASSERT(!priv->jsContext);
102 GUniquePtr<char> name(g_strdup_printf("%p-jsContext", &Thread::current()));
103 if (auto* data = g_object_get_data(G_OBJECT(priv->vm.get()), name.get())) {
104 priv->jsContext = static_cast<JSGlobalContextRef>(data);
105 g_object_set_data(G_OBJECT(priv->vm.get()), name.get(), nullptr);
106 } else
107 priv->jsContext = JSRetainPtr<JSGlobalContextRef>(Adopt, JSGlobalContextCreateInGroup(jscVirtualMachineGetContextGroup(priv->vm.get()), nullptr));
108 auto* globalObject = toJSGlobalObject(priv->jsContext.get());
109 if (!globalObject->wrapperMap())
110 globalObject->setWrapperMap(std::make_unique<JSC::WrapperMap>(priv->jsContext.get()));
111 jscVirtualMachineAddContext(priv->vm.get(), context);
112 } else if (priv->vm) {
113 ASSERT(priv->jsContext);
114 jscVirtualMachineRemoveContext(priv->vm.get(), context);
115 priv->jsContext = nullptr;
116 priv->vm = nullptr;
117 }
118}
119
120static void jscContextGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec)
121{
122 JSCContextPrivate* priv = JSC_CONTEXT(object)->priv;
123
124 switch (propID) {
125 case PROP_VIRTUAL_MACHINE:
126 g_value_set_object(value, priv->vm.get());
127 break;
128 default:
129 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
130 }
131}
132
133static void jscContextSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec)
134{
135 JSCContext* context = JSC_CONTEXT(object);
136
137 switch (propID) {
138 case PROP_VIRTUAL_MACHINE:
139 if (gpointer vm = g_value_get_object(value))
140 jscContextSetVirtualMachine(context, GRefPtr<JSCVirtualMachine>(JSC_VIRTUAL_MACHINE(vm)));
141 break;
142 default:
143 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
144 }
145}
146
147static void jscContextConstructed(GObject* object)
148{
149 G_OBJECT_CLASS(jsc_context_parent_class)->constructed(object);
150
151 JSCContext* context = JSC_CONTEXT(object);
152 if (!context->priv->vm)
153 jscContextSetVirtualMachine(context, adoptGRef(jsc_virtual_machine_new()));
154
155 context->priv->exceptionHandlers.append(JSCContextExceptionHandler([](JSCContext* context, JSCException* exception, gpointer) {
156 jsc_context_throw_exception(context, exception);
157 }));
158}
159
160static void jscContextDispose(GObject* object)
161{
162 JSCContext* context = JSC_CONTEXT(object);
163 jscContextSetVirtualMachine(context, nullptr);
164
165 G_OBJECT_CLASS(jsc_context_parent_class)->dispose(object);
166}
167
168static void jsc_context_class_init(JSCContextClass* klass)
169{
170 GObjectClass* objClass = G_OBJECT_CLASS(klass);
171 objClass->get_property = jscContextGetProperty;
172 objClass->set_property = jscContextSetProperty;
173 objClass->constructed = jscContextConstructed;
174 objClass->dispose = jscContextDispose;
175
176 /**
177 * JSCContext:virtual-machine:
178 *
179 * The #JSCVirtualMachine in which the context was created.
180 */
181 g_object_class_install_property(objClass,
182 PROP_VIRTUAL_MACHINE,
183 g_param_spec_object(
184 "virtual-machine",
185 "JSCVirtualMachine",
186 "JSC Virtual Machine",
187 JSC_TYPE_VIRTUAL_MACHINE,
188 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
189}
190
191GRefPtr<JSCContext> jscContextGetOrCreate(JSGlobalContextRef jsContext)
192{
193 auto vm = jscVirtualMachineGetOrCreate(toRef(&toJS(jsContext)->vm()));
194 if (GRefPtr<JSCContext> context = jscVirtualMachineGetContext(vm.get(), jsContext))
195 return context;
196
197 GUniquePtr<char> name(g_strdup_printf("%p-jsContext", &Thread::current()));
198 g_object_set_data(G_OBJECT(vm.get()), name.get(), jsContext);
199 return adoptGRef(jsc_context_new_with_virtual_machine(vm.get()));
200}
201
202JSGlobalContextRef jscContextGetJSContext(JSCContext* context)
203{
204 ASSERT(JSC_IS_CONTEXT(context));
205
206 JSCContextPrivate* priv = context->priv;
207 return priv->jsContext.get();
208}
209
210static JSC::WrapperMap& wrapperMap(JSCContext* context)
211{
212 auto* map = toJSGlobalObject(context->priv->jsContext.get())->wrapperMap();
213 ASSERT(map);
214 return *map;
215}
216
217GRefPtr<JSCValue> jscContextGetOrCreateValue(JSCContext* context, JSValueRef jsValue)
218{
219 return wrapperMap(context).gobjectWrapper(context, jsValue);
220}
221
222void jscContextValueDestroyed(JSCContext* context, JSValueRef jsValue)
223{
224 wrapperMap(context).unwrap(jsValue);
225}
226
227JSC::JSObject* jscContextGetJSWrapper(JSCContext* context, gpointer wrappedObject)
228{
229 return wrapperMap(context).jsWrapper(wrappedObject);
230}
231
232JSC::JSObject* jscContextGetOrCreateJSWrapper(JSCContext* context, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
233{
234 if (auto* jsWrapper = jscContextGetJSWrapper(context, wrappedObject))
235 return jsWrapper;
236
237 return wrapperMap(context).createJSWrappper(context->priv->jsContext.get(), jsClass, prototype, wrappedObject, destroyFunction);
238}
239
240JSGlobalContextRef jscContextCreateContextWithJSWrapper(JSCContext* context, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
241{
242 return wrapperMap(context).createContextWithJSWrappper(jscVirtualMachineGetContextGroup(context->priv->vm.get()), jsClass, prototype, wrappedObject, destroyFunction);
243}
244
245gpointer jscContextWrappedObject(JSCContext* context, JSObjectRef jsObject)
246{
247 return wrapperMap(context).wrappedObject(context->priv->jsContext.get(), jsObject);
248}
249
250JSCClass* jscContextGetRegisteredClass(JSCContext* context, JSClassRef jsClass)
251{
252 return wrapperMap(context).registeredClass(jsClass);
253}
254
255CallbackData jscContextPushCallback(JSCContext* context, JSValueRef calleeValue, JSValueRef thisValue, size_t argumentCount, const JSValueRef* arguments)
256{
257 Thread& thread = Thread::current();
258 auto* previousStack = static_cast<CallbackData*>(thread.m_apiData);
259 CallbackData data = { context, WTFMove(context->priv->exception), calleeValue, thisValue, argumentCount, arguments, previousStack };
260 thread.m_apiData = &data;
261 return data;
262}
263
264void jscContextPopCallback(JSCContext* context, CallbackData&& data)
265{
266 Thread& thread = Thread::current();
267 context->priv->exception = WTFMove(data.preservedException);
268 thread.m_apiData = data.next;
269}
270
271JSValueRef jscContextGArrayToJSArray(JSCContext* context, GPtrArray* gArray, JSValueRef* exception)
272{
273 JSCContextPrivate* priv = context->priv;
274 auto* jsArray = JSObjectMakeArray(priv->jsContext.get(), 0, nullptr, exception);
275 if (*exception)
276 return JSValueMakeUndefined(priv->jsContext.get());
277
278 if (!gArray)
279 return jsArray;
280
281 auto* jsArrayObject = JSValueToObject(priv->jsContext.get(), jsArray, exception);
282 if (*exception)
283 return JSValueMakeUndefined(priv->jsContext.get());
284
285 for (unsigned i = 0; i < gArray->len; ++i) {
286 gpointer item = g_ptr_array_index(gArray, i);
287 if (!item)
288 JSObjectSetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, JSValueMakeNull(priv->jsContext.get()), exception);
289 else if (JSC_IS_VALUE(item))
290 JSObjectSetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, jscValueGetJSValue(JSC_VALUE(item)), exception);
291 else
292 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), makeString("invalid item type in GPtrArray")));
293
294 if (*exception)
295 return JSValueMakeUndefined(priv->jsContext.get());
296 }
297
298 return jsArray;
299}
300
301static GRefPtr<GPtrArray> jscContextJSArrayToGArray(JSCContext* context, JSValueRef jsArray, JSValueRef* exception)
302{
303 JSCContextPrivate* priv = context->priv;
304 if (JSValueIsNull(priv->jsContext.get(), jsArray))
305 return nullptr;
306
307 if (!JSValueIsArray(priv->jsContext.get(), jsArray)) {
308 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), makeString("invalid js type for GPtrArray")));
309 return nullptr;
310 }
311
312 auto* jsArrayObject = JSValueToObject(priv->jsContext.get(), jsArray, exception);
313 if (*exception)
314 return nullptr;
315
316 JSRetainPtr<JSStringRef> lengthString(Adopt, JSStringCreateWithUTF8CString("length"));
317 auto* jsLength = JSObjectGetProperty(priv->jsContext.get(), jsArrayObject, lengthString.get(), exception);
318 if (*exception)
319 return nullptr;
320
321 auto length = JSC::toUInt32(JSValueToNumber(priv->jsContext.get(), jsLength, exception));
322 if (*exception)
323 return nullptr;
324
325 GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_with_free_func(g_object_unref));
326 for (unsigned i = 0; i < length; ++i) {
327 auto* jsItem = JSObjectGetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, exception);
328 if (*exception)
329 return nullptr;
330
331 g_ptr_array_add(gArray.get(), jsItem ? jscContextGetOrCreateValue(context, jsItem).leakRef() : nullptr);
332 }
333
334 return gArray;
335}
336
337GUniquePtr<char*> jscContextJSArrayToGStrv(JSCContext* context, JSValueRef jsArray, JSValueRef* exception)
338{
339 JSCContextPrivate* priv = context->priv;
340 if (JSValueIsNull(priv->jsContext.get(), jsArray))
341 return nullptr;
342
343 if (!JSValueIsArray(priv->jsContext.get(), jsArray)) {
344 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), makeString("invalid js type for GStrv")));
345 return nullptr;
346 }
347
348 auto* jsArrayObject = JSValueToObject(priv->jsContext.get(), jsArray, exception);
349 if (*exception)
350 return nullptr;
351
352 JSRetainPtr<JSStringRef> lengthString(Adopt, JSStringCreateWithUTF8CString("length"));
353 auto* jsLength = JSObjectGetProperty(priv->jsContext.get(), jsArrayObject, lengthString.get(), exception);
354 if (*exception)
355 return nullptr;
356
357 auto length = JSC::toUInt32(JSValueToNumber(priv->jsContext.get(), jsLength, exception));
358 if (*exception)
359 return nullptr;
360
361 GUniquePtr<char*> strv(static_cast<char**>(g_new0(char*, length + 1)));
362 for (unsigned i = 0; i < length; ++i) {
363 auto* jsItem = JSObjectGetPropertyAtIndex(priv->jsContext.get(), jsArrayObject, i, exception);
364 if (*exception)
365 return nullptr;
366
367 auto jsValueItem = jscContextGetOrCreateValue(context, jsItem);
368 if (!jsc_value_is_string(jsValueItem.get())) {
369 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), makeString("invalid js type for GStrv: item ", String::number(i), " is not a string")));
370 return nullptr;
371 }
372
373 strv.get()[i] = jsc_value_to_string(jsValueItem.get());
374 }
375
376 return strv;
377}
378
379JSValueRef jscContextGValueToJSValue(JSCContext* context, const GValue* value, JSValueRef* exception)
380{
381 JSCContextPrivate* priv = context->priv;
382
383 switch (g_type_fundamental(G_VALUE_TYPE(value))) {
384 case G_TYPE_BOOLEAN:
385 return JSValueMakeBoolean(priv->jsContext.get(), g_value_get_boolean(value));
386 case G_TYPE_CHAR:
387 case G_TYPE_INT:
388 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_int);
389 case G_TYPE_ENUM:
390 return JSValueMakeNumber(priv->jsContext.get(), g_value_get_enum(value));
391 case G_TYPE_FLAGS:
392 return JSValueMakeNumber(priv->jsContext.get(), g_value_get_flags(value));
393 case G_TYPE_UCHAR:
394 case G_TYPE_UINT:
395 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_uint);
396 case G_TYPE_FLOAT:
397 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_float);
398 case G_TYPE_DOUBLE:
399 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_double);
400 case G_TYPE_LONG:
401 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_long);
402 case G_TYPE_ULONG:
403 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_ulong);
404 case G_TYPE_INT64:
405 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_int64);
406 case G_TYPE_UINT64:
407 return JSValueMakeNumber(priv->jsContext.get(), value->data[0].v_uint64);
408 case G_TYPE_STRING:
409 if (const char* stringValue = g_value_get_string(value)) {
410 JSRetainPtr<JSStringRef> jsString(Adopt, JSStringCreateWithUTF8CString(stringValue));
411 return JSValueMakeString(priv->jsContext.get(), jsString.get());
412 }
413 return JSValueMakeNull(priv->jsContext.get());
414 case G_TYPE_POINTER:
415 case G_TYPE_OBJECT:
416 case G_TYPE_BOXED:
417 if (auto* ptr = value->data[0].v_pointer) {
418 if (auto* jsWrapper = jscContextGetJSWrapper(context, ptr))
419 return toRef(jsWrapper);
420
421 if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_VALUE))
422 return jscValueGetJSValue(JSC_VALUE(ptr));
423
424 if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_EXCEPTION))
425 return jscExceptionGetJSValue(JSC_EXCEPTION(ptr));
426
427 if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_PTR_ARRAY))
428 return jscContextGArrayToJSArray(context, static_cast<GPtrArray*>(ptr), exception);
429
430 if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_STRV)) {
431 auto** strv = static_cast<char**>(ptr);
432 auto strvLength = g_strv_length(strv);
433 GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_full(strvLength, g_object_unref));
434 for (unsigned i = 0; i < strvLength; i++)
435 g_ptr_array_add(gArray.get(), jsc_value_new_string(context, strv[i]));
436 return jscContextGArrayToJSArray(context, gArray.get(), exception);
437 }
438 } else
439 return JSValueMakeNull(priv->jsContext.get());
440
441 break;
442 case G_TYPE_PARAM:
443 case G_TYPE_INTERFACE:
444 case G_TYPE_VARIANT:
445 default:
446 break;
447 }
448
449 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), makeString("unsupported type ", g_type_name(G_VALUE_TYPE(value)))));
450 return JSValueMakeUndefined(priv->jsContext.get());
451}
452
453void jscContextJSValueToGValue(JSCContext* context, JSValueRef jsValue, GType type, GValue* value, JSValueRef* exception)
454{
455 JSCContextPrivate* priv = context->priv;
456 g_value_init(value, type);
457
458 auto fundamentalType = g_type_fundamental(G_VALUE_TYPE(value));
459 switch (fundamentalType) {
460 case G_TYPE_INT:
461 g_value_set_int(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
462 break;
463 case G_TYPE_FLOAT:
464 g_value_set_float(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
465 break;
466 case G_TYPE_DOUBLE:
467 g_value_set_double(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
468 break;
469 case G_TYPE_BOOLEAN:
470 g_value_set_boolean(value, JSValueToBoolean(priv->jsContext.get(), jsValue));
471 break;
472 case G_TYPE_STRING:
473 if (!JSValueIsNull(priv->jsContext.get(), jsValue)) {
474 JSRetainPtr<JSStringRef> jsString(Adopt, JSValueToStringCopy(priv->jsContext.get(), jsValue, exception));
475 if (*exception)
476 return;
477 size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString.get());
478 auto* string = static_cast<char*>(g_malloc(maxSize));
479 JSStringGetUTF8CString(jsString.get(), string, maxSize);
480 g_value_take_string(value, string);
481 } else
482 g_value_set_string(value, nullptr);
483 break;
484 case G_TYPE_CHAR:
485 g_value_set_schar(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
486 break;
487 case G_TYPE_UCHAR:
488 g_value_set_uchar(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
489 break;
490 case G_TYPE_UINT:
491 g_value_set_uint(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
492 break;
493 case G_TYPE_POINTER:
494 case G_TYPE_OBJECT:
495 case G_TYPE_BOXED: {
496 gpointer wrappedObject = nullptr;
497
498 if (!JSValueIsNull(priv->jsContext.get(), jsValue)) {
499 auto jsObject = JSValueToObject(priv->jsContext.get(), jsValue, exception);
500 if (*exception)
501 return;
502
503 wrappedObject = jscContextWrappedObject(context, jsObject);
504 if (!wrappedObject) {
505 if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_VALUE)) {
506 auto jscValue = jscContextGetOrCreateValue(context, jsValue);
507 g_value_set_object(value, jscValue.get());
508 return;
509 }
510
511 if (g_type_is_a(G_VALUE_TYPE(value), JSC_TYPE_EXCEPTION)) {
512 auto exception = jscExceptionCreate(context, jsValue);
513 g_value_set_object(value, exception.get());
514 return;
515 }
516
517 if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_PTR_ARRAY)) {
518 auto gArray = jscContextJSArrayToGArray(context, jsValue, exception);
519 if (!*exception)
520 g_value_take_boxed(value, gArray.leakRef());
521 return;
522 }
523
524 if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_STRV)) {
525 auto strv = jscContextJSArrayToGStrv(context, jsValue, exception);
526 if (!*exception)
527 g_value_take_boxed(value, strv.release());
528 return;
529 }
530
531 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), "invalid pointer type"_s));
532 return;
533 }
534 }
535 if (fundamentalType == G_TYPE_POINTER)
536 g_value_set_pointer(value, wrappedObject);
537 else if (fundamentalType == G_TYPE_BOXED)
538 g_value_set_boxed(value, wrappedObject);
539 else if (G_IS_OBJECT(wrappedObject))
540 g_value_set_object(value, wrappedObject);
541 else
542 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), "wrapped object is not a GObject"_s));
543 break;
544 }
545 case G_TYPE_LONG:
546 g_value_set_long(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
547 break;
548 case G_TYPE_ULONG:
549 g_value_set_ulong(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
550 break;
551 case G_TYPE_INT64:
552 g_value_set_int64(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
553 break;
554 case G_TYPE_UINT64:
555 g_value_set_uint64(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
556 break;
557 case G_TYPE_ENUM:
558 g_value_set_enum(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
559 break;
560 case G_TYPE_FLAGS:
561 g_value_set_flags(value, JSValueToNumber(priv->jsContext.get(), jsValue, exception));
562 break;
563 case G_TYPE_PARAM:
564 case G_TYPE_INTERFACE:
565 case G_TYPE_VARIANT:
566 default:
567 *exception = toRef(JSC::createTypeError(toJS(priv->jsContext.get()), makeString("unsupported type ", g_type_name(G_VALUE_TYPE(value)))));
568 break;
569 }
570}
571
572/**
573 * jsc_context_new:
574 *
575 * Create a new #JSCContext. The context is created in a new #JSCVirtualMachine.
576 * Use jsc_context_new_with_virtual_machine() to create a new #JSCContext in an
577 * existing #JSCVirtualMachine.
578 *
579 * Returns: (transfer full): the newly created #JSCContext.
580 */
581JSCContext* jsc_context_new()
582{
583 return JSC_CONTEXT(g_object_new(JSC_TYPE_CONTEXT, nullptr));
584}
585
586/**
587 * jsc_context_new_with_virtual_machine:
588 * @vm: a #JSCVirtualMachine
589 *
590 * Create a new #JSCContext in @virtual_machine.
591 *
592 * Returns: (transfer full): the newly created #JSCContext.
593 */
594JSCContext* jsc_context_new_with_virtual_machine(JSCVirtualMachine* vm)
595{
596 g_return_val_if_fail(JSC_IS_VIRTUAL_MACHINE(vm), nullptr);
597 return JSC_CONTEXT(g_object_new(JSC_TYPE_CONTEXT, "virtual-machine", vm, nullptr));
598}
599
600/**
601 * jsc_context_get_virtual_machine:
602 * @context: a #JSCContext
603 *
604 * Get the #JSCVirtualMachine where @context was created.
605 *
606 * Returns: (transfer none): the #JSCVirtualMachine where the #JSCContext was created.
607 */
608JSCVirtualMachine* jsc_context_get_virtual_machine(JSCContext* context)
609{
610 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
611
612 return context->priv->vm.get();
613}
614
615/**
616 * jsc_context_get_exception:
617 * @context: a #JSCContext
618 *
619 * Get the last unhandled exception thrown in @context by API functions calls.
620 *
621 * Returns: (transfer none) (nullable): a #JSCException or %NULL if there isn't any
622 * unhandled exception in the #JSCContext.
623 */
624JSCException* jsc_context_get_exception(JSCContext *context)
625{
626 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
627
628 return context->priv->exception.get();
629}
630
631/**
632 * jsc_context_throw:
633 * @context: a #JSCContext
634 * @error_message: an error message
635 *
636 * Throw an exception to @context using the given error message. The created #JSCException
637 * can be retrieved with jsc_context_get_exception().
638 */
639void jsc_context_throw(JSCContext* context, const char* errorMessage)
640{
641 g_return_if_fail(JSC_IS_CONTEXT(context));
642
643 context->priv->exception = adoptGRef(jsc_exception_new(context, errorMessage));
644}
645
646/**
647 * jsc_context_throw_printf:
648 * @context: a #JSCContext
649 * @format: the string format
650 * @...: the parameters to insert into the format string
651 *
652 * Throw an exception to @context using the given formatted string as error message.
653 * The created #JSCException can be retrieved with jsc_context_get_exception().
654 */
655void jsc_context_throw_printf(JSCContext* context, const char* format, ...)
656{
657 g_return_if_fail(JSC_IS_CONTEXT(context));
658
659 va_list args;
660 va_start(args, format);
661 context->priv->exception = adoptGRef(jsc_exception_new_vprintf(context, format, args));
662 va_end(args);
663}
664
665/**
666 * jsc_context_throw_with_name:
667 * @context: a #JSCContext
668 * @error_name: the error name
669 * @error_message: an error message
670 *
671 * Throw an exception to @context using the given error name and message. The created #JSCException
672 * can be retrieved with jsc_context_get_exception().
673 */
674void jsc_context_throw_with_name(JSCContext* context, const char* errorName, const char* errorMessage)
675{
676 g_return_if_fail(JSC_IS_CONTEXT(context));
677 g_return_if_fail(errorName);
678
679 context->priv->exception = adoptGRef(jsc_exception_new_with_name(context, errorName, errorMessage));
680}
681
682/**
683 * jsc_context_throw_with_name_printf:
684 * @context: a #JSCContext
685 * @error_name: the error name
686 * @format: the string format
687 * @...: the parameters to insert into the format string
688 *
689 * Throw an exception to @context using the given error name and the formatted string as error message.
690 * The created #JSCException can be retrieved with jsc_context_get_exception().
691 */
692void jsc_context_throw_with_name_printf(JSCContext* context, const char* errorName, const char* format, ...)
693{
694 g_return_if_fail(JSC_IS_CONTEXT(context));
695
696 va_list args;
697 va_start(args, format);
698 context->priv->exception = adoptGRef(jsc_exception_new_with_name_vprintf(context, errorName, format, args));
699 va_end(args);
700}
701
702/**
703 * jsc_context_throw_exception:
704 * @context: a #JSCContext
705 * @exception: a #JSCException
706 *
707 * Throw @exception to @context.
708 */
709void jsc_context_throw_exception(JSCContext* context, JSCException* exception)
710{
711 g_return_if_fail(JSC_IS_CONTEXT(context));
712 g_return_if_fail(JSC_IS_EXCEPTION(exception));
713
714 context->priv->exception = exception;
715}
716
717/**
718 * jsc_context_clear_exception:
719 * @context: a #JSCContext
720 *
721 * Clear the uncaught exception in @context if any.
722 */
723void jsc_context_clear_exception(JSCContext* context)
724{
725 g_return_if_fail(JSC_IS_CONTEXT(context));
726
727 context->priv->exception = nullptr;
728}
729
730/**
731 * JSCExceptionHandler:
732 * @context: a #JSCContext
733 * @exception: a #JSCException
734 * @user_data: user data
735 *
736 * Function used to handle JavaScript exceptions in a #JSCContext.
737 */
738
739/**
740 * jsc_context_push_exception_handler:
741 * @context: a #JSCContext
742 * @handler: a #JSCExceptionHandler
743 * @user_data: (closure): user data to pass to @handler
744 * @destroy_notify: (nullable): destroy notifier for @user_data
745 *
746 * Push an exception handler in @context. Whenever a JavaScript exception happens in
747 * the #JSCContext, the given @handler will be called. The default #JSCExceptionHandler
748 * simply calls jsc_context_throw_exception() to throw the exception to the #JSCContext.
749 * If you don't want to catch the exception, but only get notified about it, call
750 * jsc_context_throw_exception() in @handler like the default one does.
751 * The last exception handler pushed is the only one used by the #JSCContext, use
752 * jsc_context_pop_exception_handler() to remove it and set the previous one. When @handler
753 * is removed from the context, @destroy_notify i called with @user_data as parameter.
754 */
755void jsc_context_push_exception_handler(JSCContext* context, JSCExceptionHandler handler, gpointer userData, GDestroyNotify destroyNotify)
756{
757 g_return_if_fail(JSC_IS_CONTEXT(context));
758 g_return_if_fail(handler);
759
760 context->priv->exceptionHandlers.append({ handler, userData, destroyNotify });
761}
762
763/**
764 * jsc_context_pop_exception_handler:
765 * @context: a #JSCContext
766 *
767 * Remove the last #JSCExceptionHandler previously pushed to @context with
768 * jsc_context_push_exception_handler().
769 */
770void jsc_context_pop_exception_handler(JSCContext* context)
771{
772 g_return_if_fail(JSC_IS_CONTEXT(context));
773 g_return_if_fail(context->priv->exceptionHandlers.size() > 1);
774
775 context->priv->exceptionHandlers.removeLast();
776}
777
778bool jscContextHandleExceptionIfNeeded(JSCContext* context, JSValueRef jsException)
779{
780 if (!jsException)
781 return false;
782
783 auto exception = jscExceptionCreate(context, jsException);
784 ASSERT(!context->priv->exceptionHandlers.isEmpty());
785 const auto& exceptionHandler = context->priv->exceptionHandlers.last();
786 exceptionHandler.handler(context, exception.get(), exceptionHandler.userData);
787
788 return true;
789}
790
791/**
792 * jsc_context_get_current:
793 *
794 * Get the #JSCContext that is currently executing a function. This should only be
795 * called within a function or method callback, otherwise %NULL will be returned.
796 *
797 * Returns: (transfer none) (nullable): the #JSCContext that is currently executing.
798 */
799JSCContext* jsc_context_get_current()
800{
801 auto* data = static_cast<CallbackData*>(Thread::current().m_apiData);
802 return data ? data->context.get() : nullptr;
803}
804
805/**
806 * jsc_context_evaluate:
807 * @context: a #JSCContext
808 * @code: a JavaScript script to evaluate
809 * @length: length of @code, or -1 if @code is a nul-terminated string
810 *
811 * Evaluate @code in @context.
812 *
813 * Returns: (transfer full): a #JSCValue representing the last value generated by the script.
814 */
815JSCValue* jsc_context_evaluate(JSCContext* context, const char* code, gssize length)
816{
817 return jsc_context_evaluate_with_source_uri(context, code, length, nullptr, 0);
818}
819
820static JSValueRef evaluateScriptInContext(JSGlobalContextRef jsContext, String&& script, const char* uri, unsigned lineNumber, JSValueRef* exception)
821{
822 JSRetainPtr<JSStringRef> scriptJS(Adopt, OpaqueJSString::tryCreate(WTFMove(script)).leakRef());
823 JSRetainPtr<JSStringRef> sourceURI = uri ? adopt(JSStringCreateWithUTF8CString(uri)) : nullptr;
824 return JSEvaluateScript(jsContext, scriptJS.get(), nullptr, sourceURI.get(), lineNumber, exception);
825}
826
827/**
828 * jsc_context_evaluate_with_source_uri:
829 * @context: a #JSCContext
830 * @code: a JavaScript script to evaluate
831 * @length: length of @code, or -1 if @code is a nul-terminated string
832 * @uri: the source URI
833 * @line_number: the starting line number
834 *
835 * Evaluate @code in @context using @uri as the source URI. The @line_number is the starting line number
836 * in @uri; the value is one-based so the first line is 1. @uri and @line_number will be shown in exceptions and
837 * they don't affect the behavior of the script.
838 *
839 * Returns: (transfer full): a #JSCValue representing the last value generated by the script.
840 */
841JSCValue* jsc_context_evaluate_with_source_uri(JSCContext* context, const char* code, gssize length, const char* uri, unsigned lineNumber)
842{
843 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
844 g_return_val_if_fail(code, nullptr);
845
846 JSValueRef exception = nullptr;
847 JSValueRef result = evaluateScriptInContext(context->priv->jsContext.get(), String::fromUTF8(code, length < 0 ? strlen(code) : length), uri, lineNumber, &exception);
848 if (jscContextHandleExceptionIfNeeded(context, exception))
849 return jsc_value_new_undefined(context);
850
851 return jscContextGetOrCreateValue(context, result).leakRef();
852}
853
854/**
855 * jsc_context_evaluate_in_object:
856 * @context: a #JSCContext
857 * @code: a JavaScript script to evaluate
858 * @length: length of @code, or -1 if @code is a nul-terminated string
859 * @object_instance: (nullable): an object instance
860 * @object_class: (nullable): a #JSCClass or %NULL to use the default
861 * @uri: the source URI
862 * @line_number: the starting line number
863 * @object: (out) (transfer full): return location for a #JSCValue.
864 *
865 * Evaluate @code and create an new object where symbols defined in @code will be added as properties,
866 * instead of being added to @context global object. The new object is returned as @object parameter.
867 * Similar to how jsc_value_new_object() works, if @object_instance is not %NULL @object_class must be provided too.
868 * The @line_number is the starting line number in @uri; the value is one-based so the first line is 1.
869 * @uri and @line_number will be shown in exceptions and they don't affect the behavior of the script.
870 *
871 * Returns: (transfer full): a #JSCValue representing the last value generated by the script.
872 */
873JSCValue* jsc_context_evaluate_in_object(JSCContext* context, const char* code, gssize length, gpointer instance, JSCClass* objectClass, const char* uri, unsigned lineNumber, JSCValue** object)
874{
875 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
876 g_return_val_if_fail(code, nullptr);
877 g_return_val_if_fail(!instance || JSC_IS_CLASS(objectClass), nullptr);
878 g_return_val_if_fail(object && !*object, nullptr);
879
880 JSRetainPtr<JSGlobalContextRef> objectContext(Adopt,
881 instance ? jscClassCreateContextWithJSWrapper(objectClass, context, instance) : JSGlobalContextCreateInGroup(jscVirtualMachineGetContextGroup(context->priv->vm.get()), nullptr));
882 JSC::ExecState* exec = toJS(objectContext.get());
883 JSC::VM& vm = exec->vm();
884 auto* jsObject = vm.vmEntryGlobalObject(exec);
885 jsObject->setGlobalScopeExtension(JSC::JSWithScope::create(vm, jsObject, jsObject->globalScope(), toJS(JSContextGetGlobalObject(context->priv->jsContext.get()))));
886 JSValueRef exception = nullptr;
887 JSValueRef result = evaluateScriptInContext(objectContext.get(), String::fromUTF8(code, length < 0 ? strlen(code) : length), uri, lineNumber, &exception);
888 if (jscContextHandleExceptionIfNeeded(context, exception))
889 return jsc_value_new_undefined(context);
890
891 *object = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(objectContext.get())).leakRef();
892
893 return jscContextGetOrCreateValue(context, result).leakRef();
894}
895
896/**
897 * JSCCheckSyntaxMode:
898 * @JSC_CHECK_SYNTAX_MODE_SCRIPT: mode to check syntax of a script
899 * @JSC_CHECK_SYNTAX_MODE_MODULE: mode to check syntax of a module
900 *
901 * Enum values to specify a mode to check for syntax errors in jsc_context_check_syntax().
902 */
903
904/**
905 * JSCCheckSyntaxResult:
906 * @JSC_CHECK_SYNTAX_RESULT_SUCCESS: no errors
907 * @JSC_CHECK_SYNTAX_RESULT_RECOVERABLE_ERROR: recoverable syntax error
908 * @JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR: irrecoverable syntax error
909 * @JSC_CHECK_SYNTAX_RESULT_UNTERMINATED_LITERAL_ERROR: unterminated literal error
910 * @JSC_CHECK_SYNTAX_RESULT_OUT_OF_MEMORY_ERROR: out of memory error
911 * @JSC_CHECK_SYNTAX_RESULT_STACK_OVERFLOW_ERROR: stack overflow error
912 *
913 * Enum values to specify the result of jsc_context_check_syntax().
914 */
915
916/**
917 * jsc_context_check_syntax:
918 * @context: a #JSCContext
919 * @code: a JavaScript script to check
920 * @length: length of @code, or -1 if @code is a nul-terminated string
921 * @mode: a #JSCCheckSyntaxMode
922 * @uri: the source URI
923 * @line_number: the starting line number
924 * @exception: (out) (optional) (transfer full): return location for a #JSCException, or %NULL to ignore
925 *
926 * Check the given @code in @context for syntax errors. The @line_number is the starting line number in @uri;
927 * the value is one-based so the first line is 1. @uri and @line_number are only used to fill the @exception.
928 * In case of errors @exception will be set to a new #JSCException with the details. You can pass %NULL to
929 * @exception to ignore the error details.
930 *
931 * Returns: a #JSCCheckSyntaxResult
932 */
933JSCCheckSyntaxResult jsc_context_check_syntax(JSCContext* context, const char* code, gssize length, JSCCheckSyntaxMode mode, const char* uri, unsigned lineNumber, JSCException **exception)
934{
935 g_return_val_if_fail(JSC_IS_CONTEXT(context), JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR);
936 g_return_val_if_fail(code, JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR);
937 g_return_val_if_fail(!exception || !*exception, JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR);
938
939 lineNumber = std::max<unsigned>(1, lineNumber);
940
941 auto* jsContext = context->priv->jsContext.get();
942 JSC::ExecState* exec = toJS(jsContext);
943 JSC::VM& vm = exec->vm();
944 JSC::JSLockHolder locker(vm);
945
946 String sourceURLString = uri ? String::fromUTF8(uri) : String();
947 JSC::SourceCode source = JSC::makeSource(String::fromUTF8(code, length < 0 ? strlen(code) : length), JSC::SourceOrigin { sourceURLString },
948 URL({ }, sourceURLString), TextPosition(OrdinalNumber::fromOneBasedInt(lineNumber), OrdinalNumber()));
949 bool success = false;
950 JSC::ParserError error;
951 switch (mode) {
952 case JSC_CHECK_SYNTAX_MODE_SCRIPT:
953 success = !!JSC::parse<JSC::ProgramNode>(&vm, source, JSC::Identifier(), JSC::JSParserBuiltinMode::NotBuiltin,
954 JSC::JSParserStrictMode::NotStrict, JSC::JSParserScriptMode::Classic, JSC::SourceParseMode::ProgramMode, JSC::SuperBinding::NotNeeded, error);
955 break;
956 case JSC_CHECK_SYNTAX_MODE_MODULE:
957 success = !!JSC::parse<JSC::ModuleProgramNode>(&vm, source, JSC::Identifier(), JSC::JSParserBuiltinMode::NotBuiltin,
958 JSC::JSParserStrictMode::Strict, JSC::JSParserScriptMode::Module, JSC::SourceParseMode::ModuleAnalyzeMode, JSC::SuperBinding::NotNeeded, error);
959 break;
960 }
961
962 JSCCheckSyntaxResult result = JSC_CHECK_SYNTAX_RESULT_SUCCESS;
963 if (success)
964 return result;
965
966 switch (error.type()) {
967 case JSC::ParserError::ErrorType::SyntaxError: {
968 switch (error.syntaxErrorType()) {
969 case JSC::ParserError::SyntaxErrorType::SyntaxErrorIrrecoverable:
970 result = JSC_CHECK_SYNTAX_RESULT_IRRECOVERABLE_ERROR;
971 break;
972 case JSC::ParserError::SyntaxErrorType::SyntaxErrorUnterminatedLiteral:
973 result = JSC_CHECK_SYNTAX_RESULT_UNTERMINATED_LITERAL_ERROR;
974 break;
975 case JSC::ParserError::SyntaxErrorType::SyntaxErrorRecoverable:
976 result = JSC_CHECK_SYNTAX_RESULT_RECOVERABLE_ERROR;
977 break;
978 case JSC::ParserError::SyntaxErrorType::SyntaxErrorNone:
979 ASSERT_NOT_REACHED();
980 break;
981 }
982 break;
983 }
984 case JSC::ParserError::ErrorType::StackOverflow:
985 result = JSC_CHECK_SYNTAX_RESULT_STACK_OVERFLOW_ERROR;
986 break;
987 case JSC::ParserError::ErrorType::OutOfMemory:
988 result = JSC_CHECK_SYNTAX_RESULT_OUT_OF_MEMORY_ERROR;
989 break;
990 case JSC::ParserError::ErrorType::EvalError:
991 case JSC::ParserError::ErrorType::ErrorNone:
992 ASSERT_NOT_REACHED();
993 break;
994 }
995
996 if (exception) {
997 auto* jsError = error.toErrorObject(exec->lexicalGlobalObject(), source);
998 *exception = jscExceptionCreate(context, toRef(exec, jsError)).leakRef();
999 }
1000
1001 return result;
1002}
1003
1004/**
1005 * jsc_context_get_global_object:
1006 * @context: a #JSCContext
1007 *
1008 * Get a #JSCValue referencing the @context global object
1009 *
1010 * Returns: (transfer full): a #JSCValue
1011 */
1012JSCValue* jsc_context_get_global_object(JSCContext* context)
1013{
1014 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
1015
1016 return jscContextGetOrCreateValue(context, JSContextGetGlobalObject(context->priv->jsContext.get())).leakRef();
1017}
1018
1019/**
1020 * jsc_context_set_value:
1021 * @context: a #JSCContext
1022 * @name: the value name
1023 * @value: a #JSCValue
1024 *
1025 * Set a property of @context global object with @name and @value.
1026 */
1027void jsc_context_set_value(JSCContext* context, const char* name, JSCValue* value)
1028{
1029 g_return_if_fail(JSC_IS_CONTEXT(context));
1030 g_return_if_fail(name);
1031 g_return_if_fail(JSC_IS_VALUE(value));
1032
1033 auto contextObject = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(context->priv->jsContext.get()));
1034 jsc_value_object_set_property(contextObject.get(), name, value);
1035}
1036
1037/**
1038 * jsc_context_get_value:
1039 * @context: a #JSCContext
1040 * @name: the value name
1041 *
1042 * Get a property of @context global object with @name.
1043 *
1044 * Returns: (transfer full): a #JSCValue
1045 */
1046JSCValue* jsc_context_get_value(JSCContext* context, const char* name)
1047{
1048 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
1049 g_return_val_if_fail(name, nullptr);
1050
1051 auto contextObject = jscContextGetOrCreateValue(context, JSContextGetGlobalObject(context->priv->jsContext.get()));
1052 return jsc_value_object_get_property(contextObject.get(), name);
1053}
1054
1055/**
1056 * jsc_context_register_class:
1057 * @context: a #JSCContext
1058 * @name: the class name
1059 * @parent_class: (nullable): a #JSCClass or %NULL
1060 * @vtable: (nullable): an optional #JSCClassVTable or %NULL
1061 * @destroy_notify: (nullable): a destroy notifier for class instances
1062 *
1063 * Register a custom class in @context using the given @name. If the new class inherits from
1064 * another #JSCClass, the parent should be passed as @parent_class, otherwise %NULL should be
1065 * used. The optional @vtable parameter allows to provide a custom implementation for handling
1066 * the class, for example, to handle external properties not added to the prototype.
1067 * When an instance of the #JSCClass is cleared in the context, @destroy_notify is called with
1068 * the instance as parameter.
1069 *
1070 * Returns: (transfer none): a #JSCClass
1071 */
1072JSCClass* jsc_context_register_class(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction)
1073{
1074 g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
1075 g_return_val_if_fail(name, nullptr);
1076 g_return_val_if_fail(!parentClass || JSC_IS_CLASS(parentClass), nullptr);
1077
1078 auto jscClass = jscClassCreate(context, name, parentClass, vtable, destroyFunction);
1079 wrapperMap(context).registerClass(jscClass.get());
1080 return jscClass.get();
1081}
1082