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 "JSCValue.h" |
22 | |
23 | #include "APICast.h" |
24 | #include "APIUtils.h" |
25 | #include "JSCCallbackFunction.h" |
26 | #include "JSCClassPrivate.h" |
27 | #include "JSCContextPrivate.h" |
28 | #include "JSCInlines.h" |
29 | #include "JSCValuePrivate.h" |
30 | #include "JSRetainPtr.h" |
31 | #include "OpaqueJSString.h" |
32 | #include <gobject/gvaluecollector.h> |
33 | #include <wtf/glib/GRefPtr.h> |
34 | #include <wtf/glib/GUniquePtr.h> |
35 | #include <wtf/glib/WTFGType.h> |
36 | |
37 | /** |
38 | * SECTION: JSCValue |
39 | * @short_description: JavaScript value |
40 | * @title: JSCValue |
41 | * @see_also: JSCContext |
42 | * |
43 | * JSCValue represents a reference to a value in a #JSCContext. The JSCValue |
44 | * protects the referenced value from being garbage collected. |
45 | */ |
46 | |
47 | enum { |
48 | PROP_0, |
49 | |
50 | PROP_CONTEXT, |
51 | }; |
52 | |
53 | struct _JSCValuePrivate { |
54 | GRefPtr<JSCContext> context; |
55 | JSValueRef jsValue; |
56 | }; |
57 | |
58 | WEBKIT_DEFINE_TYPE(JSCValue, jsc_value, G_TYPE_OBJECT) |
59 | |
60 | static void jscValueGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec) |
61 | { |
62 | JSCValuePrivate* priv = JSC_VALUE(object)->priv; |
63 | |
64 | switch (propID) { |
65 | case PROP_CONTEXT: |
66 | g_value_set_object(value, priv->context.get()); |
67 | break; |
68 | default: |
69 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
70 | } |
71 | } |
72 | |
73 | static void jscValueSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec) |
74 | { |
75 | JSCValuePrivate* priv = JSC_VALUE(object)->priv; |
76 | |
77 | switch (propID) { |
78 | case PROP_CONTEXT: |
79 | priv->context = JSC_CONTEXT(g_value_get_object(value)); |
80 | break; |
81 | default: |
82 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
83 | } |
84 | } |
85 | |
86 | static void jscValueDispose(GObject* object) |
87 | { |
88 | JSCValuePrivate* priv = JSC_VALUE(object)->priv; |
89 | |
90 | if (priv->context) { |
91 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
92 | |
93 | JSValueUnprotect(jsContext, priv->jsValue); |
94 | jscContextValueDestroyed(priv->context.get(), priv->jsValue); |
95 | priv->jsValue = nullptr; |
96 | priv->context = nullptr; |
97 | } |
98 | |
99 | G_OBJECT_CLASS(jsc_value_parent_class)->dispose(object); |
100 | } |
101 | |
102 | static void jsc_value_class_init(JSCValueClass* klass) |
103 | { |
104 | GObjectClass* objClass = G_OBJECT_CLASS(klass); |
105 | |
106 | objClass->get_property = jscValueGetProperty; |
107 | objClass->set_property = jscValueSetProperty; |
108 | objClass->dispose = jscValueDispose; |
109 | |
110 | /** |
111 | * JSCValue:context: |
112 | * |
113 | * The #JSCContext in which the value was created. |
114 | */ |
115 | g_object_class_install_property(objClass, |
116 | PROP_CONTEXT, |
117 | g_param_spec_object( |
118 | "context" , |
119 | "JSCContext" , |
120 | "JSC Context" , |
121 | JSC_TYPE_CONTEXT, |
122 | static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
123 | } |
124 | |
125 | JSValueRef jscValueGetJSValue(JSCValue* value) |
126 | { |
127 | return value->priv->jsValue; |
128 | } |
129 | |
130 | JSCValue* jscValueCreate(JSCContext* context, JSValueRef jsValue) |
131 | { |
132 | auto* value = JSC_VALUE(g_object_new(JSC_TYPE_VALUE, "context" , context, nullptr)); |
133 | JSValueProtect(jscContextGetJSContext(context), jsValue); |
134 | value->priv->jsValue = jsValue; |
135 | return value; |
136 | } |
137 | |
138 | /** |
139 | * jsc_value_get_context: |
140 | * @value: a #JSCValue |
141 | * |
142 | * Get the #JSCContext in which @value was created. |
143 | * |
144 | * Returns: (transfer none): the #JSCValue context. |
145 | */ |
146 | JSCContext* jsc_value_get_context(JSCValue* value) |
147 | { |
148 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
149 | |
150 | return value->priv->context.get(); |
151 | } |
152 | |
153 | /** |
154 | * jsc_value_new_undefined: |
155 | * @context: a #JSCContext |
156 | * |
157 | * Create a new #JSCValue referencing <function>undefined</function> in @context. |
158 | * |
159 | * Returns: (transfer full): a #JSCValue. |
160 | */ |
161 | JSCValue* jsc_value_new_undefined(JSCContext* context) |
162 | { |
163 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
164 | |
165 | return jscContextGetOrCreateValue(context, JSValueMakeUndefined(jscContextGetJSContext(context))).leakRef(); |
166 | } |
167 | |
168 | /** |
169 | * jsc_value_is_undefined: |
170 | * @value: a #JSCValue |
171 | * |
172 | * Get whether the value referenced by @value is <function>undefined</function>. |
173 | * |
174 | * Returns: whether the value is undefined. |
175 | */ |
176 | gboolean jsc_value_is_undefined(JSCValue* value) |
177 | { |
178 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
179 | |
180 | JSCValuePrivate* priv = value->priv; |
181 | return JSValueIsUndefined(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
182 | } |
183 | |
184 | /** |
185 | * jsc_value_new_null: |
186 | * @context: a #JSCContext |
187 | * |
188 | * Create a new #JSCValue referencing <function>null</function> in @context. |
189 | * |
190 | * Returns: (transfer full): a #JSCValue. |
191 | */ |
192 | JSCValue* jsc_value_new_null(JSCContext* context) |
193 | { |
194 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
195 | |
196 | return jscContextGetOrCreateValue(context, JSValueMakeNull(jscContextGetJSContext(context))).leakRef(); |
197 | } |
198 | |
199 | /** |
200 | * jsc_value_is_null: |
201 | * @value: a #JSCValue |
202 | * |
203 | * Get whether the value referenced by @value is <function>null</function>. |
204 | * |
205 | * Returns: whether the value is null. |
206 | */ |
207 | gboolean jsc_value_is_null(JSCValue* value) |
208 | { |
209 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
210 | |
211 | JSCValuePrivate* priv = value->priv; |
212 | return JSValueIsNull(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
213 | } |
214 | |
215 | /** |
216 | * jsc_value_new_number: |
217 | * @context: a #JSCContext |
218 | * @number: a number |
219 | * |
220 | * Create a new #JSCValue from @number. |
221 | * |
222 | * Returns: (transfer full): a #JSCValue. |
223 | */ |
224 | JSCValue* jsc_value_new_number(JSCContext* context, double number) |
225 | { |
226 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
227 | |
228 | return jscContextGetOrCreateValue(context, JSValueMakeNumber(jscContextGetJSContext(context), number)).leakRef(); |
229 | } |
230 | |
231 | /** |
232 | * jsc_value_is_number: |
233 | * @value: a #JSCValue |
234 | * |
235 | * Get whether the value referenced by @value is a number. |
236 | * |
237 | * Returns: whether the value is a number. |
238 | */ |
239 | gboolean jsc_value_is_number(JSCValue* value) |
240 | { |
241 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
242 | |
243 | JSCValuePrivate* priv = value->priv; |
244 | return JSValueIsNumber(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
245 | } |
246 | |
247 | /** |
248 | * jsc_value_to_double: |
249 | * @value: a #JSCValue |
250 | * |
251 | * Convert @value to a double. |
252 | * |
253 | * Returns: a #gdouble result of the conversion. |
254 | */ |
255 | double jsc_value_to_double(JSCValue* value) |
256 | { |
257 | g_return_val_if_fail(JSC_IS_VALUE(value), std::numeric_limits<double>::quiet_NaN()); |
258 | |
259 | JSCValuePrivate* priv = value->priv; |
260 | JSValueRef exception = nullptr; |
261 | auto result = JSValueToNumber(jscContextGetJSContext(priv->context.get()), priv->jsValue, &exception); |
262 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
263 | return std::numeric_limits<double>::quiet_NaN(); |
264 | |
265 | return result; |
266 | } |
267 | |
268 | /** |
269 | * jsc_value_to_int32: |
270 | * @value: a #JSCValue |
271 | * |
272 | * Convert @value to a #gint32. |
273 | * |
274 | * Returns: a #gint32 result of the conversion. |
275 | */ |
276 | gint32 jsc_value_to_int32(JSCValue* value) |
277 | { |
278 | return JSC::toInt32(jsc_value_to_double(value)); |
279 | } |
280 | |
281 | /** |
282 | * jsc_value_new_boolean: |
283 | * @context: a #JSCContext |
284 | * @value: a #gboolean |
285 | * |
286 | * Create a new #JSCValue from @value |
287 | * |
288 | * Returns: (transfer full): a #JSCValue. |
289 | */ |
290 | JSCValue* jsc_value_new_boolean(JSCContext* context, gboolean value) |
291 | { |
292 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
293 | |
294 | return jscContextGetOrCreateValue(context, JSValueMakeBoolean(jscContextGetJSContext(context), value)).leakRef(); |
295 | } |
296 | |
297 | /** |
298 | * jsc_value_is_boolean: |
299 | * @value: a #JSCValue |
300 | * |
301 | * Get whether the value referenced by @value is a boolean. |
302 | * |
303 | * Returns: whether the value is a boolean. |
304 | */ |
305 | gboolean jsc_value_is_boolean(JSCValue* value) |
306 | { |
307 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
308 | |
309 | JSCValuePrivate* priv = value->priv; |
310 | return JSValueIsBoolean(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
311 | } |
312 | |
313 | /** |
314 | * jsc_value_to_boolean: |
315 | * @value: a #JSCValue |
316 | * |
317 | * Convert @value to a boolean. |
318 | * |
319 | * Returns: a #gboolean result of the conversion. |
320 | */ |
321 | gboolean jsc_value_to_boolean(JSCValue* value) |
322 | { |
323 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
324 | |
325 | JSCValuePrivate* priv = value->priv; |
326 | return JSValueToBoolean(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
327 | } |
328 | |
329 | /** |
330 | * jsc_value_new_string: |
331 | * @context: a #JSCContext |
332 | * @string: (nullable): a null-terminated string |
333 | * |
334 | * Create a new #JSCValue from @string. If you need to create a #JSCValue from a |
335 | * string containing null characters, use jsc_value_new_string_from_bytes() instead. |
336 | * |
337 | * Returns: (transfer full): a #JSCValue. |
338 | */ |
339 | JSCValue* jsc_value_new_string(JSCContext* context, const char* string) |
340 | { |
341 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
342 | |
343 | JSValueRef jsStringValue; |
344 | if (string) { |
345 | JSRetainPtr<JSStringRef> jsString(Adopt, JSStringCreateWithUTF8CString(string)); |
346 | jsStringValue = JSValueMakeString(jscContextGetJSContext(context), jsString.get()); |
347 | } else |
348 | jsStringValue = JSValueMakeString(jscContextGetJSContext(context), nullptr); |
349 | return jscContextGetOrCreateValue(context, jsStringValue).leakRef(); |
350 | } |
351 | |
352 | /** |
353 | * jsc_value_new_string_from_bytes: |
354 | * @context: a #JSCContext |
355 | * @bytes: (nullable): a #GBytes |
356 | * |
357 | * Create a new #JSCValue from @bytes. |
358 | * |
359 | * Returns: (transfer full): a #JSCValue. |
360 | */ |
361 | JSCValue* jsc_value_new_string_from_bytes(JSCContext* context, GBytes* bytes) |
362 | { |
363 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
364 | |
365 | if (!bytes) |
366 | return jsc_value_new_string(context, nullptr); |
367 | |
368 | gsize dataSize; |
369 | const auto* data = static_cast<const char*>(g_bytes_get_data(bytes, &dataSize)); |
370 | auto string = String::fromUTF8(data, dataSize); |
371 | JSRetainPtr<JSStringRef> jsString(Adopt, OpaqueJSString::tryCreate(WTFMove(string)).leakRef()); |
372 | return jscContextGetOrCreateValue(context, JSValueMakeString(jscContextGetJSContext(context), jsString.get())).leakRef(); |
373 | } |
374 | |
375 | /** |
376 | * jsc_value_is_string: |
377 | * @value: a #JSCValue |
378 | * |
379 | * Get whether the value referenced by @value is a string |
380 | * |
381 | * Returns: whether the value is a string |
382 | */ |
383 | gboolean jsc_value_is_string(JSCValue* value) |
384 | { |
385 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
386 | |
387 | JSCValuePrivate* priv = value->priv; |
388 | return JSValueIsString(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
389 | } |
390 | |
391 | /** |
392 | * jsc_value_to_string: |
393 | * @value: a #JSCValue |
394 | * |
395 | * Convert @value to a string. Use jsc_value_to_string_as_bytes() instead, if you need to |
396 | * handle strings containing null characters. |
397 | * |
398 | * Returns: (transfer full): a null-terminated string result of the conversion. |
399 | */ |
400 | char* jsc_value_to_string(JSCValue* value) |
401 | { |
402 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
403 | |
404 | JSCValuePrivate* priv = value->priv; |
405 | JSValueRef exception = nullptr; |
406 | JSRetainPtr<JSStringRef> jsString(Adopt, JSValueToStringCopy(jscContextGetJSContext(priv->context.get()), priv->jsValue, &exception)); |
407 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
408 | return nullptr; |
409 | |
410 | if (!jsString) |
411 | return nullptr; |
412 | |
413 | size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString.get()); |
414 | auto* string = static_cast<char*>(g_malloc(maxSize)); |
415 | if (!JSStringGetUTF8CString(jsString.get(), string, maxSize)) { |
416 | g_free(string); |
417 | return nullptr; |
418 | } |
419 | |
420 | return string; |
421 | } |
422 | |
423 | /** |
424 | * jsc_value_to_string_as_bytes: |
425 | * @value: a #JSCValue |
426 | * |
427 | * Convert @value to a string and return the results as #GBytes. This is needed |
428 | * to handle strings with null characters. |
429 | * |
430 | * Returns: (transfer full): a #GBytes with the result of the conversion. |
431 | */ |
432 | GBytes* jsc_value_to_string_as_bytes(JSCValue* value) |
433 | { |
434 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
435 | |
436 | JSCValuePrivate* priv = value->priv; |
437 | JSValueRef exception = nullptr; |
438 | JSRetainPtr<JSStringRef> jsString(Adopt, JSValueToStringCopy(jscContextGetJSContext(priv->context.get()), priv->jsValue, &exception)); |
439 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
440 | return nullptr; |
441 | |
442 | if (!jsString) |
443 | return nullptr; |
444 | |
445 | size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString.get()); |
446 | if (maxSize == 1) |
447 | return g_bytes_new_static("" , 0); |
448 | |
449 | auto* string = static_cast<char*>(fastMalloc(maxSize)); |
450 | auto stringSize = JSStringGetUTF8CString(jsString.get(), string, maxSize); |
451 | if (!stringSize) { |
452 | fastFree(string); |
453 | return nullptr; |
454 | } |
455 | |
456 | // Ignore the null character added by JSStringGetUTF8CString. |
457 | return g_bytes_new_with_free_func(string, stringSize - 1, fastFree, string); |
458 | } |
459 | |
460 | /** |
461 | * jsc_value_new_array: (skip) |
462 | * @context: a #JSCContext |
463 | * @first_item_type: #GType of first item, or %G_TYPE_NONE |
464 | * @...: value of the first item, followed optionally by more type/value pairs, followed by %G_TYPE_NONE. |
465 | * |
466 | * Create a new #JSCValue referencing an array with the given items. If @first_item_type |
467 | * is %G_TYPE_NONE an empty array is created. |
468 | * |
469 | * Returns: (transfer full): a #JSCValue. |
470 | */ |
471 | JSCValue* jsc_value_new_array(JSCContext* context, GType firstItemType, ...) |
472 | { |
473 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
474 | |
475 | JSValueRef exception = nullptr; |
476 | auto* jsContext = jscContextGetJSContext(context); |
477 | auto* jsArray = JSObjectMakeArray(jsContext, 0, nullptr, &exception); |
478 | if (jscContextHandleExceptionIfNeeded(context, exception)) |
479 | return nullptr; |
480 | |
481 | auto* jsArrayObject = JSValueToObject(jsContext, jsArray, &exception); |
482 | if (jscContextHandleExceptionIfNeeded(context, exception)) |
483 | return nullptr; |
484 | |
485 | unsigned index = 0; |
486 | va_list args; |
487 | va_start(args, firstItemType); |
488 | GType itemType = firstItemType; |
489 | while (itemType != G_TYPE_NONE) { |
490 | GValue item; |
491 | GUniqueOutPtr<char> error; |
492 | G_VALUE_COLLECT_INIT(&item, itemType, args, G_VALUE_NOCOPY_CONTENTS, &error.outPtr()); |
493 | if (error) { |
494 | exception = toRef(JSC::createTypeError(toJS(jsContext), makeString("failed to collect array item: " , error.get()))); |
495 | jscContextHandleExceptionIfNeeded(context, exception); |
496 | jsArray = nullptr; |
497 | break; |
498 | } |
499 | |
500 | auto* jsValue = jscContextGValueToJSValue(context, &item, &exception); |
501 | g_value_unset(&item); |
502 | if (jscContextHandleExceptionIfNeeded(context, exception)) { |
503 | jsArray = nullptr; |
504 | break; |
505 | } |
506 | |
507 | JSObjectSetPropertyAtIndex(jsContext, jsArrayObject, index, jsValue, &exception); |
508 | if (jscContextHandleExceptionIfNeeded(context, exception)) { |
509 | jsArray = nullptr; |
510 | break; |
511 | } |
512 | |
513 | itemType = va_arg(args, GType); |
514 | index++; |
515 | } |
516 | va_end(args); |
517 | |
518 | return jsArray ? jscContextGetOrCreateValue(context, jsArray).leakRef() : nullptr; |
519 | } |
520 | |
521 | /** |
522 | * jsc_value_new_array_from_garray: |
523 | * @context: a #JSCContext |
524 | * @array: (nullable) (element-type JSCValue): a #GPtrArray |
525 | * |
526 | * Create a new #JSCValue referencing an array with the items from @array. If @array |
527 | * is %NULL or empty a new empty array will be created. Elements of @array should be |
528 | * pointers to a #JSCValue. |
529 | * |
530 | * Returns: (transfer full): a #JSCValue. |
531 | */ |
532 | JSCValue* jsc_value_new_array_from_garray(JSCContext* context, GPtrArray* gArray) |
533 | { |
534 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
535 | |
536 | if (!gArray || !gArray->len) |
537 | return jsc_value_new_array(context, G_TYPE_NONE); |
538 | |
539 | JSValueRef exception = nullptr; |
540 | auto* jsArray = jscContextGArrayToJSArray(context, gArray, &exception); |
541 | if (jscContextHandleExceptionIfNeeded(context, exception)) |
542 | return nullptr; |
543 | |
544 | return jscContextGetOrCreateValue(context, jsArray).leakRef(); |
545 | } |
546 | |
547 | /** |
548 | * jsc_value_new_array_from_strv: |
549 | * @context: a #JSCContext |
550 | * @strv: (array zero-terminated=1) (element-type utf8): a %NULL-terminated array of strings |
551 | * |
552 | * Create a new #JSCValue referencing an array of strings with the items from @strv. If @array |
553 | * is %NULL or empty a new empty array will be created. |
554 | * |
555 | * Returns: (transfer full): a #JSCValue. |
556 | */ |
557 | JSCValue* jsc_value_new_array_from_strv(JSCContext* context, const char* const* strv) |
558 | { |
559 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
560 | |
561 | auto strvLength = strv ? g_strv_length(const_cast<char**>(strv)) : 0; |
562 | if (!strvLength) |
563 | return jsc_value_new_array(context, G_TYPE_NONE); |
564 | |
565 | GRefPtr<GPtrArray> gArray = adoptGRef(g_ptr_array_new_full(strvLength, g_object_unref)); |
566 | for (unsigned i = 0; i < strvLength; i++) |
567 | g_ptr_array_add(gArray.get(), jsc_value_new_string(context, strv[i])); |
568 | |
569 | return jsc_value_new_array_from_garray(context, gArray.get()); |
570 | } |
571 | |
572 | /** |
573 | * jsc_value_is_array: |
574 | * @value: a #JSCValue |
575 | * |
576 | * Get whether the value referenced by @value is an array. |
577 | * |
578 | * Returns: whether the value is an array. |
579 | */ |
580 | gboolean jsc_value_is_array(JSCValue* value) |
581 | { |
582 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
583 | |
584 | JSCValuePrivate* priv = value->priv; |
585 | return JSValueIsArray(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
586 | } |
587 | |
588 | /** |
589 | * jsc_value_new_object: |
590 | * @context: a #JSCContext |
591 | * @instance: (nullable) (transfer full): an object instance or %NULL |
592 | * @jsc_class: (nullable): the #JSCClass of @instance |
593 | * |
594 | * Create a new #JSCValue from @instance. If @instance is %NULL a new empty object is created. |
595 | * When @instance is provided, @jsc_class must be provided too. @jsc_class takes ownership of |
596 | * @instance that will be freed by the #GDestroyNotify passed to jsc_context_register_class(). |
597 | * |
598 | * Returns: (transfer full): a #JSCValue. |
599 | */ |
600 | JSCValue* jsc_value_new_object(JSCContext* context, gpointer instance, JSCClass* jscClass) |
601 | { |
602 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
603 | g_return_val_if_fail(!instance || JSC_IS_CLASS(jscClass), nullptr); |
604 | |
605 | return jscContextGetOrCreateValue(context, instance ? toRef(jscClassGetOrCreateJSWrapper(jscClass, context, instance)) : JSObjectMake(jscContextGetJSContext(context), nullptr, nullptr)).leakRef(); |
606 | } |
607 | |
608 | /** |
609 | * jsc_value_is_object: |
610 | * @value: a #JSCValue |
611 | * |
612 | * Get whether the value referenced by @value is an object. |
613 | * |
614 | * Returns: whether the value is an object. |
615 | */ |
616 | gboolean jsc_value_is_object(JSCValue* value) |
617 | { |
618 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
619 | |
620 | JSCValuePrivate* priv = value->priv; |
621 | return JSValueIsObject(jscContextGetJSContext(priv->context.get()), priv->jsValue); |
622 | } |
623 | |
624 | /** |
625 | * jsc_value_object_is_instance_of: |
626 | * @value: a #JSCValue |
627 | * @name: a class name |
628 | * |
629 | * Get whether the value referenced by @value is an instance of class @name. |
630 | * |
631 | * Returns: whether the value is an object instance of class @name. |
632 | */ |
633 | gboolean jsc_value_object_is_instance_of(JSCValue* value, const char* name) |
634 | { |
635 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
636 | g_return_val_if_fail(name, FALSE); |
637 | |
638 | JSCValuePrivate* priv = value->priv; |
639 | // We use evaluate here and not get_value because classes are not necessarily a property of the global object. |
640 | // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records |
641 | GRefPtr<JSCValue> constructor = adoptGRef(jsc_context_evaluate(priv->context.get(), name, -1)); |
642 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
643 | |
644 | JSValueRef exception = nullptr; |
645 | JSObjectRef object = JSValueToObject(jsContext, constructor->priv->jsValue, &exception); |
646 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
647 | return FALSE; |
648 | |
649 | gboolean returnValue = JSValueIsInstanceOfConstructor(jsContext, priv->jsValue, object, &exception); |
650 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
651 | return FALSE; |
652 | |
653 | return returnValue; |
654 | } |
655 | |
656 | /** |
657 | * jsc_value_object_set_property: |
658 | * @value: a #JSCValue |
659 | * @name: the property name |
660 | * @property: the #JSCValue to set |
661 | * |
662 | * Set @property with @name on @value. |
663 | */ |
664 | void jsc_value_object_set_property(JSCValue* value, const char* name, JSCValue* property) |
665 | { |
666 | g_return_if_fail(JSC_IS_VALUE(value)); |
667 | g_return_if_fail(name); |
668 | g_return_if_fail(JSC_IS_VALUE(property)); |
669 | |
670 | JSCValuePrivate* priv = value->priv; |
671 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
672 | JSValueRef exception = nullptr; |
673 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
674 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
675 | return; |
676 | |
677 | JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
678 | JSObjectSetProperty(jsContext, object, propertyName.get(), property->priv->jsValue, kJSPropertyAttributeNone, &exception); |
679 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
680 | } |
681 | |
682 | /** |
683 | * jsc_value_object_get_property: |
684 | * @value: a #JSCValue |
685 | * @name: the property name |
686 | * |
687 | * Get property with @name from @value. |
688 | * |
689 | * Returns: (transfer full): the property #JSCValue. |
690 | */ |
691 | JSCValue* jsc_value_object_get_property(JSCValue* value, const char* name) |
692 | { |
693 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
694 | g_return_val_if_fail(name, nullptr); |
695 | |
696 | JSCValuePrivate* priv = value->priv; |
697 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
698 | JSValueRef exception = nullptr; |
699 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
700 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
701 | return jsc_value_new_undefined(priv->context.get()); |
702 | |
703 | JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
704 | JSValueRef result = JSObjectGetProperty(jsContext, object, propertyName.get(), &exception); |
705 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
706 | return jsc_value_new_undefined(priv->context.get()); |
707 | |
708 | return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
709 | } |
710 | |
711 | /** |
712 | * jsc_value_object_set_property_at_index: |
713 | * @value: a #JSCValue |
714 | * @index: the property index |
715 | * @property: the #JSCValue to set |
716 | * |
717 | * Set @property at @index on @value. |
718 | */ |
719 | void jsc_value_object_set_property_at_index(JSCValue* value, unsigned index, JSCValue* property) |
720 | { |
721 | g_return_if_fail(JSC_IS_VALUE(value)); |
722 | g_return_if_fail(JSC_IS_VALUE(property)); |
723 | |
724 | JSCValuePrivate* priv = value->priv; |
725 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
726 | JSValueRef exception = nullptr; |
727 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
728 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
729 | return; |
730 | |
731 | JSObjectSetPropertyAtIndex(jsContext, object, index, property->priv->jsValue, &exception); |
732 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
733 | } |
734 | |
735 | /** |
736 | * jsc_value_object_get_property_at_index: |
737 | * @value: a #JSCValue |
738 | * @index: the property index |
739 | * |
740 | * Get property at @index from @value. |
741 | * |
742 | * Returns: (transfer full): the property #JSCValue. |
743 | */ |
744 | JSCValue* jsc_value_object_get_property_at_index(JSCValue* value, unsigned index) |
745 | { |
746 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
747 | |
748 | JSCValuePrivate* priv = value->priv; |
749 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
750 | JSValueRef exception = nullptr; |
751 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
752 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
753 | return jsc_value_new_undefined(priv->context.get()); |
754 | |
755 | JSValueRef result = JSObjectGetPropertyAtIndex(jsContext, object, index, &exception); |
756 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
757 | return jsc_value_new_undefined(priv->context.get()); |
758 | |
759 | return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
760 | } |
761 | |
762 | /** |
763 | * jsc_value_object_has_property: |
764 | * @value: a #JSCValue |
765 | * @name: the property name |
766 | * |
767 | * Get whether @value has property with @name. |
768 | * |
769 | * Returns: %TRUE if @value has a property with @name, or %FALSE otherwise |
770 | */ |
771 | gboolean jsc_value_object_has_property(JSCValue* value, const char* name) |
772 | { |
773 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
774 | g_return_val_if_fail(name, FALSE); |
775 | |
776 | JSCValuePrivate* priv = value->priv; |
777 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
778 | JSValueRef exception = nullptr; |
779 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
780 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
781 | return FALSE; |
782 | |
783 | JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
784 | return JSObjectHasProperty(jsContext, object, propertyName.get()); |
785 | } |
786 | |
787 | /** |
788 | * jsc_value_object_delete_property: |
789 | * @value: a #JSCValue |
790 | * @name: the property name |
791 | * |
792 | * Try to delete property with @name from @value. This function will return %FALSE if |
793 | * the property was defined without %JSC_VALUE_PROPERTY_CONFIGURABLE flag. |
794 | * |
795 | * Returns: %TRUE if the property was deleted, or %FALSE otherwise. |
796 | */ |
797 | gboolean jsc_value_object_delete_property(JSCValue* value, const char* name) |
798 | { |
799 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
800 | g_return_val_if_fail(name, FALSE); |
801 | |
802 | JSCValuePrivate* priv = value->priv; |
803 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
804 | JSValueRef exception = nullptr; |
805 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
806 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
807 | return FALSE; |
808 | |
809 | JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
810 | gboolean result = JSObjectDeleteProperty(jsContext, object, propertyName.get(), &exception); |
811 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
812 | return FALSE; |
813 | |
814 | return result; |
815 | } |
816 | |
817 | /** |
818 | * jsc_value_object_enumerate_properties: |
819 | * @value: a #JSCValue |
820 | * |
821 | * Get the list of property names of @value. Only properties defined with %JSC_VALUE_PROPERTY_ENUMERABLE |
822 | * flag will be collected. |
823 | * |
824 | * Returns: (array zero-terminated=1) (transfer full) (nullable): a %NULL-terminated array of strings containing the |
825 | * property names, or %NULL if @value doesn't have enumerable properties. Use g_strfreev() to free. |
826 | */ |
827 | char** jsc_value_object_enumerate_properties(JSCValue* value) |
828 | { |
829 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
830 | |
831 | JSCValuePrivate* priv = value->priv; |
832 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
833 | JSValueRef exception = nullptr; |
834 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
835 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
836 | return nullptr; |
837 | |
838 | auto* propertiesArray = JSObjectCopyPropertyNames(jsContext, object); |
839 | if (!propertiesArray) |
840 | return nullptr; |
841 | |
842 | auto propertiesArraySize = JSPropertyNameArrayGetCount(propertiesArray); |
843 | if (!propertiesArraySize) { |
844 | JSPropertyNameArrayRelease(propertiesArray); |
845 | return nullptr; |
846 | } |
847 | |
848 | auto* result = static_cast<char**>(g_new0(char*, propertiesArraySize + 1)); |
849 | for (unsigned i = 0; i < propertiesArraySize; ++i) { |
850 | auto* jsString = JSPropertyNameArrayGetNameAtIndex(propertiesArray, i); |
851 | size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString); |
852 | auto* string = static_cast<char*>(g_malloc(maxSize)); |
853 | JSStringGetUTF8CString(jsString, string, maxSize); |
854 | result[i] = string; |
855 | } |
856 | JSPropertyNameArrayRelease(propertiesArray); |
857 | |
858 | return result; |
859 | } |
860 | |
861 | static JSValueRef jsObjectCall(JSGlobalContextRef jsContext, JSObjectRef function, JSC::JSCCallbackFunction::Type functionType, JSObjectRef thisObject, const Vector<JSValueRef>& arguments, JSValueRef* exception) |
862 | { |
863 | switch (functionType) { |
864 | case JSC::JSCCallbackFunction::Type::Constructor: |
865 | return JSObjectCallAsConstructor(jsContext, function, arguments.size(), arguments.data(), exception); |
866 | break; |
867 | case JSC::JSCCallbackFunction::Type::Method: |
868 | ASSERT(thisObject); |
869 | FALLTHROUGH; |
870 | case JSC::JSCCallbackFunction::Type::Function: |
871 | return JSObjectCallAsFunction(jsContext, function, thisObject, arguments.size(), arguments.data(), exception); |
872 | break; |
873 | } |
874 | RELEASE_ASSERT_NOT_REACHED(); |
875 | } |
876 | |
877 | static GRefPtr<JSCValue> jscValueCallFunction(JSCValue* value, JSObjectRef function, JSC::JSCCallbackFunction::Type functionType, JSObjectRef thisObject, GType firstParameterType, va_list args) |
878 | { |
879 | JSCValuePrivate* priv = value->priv; |
880 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
881 | |
882 | JSValueRef exception = nullptr; |
883 | Vector<JSValueRef> arguments; |
884 | GType parameterType = firstParameterType; |
885 | while (parameterType != G_TYPE_NONE) { |
886 | GValue parameter; |
887 | GUniqueOutPtr<char> error; |
888 | G_VALUE_COLLECT_INIT(¶meter, parameterType, args, G_VALUE_NOCOPY_CONTENTS, &error.outPtr()); |
889 | if (error) { |
890 | exception = toRef(JSC::createTypeError(toJS(jsContext), makeString("failed to collect function paramater: " , error.get()))); |
891 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
892 | return adoptGRef(jsc_value_new_undefined(priv->context.get())); |
893 | } |
894 | |
895 | auto* jsValue = jscContextGValueToJSValue(priv->context.get(), ¶meter, &exception); |
896 | g_value_unset(¶meter); |
897 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
898 | return jscContextGetOrCreateValue(priv->context.get(), jsValue); |
899 | |
900 | arguments.append(jsValue); |
901 | parameterType = va_arg(args, GType); |
902 | } |
903 | |
904 | auto result = jsObjectCall(jsContext, function, functionType, thisObject, arguments, &exception); |
905 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
906 | return adoptGRef(jsc_value_new_undefined(priv->context.get())); |
907 | |
908 | return jscContextGetOrCreateValue(priv->context.get(), result); |
909 | } |
910 | |
911 | /** |
912 | * jsc_value_object_invoke_method: (skip) |
913 | * @value: a #JSCValue |
914 | * @name: the method name |
915 | * @first_parameter_type: #GType of first parameter, or %G_TYPE_NONE |
916 | * @...: value of the first parameter, followed optionally by more type/value pairs, followed by %G_TYPE_NONE |
917 | * |
918 | * Invoke method with @name on object referenced by @value, passing the given parameters. If |
919 | * @first_parameter_type is %G_TYPE_NONE no parameters will be passed to the method. |
920 | * The object instance will be handled automatically even when the method is a custom one |
921 | * registered with jsc_class_add_method(), so it should never be passed explicitly as parameter |
922 | * of this function. |
923 | * |
924 | * This function always returns a #JSCValue, in case of void methods a #JSCValue referencing |
925 | * <function>undefined</function> is returned. |
926 | * |
927 | * Returns: (transfer full): a #JSCValue with the return value of the method. |
928 | */ |
929 | JSCValue* jsc_value_object_invoke_method(JSCValue* value, const char* name, GType firstParameterType, ...) |
930 | { |
931 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
932 | g_return_val_if_fail(name, nullptr); |
933 | |
934 | JSCValuePrivate* priv = value->priv; |
935 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
936 | JSValueRef exception = nullptr; |
937 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
938 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
939 | return jsc_value_new_undefined(priv->context.get()); |
940 | |
941 | JSRetainPtr<JSStringRef> methodName(Adopt, JSStringCreateWithUTF8CString(name)); |
942 | JSValueRef functionValue = JSObjectGetProperty(jsContext, object, methodName.get(), &exception); |
943 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
944 | return jsc_value_new_undefined(priv->context.get()); |
945 | |
946 | JSObjectRef function = JSValueToObject(jsContext, functionValue, &exception); |
947 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
948 | return jsc_value_new_undefined(priv->context.get()); |
949 | |
950 | va_list args; |
951 | va_start(args, firstParameterType); |
952 | auto result = jscValueCallFunction(value, function, JSC::JSCCallbackFunction::Type::Method, object, firstParameterType, args); |
953 | va_end(args); |
954 | |
955 | return result.leakRef(); |
956 | } |
957 | |
958 | /** |
959 | * jsc_value_object_invoke_methodv: (rename-to jsc_value_object_invoke_method) |
960 | * @value: a #JSCValue |
961 | * @name: the method name |
962 | * @n_parameters: the number of parameters |
963 | * @parameters: (nullable) (array length=n_parameters) (element-type JSCValue): the #JSCValue<!-- -->s to pass as parameters to the method, or %NULL |
964 | * |
965 | * Invoke method with @name on object referenced by @value, passing the given @parameters. If |
966 | * @n_parameters is 0 no parameters will be passed to the method. |
967 | * The object instance will be handled automatically even when the method is a custom one |
968 | * registered with jsc_class_add_method(), so it should never be passed explicitly as parameter |
969 | * of this function. |
970 | * |
971 | * This function always returns a #JSCValue, in case of void methods a #JSCValue referencing |
972 | * <function>undefined</function> is returned. |
973 | * |
974 | * Returns: (transfer full): a #JSCValue with the return value of the method. |
975 | */ |
976 | JSCValue* jsc_value_object_invoke_methodv(JSCValue* value, const char* name, unsigned parametersCount, JSCValue** parameters) |
977 | { |
978 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
979 | g_return_val_if_fail(name, nullptr); |
980 | g_return_val_if_fail(!parametersCount || parameters, nullptr); |
981 | |
982 | JSCValuePrivate* priv = value->priv; |
983 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
984 | JSValueRef exception = nullptr; |
985 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
986 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
987 | return jsc_value_new_undefined(priv->context.get()); |
988 | |
989 | JSRetainPtr<JSStringRef> methodName(Adopt, JSStringCreateWithUTF8CString(name)); |
990 | JSValueRef functionValue = JSObjectGetProperty(jsContext, object, methodName.get(), &exception); |
991 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
992 | return jsc_value_new_undefined(priv->context.get()); |
993 | |
994 | JSObjectRef function = JSValueToObject(jsContext, functionValue, &exception); |
995 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
996 | return jsc_value_new_undefined(priv->context.get()); |
997 | |
998 | Vector<JSValueRef> arguments; |
999 | if (parametersCount) { |
1000 | arguments.reserveInitialCapacity(parametersCount); |
1001 | for (unsigned i = 0; i < parametersCount; ++i) |
1002 | arguments.uncheckedAppend(jscValueGetJSValue(parameters[i])); |
1003 | } |
1004 | |
1005 | auto result = jsObjectCall(jsContext, function, JSC::JSCCallbackFunction::Type::Method, object, arguments, &exception); |
1006 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1007 | jsc_value_new_undefined(priv->context.get()); |
1008 | |
1009 | return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
1010 | } |
1011 | |
1012 | /** |
1013 | * JSCValuePropertyFlags: |
1014 | * @JSC_VALUE_PROPERTY_CONFIGURABLE: the type of the property descriptor may be changed and the |
1015 | * property may be deleted from the corresponding object. |
1016 | * @JSC_VALUE_PROPERTY_ENUMERABLE: the property shows up during enumeration of the properties on |
1017 | * the corresponding object. |
1018 | * @JSC_VALUE_PROPERTY_WRITABLE: the value associated with the property may be changed with an |
1019 | * assignment operator. This doesn't have any effect when passed to jsc_value_object_define_property_accessor(). |
1020 | * |
1021 | * Flags used when defining properties with jsc_value_object_define_property_data() and |
1022 | * jsc_value_object_define_property_accessor(). |
1023 | */ |
1024 | |
1025 | /** |
1026 | * jsc_value_object_define_property_data: |
1027 | * @value: a #JSCValue |
1028 | * @property_name: the name of the property to define |
1029 | * @flags: #JSCValuePropertyFlags |
1030 | * @property_value: (nullable): the default property value |
1031 | * |
1032 | * Define or modify a property with @property_name in object referenced by @value. This is equivalent to |
1033 | * JavaScript <function>Object.defineProperty()</function> when used with a data descriptor. |
1034 | */ |
1035 | void jsc_value_object_define_property_data(JSCValue* value, const char* propertyName, JSCValuePropertyFlags flags, JSCValue* propertyValue) |
1036 | { |
1037 | g_return_if_fail(JSC_IS_VALUE(value)); |
1038 | g_return_if_fail(propertyName); |
1039 | |
1040 | JSCValuePrivate* priv = value->priv; |
1041 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1042 | JSC::ExecState* exec = toJS(jsContext); |
1043 | JSC::VM& vm = exec->vm(); |
1044 | JSC::JSLockHolder locker(vm); |
1045 | auto scope = DECLARE_CATCH_SCOPE(vm); |
1046 | |
1047 | JSC::JSValue jsValue = toJS(exec, priv->jsValue); |
1048 | JSC::JSObject* object = jsValue.toObject(exec); |
1049 | JSValueRef exception = nullptr; |
1050 | if (handleExceptionIfNeeded(scope, exec, &exception) == ExceptionStatus::DidThrow) { |
1051 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
1052 | return; |
1053 | } |
1054 | |
1055 | auto name = OpaqueJSString::tryCreate(String::fromUTF8(propertyName)); |
1056 | if (!name) |
1057 | return; |
1058 | |
1059 | JSC::PropertyDescriptor descriptor; |
1060 | descriptor.setValue(toJS(exec, propertyValue->priv->jsValue)); |
1061 | descriptor.setEnumerable(flags & JSC_VALUE_PROPERTY_ENUMERABLE); |
1062 | descriptor.setConfigurable(flags & JSC_VALUE_PROPERTY_CONFIGURABLE); |
1063 | descriptor.setWritable(flags & JSC_VALUE_PROPERTY_WRITABLE); |
1064 | object->methodTable(vm)->defineOwnProperty(object, exec, name->identifier(&vm), descriptor, true); |
1065 | if (handleExceptionIfNeeded(scope, exec, &exception) == ExceptionStatus::DidThrow) { |
1066 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
1067 | return; |
1068 | } |
1069 | } |
1070 | |
1071 | /** |
1072 | * jsc_value_object_define_property_accessor: |
1073 | * @value: a #JSCValue |
1074 | * @property_name: the name of the property to define |
1075 | * @flags: #JSCValuePropertyFlags |
1076 | * @property_type: the #GType of the property |
1077 | * @getter: (scope async) (nullable): a #GCallback to be called to get the property value |
1078 | * @setter: (scope async) (nullable): a #GCallback to be called to set the property value |
1079 | * @user_data: (closure): user data to pass to @getter and @setter |
1080 | * @destroy_notify: (nullable): destroy notifier for @user_data |
1081 | * |
1082 | * Define or modify a property with @property_name in object referenced by @value. When the |
1083 | * property value needs to be getted or set, @getter and @setter callbacks will be called. |
1084 | * When the property is cleared in the #JSCClass context, @destroy_notify is called with |
1085 | * @user_data as parameter. This is equivalent to JavaScript <function>Object.defineProperty()</function> |
1086 | * when used with an accessor descriptor. |
1087 | * |
1088 | * Note that the value returned by @getter must be fully transferred. In case of boxed types, you could use |
1089 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
1090 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
1091 | * with jsc_value_new_object() that receives the copy as instance parameter. |
1092 | */ |
1093 | void jsc_value_object_define_property_accessor(JSCValue* value, const char* propertyName, JSCValuePropertyFlags flags, GType propertyType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify) |
1094 | { |
1095 | g_return_if_fail(JSC_IS_VALUE(value)); |
1096 | g_return_if_fail(propertyName); |
1097 | g_return_if_fail(propertyType != G_TYPE_INVALID && propertyType != G_TYPE_NONE); |
1098 | g_return_if_fail(getter || setter); |
1099 | |
1100 | JSCValuePrivate* priv = value->priv; |
1101 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1102 | JSC::ExecState* exec = toJS(jsContext); |
1103 | JSC::VM& vm = exec->vm(); |
1104 | JSC::JSLockHolder locker(vm); |
1105 | auto scope = DECLARE_CATCH_SCOPE(vm); |
1106 | |
1107 | JSC::JSValue jsValue = toJS(exec, priv->jsValue); |
1108 | JSC::JSObject* object = jsValue.toObject(exec); |
1109 | JSValueRef exception = nullptr; |
1110 | if (handleExceptionIfNeeded(scope, exec, &exception) == ExceptionStatus::DidThrow) { |
1111 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
1112 | return; |
1113 | } |
1114 | |
1115 | auto name = OpaqueJSString::tryCreate(String::fromUTF8(propertyName)); |
1116 | if (!name) |
1117 | return; |
1118 | |
1119 | JSC::PropertyDescriptor descriptor; |
1120 | descriptor.setEnumerable(flags & JSC_VALUE_PROPERTY_ENUMERABLE); |
1121 | descriptor.setConfigurable(flags & JSC_VALUE_PROPERTY_CONFIGURABLE); |
1122 | if (getter) { |
1123 | GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(getter, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
1124 | auto function = JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), "get"_s , |
1125 | JSC::JSCCallbackFunction::Type::Method, nullptr, WTFMove(closure), propertyType, Vector<GType> { }); |
1126 | descriptor.setGetter(function); |
1127 | } |
1128 | if (setter) { |
1129 | GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(setter, userData, getter ? nullptr : reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
1130 | auto function = JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), "set"_s , |
1131 | JSC::JSCCallbackFunction::Type::Method, nullptr, WTFMove(closure), G_TYPE_NONE, Vector<GType> { propertyType }); |
1132 | descriptor.setSetter(function); |
1133 | } |
1134 | object->methodTable(vm)->defineOwnProperty(object, exec, name->identifier(&vm), descriptor, true); |
1135 | if (handleExceptionIfNeeded(scope, exec, &exception) == ExceptionStatus::DidThrow) { |
1136 | jscContextHandleExceptionIfNeeded(priv->context.get(), exception); |
1137 | return; |
1138 | } |
1139 | } |
1140 | |
1141 | static GRefPtr<JSCValue> jscValueFunctionCreate(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional<Vector<GType>>&& parameters) |
1142 | { |
1143 | GRefPtr<GClosure> closure; |
1144 | // If the function doesn't have arguments, we need to swap the fake instance and user data to ensure |
1145 | // user data is the first parameter and fake instance ignored. |
1146 | if (parameters && parameters->isEmpty() && userData) |
1147 | closure = adoptGRef(g_cclosure_new_swap(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
1148 | else |
1149 | closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
1150 | JSC::ExecState* exec = toJS(jscContextGetJSContext(context)); |
1151 | JSC::VM& vm = exec->vm(); |
1152 | JSC::JSLockHolder locker(vm); |
1153 | auto* functionObject = toRef(JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), name ? String::fromUTF8(name) : "anonymous"_s , |
1154 | JSC::JSCCallbackFunction::Type::Function, nullptr, WTFMove(closure), returnType, WTFMove(parameters))); |
1155 | return jscContextGetOrCreateValue(context, functionObject); |
1156 | } |
1157 | |
1158 | /** |
1159 | * jsc_value_new_function: (skip) |
1160 | * @context: a #JSCContext: |
1161 | * @name: (nullable): the function name or %NULL |
1162 | * @callback: (scope async): a #GCallback. |
1163 | * @user_data: (closure): user data to pass to @callback. |
1164 | * @destroy_notify: (nullable): destroy notifier for @user_data |
1165 | * @return_type: the #GType of the function return value, or %G_TYPE_NONE if the function is void. |
1166 | * @n_params: the number of parameter types to follow or 0 if the function doesn't receive parameters. |
1167 | * @...: a list of #GType<!-- -->s, one for each parameter. |
1168 | * |
1169 | * Create a function in @context. If @name is %NULL an anonymous function will be created. |
1170 | * When the function is called by JavaScript or jsc_value_function_call(), @callback is called |
1171 | * receiving the function parameters and then @user_data as last parameter. When the function is |
1172 | * cleared in @context, @destroy_notify is called with @user_data as parameter. |
1173 | * |
1174 | * Note that the value returned by @callback must be fully transferred. In case of boxed types, you could use |
1175 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
1176 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
1177 | * with jsc_value_new_object() that receives the copy as instance parameter. |
1178 | * |
1179 | * Returns: (transfer full): a #JSCValue. |
1180 | */ |
1181 | JSCValue* jsc_value_new_function(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...) |
1182 | { |
1183 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
1184 | g_return_val_if_fail(callback, nullptr); |
1185 | |
1186 | va_list args; |
1187 | va_start(args, paramCount); |
1188 | Vector<GType> parameters; |
1189 | if (paramCount) { |
1190 | parameters.reserveInitialCapacity(paramCount); |
1191 | for (unsigned i = 0; i < paramCount; ++i) |
1192 | parameters.uncheckedAppend(va_arg(args, GType)); |
1193 | } |
1194 | va_end(args); |
1195 | |
1196 | return jscValueFunctionCreate(context, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); |
1197 | } |
1198 | |
1199 | /** |
1200 | * jsc_value_new_functionv: (rename-to jsc_value_new_function) |
1201 | * @context: a #JSCContext |
1202 | * @name: (nullable): the function name or %NULL |
1203 | * @callback: (scope async): a #GCallback. |
1204 | * @user_data: (closure): user data to pass to @callback. |
1205 | * @destroy_notify: (nullable): destroy notifier for @user_data |
1206 | * @return_type: the #GType of the function return value, or %G_TYPE_NONE if the function is void. |
1207 | * @n_parameters: the number of parameters |
1208 | * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GType<!-- -->s, one for each parameter, or %NULL |
1209 | * |
1210 | * Create a function in @context. If @name is %NULL an anonymous function will be created. |
1211 | * When the function is called by JavaScript or jsc_value_function_call(), @callback is called |
1212 | * receiving the function parameters and then @user_data as last parameter. When the function is |
1213 | * cleared in @context, @destroy_notify is called with @user_data as parameter. |
1214 | * |
1215 | * Note that the value returned by @callback must be fully transferred. In case of boxed types, you could use |
1216 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
1217 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
1218 | * with jsc_value_new_object() that receives the copy as instance parameter. |
1219 | * |
1220 | * Returns: (transfer full): a #JSCValue. |
1221 | */ |
1222 | JSCValue* jsc_value_new_functionv(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType *parameterTypes) |
1223 | { |
1224 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
1225 | g_return_val_if_fail(callback, nullptr); |
1226 | g_return_val_if_fail(!parametersCount || parameterTypes, nullptr); |
1227 | |
1228 | Vector<GType> parameters; |
1229 | if (parametersCount) { |
1230 | parameters.reserveInitialCapacity(parametersCount); |
1231 | for (unsigned i = 0; i < parametersCount; ++i) |
1232 | parameters.uncheckedAppend(parameterTypes[i]); |
1233 | } |
1234 | |
1235 | return jscValueFunctionCreate(context, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); |
1236 | } |
1237 | |
1238 | /** |
1239 | * jsc_value_new_function_variadic: |
1240 | * @context: a #JSCContext |
1241 | * @name: (nullable): the function name or %NULL |
1242 | * @callback: (scope async): a #GCallback. |
1243 | * @user_data: (closure): user data to pass to @callback. |
1244 | * @destroy_notify: (nullable): destroy notifier for @user_data |
1245 | * @return_type: the #GType of the function return value, or %G_TYPE_NONE if the function is void. |
1246 | * |
1247 | * Create a function in @context. If @name is %NULL an anonymous function will be created. |
1248 | * When the function is called by JavaScript or jsc_value_function_call(), @callback is called |
1249 | * receiving an #GPtrArray of #JSCValue<!-- -->s with the arguments and then @user_data as last parameter. |
1250 | * When the function is cleared in @context, @destroy_notify is called with @user_data as parameter. |
1251 | * |
1252 | * Note that the value returned by @callback must be fully transferred. In case of boxed types, you could use |
1253 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
1254 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
1255 | * with jsc_value_new_object() that receives the copy as instance parameter. |
1256 | * |
1257 | * Returns: (transfer full): a #JSCValue. |
1258 | */ |
1259 | JSCValue* jsc_value_new_function_variadic(JSCContext* context, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType) |
1260 | { |
1261 | g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr); |
1262 | g_return_val_if_fail(callback, nullptr); |
1263 | |
1264 | return jscValueFunctionCreate(context, name, callback, userData, destroyNotify, returnType, WTF::nullopt).leakRef(); |
1265 | } |
1266 | |
1267 | /** |
1268 | * jsc_value_is_function: |
1269 | * @value: a #JSCValue |
1270 | * |
1271 | * Get whether the value referenced by @value is a function |
1272 | * |
1273 | * Returns: whether the value is a function. |
1274 | */ |
1275 | gboolean jsc_value_is_function(JSCValue* value) |
1276 | { |
1277 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
1278 | |
1279 | JSCValuePrivate* priv = value->priv; |
1280 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1281 | JSValueRef exception = nullptr; |
1282 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
1283 | return !exception ? JSObjectIsFunction(jsContext, object) : FALSE; |
1284 | } |
1285 | |
1286 | /** |
1287 | * jsc_value_function_call: (skip) |
1288 | * @value: a #JSCValue |
1289 | * @first_parameter_type: #GType of first parameter, or %G_TYPE_NONE |
1290 | * @...: value of the first parameter, followed optionally by more type/value pairs, followed by %G_TYPE_NONE |
1291 | * |
1292 | * Call function referenced by @value, passing the given parameters. If @first_parameter_type |
1293 | * is %G_TYPE_NONE no parameters will be passed to the function. |
1294 | * |
1295 | * This function always returns a #JSCValue, in case of void functions a #JSCValue referencing |
1296 | * <function>undefined</function> is returned |
1297 | * |
1298 | * Returns: (transfer full): a #JSCValue with the return value of the function. |
1299 | */ |
1300 | JSCValue* jsc_value_function_call(JSCValue* value, GType firstParameterType, ...) |
1301 | { |
1302 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
1303 | |
1304 | JSCValuePrivate* priv = value->priv; |
1305 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1306 | JSValueRef exception = nullptr; |
1307 | JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
1308 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1309 | return jsc_value_new_undefined(priv->context.get()); |
1310 | |
1311 | va_list args; |
1312 | va_start(args, firstParameterType); |
1313 | auto result = jscValueCallFunction(value, function, JSC::JSCCallbackFunction::Type::Function, nullptr, firstParameterType, args); |
1314 | va_end(args); |
1315 | |
1316 | return result.leakRef(); |
1317 | } |
1318 | |
1319 | /** |
1320 | * jsc_value_function_callv: (rename-to jsc_value_function_call) |
1321 | * @value: a #JSCValue |
1322 | * @n_parameters: the number of parameters |
1323 | * @parameters: (nullable) (array length=n_parameters) (element-type JSCValue): the #JSCValue<!-- -->s to pass as parameters to the function, or %NULL |
1324 | * |
1325 | * Call function referenced by @value, passing the given @parameters. If @n_parameters |
1326 | * is 0 no parameters will be passed to the function. |
1327 | * |
1328 | * This function always returns a #JSCValue, in case of void functions a #JSCValue referencing |
1329 | * <function>undefined</function> is returned |
1330 | * |
1331 | * Returns: (transfer full): a #JSCValue with the return value of the function. |
1332 | */ |
1333 | JSCValue* jsc_value_function_callv(JSCValue* value, unsigned parametersCount, JSCValue** parameters) |
1334 | { |
1335 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
1336 | g_return_val_if_fail(!parametersCount || parameters, nullptr); |
1337 | |
1338 | JSCValuePrivate* priv = value->priv; |
1339 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1340 | JSValueRef exception = nullptr; |
1341 | JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
1342 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1343 | return jsc_value_new_undefined(priv->context.get()); |
1344 | |
1345 | Vector<JSValueRef> arguments; |
1346 | if (parametersCount) { |
1347 | arguments.reserveInitialCapacity(parametersCount); |
1348 | for (unsigned i = 0; i < parametersCount; ++i) |
1349 | arguments.uncheckedAppend(jscValueGetJSValue(parameters[i])); |
1350 | } |
1351 | |
1352 | auto result = jsObjectCall(jsContext, function, JSC::JSCCallbackFunction::Type::Function, nullptr, arguments, &exception); |
1353 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1354 | return jsc_value_new_undefined(priv->context.get()); |
1355 | |
1356 | return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
1357 | } |
1358 | |
1359 | /** |
1360 | * jsc_value_is_constructor: |
1361 | * @value: a #JSCValue |
1362 | * |
1363 | * Get whether the value referenced by @value is a constructor. |
1364 | * |
1365 | * Returns: whether the value is a constructor. |
1366 | */ |
1367 | gboolean jsc_value_is_constructor(JSCValue* value) |
1368 | { |
1369 | g_return_val_if_fail(JSC_IS_VALUE(value), FALSE); |
1370 | |
1371 | JSCValuePrivate* priv = value->priv; |
1372 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1373 | JSValueRef exception = nullptr; |
1374 | JSObjectRef object = JSValueToObject(jsContext, priv->jsValue, &exception); |
1375 | return !exception ? JSObjectIsConstructor(jsContext, object) : FALSE; |
1376 | } |
1377 | |
1378 | /** |
1379 | * jsc_value_constructor_call: (skip) |
1380 | * @value: a #JSCValue |
1381 | * @first_parameter_type: #GType of first parameter, or %G_TYPE_NONE |
1382 | * @...: value of the first parameter, followed optionally by more type/value pairs, followed by %G_TYPE_NONE |
1383 | * |
1384 | * Invoke <function>new</function> with constructor referenced by @value. If @first_parameter_type |
1385 | * is %G_TYPE_NONE no parameters will be passed to the constructor. |
1386 | * |
1387 | * Returns: (transfer full): a #JSCValue referencing the newly created object instance. |
1388 | */ |
1389 | JSCValue* jsc_value_constructor_call(JSCValue* value, GType firstParameterType, ...) |
1390 | { |
1391 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
1392 | |
1393 | JSCValuePrivate* priv = value->priv; |
1394 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1395 | JSValueRef exception = nullptr; |
1396 | JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
1397 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1398 | return jsc_value_new_undefined(priv->context.get()); |
1399 | |
1400 | va_list args; |
1401 | va_start(args, firstParameterType); |
1402 | auto result = jscValueCallFunction(value, function, JSC::JSCCallbackFunction::Type::Constructor, nullptr, firstParameterType, args); |
1403 | va_end(args); |
1404 | |
1405 | return result.leakRef(); |
1406 | } |
1407 | |
1408 | /** |
1409 | * jsc_value_constructor_callv: (rename-to jsc_value_constructor_call) |
1410 | * @value: a #JSCValue |
1411 | * @n_parameters: the number of parameters |
1412 | * @parameters: (nullable) (array length=n_parameters) (element-type JSCValue): the #JSCValue<!-- -->s to pass as parameters to the constructor, or %NULL |
1413 | * |
1414 | * Invoke <function>new</function> with constructor referenced by @value. If @n_parameters |
1415 | * is 0 no parameters will be passed to the constructor. |
1416 | * |
1417 | * Returns: (transfer full): a #JSCValue referencing the newly created object instance. |
1418 | */ |
1419 | JSCValue* jsc_value_constructor_callv(JSCValue* value, unsigned parametersCount, JSCValue** parameters) |
1420 | { |
1421 | g_return_val_if_fail(JSC_IS_VALUE(value), nullptr); |
1422 | g_return_val_if_fail(!parametersCount || parameters, nullptr); |
1423 | |
1424 | JSCValuePrivate* priv = value->priv; |
1425 | auto* jsContext = jscContextGetJSContext(priv->context.get()); |
1426 | JSValueRef exception = nullptr; |
1427 | JSObjectRef function = JSValueToObject(jsContext, priv->jsValue, &exception); |
1428 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1429 | return jsc_value_new_undefined(priv->context.get()); |
1430 | |
1431 | Vector<JSValueRef> arguments; |
1432 | if (parametersCount) { |
1433 | arguments.reserveInitialCapacity(parametersCount); |
1434 | for (unsigned i = 0; i < parametersCount; ++i) |
1435 | arguments.uncheckedAppend(jscValueGetJSValue(parameters[i])); |
1436 | } |
1437 | |
1438 | auto result = jsObjectCall(jsContext, function, JSC::JSCCallbackFunction::Type::Constructor, nullptr, arguments, &exception); |
1439 | if (jscContextHandleExceptionIfNeeded(priv->context.get(), exception)) |
1440 | return jsc_value_new_undefined(priv->context.get()); |
1441 | |
1442 | return jscContextGetOrCreateValue(priv->context.get(), result).leakRef(); |
1443 | } |
1444 | |