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