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 "JSCClass.h" |
22 | |
23 | #include "APICast.h" |
24 | #include "JSAPIWrapperGlobalObject.h" |
25 | #include "JSAPIWrapperObject.h" |
26 | #include "JSCCallbackFunction.h" |
27 | #include "JSCClassPrivate.h" |
28 | #include "JSCContextPrivate.h" |
29 | #include "JSCExceptionPrivate.h" |
30 | #include "JSCInlines.h" |
31 | #include "JSCValuePrivate.h" |
32 | #include "JSCallbackObject.h" |
33 | #include "JSRetainPtr.h" |
34 | #include <wtf/glib/GUniquePtr.h> |
35 | #include <wtf/glib/WTFGType.h> |
36 | |
37 | /** |
38 | * SECTION: JSCClass |
39 | * @short_description: JavaScript custom class |
40 | * @title: JSCClass |
41 | * @see_also: JSCContext |
42 | * |
43 | * A JSSClass represents a custom JavaScript class registered by the user in a #JSCContext. |
44 | * It allows to create new JavaScripts objects whose instances are created by the user using |
45 | * this API. |
46 | * It's possible to add constructors, properties and methods for a JSSClass by providing |
47 | * #GCallback<!-- -->s to implement them. |
48 | */ |
49 | |
50 | enum { |
51 | PROP_0, |
52 | |
53 | PROP_CONTEXT, |
54 | PROP_NAME, |
55 | PROP_PARENT |
56 | }; |
57 | |
58 | typedef struct _JSCClassPrivate { |
59 | JSCContext* context; |
60 | CString name; |
61 | JSClassRef jsClass; |
62 | JSCClassVTable* vtable; |
63 | GDestroyNotify destroyFunction; |
64 | JSCClass* parentClass; |
65 | JSC::Weak<JSC::JSObject> prototype; |
66 | HashMap<CString, JSC::Weak<JSC::JSObject>> constructors; |
67 | } JSCClassPrivate; |
68 | |
69 | struct _JSCClass { |
70 | GObject parent; |
71 | |
72 | JSCClassPrivate* priv; |
73 | }; |
74 | |
75 | struct _JSCClassClass { |
76 | GObjectClass parent_class; |
77 | }; |
78 | |
79 | WEBKIT_DEFINE_TYPE(JSCClass, jsc_class, G_TYPE_OBJECT) |
80 | |
81 | class VTableExceptionHandler { |
82 | public: |
83 | VTableExceptionHandler(JSCContext* context, JSValueRef* exception) |
84 | : m_context(context) |
85 | , m_exception(exception) |
86 | , m_savedException(exception ? jsc_context_get_exception(m_context) : nullptr) |
87 | { |
88 | } |
89 | |
90 | ~VTableExceptionHandler() |
91 | { |
92 | if (!m_exception) |
93 | return; |
94 | |
95 | auto* exception = jsc_context_get_exception(m_context); |
96 | if (m_savedException.get() == exception) |
97 | return; |
98 | |
99 | *m_exception = jscExceptionGetJSValue(exception); |
100 | if (m_savedException) |
101 | jsc_context_throw_exception(m_context, m_savedException.get()); |
102 | else |
103 | jsc_context_clear_exception(m_context); |
104 | } |
105 | |
106 | private: |
107 | JSCContext* m_context { nullptr }; |
108 | JSValueRef* m_exception { nullptr }; |
109 | GRefPtr<JSCException> m_savedException; |
110 | }; |
111 | |
112 | static bool isWrappedObject(JSC::JSObject* jsObject) |
113 | { |
114 | JSC::ExecState* exec = jsObject->globalObject()->globalExec(); |
115 | if (jsObject->isGlobalObject()) |
116 | return jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>>(exec->vm()); |
117 | return jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(exec->vm()); |
118 | } |
119 | |
120 | static JSClassRef wrappedObjectClass(JSC::JSObject* jsObject) |
121 | { |
122 | ASSERT(isWrappedObject(jsObject)); |
123 | if (jsObject->isGlobalObject()) |
124 | return JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>*>(jsObject)->classRef(); |
125 | return JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef(); |
126 | } |
127 | |
128 | static GRefPtr<JSCContext> jscContextForObject(JSC::JSObject* jsObject) |
129 | { |
130 | ASSERT(isWrappedObject(jsObject)); |
131 | JSC::JSGlobalObject* globalObject = jsObject->globalObject(); |
132 | JSC::ExecState* exec = globalObject->globalExec(); |
133 | if (jsObject->isGlobalObject()) { |
134 | JSC::VM& vm = globalObject->vm(); |
135 | if (auto* globalScopeExtension = vm.vmEntryGlobalObject(exec)->globalScopeExtension()) |
136 | exec = JSC::JSScope::objectAtScope(globalScopeExtension)->globalObject()->globalExec(); |
137 | } |
138 | return jscContextGetOrCreate(toGlobalRef(exec)); |
139 | } |
140 | |
141 | static JSValueRef getProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) |
142 | { |
143 | JSC::JSLockHolder locker(toJS(callerContext)); |
144 | auto* jsObject = toJS(object); |
145 | if (!isWrappedObject(jsObject)) |
146 | return nullptr; |
147 | |
148 | auto context = jscContextForObject(jsObject); |
149 | gpointer instance = jscContextWrappedObject(context.get(), object); |
150 | if (!instance) |
151 | return nullptr; |
152 | |
153 | VTableExceptionHandler exceptionHandler(context.get(), exception); |
154 | |
155 | JSClassRef jsClass = wrappedObjectClass(jsObject); |
156 | for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { |
157 | if (!jscClass->priv->vtable) |
158 | continue; |
159 | |
160 | if (auto* getPropertyFunction = jscClass->priv->vtable->get_property) { |
161 | if (GRefPtr<JSCValue> value = adoptGRef(getPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data()))) |
162 | return jscValueGetJSValue(value.get()); |
163 | } |
164 | } |
165 | return nullptr; |
166 | } |
167 | |
168 | static bool setProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) |
169 | { |
170 | JSC::JSLockHolder locker(toJS(callerContext)); |
171 | auto* jsObject = toJS(object); |
172 | if (!isWrappedObject(jsObject)) |
173 | return false; |
174 | |
175 | auto context = jscContextForObject(jsObject); |
176 | gpointer instance = jscContextWrappedObject(context.get(), object); |
177 | if (!instance) |
178 | return false; |
179 | |
180 | VTableExceptionHandler exceptionHandler(context.get(), exception); |
181 | |
182 | GRefPtr<JSCValue> propertyValue; |
183 | JSClassRef jsClass = wrappedObjectClass(jsObject); |
184 | for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { |
185 | if (!jscClass->priv->vtable) |
186 | continue; |
187 | |
188 | if (auto* setPropertyFunction = jscClass->priv->vtable->set_property) { |
189 | if (!propertyValue) |
190 | propertyValue = jscContextGetOrCreateValue(context.get(), value); |
191 | if (setPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data(), propertyValue.get())) |
192 | return true; |
193 | } |
194 | } |
195 | return false; |
196 | } |
197 | |
198 | static bool hasProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName) |
199 | { |
200 | JSC::JSLockHolder locker(toJS(callerContext)); |
201 | auto* jsObject = toJS(object); |
202 | if (!isWrappedObject(jsObject)) |
203 | return false; |
204 | |
205 | auto context = jscContextForObject(jsObject); |
206 | gpointer instance = jscContextWrappedObject(context.get(), object); |
207 | if (!instance) |
208 | return false; |
209 | |
210 | JSClassRef jsClass = wrappedObjectClass(jsObject); |
211 | for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { |
212 | if (!jscClass->priv->vtable) |
213 | continue; |
214 | |
215 | if (auto* hasPropertyFunction = jscClass->priv->vtable->has_property) { |
216 | if (hasPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data())) |
217 | return true; |
218 | } |
219 | } |
220 | |
221 | return false; |
222 | } |
223 | |
224 | static bool deleteProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) |
225 | { |
226 | JSC::JSLockHolder locker(toJS(callerContext)); |
227 | auto* jsObject = toJS(object); |
228 | if (!isWrappedObject(jsObject)) |
229 | return false; |
230 | |
231 | auto context = jscContextForObject(jsObject); |
232 | gpointer instance = jscContextWrappedObject(context.get(), object); |
233 | if (!instance) |
234 | return false; |
235 | |
236 | VTableExceptionHandler exceptionHandler(context.get(), exception); |
237 | |
238 | JSClassRef jsClass = wrappedObjectClass(jsObject); |
239 | for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { |
240 | if (!jscClass->priv->vtable) |
241 | continue; |
242 | |
243 | if (auto* deletePropertyFunction = jscClass->priv->vtable->delete_property) { |
244 | if (deletePropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data())) |
245 | return true; |
246 | } |
247 | } |
248 | return false; |
249 | } |
250 | |
251 | static void getPropertyNames(JSContextRef callerContext, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) |
252 | { |
253 | JSC::JSLockHolder locker(toJS(callerContext)); |
254 | auto* jsObject = toJS(object); |
255 | if (!isWrappedObject(jsObject)) |
256 | return; |
257 | |
258 | auto context = jscContextForObject(jsObject); |
259 | gpointer instance = jscContextWrappedObject(context.get(), object); |
260 | if (!instance) |
261 | return; |
262 | |
263 | JSClassRef jsClass = wrappedObjectClass(jsObject); |
264 | for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { |
265 | if (!jscClass->priv->vtable) |
266 | continue; |
267 | |
268 | if (auto* enumeratePropertiesFunction = jscClass->priv->vtable->enumerate_properties) { |
269 | GUniquePtr<char*> properties(enumeratePropertiesFunction(jscClass, context.get(), instance)); |
270 | if (properties) { |
271 | unsigned i = 0; |
272 | while (const auto* name = properties.get()[i++]) { |
273 | JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name)); |
274 | JSPropertyNameAccumulatorAddName(propertyNames, propertyName.get()); |
275 | } |
276 | } |
277 | } |
278 | } |
279 | } |
280 | |
281 | static void jscClassGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec) |
282 | { |
283 | JSCClass* jscClass = JSC_CLASS(object); |
284 | |
285 | switch (propID) { |
286 | case PROP_CONTEXT: |
287 | g_value_set_object(value, jscClass->priv->context); |
288 | break; |
289 | case PROP_NAME: |
290 | g_value_set_string(value, jscClass->priv->name.data()); |
291 | break; |
292 | case PROP_PARENT: |
293 | g_value_set_object(value, jscClass->priv->parentClass); |
294 | break; |
295 | default: |
296 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
297 | } |
298 | } |
299 | |
300 | static void jscClassSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec) |
301 | { |
302 | JSCClass* jscClass = JSC_CLASS(object); |
303 | |
304 | switch (propID) { |
305 | case PROP_CONTEXT: |
306 | jscClass->priv->context = JSC_CONTEXT(g_value_get_object(value)); |
307 | break; |
308 | case PROP_NAME: |
309 | jscClass->priv->name = g_value_get_string(value); |
310 | break; |
311 | case PROP_PARENT: |
312 | if (auto* parent = g_value_get_object(value)) |
313 | jscClass->priv->parentClass = JSC_CLASS(parent); |
314 | break; |
315 | default: |
316 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
317 | } |
318 | } |
319 | |
320 | static void jscClassDispose(GObject* object) |
321 | { |
322 | JSCClass* jscClass = JSC_CLASS(object); |
323 | if (jscClass->priv->jsClass) { |
324 | JSClassRelease(jscClass->priv->jsClass); |
325 | jscClass->priv->jsClass = nullptr; |
326 | } |
327 | |
328 | G_OBJECT_CLASS(jsc_class_parent_class)->dispose(object); |
329 | } |
330 | |
331 | static void jsc_class_class_init(JSCClassClass* klass) |
332 | { |
333 | GObjectClass* objClass = G_OBJECT_CLASS(klass); |
334 | objClass->dispose = jscClassDispose; |
335 | objClass->get_property = jscClassGetProperty; |
336 | objClass->set_property = jscClassSetProperty; |
337 | |
338 | /** |
339 | * JSCClass:context: |
340 | * |
341 | * The #JSCContext in which the class was registered. |
342 | */ |
343 | g_object_class_install_property(objClass, |
344 | PROP_CONTEXT, |
345 | g_param_spec_object( |
346 | "context" , |
347 | "JSCContext" , |
348 | "JSC Context" , |
349 | JSC_TYPE_CONTEXT, |
350 | static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
351 | |
352 | /** |
353 | * JSCClass:name: |
354 | * |
355 | * The name of the class. |
356 | */ |
357 | g_object_class_install_property(objClass, |
358 | PROP_NAME, |
359 | g_param_spec_string( |
360 | "name" , |
361 | "Name" , |
362 | "The class name" , |
363 | nullptr, |
364 | static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
365 | |
366 | /** |
367 | * JSCClass:parent: |
368 | * |
369 | * The parent class or %NULL in case of final classes. |
370 | */ |
371 | g_object_class_install_property(objClass, |
372 | PROP_PARENT, |
373 | g_param_spec_object( |
374 | "parent" , |
375 | "Partent" , |
376 | "The parent class" , |
377 | JSC_TYPE_CLASS, |
378 | static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
379 | } |
380 | |
381 | /** |
382 | * JSCClassGetPropertyFunction: |
383 | * @jsc_class: a #JSCClass |
384 | * @context: a #JSCContext |
385 | * @instance: the @jsc_class instance |
386 | * @name: the property name |
387 | * |
388 | * The type of get_property in #JSCClassVTable. This is only required when you need to handle |
389 | * external properties not added to the prototype. |
390 | * |
391 | * Returns: (transfer full) (nullable): a #JSCValue or %NULL to forward the request to |
392 | * the parent class or prototype chain |
393 | */ |
394 | |
395 | /** |
396 | * JSCClassSetPropertyFunction: |
397 | * @jsc_class: a #JSCClass |
398 | * @context: a #JSCContext |
399 | * @instance: the @jsc_class instance |
400 | * @name: the property name |
401 | * @value: the #JSCValue to set |
402 | * |
403 | * The type of set_property in #JSCClassVTable. This is only required when you need to handle |
404 | * external properties not added to the prototype. |
405 | * |
406 | * Returns: %TRUE if handled or %FALSE to forward the request to the parent class or prototype chain. |
407 | */ |
408 | |
409 | /** |
410 | * JSCClassHasPropertyFunction: |
411 | * @jsc_class: a #JSCClass |
412 | * @context: a #JSCContext |
413 | * @instance: the @jsc_class instance |
414 | * @name: the property name |
415 | * |
416 | * The type of has_property in #JSCClassVTable. This is only required when you need to handle |
417 | * external properties not added to the prototype. |
418 | * |
419 | * Returns: %TRUE if @instance has a property with @name or %FALSE to forward the request |
420 | * to the parent class or prototype chain. |
421 | */ |
422 | |
423 | /** |
424 | * JSCClassDeletePropertyFunction: |
425 | * @jsc_class: a #JSCClass |
426 | * @context: a #JSCContext |
427 | * @instance: the @jsc_class instance |
428 | * @name: the property name |
429 | * |
430 | * The type of delete_property in #JSCClassVTable. This is only required when you need to handle |
431 | * external properties not added to the prototype. |
432 | * |
433 | * Returns: %TRUE if handled or %FALSE to to forward the request to the parent class or prototype chain. |
434 | */ |
435 | |
436 | /** |
437 | * JSCClassEnumeratePropertiesFunction: |
438 | * @jsc_class: a #JSCClass |
439 | * @context: a #JSCContext |
440 | * @instance: the @jsc_class instance |
441 | * |
442 | * The type of enumerate_properties in #JSCClassVTable. This is only required when you need to handle |
443 | * external properties not added to the prototype. |
444 | * |
445 | * Returns: (array zero-terminated=1) (transfer full) (nullable): a %NULL-terminated array of strings |
446 | * containing the property names, or %NULL if @instance doesn't have enumerable properties. |
447 | */ |
448 | |
449 | /** |
450 | * JSCClassVTable: |
451 | * @get_property: a #JSCClassGetPropertyFunction for getting a property. |
452 | * @set_property: a #JSCClassSetPropertyFunction for setting a property. |
453 | * @has_property: a #JSCClassHasPropertyFunction for querying a property. |
454 | * @delete_property: a #JSCClassDeletePropertyFunction for deleting a property. |
455 | * @enumerate_properties: a #JSCClassEnumeratePropertiesFunction for enumerating properties. |
456 | * |
457 | * Virtual table for a JSCClass. This can be optionally used when registering a #JSCClass in a #JSCContext |
458 | * to provide a custom implementation for the class. All virtual functions are optional and can be set to |
459 | * %NULL to fallback to the default implementation. |
460 | */ |
461 | |
462 | GRefPtr<JSCClass> jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction) |
463 | { |
464 | GRefPtr<JSCClass> jscClass = adoptGRef(JSC_CLASS(g_object_new(JSC_TYPE_CLASS, "context" , context, "name" , name, "parent" , parentClass, nullptr))); |
465 | |
466 | JSCClassPrivate* priv = jscClass->priv; |
467 | priv->vtable = vtable; |
468 | priv->destroyFunction = destroyFunction; |
469 | |
470 | JSClassDefinition definition = kJSClassDefinitionEmpty; |
471 | definition.className = priv->name.data(); |
472 | |
473 | #define SET_IMPL_IF_NEEDED(definitionFunc, vtableFunc) \ |
474 | for (auto* klass = jscClass.get(); klass; klass = klass->priv->parentClass) { \ |
475 | if (klass->priv->vtable && klass->priv->vtable->vtableFunc) { \ |
476 | definition.definitionFunc = definitionFunc; \ |
477 | break; \ |
478 | } \ |
479 | } |
480 | |
481 | SET_IMPL_IF_NEEDED(getProperty, get_property); |
482 | SET_IMPL_IF_NEEDED(setProperty, set_property); |
483 | SET_IMPL_IF_NEEDED(hasProperty, has_property); |
484 | SET_IMPL_IF_NEEDED(deleteProperty, delete_property); |
485 | SET_IMPL_IF_NEEDED(getPropertyNames, enumerate_properties); |
486 | |
487 | #undef SET_IMPL_IF_NEEDED |
488 | |
489 | priv->jsClass = JSClassCreate(&definition); |
490 | |
491 | GUniquePtr<char> prototypeName(g_strdup_printf("%sPrototype" , priv->name.data())); |
492 | JSClassDefinition prototypeDefinition = kJSClassDefinitionEmpty; |
493 | prototypeDefinition.className = prototypeName.get(); |
494 | JSClassRef prototypeClass = JSClassCreate(&prototypeDefinition); |
495 | priv->prototype = jscContextGetOrCreateJSWrapper(priv->context, prototypeClass); |
496 | JSClassRelease(prototypeClass); |
497 | |
498 | if (priv->parentClass) |
499 | JSObjectSetPrototype(jscContextGetJSContext(priv->context), toRef(priv->prototype.get()), toRef(priv->parentClass->priv->prototype.get())); |
500 | return jscClass; |
501 | } |
502 | |
503 | JSClassRef jscClassGetJSClass(JSCClass* jscClass) |
504 | { |
505 | return jscClass->priv->jsClass; |
506 | } |
507 | |
508 | JSC::JSObject* jscClassGetOrCreateJSWrapper(JSCClass* jscClass, gpointer wrappedObject) |
509 | { |
510 | JSCClassPrivate* priv = jscClass->priv; |
511 | return jscContextGetOrCreateJSWrapper(priv->context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction); |
512 | } |
513 | |
514 | JSGlobalContextRef jscClassCreateContextWithJSWrapper(JSCClass* jscClass, gpointer wrappedObject) |
515 | { |
516 | JSCClassPrivate* priv = jscClass->priv; |
517 | return jscContextCreateContextWithJSWrapper(priv->context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction); |
518 | } |
519 | |
520 | void jscClassInvalidate(JSCClass* jscClass) |
521 | { |
522 | jscClass->priv->context = nullptr; |
523 | } |
524 | |
525 | /** |
526 | * jsc_class_get_name: |
527 | * @jsc_class: a @JSCClass |
528 | * |
529 | * Get the class name of @jsc_class |
530 | * |
531 | * Returns: (transfer none): the name of @jsc_class |
532 | */ |
533 | const char* jsc_class_get_name(JSCClass* jscClass) |
534 | { |
535 | g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); |
536 | |
537 | return jscClass->priv->name.data(); |
538 | } |
539 | |
540 | /** |
541 | * jsc_class_get_parent: |
542 | * @jsc_class: a @JSCClass |
543 | * |
544 | * Get the parent class of @jsc_class |
545 | * |
546 | * Returns: (transfer none): the parent class of @jsc_class |
547 | */ |
548 | JSCClass* jsc_class_get_parent(JSCClass* jscClass) |
549 | { |
550 | g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); |
551 | |
552 | return jscClass->priv->parentClass; |
553 | } |
554 | |
555 | static GRefPtr<JSCValue> jscClassCreateConstructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional<Vector<GType>>&& parameters) |
556 | { |
557 | // If the constructor doesn't have arguments, we need to swap the fake instance and user data to ensure |
558 | // user data is the first parameter and fake instance ignored. |
559 | GRefPtr<GClosure> closure; |
560 | if (parameters && parameters->isEmpty() && userData) |
561 | closure = adoptGRef(g_cclosure_new_swap(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
562 | else |
563 | closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
564 | JSCClassPrivate* priv = jscClass->priv; |
565 | JSC::ExecState* exec = toJS(jscContextGetJSContext(priv->context)); |
566 | JSC::VM& vm = exec->vm(); |
567 | JSC::JSLockHolder locker(vm); |
568 | auto* functionObject = JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), String::fromUTF8(name), |
569 | JSC::JSCCallbackFunction::Type::Constructor, jscClass, WTFMove(closure), returnType, WTFMove(parameters)); |
570 | auto constructor = jscContextGetOrCreateValue(priv->context, toRef(functionObject)); |
571 | GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get())); |
572 | auto nonEnumerable = static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE); |
573 | jsc_value_object_define_property_data(constructor.get(), "prototype" , nonEnumerable, prototype.get()); |
574 | jsc_value_object_define_property_data(prototype.get(), "constructor" , nonEnumerable, constructor.get()); |
575 | priv->constructors.set(name, functionObject); |
576 | return constructor; |
577 | } |
578 | |
579 | /** |
580 | * jsc_class_add_constructor: (skip) |
581 | * @jsc_class: a #JSCClass |
582 | * @name: (nullable): the constructor name or %NULL |
583 | * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class |
584 | * @user_data: (closure): user data to pass to @callback |
585 | * @destroy_notify: (nullable): destroy notifier for @user_data |
586 | * @return_type: the #GType of the constructor return value |
587 | * @n_params: the number of parameter types to follow or 0 if constructor doesn't receive parameters. |
588 | * @...: a list of #GType<!-- -->s, one for each parameter. |
589 | * |
590 | * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function> |
591 | * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving the |
592 | * parameters and @user_data as the last parameter. When the constructor object is cleared in the #JSCClass context, |
593 | * @destroy_notify is called with @user_data as parameter. |
594 | * |
595 | * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use |
596 | * jsc_context_set_value() to make the constructor available in the global object. |
597 | * |
598 | * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to |
599 | * jsc_context_register_class() is responsible for disposing of it. |
600 | * |
601 | * Returns: (transfer full): a #JSCValue representing the class constructor. |
602 | */ |
603 | JSCValue* jsc_class_add_constructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...) |
604 | { |
605 | g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); |
606 | g_return_val_if_fail(callback, nullptr); |
607 | |
608 | JSCClassPrivate* priv = jscClass->priv; |
609 | g_return_val_if_fail(priv->context, nullptr); |
610 | |
611 | if (!name) |
612 | name = priv->name.data(); |
613 | |
614 | va_list args; |
615 | va_start(args, paramCount); |
616 | Vector<GType> parameters; |
617 | if (paramCount) { |
618 | parameters.reserveInitialCapacity(paramCount); |
619 | for (unsigned i = 0; i < paramCount; ++i) |
620 | parameters.uncheckedAppend(va_arg(args, GType)); |
621 | } |
622 | va_end(args); |
623 | |
624 | return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); |
625 | |
626 | } |
627 | |
628 | /** |
629 | * jsc_class_add_constructorv: (rename-to jsc_class_add_constructor) |
630 | * @jsc_class: a #JSCClass |
631 | * @name: (nullable): the constructor name or %NULL |
632 | * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class |
633 | * @user_data: (closure): user data to pass to @callback |
634 | * @destroy_notify: (nullable): destroy notifier for @user_data |
635 | * @return_type: the #GType of the constructor return value |
636 | * @n_parameters: the number of parameters |
637 | * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GType<!-- -->s, one for each parameter, or %NULL |
638 | * |
639 | * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function> |
640 | * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving the |
641 | * parameters and @user_data as the last parameter. When the constructor object is cleared in the #JSCClass context, |
642 | * @destroy_notify is called with @user_data as parameter. |
643 | * |
644 | * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use |
645 | * jsc_context_set_value() to make the constructor available in the global object. |
646 | * |
647 | * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to |
648 | * jsc_context_register_class() is responsible for disposing of it. |
649 | * |
650 | * Returns: (transfer full): a #JSCValue representing the class constructor. |
651 | */ |
652 | JSCValue* jsc_class_add_constructorv(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType* parameterTypes) |
653 | { |
654 | g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); |
655 | g_return_val_if_fail(callback, nullptr); |
656 | g_return_val_if_fail(!parametersCount || parameterTypes, nullptr); |
657 | |
658 | JSCClassPrivate* priv = jscClass->priv; |
659 | g_return_val_if_fail(priv->context, nullptr); |
660 | |
661 | if (!name) |
662 | name = priv->name.data(); |
663 | |
664 | Vector<GType> parameters; |
665 | if (parametersCount) { |
666 | parameters.reserveInitialCapacity(parametersCount); |
667 | for (unsigned i = 0; i < parametersCount; ++i) |
668 | parameters.uncheckedAppend(parameterTypes[i]); |
669 | } |
670 | |
671 | return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); |
672 | } |
673 | |
674 | /** |
675 | * jsc_class_add_constructor_variadic: |
676 | * @jsc_class: a #JSCClass |
677 | * @name: (nullable): the constructor name or %NULL |
678 | * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class |
679 | * @user_data: (closure): user data to pass to @callback |
680 | * @destroy_notify: (nullable): destroy notifier for @user_data |
681 | * @return_type: the #GType of the constructor return value |
682 | * |
683 | * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function> |
684 | * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving |
685 | * a #GPtrArray of #JSCValue<!-- -->s as arguments and @user_data as the last parameter. When the constructor object |
686 | * is cleared in the #JSCClass context, @destroy_notify is called with @user_data as parameter. |
687 | * |
688 | * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use |
689 | * jsc_context_set_value() to make the constructor available in the global object. |
690 | * |
691 | * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to |
692 | * jsc_context_register_class() is responsible for disposing of it. |
693 | * |
694 | * Returns: (transfer full): a #JSCValue representing the class constructor. |
695 | */ |
696 | JSCValue* jsc_class_add_constructor_variadic(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType) |
697 | { |
698 | g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); |
699 | g_return_val_if_fail(callback, nullptr); |
700 | |
701 | JSCClassPrivate* priv = jscClass->priv; |
702 | g_return_val_if_fail(jscClass->priv->context, nullptr); |
703 | |
704 | if (!name) |
705 | name = priv->name.data(); |
706 | |
707 | return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTF::nullopt).leakRef(); |
708 | } |
709 | |
710 | static void jscClassAddMethod(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional<Vector<GType>>&& parameters) |
711 | { |
712 | JSCClassPrivate* priv = jscClass->priv; |
713 | GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify)))); |
714 | JSC::ExecState* exec = toJS(jscContextGetJSContext(priv->context)); |
715 | JSC::VM& vm = exec->vm(); |
716 | JSC::JSLockHolder locker(vm); |
717 | auto* functionObject = toRef(JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), String::fromUTF8(name), |
718 | JSC::JSCCallbackFunction::Type::Method, jscClass, WTFMove(closure), returnType, WTFMove(parameters))); |
719 | auto method = jscContextGetOrCreateValue(priv->context, functionObject); |
720 | GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get())); |
721 | auto nonEnumerable = static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE); |
722 | jsc_value_object_define_property_data(prototype.get(), name, nonEnumerable, method.get()); |
723 | } |
724 | |
725 | /** |
726 | * jsc_class_add_method: (skip) |
727 | * @jsc_class: a #JSCClass |
728 | * @name: the method name |
729 | * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class |
730 | * @user_data: (closure): user data to pass to @callback |
731 | * @destroy_notify: (nullable): destroy notifier for @user_data |
732 | * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void. |
733 | * @n_params: the number of parameter types to follow or 0 if the method doesn't receive parameters. |
734 | * @...: a list of #GType<!-- -->s, one for each parameter. |
735 | * |
736 | * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(), |
737 | * @callback is called receiving the class instance as first parameter, followed by the method parameters and then |
738 | * @user_data as last parameter. When the method is cleared in the #JSCClass context, @destroy_notify is called with |
739 | * @user_data as parameter. |
740 | * |
741 | * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use |
742 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
743 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
744 | * with jsc_value_new_object() that receives the copy as the instance parameter. |
745 | */ |
746 | void jsc_class_add_method(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...) |
747 | { |
748 | g_return_if_fail(JSC_IS_CLASS(jscClass)); |
749 | g_return_if_fail(name); |
750 | g_return_if_fail(callback); |
751 | g_return_if_fail(jscClass->priv->context); |
752 | |
753 | va_list args; |
754 | va_start(args, paramCount); |
755 | Vector<GType> parameters; |
756 | if (paramCount) { |
757 | parameters.reserveInitialCapacity(paramCount); |
758 | for (unsigned i = 0; i < paramCount; ++i) |
759 | parameters.uncheckedAppend(va_arg(args, GType)); |
760 | } |
761 | va_end(args); |
762 | |
763 | jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)); |
764 | } |
765 | |
766 | /** |
767 | * jsc_class_add_methodv: (rename-to jsc_class_add_method) |
768 | * @jsc_class: a #JSCClass |
769 | * @name: the method name |
770 | * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class |
771 | * @user_data: (closure): user data to pass to @callback |
772 | * @destroy_notify: (nullable): destroy notifier for @user_data |
773 | * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void. |
774 | * @n_parameters: the number of parameter types to follow or 0 if the method doesn't receive parameters. |
775 | * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GType<!-- -->s, one for each parameter, or %NULL |
776 | * |
777 | * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(), |
778 | * @callback is called receiving the class instance as first parameter, followed by the method parameters and then |
779 | * @user_data as last parameter. When the method is cleared in the #JSCClass context, @destroy_notify is called with |
780 | * @user_data as parameter. |
781 | * |
782 | * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use |
783 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
784 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
785 | * with jsc_value_new_object() that receives the copy as the instance parameter. |
786 | */ |
787 | void jsc_class_add_methodv(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType *parameterTypes) |
788 | { |
789 | g_return_if_fail(JSC_IS_CLASS(jscClass)); |
790 | g_return_if_fail(name); |
791 | g_return_if_fail(callback); |
792 | g_return_if_fail(!parametersCount || parameterTypes); |
793 | g_return_if_fail(jscClass->priv->context); |
794 | |
795 | Vector<GType> parameters; |
796 | if (parametersCount) { |
797 | parameters.reserveInitialCapacity(parametersCount); |
798 | for (unsigned i = 0; i < parametersCount; ++i) |
799 | parameters.uncheckedAppend(parameterTypes[i]); |
800 | } |
801 | |
802 | jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)); |
803 | } |
804 | |
805 | /** |
806 | * jsc_class_add_method_variadic: |
807 | * @jsc_class: a #JSCClass |
808 | * @name: the method name |
809 | * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class |
810 | * @user_data: (closure): user data to pass to @callback |
811 | * @destroy_notify: (nullable): destroy notifier for @user_data |
812 | * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void. |
813 | * |
814 | * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(), |
815 | * @callback is called receiving the class instance as first parameter, followed by a #GPtrArray of #JSCValue<!-- -->s |
816 | * with the method arguments and then @user_data as last parameter. When the method is cleared in the #JSCClass context, |
817 | * @destroy_notify is called with @user_data as parameter. |
818 | * |
819 | * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use |
820 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
821 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
822 | * with jsc_value_new_object() that receives the copy as the instance parameter. |
823 | */ |
824 | void jsc_class_add_method_variadic(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType) |
825 | { |
826 | g_return_if_fail(JSC_IS_CLASS(jscClass)); |
827 | g_return_if_fail(name); |
828 | g_return_if_fail(callback); |
829 | g_return_if_fail(jscClass->priv->context); |
830 | |
831 | jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTF::nullopt); |
832 | } |
833 | |
834 | /** |
835 | * jsc_class_add_property: |
836 | * @jsc_class: a #JSCClass |
837 | * @name: the property name |
838 | * @property_type: the #GType of the property value |
839 | * @getter: (scope async) (nullable): a #GCallback to be called to get the property value |
840 | * @setter: (scope async) (nullable): a #GCallback to be called to set the property value |
841 | * @user_data: (closure): user data to pass to @getter and @setter |
842 | * @destroy_notify: (nullable): destroy notifier for @user_data |
843 | * |
844 | * Add a property with @name to @jsc_class. When the property value needs to be getted, @getter is called |
845 | * receiving the the class instance as first parameter and @user_data as last parameter. When the property |
846 | * value needs to be set, @setter is called receiving the the class instance as first parameter, followed |
847 | * by the value to be set and then @user_data as the last parameter. When the property is cleared in the |
848 | * #JSCClass context, @destroy_notify is called with @user_data as parameter. |
849 | * |
850 | * Note that the value returned by @getter must be transfer full. In case of non-refcounted boxed types, you should use |
851 | * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. |
852 | * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created |
853 | * with jsc_value_new_object() that receives the copy as the instance parameter. |
854 | */ |
855 | void jsc_class_add_property(JSCClass* jscClass, const char* name, GType propertyType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify) |
856 | { |
857 | g_return_if_fail(JSC_IS_CLASS(jscClass)); |
858 | g_return_if_fail(name); |
859 | g_return_if_fail(propertyType != G_TYPE_INVALID && propertyType != G_TYPE_NONE); |
860 | g_return_if_fail(getter || setter); |
861 | |
862 | JSCClassPrivate* priv = jscClass->priv; |
863 | g_return_if_fail(priv->context); |
864 | |
865 | GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get())); |
866 | jsc_value_object_define_property_accessor(prototype.get(), name, JSC_VALUE_PROPERTY_CONFIGURABLE, propertyType, getter, setter, userData, destroyNotify); |
867 | } |
868 | |