History

Initial V8 support, not working yet

ksuprynowicz committed 4 years ago

Commit 7e9ad391 parent df507a740 files · +4040 −3087Browse files

Showing the changes to libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp (1 of 40 files). Show all

Unified · Split

libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp+1105−0
@@ -0,0 +1,1105 @@
1//
2// ScriptObjectV8Proxy.cpp
3// libraries/script-engine/src/v8
4//
5// Created by Heather Anderson on 12/5/21.
6// Modified for V8 by dr Karol Suprynowicz on 2022/10/08
7// Copyright 2021 Vircadia contributors.
8// Copyright 2022 Overte e.V.
9//
10// Distributed under the Apache License, Version 2.0.
11// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
12//
13
14#include "ScriptObjectV8Proxy.h"
15
16#include <QtCore/QList>
17#include <QtCore/QSharedPointer>
18
19#include "../ScriptEngineLogging.h"
20
21#include "ScriptContextV8Wrapper.h"
22#include "ScriptValueV8Wrapper.h"
23
24Q_DECLARE_METATYPE(V8ScriptContext*)
25Q_DECLARE_METATYPE(ScriptValue)
26//V8TODO?
27//Q_DECLARE_METATYPE(V8ScriptValue)
28
29Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>)
30Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>)
31
32// Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1
33static const void *internalPointsToQObjectProxy = (void *)0x13370000;
34static const void *internalPointsToQVariantProxy = (void *)0x13371000;
35static const void *internalPointsToSignalProxy = (void *)0x13372000;
36static const void *internalPointsToMethodProxy = (void *)0x13372000;
37
38// Used strictly to replace the "this" object value for property access. May expand to a full context element
39// if we find it necessary to, but hopefully not needed
40class ScriptPropertyContextQtWrapper final : public ScriptContext {
41public: // construction
42 inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) :
43 _parent(parentContext), _object(object) {}
44
45public: // ScriptContext implementation
46 virtual int argumentCount() const override { return _parent->argumentCount(); }
47 virtual ScriptValue argument(int index) const override { return _parent->argument(index); }
48 virtual QStringList backtrace() const override { return _parent->backtrace(); }
49 virtual ScriptValue callee() const override { return _parent->callee(); }
50 virtual ScriptEnginePointer engine() const override { return _parent->engine(); }
51 virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); }
52 virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); }
53 virtual ScriptValue thisObject() const override { return _object; }
54 virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); }
55 virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); }
56
57private: // storage
58 ScriptContext* _parent;
59 const ScriptValue& _object;
60};
61
62ScriptObjectV8Proxy::ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) :
63 _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object),
64 _v8ObjectTemplate(engine->getIsolate(), v8::ObjectTemplate::New(engine->getIsolate())) {
65 investigate();
66}
67
68V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* object,
69 ScriptEngine::ValueOwnership ownership,
70 const ScriptEngine::QObjectWrapOptions& options) {
71 // do we already have a valid wrapper for this QObject?
72 {
73 QMutexLocker guard(&engine->_qobjectWrapperMapProtect);
74 ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);
75 if (lookup != engine->_qobjectWrapperMap.end()) {
76 QSharedPointer<ScriptObjectV8Proxy> proxy = lookup.value().lock();
77 // V8TODO: is conversion to QVariant and back needed?
78 if (proxy) {
79 return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value());
80 }
81 //if (proxy) return engine->newObject(proxy.get(), engine->newVariant(QVariant::fromValue(proxy)));;
82 }
83 }
84
85 bool ownsObject;
86 switch (ownership) {
87 case ScriptEngine::QtOwnership:
88 ownsObject = false;
89 break;
90 case ScriptEngine::ScriptOwnership:
91 ownsObject = true;
92 break;
93 case ScriptEngine::AutoOwnership:
94 ownsObject = !object->parent();
95 break;
96 default:
97 ownsObject = false;
98 qCritical() << "Wrong ScriptEngine::ValueOwnership value: " << ownership;
99 break;
100 }
101
102 // create the wrapper
103 auto proxy = QSharedPointer<ScriptObjectV8Proxy>::create(engine, object, ownsObject, options);
104
105 {
106 QMutexLocker guard(&engine->_qobjectWrapperMapProtect);
107
108 // check again to see if someone else created the wrapper while we were busy
109 ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object);
110 if (lookup != engine->_qobjectWrapperMap.end()) {
111 QSharedPointer<ScriptObjectV8Proxy> proxy = lookup.value().lock();
112 //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));;
113 if (proxy) {
114 return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value());
115 }
116 }
117
118 // register the wrapper with the engine and make sure it cleans itself up
119 engine->_qobjectWrapperMap.insert(object, proxy);
120 QPointer<ScriptEngineV8> enginePtr = engine;
121 object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() {
122 if (!enginePtr) return;
123 QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect);
124 ScriptEngineV8::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object);
125 if (lookup != enginePtr->_qobjectWrapperMap.end()) {
126 enginePtr->_qobjectWrapperMap.erase(lookup);
127 }
128 });
129 }
130
131 return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value());
132 //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));
133}
134
135ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) {
136 auto v8Value = val.constGet();
137 if (!v8Value->IsObject()) {
138 return nullptr;
139 }
140 v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked();
141 if (v8Object->InternalFieldCount() != 2) {
142 return nullptr;
143 }
144 if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQObjectProxy) {
145 return nullptr;
146 }
147 return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));
148}
149
150QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) {
151 ScriptObjectV8Proxy* proxy = unwrapProxy(val);
152 return proxy ? proxy->toQObject() : nullptr;
153}
154
155ScriptObjectV8Proxy::~ScriptObjectV8Proxy() {
156 if (_ownsObject) {
157 QObject* qobject = _object;
158 if(qobject) qobject->deleteLater();
159 }
160}
161
162void ScriptObjectV8Proxy::investigate() {
163 QObject* qobject = _object;
164 Q_ASSERT(qobject);
165 if (!qobject) return;
166
167 auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate());
168 objectTemplate->SetInternalFieldCount(3);
169 objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set));
170
171 const QMetaObject* metaObject = qobject->metaObject();
172
173 // discover properties
174 int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0;
175 int num = metaObject->propertyCount();
176 for (int idx = startIdx; idx < num; ++idx) {
177 QMetaProperty prop = metaObject->property(idx);
178 if (!prop.isScriptable()) continue;
179
180 // always exclude child objects (at least until we decide otherwise)
181 int metaTypeId = prop.userType();
182 if (metaTypeId != QMetaType::UnknownType) {
183 QMetaType metaType(metaTypeId);
184 if (metaType.flags() & QMetaType::PointerToQObject) {
185 continue;
186 }
187 }
188
189 auto v8Name = v8::String::NewFromUtf8(_engine->getIsolate(), prop.name()).ToLocalChecked();
190 PropertyDef& propDef = _props.insert(idx, PropertyDef(_engine->getIsolate(), v8Name)).value();
191 propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter |
192 ScriptValue::QObjectMember;
193 if (prop.isConstant()) propDef.flags |= ScriptValue::ReadOnly;
194 }
195
196 // discover methods
197 startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0;
198 num = metaObject->methodCount();
199 QHash<V8ScriptString, int> methodNames;
200 for (int idx = startIdx; idx < num; ++idx) {
201 QMetaMethod method = metaObject->method(idx);
202
203 // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible
204 /* if (method.access() != QMetaMethod::Public) continue;*/
205
206 bool isSignal = false;
207 QByteArray szName = method.name();
208
209 switch (method.methodType()) {
210 case QMetaMethod::Constructor:
211 continue;
212 case QMetaMethod::Signal:
213 isSignal = true;
214 break;
215 case QMetaMethod::Slot:
216 if (_wrapOptions & ScriptEngine::ExcludeSlots) {
217 continue;
218 }
219 if (szName == "deleteLater") {
220 continue;
221 }
222 break;
223 default:
224 break;
225 }
226
227 V8ScriptString name(_engine->getIsolate(), v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked());
228 auto nameLookup = methodNames.find(name);
229 if (isSignal) {
230 if (nameLookup == methodNames.end()) {
231 SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine->getIsolate(), name.get())).value();
232 signalDef.name = name;
233 signalDef.signal = method;
234 methodNames.insert(name, idx);
235 } else {
236 int originalMethodId = nameLookup.value();
237 SignalDefMap::iterator signalLookup = _signals.find(originalMethodId);
238 Q_ASSERT(signalLookup != _signals.end());
239 SignalDef& signalDef = signalLookup.value();
240 Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount());
241 if (signalDef.signal.parameterCount() < method.parameterCount()) {
242 signalDef.signal = method;
243 }
244 }
245 } else {
246 int parameterCount = method.parameterCount();
247 if(method.returnType() == QMetaType::UnknownType) {
248 qCritical(scriptengine) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value";
249 }
250 for (int i = 0; i < method.parameterCount(); i++) {
251 if (method.parameterType(i) == QMetaType::UnknownType) {
252 qCritical(scriptengine) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType";
253 }
254 }
255 if (nameLookup == methodNames.end()) {
256 MethodDef& methodDef = _methods.insert(idx, MethodDef(_engine->getIsolate(), name.get())).value();
257 methodDef.name = name;
258 methodDef.numMaxParms = parameterCount;
259 methodDef.methods.append(method);
260 methodNames.insert(name, idx);
261 } else {
262 int originalMethodId = nameLookup.value();
263 MethodDefMap::iterator methodLookup = _methods.find(originalMethodId);
264 Q_ASSERT(methodLookup != _methods.end());
265 MethodDef& methodDef = methodLookup.value();
266 if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount;
267 methodDef.methods.append(method);
268 }
269 }
270 }
271
272 v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked();
273 v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy));
274 v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this));
275
276 _v8Object.Reset(_engine->getIsolate(), v8Object);
277}
278
279QString ScriptObjectV8Proxy::name() const {
280 Q_ASSERT(_object);
281 if (!_object) return "";
282 return _object ? _object->objectName() : "";
283 QString objectName = _object->objectName();
284 if (!objectName.isEmpty()) return objectName;
285 return _object->metaObject()->className();
286}
287
288ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) {
289 // check for properties
290 for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) {
291 const PropertyDef& propDef = trans.value();
292 if (propDef.name.constGet() != name.constGet()) continue;
293 *id = trans.key() | PROPERTY_TYPE;
294 return flags & (HandlesReadAccess | HandlesWriteAccess);
295 }
296
297 // check for methods
298 for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) {
299 if (trans.value().name.constGet() != name.constGet()) continue;
300 *id = trans.key() | METHOD_TYPE;
301 return flags & (HandlesReadAccess | HandlesWriteAccess);
302 }
303
304 // check for signals
305 for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) {
306 if (trans.value().name.constGet() != name.constGet()) continue;
307 *id = trans.key() | SIGNAL_TYPE;
308 return flags & (HandlesReadAccess | HandlesWriteAccess);
309 }
310
311 return QueryFlags();
312}
313
314ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) {
315 QObject* qobject = _object;
316 if (!qobject) {
317 return ScriptValue::PropertyFlags();
318 }
319
320 switch (id & TYPE_MASK) {
321 case PROPERTY_TYPE: {
322 PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK);
323 if (lookup == _props.cend()) return ScriptValue::PropertyFlags();
324 const PropertyDef& propDef = lookup.value();
325 return propDef.flags;
326 }
327 case METHOD_TYPE: {
328 MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK);
329 if (lookup == _methods.cend()) return ScriptValue::PropertyFlags();
330 return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember;
331 }
332 case SIGNAL_TYPE: {
333 SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK);
334 if (lookup == _signals.cend()) return ScriptValue::PropertyFlags();
335 return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember;
336 }
337 }
338 return ScriptValue::PropertyFlags();
339}
340
341void ScriptObjectV8Proxy::v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
342 // V8TODO
343 //info.GetReturnValue().Set();
344}
345
346void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) {
347 // V8TODO
348 info.GetReturnValue().Set(value);
349}
350
351
352V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) {
353 QObject* qobject = _object;
354 if (!qobject) {
355 _engine->getIsolate()->ThrowError("Referencing deleted native object");
356 return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate()));
357 }
358
359 const QMetaObject* metaObject = qobject->metaObject();
360
361 switch (id & TYPE_MASK) {
362 case PROPERTY_TYPE: {
363 int propId = id & ~TYPE_MASK;
364 PropertyDefMap::const_iterator lookup = _props.find(propId);
365 if (lookup == _props.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate()));
366
367 QMetaProperty prop = metaObject->property(propId);
368 ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object));
369 ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext());
370 ScriptContextGuard guard(&ourContext);
371
372 QVariant varValue = prop.read(qobject);
373 return _engine->castVariantToValue(varValue);
374 }
375 case METHOD_TYPE: {
376 int methodId = id & ~TYPE_MASK;
377 MethodDefMap::const_iterator lookup = _methods.find(methodId);
378 if (lookup == _methods.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate()));
379 const MethodDef& methodDef = lookup.value();
380 for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) {
381 if((*iter).returnType() == QMetaType::UnknownType) {
382 qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name();
383 }
384 }
385 return ScriptMethodV8Proxy::newMethod(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms);
386 }
387 case SIGNAL_TYPE: {
388 int signalId = id & ~TYPE_MASK;
389 SignalDefMap::const_iterator defLookup = _signals.find(signalId);
390 if (defLookup == _signals.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate()));
391
392 InstanceMap::const_iterator instLookup = _signalInstances.find(signalId);
393 if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) {
394 instLookup = _signalInstances.insert(signalId,
395 new ScriptSignalV8Proxy(_engine, qobject, object, defLookup.value().signal));
396 Q_ASSERT(instLookup != _signalInstances.cend());
397 }
398 ScriptSignalV8Proxy* proxy = instLookup.value();
399
400 ScriptEngine::QObjectWrapOptions options = ScriptEngine::ExcludeSuperClassContents |
401 //V8TODO ScriptEngine::ExcludeDeleteLater |
402 ScriptEngine::PreferExistingWrapperObject;
403 return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options);
404 //return _engine->newQObject(proxy, ScriptEngine::ScriptOwnership, options);
405 }
406 }
407 return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate()));
408}
409
410void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) {
411 if (!(id & PROPERTY_TYPE)) return;
412 QObject* qobject = _object;
413 if (!qobject) {
414 _engine->getIsolate()->ThrowError("Referencing deleted native object");
415 return;
416 }
417
418 int propId = id & ~TYPE_MASK;
419 PropertyDefMap::const_iterator lookup = _props.find(propId);
420 if (lookup == _props.cend()) return;
421 const PropertyDef& propDef = lookup.value();
422 if (propDef.flags & ScriptValue::ReadOnly) return;
423
424 const QMetaObject* metaObject = qobject->metaObject();
425 QMetaProperty prop = metaObject->property(propId);
426
427 ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object));
428 ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext());
429 ScriptContextGuard guard(&ourContext);
430
431 int propTypeId = prop.userType();
432 Q_ASSERT(propTypeId != QMetaType::UnknownType);
433 QVariant varValue;
434 if(!_engine->castValueToVariant(value, varValue, propTypeId)) {
435 QByteArray propTypeName = QMetaType(propTypeId).name();
436 QByteArray valTypeName = _engine->valueType(value).toLatin1();
437 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName).toStdString().c_str()).ToLocalChecked());
438 return;
439 }
440 prop.write(qobject, varValue);
441}
442
443ScriptVariantV8Proxy::ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto) :
444 _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) {
445 _name = QString::fromLatin1(variant.typeName());
446}
447
448V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) {
449 ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto);
450 if (!protoProxy) {
451 Q_ASSERT(protoProxy);
452 //return engine->newVariant(variant);
453 return V8ScriptValue(engine->getIsolate(), v8::Undefined(engine->getIsolate()));
454 }
455 // V8TODO probably needs connection to be deleted
456 // V8TODO what to do with proto variable?
457 auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy);
458 auto variantDataTemplate = v8::ObjectTemplate::New(engine->getIsolate());
459 variantDataTemplate->SetInternalFieldCount(2);
460 auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked();
461 variantData->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQVariantProxy));
462 variantData->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(proxy));
463 return V8ScriptValue(engine->getIsolate(), variantData);
464}
465
466ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) {
467 auto v8Value = val.constGet();
468 if (!v8Value->IsObject()) {
469 return nullptr;
470 }
471 v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked();
472 if (v8Object->InternalFieldCount() != 2) {
473 return nullptr;
474 }
475 if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQVariantProxy) {
476 return nullptr;
477 }
478 return reinterpret_cast<ScriptVariantV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));
479}
480
481QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) {
482 ScriptVariantV8Proxy* proxy = unwrapProxy(val);
483 return proxy ? proxy->toQVariant() : QVariant();
484}
485
486ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
487 const QList<QMetaMethod>& metas, int numMaxParms) :
488 _numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) {
489}
490
491V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
492 const QList<QMetaMethod>& metas, int numMaxParams) {
493 auto methodDataTemplate = v8::ObjectTemplate::New(engine->getIsolate());
494 methodDataTemplate->SetInternalFieldCount(2);
495 auto methodData = methodDataTemplate->NewInstance(engine->getContext()).ToLocalChecked();
496 methodData->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToMethodProxy));
497 // V8TODO it needs to be deleted somehow on object destruction
498 methodData->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(new ScriptMethodV8Proxy(engine, object, lifetime, metas, numMaxParams)));
499 auto v8Function = v8::Function::New(engine->getContext(), callback, methodData, numMaxParams).ToLocalChecked();
500 return V8ScriptValue(engine->getIsolate(), v8Function);
501}
502
503QString ScriptMethodV8Proxy::fullName() const {
504 Q_ASSERT(_object);
505 if (!_object) return "";
506 Q_ASSERT(!_metas.isEmpty());
507 const QMetaMethod& firstMethod = _metas.front();
508 QString objectName = _object->objectName();
509 if (!objectName.isEmpty()) {
510 return QString("%1.%2").arg(objectName, firstMethod.name());
511 }
512 return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name());
513}
514
515// V8TODO
516/*bool ScriptMethodV8Proxy::supportsExtension(Extension extension) const {
517 switch (extension) {
518 case Callable:
519 return true;
520 default:
521 return false;
522 }
523}*/
524
525void ScriptMethodV8Proxy::callback(const v8::FunctionCallbackInfo<v8::Value>& arguments) {
526 if (!arguments.This()->IsObject()) {
527 arguments.GetIsolate()->ThrowError("Method value is not an object");
528 return;
529 }
530 if (!arguments.This()->IsCallable()) {
531 arguments.GetIsolate()->ThrowError("Method value is not callable");
532 return;
533 }
534 if (arguments.This()->InternalFieldCount() != 2) {
535 arguments.GetIsolate()->ThrowError("Incorrect number of internal fields during method call");
536 return;
537 }
538 if (arguments.This()->GetAlignedPointerFromInternalField(0) == internalPointsToMethodProxy) {
539 arguments.GetIsolate()->ThrowError("Internal field 0 of ScriptMethodV8Proxy V8 object has wrong value");
540 return;
541 }
542 ScriptMethodV8Proxy *proxy = reinterpret_cast<ScriptMethodV8Proxy*>(arguments.This()->GetAlignedPointerFromInternalField(1));
543 proxy->call(arguments);
544}
545
546void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo<v8::Value>& arguments) {
547 QObject* qobject = _object;
548 v8::Isolate *isolate = arguments.GetIsolate();
549 if (!qobject) {
550 isolate->ThrowError("Referencing deleted native object");
551 return;
552 }
553
554 int scriptNumArgs = arguments.Length();
555 int numArgs = std::min(scriptNumArgs, _numMaxParms);
556
557 const int scriptValueTypeId = qMetaTypeId<ScriptValue>();
558
559 int parameterConversionFailureId = 0;
560 int parameterConversionFailureCount = 0;
561
562 int num_metas = _metas.size();
563 QVector< QList<ScriptValue> > qScriptArgLists;
564 QVector< QVector <QGenericArgument> > qGenArgsVectors;
565 QVector< QList<QVariant> > qVarArgLists;
566 qScriptArgLists.resize(num_metas);
567 qGenArgsVectors.resize(num_metas);
568 qVarArgLists.resize(num_metas);
569 bool isValidMetaSelected = false;
570 int bestMeta = 0;
571 int bestConversionPenaltyScore = 0;
572
573 for (int i = 0; i < num_metas; i++) {
574 const QMetaMethod& meta = _metas[i];
575 int methodNumArgs = meta.parameterCount();
576 if (methodNumArgs != numArgs) {
577 continue;
578 }
579
580 qGenArgsVectors[i].resize(10);
581 int conversionPenaltyScore = 0;
582 int conversionFailures = 0;
583
584 for (int arg = 0; arg < numArgs; ++arg) {
585 int methodArgTypeId = meta.parameterType(arg);
586 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
587 v8::Local<v8::Value> argVal = arguments[arg];
588 if (methodArgTypeId == scriptValueTypeId) {
589 qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(isolate, argVal))));
590 qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back());
591 } else if (methodArgTypeId == QMetaType::QVariant) {
592 QVariant varArgVal;
593 if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) {
594 conversionFailures++;
595 } else {
596 qVarArgLists[i].append(varArgVal);
597 qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back());
598 }
599 } else {
600 QVariant varArgVal;
601 if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) {
602 conversionFailures++;
603 } else {
604 qVarArgLists[i].append(varArgVal);
605 const QVariant& converted = qVarArgLists[i].back();
606 conversionPenaltyScore = _engine->computeCastPenalty(V8ScriptValue(isolate, argVal), methodArgTypeId);
607
608 // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
609 // A const_cast is needed because calling data() would detach the QVariant.
610 qGenArgsVectors[i][arg] =
611 QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData()));
612 }
613 }
614 }
615 if (conversionFailures) {
616 if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) {
617 parameterConversionFailureCount = conversionFailures;
618 parameterConversionFailureId = meta.methodIndex();
619 }
620 continue;
621 }
622
623 if (!isValidMetaSelected) {
624 isValidMetaSelected = true;
625 bestMeta = i;
626 bestConversionPenaltyScore = conversionPenaltyScore;
627 }
628 if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) {
629 bestMeta = i;
630 bestConversionPenaltyScore = conversionPenaltyScore;
631 }
632 }
633
634 if (isValidMetaSelected) {
635 //ScriptContextV8Wrapper ourContext(_engine, context);
636 //ScriptContextGuard guard(&ourContext);
637 const QMetaMethod& meta = _metas[bestMeta];
638 int returnTypeId = meta.returnType();
639 QVector <QGenericArgument> &qGenArgs = qGenArgsVectors[bestMeta];
640
641 // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's
642 // nothing in there for return values so these need to be explicitly runtime-registered!
643 Q_ASSERT(returnTypeId != QMetaType::UnknownType);
644 if (returnTypeId == QMetaType::UnknownType) {
645 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked());
646 return;
647 } else if (returnTypeId == QMetaType::Void) {
648 bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
649 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
650 if (!success) {
651 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked());
652 }
653 return;
654 } else if (returnTypeId == scriptValueTypeId) {
655 ScriptValue result;
656 bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0],
657 qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6],
658 qGenArgs[7], qGenArgs[8], qGenArgs[9]);
659 if (!success) {
660 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked());
661 return;
662 }
663 V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result);
664 arguments.GetReturnValue().Set(v8Result.get());
665 return;
666 } else {
667 // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
668 const char* typeName = meta.typeName();
669 QVariant qRetVal(returnTypeId, static_cast<void*>(NULL));
670 QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData()));
671
672 bool success =
673 meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
674 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
675 if (!success) {
676 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked());
677 return;
678 }
679 V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal);
680 arguments.GetReturnValue().Set(v8Result.get());
681 return;
682 }
683 }
684
685 // we failed to convert the call to C++, try to create a somewhat sane error message
686 if (parameterConversionFailureCount == 0) {
687 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked());
688 return;
689 }
690
691 const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId);
692 int methodNumArgs = meta.parameterCount();
693 Q_ASSERT(methodNumArgs == numArgs);
694
695 for (int arg = 0; arg < numArgs; ++arg) {
696 int methodArgTypeId = meta.parameterType(arg);
697 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
698 v8::Local<v8::Value> argVal = arguments[arg];
699 if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) {
700 QVariant varArgVal;
701 if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) {
702 QByteArray methodTypeName = QMetaType(methodArgTypeId).name();
703 QByteArray argTypeName = _engine->valueType(V8ScriptValue(isolate, argVal)).toLatin1();
704 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4")
705 .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked());
706 //context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4")
707 // .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName));
708 return;
709 }
710 }
711 }
712
713 isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()).toStdString().c_str()).ToLocalChecked());
714 Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now?
715 return;
716}
717
718/*QVariant ScriptMethodV8Proxy::extension(Extension extension, const QVariant& argument) {
719 if (extension != Callable) return QVariant();
720 V8ScriptContext* context = qvariant_cast<V8ScriptContext*>(argument);
721
722 QObject* qobject = _object;
723 if (!qobject) {
724 context->throwError(V8ScriptContext::ReferenceError, "Referencing deleted native object");
725 return QVariant();
726 }
727
728 int scriptNumArgs = context->argumentCount();
729 int numArgs = std::min(scriptNumArgs, _numMaxParms);
730
731 const int scriptValueTypeId = qMetaTypeId<ScriptValue>();
732
733 int parameterConversionFailureId = 0;
734 int parameterConversionFailureCount = 0;
735
736 int num_metas = _metas.size();
737 QVector< QList<ScriptValue> > qScriptArgLists;
738 QVector< QVector <QGenericArgument> > qGenArgsVectors;
739 QVector< QList<QVariant> > qVarArgLists;
740 qScriptArgLists.resize(num_metas);
741 qGenArgsVectors.resize(num_metas);
742 qVarArgLists.resize(num_metas);
743 bool isValidMetaSelected = false;
744 int bestMeta = 0;
745 int bestConversionPenaltyScore = 0;
746
747 for (int i = 0; i < num_metas; i++) {
748 const QMetaMethod& meta = _metas[i];
749 int methodNumArgs = meta.parameterCount();
750 if (methodNumArgs != numArgs) {
751 continue;
752 }
753
754 qGenArgsVectors[i].resize(10);
755 int conversionPenaltyScore = 0;
756 int conversionFailures = 0;
757
758 for (int arg = 0; arg < numArgs; ++arg) {
759 int methodArgTypeId = meta.parameterType(arg);
760 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
761 V8ScriptValue argVal = context->argument(arg);
762 if (methodArgTypeId == scriptValueTypeId) {
763 qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, argVal)));
764 qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back());
765 } else if (methodArgTypeId == QMetaType::QVariant) {
766 qVarArgLists[i].append(argVal.toVariant());
767 qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back());
768 } else {
769 QVariant varArgVal;
770 if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {
771 conversionFailures++;
772 } else {
773 qVarArgLists[i].append(varArgVal);
774 const QVariant& converted = qVarArgLists[i].back();
775 conversionPenaltyScore = _engine->computeCastPenalty(argVal, methodArgTypeId);
776
777 // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
778 // A const_cast is needed because calling data() would detach the QVariant.
779 qGenArgsVectors[i][arg] =
780 QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData()));
781 }
782 }
783 }
784 if (conversionFailures) {
785 if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) {
786 parameterConversionFailureCount = conversionFailures;
787 parameterConversionFailureId = meta.methodIndex();
788 }
789 continue;
790 }
791
792 if (!isValidMetaSelected) {
793 isValidMetaSelected = true;
794 bestMeta = i;
795 bestConversionPenaltyScore = conversionPenaltyScore;
796 }
797 if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) {
798 bestMeta = i;
799 bestConversionPenaltyScore = conversionPenaltyScore;
800 }
801 }
802
803 if (isValidMetaSelected) {
804 ScriptContextV8Wrapper ourContext(_engine, context);
805 ScriptContextGuard guard(&ourContext);
806 const QMetaMethod& meta = _metas[bestMeta];
807 int returnTypeId = meta.returnType();
808 QVector <QGenericArgument> &qGenArgs = qGenArgsVectors[bestMeta];
809
810 // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's
811 // nothing in there for return values so these need to be explicitly runtime-registered!
812 Q_ASSERT(returnTypeId != QMetaType::UnknownType);
813 if (returnTypeId == QMetaType::UnknownType) {
814 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked());
815 return QVariant();
816 } else if (returnTypeId == QMetaType::Void) {
817 bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
818 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
819 if (!success) {
820 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked());
821 }
822 return QVariant();
823 } else if (returnTypeId == scriptValueTypeId) {
824 ScriptValue result;
825 bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0],
826 qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6],
827 qGenArgs[7], qGenArgs[8], qGenArgs[9]);
828 if (!success) {
829 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked());
830 return QVariant();
831 }
832 V8ScriptValue qResult = ScriptValueV8Wrapper::fullUnwrap(_engine, result);
833 return QVariant::fromValue(qResult);
834 } else {
835 // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant
836 const char* typeName = meta.typeName();
837 QVariant qRetVal(returnTypeId, static_cast<void*>(NULL));
838 QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData()));
839
840 bool success =
841 meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3],
842 qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]);
843 if (!success) {
844 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked());
845 return QVariant();
846 }
847 V8ScriptValue qResult = _engine->castVariantToValue(qRetVal);
848 return QVariant::fromValue(qResult);
849 }
850 }
851
852 // we failed to convert the call to C++, try to create a somewhat sane error message
853 if (parameterConversionFailureCount == 0) {
854 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked());
855 return QVariant();
856 }
857
858 const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId);
859 int methodNumArgs = meta.parameterCount();
860 Q_ASSERT(methodNumArgs == numArgs);
861
862 for (int arg = 0; arg < numArgs; ++arg) {
863 int methodArgTypeId = meta.parameterType(arg);
864 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
865 V8ScriptValue argVal = context->argument(arg);
866 if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) {
867 QVariant varArgVal;
868 if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) {
869 QByteArray methodTypeName = QMetaType(methodArgTypeId).name();
870 QByteArray argTypeName = _engine->valueType(argVal).toLatin1();
871 _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4")
872 .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked());
873 context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4")
874 .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName));
875 return QVariant();
876 }
877 }
878 }
879
880 context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()));
881 Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now?
882 return QVariant();
883}*/
884
885QString ScriptSignalV8Proxy::fullName() const {
886 Q_ASSERT(_object);
887 if (!_object) return "";
888 QString objectName = _object->objectName();
889 if (!objectName.isEmpty()) {
890 return QString("%1.%2").arg(objectName, _meta.name());
891 }
892 return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name());
893}
894
895// Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it
896int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) {
897 id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments);
898 if (id != 0 || call != QMetaObject::InvokeMetaMethod) {
899 return id;
900 }
901
902 //V8ScriptValueList args(isolate, v8::Null(isolate));
903 v8::Local<v8::Value> args[Q_METAMETHOD_INVOKE_MAX_ARGS];
904 int numArgs = _meta.parameterCount();
905 for (int arg = 0; arg < numArgs; ++arg) {
906 int methodArgTypeId = _meta.parameterType(arg);
907 Q_ASSERT(methodArgTypeId != QMetaType::UnknownType);
908 QVariant argValue(methodArgTypeId, arguments[arg+1]);
909 args[arg] = _engine->castVariantToValue(argValue).get();
910 }
911
912 QList<Connection> connections;
913 withReadLock([&]{
914 connections = _connections;
915 });
916
917 for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) {
918 Connection& conn = *iter;
919 v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(conn.callback.get());
920 v8::Local<v8::Value> v8This;
921 if (conn.thisValue.get()->IsNull()) {
922 v8This = _engine->getContext()->Global();
923 } else {
924 v8This = conn.thisValue.get();
925 }
926 //V8TODO: should there be a trycatch here?
927 callback->Call(_engine->getContext(), v8This, numArgs, args);
928 }
929
930 return -1;
931}
932
933int ScriptSignalV8Proxy::discoverMetaCallIdx() {
934 const QMetaObject* ourMeta = metaObject();
935 return ourMeta->methodCount();
936}
937
938ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) {
939 auto iterOut = resultWithReadLock<ScriptSignalV8Proxy::ConnectionList::iterator>([&]{
940 ConnectionList::iterator iter;
941 for (iter = _connections.begin(); iter != _connections.end(); ++iter) {
942 Connection& conn = *iter;
943 if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) {
944 break;
945 }
946 }
947 return iter;
948 });
949 return iterOut;
950}
951
952
953void ScriptSignalV8Proxy::connect(V8ScriptValue arg0, V8ScriptValue arg1) {
954 QObject* qobject = _object;
955 if (!qobject) {
956 _engine->getIsolate()->ThrowError("Referencing deleted native object");
957 return;
958 }
959
960 v8::Isolate *isolate = _engine->getIsolate();
961 // untangle the arguments
962 V8ScriptValue callback(isolate, v8::Null(isolate));
963 V8ScriptValue callbackThis(isolate, v8::Null(isolate));
964 if (arg1.get()->IsFunction()) {
965 callbackThis = arg0;
966 callback = arg1;
967 } else {
968 callback = arg0;
969 }
970 if (!callback.get()->IsFunction()) {
971 _engine->getIsolate()->ThrowError("Function expected as argument to 'connect'");
972 return;
973 }
974
975 // are we already connected?
976 {
977 ConnectionList::iterator lookup = findConnection(callbackThis, callback);
978 if (lookup != _connections.end()) {
979 return; // already exists
980 }
981 }
982
983 // add a reference to ourselves to the destination callback
984 v8::Local<v8::Function> destFunction = v8::Local<v8::Function>::Cast(callback.get());
985 v8::Local<v8::String> destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked();
986 v8::Local<v8::Value> destData;
987 auto destFunctionContext = destFunction->CreationContext();
988 V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject());
989 //Q_ASSERT(destFunction->InternalFieldCount() == 4);
990 //Q_ASSERT(destData.get()->IsArray());
991 //v8::Local<v8::Value> destData = destFunction->GetInternalField(3);
992 if (destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData) && destData->IsArray()) {
993 v8::Local<v8::Array> destArray = v8::Local<v8::Array>::Cast(destData);
994 int length = destArray->Length();//destData.property("length").toInteger();
995 v8::Local<v8::Array> newArray = v8::Array::New(isolate, length + 1);
996 bool foundIt = false;
997 for (int idx = 0; idx < length && !foundIt; ++idx) {
998 v8::Local<v8::Value> entry = destArray->Get(destFunctionContext, idx).ToLocalChecked();
999 newArray->Set(destFunctionContext, idx, entry);
1000 }
1001 newArray->Set(destFunctionContext, length, v8ThisObject.get());
1002 destFunction->Set(destFunctionContext, destDataName, newArray);
1003 } else {
1004 v8::Local<v8::Array> newArray = v8::Array::New(isolate, 1);
1005 newArray->Set(destFunctionContext, 0, v8ThisObject.get());
1006 destFunction->Set(destFunctionContext, destDataName, newArray);
1007 }
1008 /*{
1009 V8ScriptValueList args;
1010 args << thisObject();
1011 destData.property("push").call(destData, args);
1012 }*/
1013
1014 // add this to our internal list of connections
1015 Connection newConnection(callbackThis, callback);
1016 //newConn.callback = callback;
1017 //newConn.thisValue = callbackThis;
1018
1019 withWriteLock([&]{
1020 _connections.append(newConnection);
1021 });
1022
1023 // inform Qt that we're connecting to this signal
1024 if (!_isConnected) {
1025 auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId);
1026 Q_ASSERT(result);
1027 _isConnected = true;
1028 }
1029}
1030
1031void ScriptSignalV8Proxy::disconnect(V8ScriptValue arg0, V8ScriptValue arg1) {
1032 QObject* qobject = _object;
1033 if (!qobject) {
1034 _engine->getIsolate()->ThrowError("Referencing deleted native object");
1035 return;
1036 }
1037 v8::Isolate *isolate = _engine->getIsolate();
1038
1039 // untangle the arguments
1040 V8ScriptValue callback(isolate, v8::Null(isolate));
1041 V8ScriptValue callbackThis(isolate, v8::Null(isolate));
1042 if (arg1.get()->IsFunction()) {
1043 callbackThis = arg0;
1044 callback = arg1;
1045 } else {
1046 callback = arg0;
1047 }
1048 if (!callback.get()->IsFunction()) {
1049 _engine->getIsolate()->ThrowError("Function expected as argument to 'disconnect'");
1050 return;
1051 }
1052
1053 // locate this connection in our list of connections
1054 {
1055 ConnectionList::iterator lookup = findConnection(callbackThis, callback);
1056 if (lookup == _connections.end()) {
1057 return; // not here
1058 }
1059
1060 // remove it from our internal list of connections
1061 withWriteLock([&]{
1062 _connections.erase(lookup);
1063 });
1064 }
1065
1066 // remove a reference to ourselves from the destination callback
1067 v8::Local<v8::Function> destFunction = v8::Local<v8::Function>::Cast(callback.get());
1068 v8::Local<v8::String> destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked();
1069 v8::Local<v8::Value> destData;
1070 auto destFunctionContext = destFunction->CreationContext();
1071 V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject());
1072 //V8ScriptValue destData = callback.data();
1073 //Q_ASSERT(destData->IsArray());
1074 if (destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData) && destData->IsArray()) {
1075 v8::Local<v8::Array> destArray = v8::Local<v8::Array>::Cast(destData);
1076 int length = destArray->Length();//destData.property("length").toInteger();
1077 v8::Local<v8::Array> newArray = v8::Array::New(isolate, length - 1);
1078 bool foundIt = false;
1079 int newIndex = 0;
1080 for (int idx = 0; idx < length && !foundIt; ++idx) {
1081 v8::Local<v8::Value> entry = destArray->Get(destFunctionContext, idx).ToLocalChecked();
1082 if (entry->StrictEquals(v8ThisObject.get())) {
1083 foundIt = true;
1084 //V8ScriptValueList args;
1085 //args << idx << 1;
1086 //destData.property("splice").call(destData, args);
1087 } else {
1088 newArray->Set(destFunctionContext, newIndex, entry);
1089 newIndex++;
1090 }
1091 }
1092 Q_ASSERT(foundIt);
1093 destFunction->Set(destFunctionContext, destDataName, newArray);
1094 } else {
1095 Q_ASSERT(false);
1096 }
1097
1098 // inform Qt that we're no longer connected to this signal
1099 if (_connections.empty()) {
1100 Q_ASSERT(_isConnected);
1101 bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId);
1102 Q_ASSERT(result);
1103 _isConnected = false;
1104 }
1105}