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+
24+Q_DECLARE_METATYPE(V8ScriptContext*)
25+Q_DECLARE_METATYPE(ScriptValue)
26+//V8TODO?
27+//Q_DECLARE_METATYPE(V8ScriptValue)
28+
29+Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>)
30+Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>)
31+
32+// Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1
33+static const void *internalPointsToQObjectProxy = (void *)0x13370000;
34+static const void *internalPointsToQVariantProxy = (void *)0x13371000;
35+static const void *internalPointsToSignalProxy = (void *)0x13372000;
36+static 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
40+class ScriptPropertyContextQtWrapper final : public ScriptContext {
41+public: // construction
42+ inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) :
43+ _parent(parentContext), _object(object) {}
44+
45+public: // 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+
57+private: // storage
58+ ScriptContext* _parent;
59+ const ScriptValue& _object;
60+};
61+
62+ScriptObjectV8Proxy::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+
68+V8ScriptValue 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+
135+ScriptObjectV8Proxy* 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+
150+QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) {
151+ ScriptObjectV8Proxy* proxy = unwrapProxy(val);
152+ return proxy ? proxy->toQObject() : nullptr;
153+}
154+
155+ScriptObjectV8Proxy::~ScriptObjectV8Proxy() {
156+ if (_ownsObject) {
157+ QObject* qobject = _object;
158+ if(qobject) qobject->deleteLater();
159+ }
160+}
161+
162+void 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+
279+QString 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+
288+ScriptObjectV8Proxy::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+
314+ScriptValue::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+
341+void ScriptObjectV8Proxy::v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
342+ // V8TODO
343+ //info.GetReturnValue().Set();
344+}
345+
346+void 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+
352+V8ScriptValue 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+
410+void 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+
443+ScriptVariantV8Proxy::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+
448+V8ScriptValue 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+
466+ScriptVariantV8Proxy* 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+
481+QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) {
482+ ScriptVariantV8Proxy* proxy = unwrapProxy(val);
483+ return proxy ? proxy->toQVariant() : QVariant();
484+}
485+
486+ScriptMethodV8Proxy::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+
491+V8ScriptValue 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+
503+QString 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+
525+void 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+
546+void 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+
885+QString 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
896+int 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+
933+int ScriptSignalV8Proxy::discoverMetaCallIdx() {
934+ const QMetaObject* ourMeta = metaObject();
935+ return ourMeta->methodCount();
936+}
937+
938+ScriptSignalV8Proxy::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+
953+void 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+
1031+void 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+}