// // ScriptObjectV8Proxy.cpp // libraries/script-engine/src/v8 // // Created by Heather Anderson on 12/5/21. // Modified for V8 by dr Karol Suprynowicz on 2022/10/08 // Copyright 2021 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "ScriptObjectV8Proxy.h" #include #include #include #include "../ScriptEngineLogging.h" #include "ScriptContextV8Wrapper.h" #include "ScriptValueV8Wrapper.h" //V8TODO: is this needed for anything? It could cause trouble with multithreading if V8ScriptContext is v8::Persistent //Q_DECLARE_METATYPE(V8ScriptContext*) Q_DECLARE_METATYPE(ScriptValue) //V8TODO: default constructor would be needed //Q_DECLARE_METATYPE(V8ScriptValue) Q_DECLARE_METATYPE(QSharedPointer) Q_DECLARE_METATYPE(QSharedPointer) // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1 static const void *internalPointsToQObjectProxy = (void *)0x13370000; static const void *internalPointsToQVariantProxy = (void *)0x13371000; //static const void *internalPointsToSignalProxy = (void *)0x13372000; static const void *internalPointsToMethodProxy = (void *)0x13373000; // This is used to pass object in ScriptVariantV8Proxy to methods of prototype object, for example passing AnimationPointer to AnimationObject // Object is then converted using scriptvalue_cast for use inside the prototype static const void *internalPointsToQVariant = (void *)0x13374000; // Used strictly to replace the "this" object value for property access. May expand to a full context element // if we find it necessary to, but hopefully not needed class ScriptPropertyContextV8Wrapper final : public ScriptContext { public: // construction inline ScriptPropertyContextV8Wrapper(const ScriptValue& object, ScriptContext* parentContext) : _parent(parentContext), _object(object) {} public: // ScriptContext implementation virtual int argumentCount() const override { return _parent->argumentCount(); } virtual ScriptValue argument(int index) const override { return _parent->argument(index); } virtual QStringList backtrace() const override { return _parent->backtrace(); } virtual ScriptValue callee() const override { return _parent->callee(); } virtual ScriptEnginePointer engine() const override { return _parent->engine(); } virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); } virtual ScriptValue thisObject() const override { return _object; } virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); } virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } private: // storage ScriptContext* _parent; const ScriptValue& _object; }; ScriptObjectV8Proxy::ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) : _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object) { //_v8ObjectTemplate(engine->getIsolate(), v8::ObjectTemplate::New(engine->getIsolate()) Q_ASSERT(_engine != nullptr); investigate(); } V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* object, ScriptEngine::ValueOwnership ownership, const ScriptEngine::QObjectWrapOptions& options) { // do we already have a valid wrapper for this QObject? { QMutexLocker guard(&engine->_qobjectWrapperMapProtect); ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); if (lookup != engine->_qobjectWrapperMap.end()) { QSharedPointer proxy = lookup.value().lock(); // V8TODO: is conversion to QVariant and back needed? if (proxy) { return V8ScriptValue(engine, proxy.get()->toV8Value()); } //if (proxy) return engine->newObject(proxy.get(), engine->newVariant(QVariant::fromValue(proxy)));; } } bool ownsObject; switch (ownership) { case ScriptEngine::QtOwnership: ownsObject = false; break; case ScriptEngine::ScriptOwnership: ownsObject = true; break; case ScriptEngine::AutoOwnership: ownsObject = !object->parent(); break; default: ownsObject = false; qCritical() << "Wrong ScriptEngine::ValueOwnership value: " << ownership; break; } // create the wrapper auto proxy = QSharedPointer::create(engine, object, ownsObject, options); { QMutexLocker guard(&engine->_qobjectWrapperMapProtect); // check again to see if someone else created the wrapper while we were busy ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); if (lookup != engine->_qobjectWrapperMap.end()) { QSharedPointer proxy = lookup.value().lock(); //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; if (proxy) { return V8ScriptValue(engine, proxy.get()->toV8Value()); } } // V8TODO add a V8 callback that removes pointer for the script engine owned ob from the map so that it gets deleted // register the wrapper with the engine and make sure it cleans itself up engine->_qobjectWrapperMap.insert(object, proxy); engine->_qobjectWrapperMapV8.insert(object, proxy); QPointer enginePtr = engine; object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { if (!enginePtr) return; QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); ScriptEngineV8::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); if (lookup != enginePtr->_qobjectWrapperMap.end()) { enginePtr->_qobjectWrapperMap.erase(lookup); } auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object); if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) { enginePtr->_qobjectWrapperMapV8.erase(lookupV8); } }); } return V8ScriptValue(engine, proxy.get()->toV8Value()); //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); } ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) { auto isolate = val.getEngine()->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Local context = val.constGetContext(); v8::Context::Scope contextScope(context); //V8TODO This shouldn't cause problems but I'm not sure if it's ok //v8::HandleScope handleScope(const_cast(val.constGetIsolate())); auto v8Value = val.constGet(); if (v8Value->IsNullOrUndefined()) { return nullptr; } if (!v8Value->IsObject()) { //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object"; return nullptr; } v8::Local v8Object = v8::Local::Cast(v8Value); if (v8Object->InternalFieldCount() != 3) { //qDebug(scriptengine) << "Cannot unwrap proxy - wrong number of internal fields"; return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { qDebug(scriptengine) << "Cannot unwrap proxy - internal fields don't point to object proxy"; return nullptr; } return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); } ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local &value) { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); //V8TODO: shouldn't there be context scope here? //v8::Local context = val.constGetContext(); //v8::Context::Scope contextScope(context); if (value->IsNullOrUndefined()) { //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object"; return nullptr; } if (!value->IsObject()) { //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object"; return nullptr; } v8::Local v8Object = v8::Local::Cast(value); if (v8Object->InternalFieldCount() != 3) { //qDebug(scriptengine) << "Cannot unwrap proxy - wrong number of internal fields"; return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { qDebug(scriptengine) << "Cannot unwrap proxy - internal fields don't point to object proxy"; return nullptr; } return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); } QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { ScriptObjectV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQObject() : nullptr; } ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); _v8Object.Reset(); if(_object) qDebug(scriptengine) << "Deleting object proxy: " << name(); if (_ownsObject) { QObject* qobject = _object; if(qobject) qobject->deleteLater(); } } void ScriptObjectV8Proxy::investigate() { QObject* qobject = _object; if (!qobject) { QStringList backtrace = _engine->currentContext()->backtrace(); qDebug(scriptengine) << "ScriptObjectV8Proxy::investigate: Object pointer is NULL, " << backtrace; } if (!qobject) return; auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope contextScope(_engine->getContext()); //auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate()); auto objectTemplate = v8::ObjectTemplate::New(_engine->getIsolate()); objectTemplate->SetInternalFieldCount(3); objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set, nullptr, nullptr, v8GetPropertyNames)); const QMetaObject* metaObject = qobject->metaObject(); //qDebug(scriptengine) << "Investigate: " << metaObject->className(); if (QString("ConsoleScriptingInterface") == metaObject->className()) { printf("ConsoleScriptingInterface"); } // discover properties int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; int num = metaObject->propertyCount(); for (int idx = startIdx; idx < num; ++idx) { QMetaProperty prop = metaObject->property(idx); if (!prop.isScriptable()) continue; //qDebug(scriptengine) << "Investigate: " << metaObject->className() << " Property: " << prop.name(); // always exclude child objects (at least until we decide otherwise) int metaTypeId = prop.userType(); if (metaTypeId != QMetaType::UnknownType) { QMetaType metaType(metaTypeId); if (metaType.flags() & QMetaType::PointerToQObject) { continue; } } auto v8Name = v8::String::NewFromUtf8(_engine->getIsolate(), prop.name()).ToLocalChecked(); PropertyDef& propDef = _props.insert(idx, PropertyDef(_engine, v8Name)).value(); propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter | ScriptValue::QObjectMember; if (prop.isConstant()) propDef.flags |= ScriptValue::ReadOnly; } // discover methods startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0; num = metaObject->methodCount(); QHash methodNames; for (int idx = startIdx; idx < num; ++idx) { QMetaMethod method = metaObject->method(idx); //qDebug(scriptengine) << "Investigate: " << metaObject->className() << " Method: " << method.name(); // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible /* if (method.access() != QMetaMethod::Public) continue;*/ bool isSignal = false; QByteArray szName = method.name(); switch (method.methodType()) { case QMetaMethod::Constructor: continue; case QMetaMethod::Signal: isSignal = true; break; case QMetaMethod::Slot: if (_wrapOptions & ScriptEngine::ExcludeSlots) { continue; } if (szName == "deleteLater") { continue; } break; default: break; } auto nameString = v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked(); V8ScriptString name(_engine, nameString); auto nameLookup = methodNames.find(name); if (isSignal) { if (nameLookup == methodNames.end()) { SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine, name.get())).value(); signalDef.name = name; signalDef.signal = method; //qDebug(scriptengine) << "Utf8Value 1: " << QString(*v8::String::Utf8Value(const_cast(_engine->getIsolate()), nameString)); //qDebug(scriptengine) << "Utf8Value 2: " << QString(*v8::String::Utf8Value(const_cast(_engine->getIsolate()), name.constGet())); //qDebug(scriptengine) << "toQString: " << name.toQString(); methodNames.insert(name, idx); } else { int originalMethodId = nameLookup.value(); SignalDefMap::iterator signalLookup = _signals.find(originalMethodId); Q_ASSERT(signalLookup != _signals.end()); SignalDef& signalDef = signalLookup.value(); Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount()); if (signalDef.signal.parameterCount() < method.parameterCount()) { signalDef.signal = method; } } } else { int parameterCount = method.parameterCount(); if(method.returnType() == QMetaType::UnknownType) { qCritical(scriptengine) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value"; } for (int i = 0; i < method.parameterCount(); i++) { if (method.parameterType(i) == QMetaType::UnknownType) { qCritical(scriptengine) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType"; } } if (nameLookup == methodNames.end()) { MethodDef& methodDef = _methods.insert(idx, MethodDef(_engine, name.get())).value(); methodDef.name = name; methodDef.numMaxParms = parameterCount; methodDef.methods.append(method); methodNames.insert(name, idx); } else { int originalMethodId = nameLookup.value(); MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); Q_ASSERT(methodLookup != _methods.end()); MethodDef& methodDef = methodLookup.value(); if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount; methodDef.methods.append(method); } } } v8::Local v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked(); v8Object->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQObjectProxy)); v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast(this)); // Properties added later will be stored in this object v8::Local propertiesObject = v8::Object::New(_engine->getIsolate()); v8Object->SetInternalField(2, propertiesObject); _v8Object.Reset(_engine->getIsolate(), v8Object); // Add all the methods objects as properties - this allows adding properties to a given method later. Is used by Script.request. // V8TODO: Should these be deleted when the script-owned object is destroyed? It needs checking if script-owned objects will be garbage-collected, or will self-referencing prevent it. for (auto i = _methods.begin(); i != _methods.end(); i++) { V8ScriptValue method = ScriptMethodV8Proxy::newMethod(_engine, qobject, V8ScriptValue(_engine, v8Object), i.value().methods, i.value().numMaxParms); if(!propertiesObject->Set(_engine->getContext(), i.value().name.constGet(), method.get()).FromMaybe(false)) { Q_ASSERT(false); } } } QString ScriptObjectV8Proxy::name() const { Q_ASSERT(_object); if (!_object) return ""; return _object ? _object->objectName() : ""; QString objectName = _object->objectName(); if (!objectName.isEmpty()) return objectName; return _object->metaObject()->className(); } ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); // V8TODO: this might be inefficient when there's large number of properties v8::Local context = _engine->getContext(); v8::Context::Scope contextScope(context); v8::String::Utf8Value nameStr(isolate, name.constGet()); // check for methods for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { v8::String::Utf8Value methodNameStr(isolate, trans.value().name.constGet()); //qDebug(scriptengine) << "queryProperty : " << *nameStr << " method: " << *methodNameStr; if (!(trans.value().name == name)) continue; *id = trans.key() | METHOD_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for properties for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { const PropertyDef& propDef = trans.value(); if (!(propDef.name == name)) continue; *id = trans.key() | PROPERTY_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for signals for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { if (!(trans.value().name == name)) continue; *id = trans.key() | SIGNAL_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } return QueryFlags(); } ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QObject* qobject = _object; if (!qobject) { return ScriptValue::PropertyFlags(); } switch (id & TYPE_MASK) { case PROPERTY_TYPE: { PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); if (lookup == _props.cend()) return ScriptValue::PropertyFlags(); const PropertyDef& propDef = lookup.value(); return propDef.flags; } case METHOD_TYPE: { MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK); if (lookup == _methods.cend()) return ScriptValue::PropertyFlags(); return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; } case SIGNAL_TYPE: { SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); if (lookup == _signals.cend()) return ScriptValue::PropertyFlags(); return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; } } return ScriptValue::PropertyFlags(); } void ScriptObjectV8Proxy::v8Get(v8::Local name, const v8::PropertyCallbackInfo& info) { v8::HandleScope handleScope(info.GetIsolate()); //V8TODO: should there be a context scope here? v8::String::Utf8Value utf8Value(info.GetIsolate(), name); //qDebug(scriptengine) << "Get: " << *utf8Value; v8::Local objectV8 = info.This(); ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qDebug(scriptengine) << "Proxy object not found when getting: " << *utf8Value; return; } V8ScriptValue object(proxy->_engine, objectV8); if (!name->IsString() && !name->IsSymbol()) { QString notStringMessage("ScriptObjectV8Proxy::v8Get: " + proxy->_engine->scriptValueDebugDetailsV8(V8ScriptValue(proxy->_engine, name))); qDebug(scriptengine) << notStringMessage; Q_ASSERT(false); } v8::Local v8NameString; /*if (name->IsString()) { v8NameString = v8::Local::Cast(name); } else { if (!name->ToString(info.GetIsolate()->GetCurrentContext()).ToLocal(&v8NameString)) { Q_ASSERT(false); } } if (name->IsSymbol()) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8Set: symbol: " + nameString.toQString(); }*/ if (name->IsString()) { V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); uint id; QueryFlags flags = proxy->queryProperty(object, nameString, HandlesReadAccess, &id); if (flags) { V8ScriptValue value = proxy->property(object, nameString, id); info.GetReturnValue().Set(value.get()); return; } } v8::Local property; if(info.This()->GetInternalField(2).As()->Get(proxy->_engine->getContext(), name).ToLocal(&property)) { info.GetReturnValue().Set(property); } else { qDebug(scriptengine) << "Value not found: " << *utf8Value; } } void ScriptObjectV8Proxy::v8Set(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { v8::HandleScope handleScope(info.GetIsolate()); //V8TODO: should there be a context scope here? v8::String::Utf8Value utf8Value(info.GetIsolate(), name); //qDebug(scriptengine) << "Set: " << *utf8Value; v8::Local objectV8 = info.This(); ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qDebug(scriptengine) << "Proxy object not found when setting: " << *utf8Value; return; } V8ScriptValue object(proxy->_engine, objectV8); if (!name->IsString() && !name->IsSymbol()) { QString notStringMessage("ScriptObjectV8Proxy::v8Set: " + proxy->_engine->scriptValueDebugDetailsV8(V8ScriptValue(proxy->_engine, name))); qDebug(scriptengine) << notStringMessage; Q_ASSERT(false); } /*v8::Local v8NameString; if (name->IsString()) { v8NameString = v8::Local::Cast(name); } else { if (!name->ToString(info.GetIsolate()->GetCurrentContext()).ToLocal(&v8NameString)) { Q_ASSERT(false); } } if (name->IsSymbol()) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8Set: symbol: " + nameString.toQString(); }*/ //V8ScriptString nameString(info.GetIsolate(), name->ToString(proxy->_engine->getContext()).ToLocalChecked()); if (name->IsString()) { V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); uint id; QueryFlags flags = proxy->queryProperty(object, nameString, HandlesWriteAccess, &id); if (flags) { proxy->setProperty(object, nameString, id, V8ScriptValue(proxy->_engine, value)); info.GetReturnValue().Set(value); return; } } // V8TODO: Should it be v8::Object or v8::Local? if (info.This()->GetInternalField(2).As()->Set(proxy->_engine->getContext(), name, value).FromMaybe(false)) { info.GetReturnValue().Set(value); } else { qDebug(scriptengine) << "Set failed: " << *utf8Value; } } void ScriptObjectV8Proxy::v8GetPropertyNames(const v8::PropertyCallbackInfo& info) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames called"; v8::HandleScope handleScope(info.GetIsolate()); auto context = info.GetIsolate()->GetCurrentContext(); v8::Context::Scope contextScope(context); v8::Local objectV8 = info.This(); ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames: Proxy object not found when listing"; return; } V8ScriptValue object(proxy->_engine, objectV8); //uint id; v8::Local properties = proxy->getPropertyNames(); v8::Local objectProperties; uint32_t propertiesLength = properties->Length(); if (info.This()->GetInternalField(2).As()->GetPropertyNames(context).ToLocal(&objectProperties)) { for (uint32_t n = 0; n < objectProperties->Length(); n++) { if(!properties->Set(context, propertiesLength+n, objectProperties->Get(context, n).ToLocalChecked()).FromMaybe(false)) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames: Cannot add member name"; } } } info.GetReturnValue().Set(properties); } v8::Local ScriptObjectV8Proxy::getPropertyNames() { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::EscapableHandleScope handleScope(_engine->getIsolate()); auto context = _engine->getContext(); v8::Context::Scope contextScope(_engine->getContext()); v8::Local properties = v8::Array::New(isolate, _props.size() + _methods.size() + _signals.size()); uint32_t position = 0; for (PropertyDefMap::const_iterator i = _props.begin(); i != _props.end(); i++){ if(!properties->Set(context, position++, i.value().name.constGet()).FromMaybe(false)) { qDebug(scriptengine) << "ScriptObjectV8Proxy::getPropertyNames: Cannot add property member name"; } } for (MethodDefMap::const_iterator i = _methods.begin(); i != _methods.end(); i++){ if(!properties->Set(context, position++, i.value().name.constGet()).FromMaybe(false)) { qDebug(scriptengine) << "ScriptObjectV8Proxy::getPropertyNames: Cannot add property member name"; } } for (SignalDefMap::const_iterator i = _signals.begin(); i != _signals.end(); i++){ if(!properties->Set(context, position++, i.value().name.constGet()).FromMaybe(false)) { qDebug(scriptengine) << "ScriptObjectV8Proxy::getPropertyNames: Cannot add property member name"; } } return handleScope.Escape(properties); } V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(_engine->getContext()); QObject* qobject = _object; if (!qobject) { _engine->getIsolate()->ThrowError("Referencing deleted native object"); return V8ScriptValue(_engine, v8::Null(isolate)); } const QMetaObject* metaObject = qobject->metaObject(); switch (id & TYPE_MASK) { case PROPERTY_TYPE: { int propId = id & ~TYPE_MASK; PropertyDefMap::const_iterator lookup = _props.find(propId); if (lookup == _props.cend()) return V8ScriptValue(_engine, v8::Null(isolate)); QMetaProperty prop = metaObject->property(propId); ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); ScriptPropertyContextV8Wrapper ourContext(scriptThis, _engine->currentContext()); ScriptContextGuard guard(&ourContext); QVariant varValue = prop.read(qobject); return _engine->castVariantToValue(varValue); } case METHOD_TYPE: { int methodId = id & ~TYPE_MASK; MethodDefMap::const_iterator lookup = _methods.find(methodId); if (lookup == _methods.cend()) return V8ScriptValue(_engine, v8::Null(isolate)); const MethodDef& methodDef = lookup.value(); for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) { if((*iter).returnType() == QMetaType::UnknownType) { qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name(); } } //V8TODO: is new method created during every call? It needs to be cached instead //bool isMethodDefined = false; v8::Local property; if(_v8Object.Get(isolate)->GetInternalField(2).As()->Get(_engine->getContext(), name.constGet()).ToLocal(&property)) { if (!property->IsUndefined()) { return V8ScriptValue(_engine, property); } } Q_ASSERT(false); qDebug(scriptengine) << "(This should not happen) Creating new method object for " << metaObject->className() << " " << name.toQString(); return ScriptMethodV8Proxy::newMethod(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms); } case SIGNAL_TYPE: { int signalId = id & ~TYPE_MASK; SignalDefMap::const_iterator defLookup = _signals.find(signalId); if (defLookup == _signals.cend()) return V8ScriptValue(_engine, v8::Null(isolate)); InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { instLookup = _signalInstances.insert(signalId, new ScriptSignalV8Proxy(_engine, qobject, object, defLookup.value().signal)); Q_ASSERT(instLookup != _signalInstances.cend()); } ScriptSignalV8Proxy* proxy = instLookup.value(); ScriptEngine::QObjectWrapOptions options = ScriptEngine::ExcludeSuperClassContents | //V8TODO ScriptEngine::ExcludeDeleteLater | ScriptEngine::PreferExistingWrapperObject; // It's not necessarily new, newQObject looks for it first in object wrapper map return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options); //return _engine->newQObject(proxy, ScriptEngine::ScriptOwnership, options); } } return V8ScriptValue(_engine, v8::Null(isolate)); } void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(_engine->getContext()); if (!(id & PROPERTY_TYPE)) return; QObject* qobject = _object; if (!qobject) { _engine->getIsolate()->ThrowError("Referencing deleted native object"); return; } int propId = id & ~TYPE_MASK; PropertyDefMap::const_iterator lookup = _props.find(propId); if (lookup == _props.cend()) return; const PropertyDef& propDef = lookup.value(); if (propDef.flags & ScriptValue::ReadOnly) return; const QMetaObject* metaObject = qobject->metaObject(); QMetaProperty prop = metaObject->property(propId); ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); ScriptPropertyContextV8Wrapper ourContext(scriptThis, _engine->currentContext()); ScriptContextGuard guard(&ourContext); int propTypeId = prop.userType(); Q_ASSERT(propTypeId != QMetaType::UnknownType); QVariant varValue; if(!_engine->castValueToVariant(value, varValue, propTypeId)) { QByteArray propTypeName = QMetaType(propTypeId).name(); QByteArray valTypeName = _engine->valueType(value).toLatin1(); isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName).toStdString().c_str()).ToLocalChecked()); return; } prop.write(qobject, varValue); } ScriptVariantV8Proxy::ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto) : _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { auto isolate = engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(engine->getContext()); auto variantDataTemplate = v8::ObjectTemplate::New(isolate); variantDataTemplate->SetInternalFieldCount(2); auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); variantData->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQVariant)); variantData->SetAlignedPointerInInternalField(1, reinterpret_cast(&_variant)); _v8Object.Reset(isolate, v8::Local::Cast(variantData)); _name = QString::fromLatin1(variant.typeName()); } ScriptVariantV8Proxy::~ScriptVariantV8Proxy() { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); //_v8ObjectTemplate.Reset(); _v8Object.Reset(); } V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) { auto isolate = engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(engine->getContext()); ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto); if (!protoProxy) { Q_ASSERT(protoProxy); //return engine->newVariant(variant); return V8ScriptValue(engine, v8::Undefined(isolate)); } // V8TODO probably needs connection to be deleted auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy); auto variantProxyTemplate = v8::ObjectTemplate::New(isolate); variantProxyTemplate->SetInternalFieldCount(2); variantProxyTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set, nullptr, nullptr, v8GetPropertyNames)); auto variantProxy = variantProxyTemplate->NewInstance(engine->getContext()).ToLocalChecked(); variantProxy->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQVariantProxy)); variantProxy->SetAlignedPointerInInternalField(1, reinterpret_cast(proxy)); return V8ScriptValue(engine, variantProxy); } ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) { auto isolate = val.getEngine()->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(val.getEngine()->getContext()); auto v8Value = val.constGet(); if (!v8Value->IsObject()) { return nullptr; } v8::Local v8Object = v8::Local::Cast(v8Value); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariantProxy) { return nullptr; } return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); } ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local &value) { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); if (!value->IsObject()) { return nullptr; } v8::Local v8Object = v8::Local::Cast(value); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariantProxy) { return nullptr; } return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); } QVariant* ScriptVariantV8Proxy::unwrapQVariantPointer(v8::Isolate* isolate, const v8::Local &value) { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); if (!value->IsObject()) { return nullptr; } v8::Local v8Object = v8::Local::Cast(value); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariant) { return nullptr; } return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); } void ScriptVariantV8Proxy::v8Get(v8::Local name, const v8::PropertyCallbackInfo& info) { v8::HandleScope handleScope(info.GetIsolate()); v8::String::Utf8Value utf8Name(info.GetIsolate(), name); v8::Local objectV8 = info.This(); ScriptVariantV8Proxy *proxy = ScriptVariantV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qDebug(scriptengine) << "Proxy object not found when getting: " << *utf8Name; return; } V8ScriptValue object(proxy->_engine, proxy->_v8Object.Get(info.GetIsolate())); if (name->IsString()) { V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); uint id; ScriptObjectV8Proxy::QueryFlags flags = proxy->_proto->queryProperty(object, nameString, ScriptObjectV8Proxy::HandlesReadAccess, &id); if (flags) { V8ScriptValue value = proxy->property(object, nameString, id); info.GetReturnValue().Set(value.get()); return; } } qDebug(scriptengine) << "Value not found: " << *utf8Name; // V8TODO: this is done differently for variant proxy - use internal field of _v8Object instead? /*v8::Local property; if(info.This()->GetInternalField(2).As()->Get(proxy->_engine->getContext(), name).ToLocal(&property)) { info.GetReturnValue().Set(property); } else { qDebug(scriptengine) << "Value not found: " << *utf8Value; }*/ } void ScriptVariantV8Proxy::v8Set(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { v8::HandleScope handleScope(info.GetIsolate()); v8::String::Utf8Value utf8Name(info.GetIsolate(), name); v8::Local objectV8 = info.This(); ScriptVariantV8Proxy *proxy = ScriptVariantV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qDebug(scriptengine) << "Proxy object not found when getting: " << *utf8Name; return; } V8ScriptValue object(proxy->_engine, objectV8); if (!name->IsString() && !name->IsSymbol()) { QString notStringMessage("ScriptObjectV8Proxy::v8Set: " + proxy->_engine->scriptValueDebugDetailsV8(V8ScriptValue(proxy->_engine, name))); qDebug(scriptengine) << notStringMessage; Q_ASSERT(false); } if (name->IsString()) { V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); uint id; ScriptObjectV8Proxy::QueryFlags flags = proxy->_proto->queryProperty(object, nameString, ScriptObjectV8Proxy::HandlesWriteAccess, &id); if (flags) { proxy->setProperty(object, nameString, id, V8ScriptValue(proxy->_engine, value)); info.GetReturnValue().Set(value); return; } } // V8TODO: this is done differently for variant proxy - use internal field of _v8Object instead? /*if (info.This()->GetInternalField(2).As()->Set(proxy->_engine->getContext(), name, value).FromMaybe(false)) { info.GetReturnValue().Set(value); } else { qDebug(scriptengine) << "Set failed: " << *utf8Name; }*/ qDebug(scriptengine) << "Set failed: " << *utf8Name; } void ScriptVariantV8Proxy::v8GetPropertyNames(const v8::PropertyCallbackInfo& info) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames called"; v8::HandleScope handleScope(info.GetIsolate()); auto context = info.GetIsolate()->GetCurrentContext(); v8::Context::Scope contextScope(context); v8::Local objectV8 = info.This(); ScriptVariantV8Proxy *proxy = ScriptVariantV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames: Proxy object not found when listing"; return; } V8ScriptValue object(proxy->_engine, objectV8); v8::Local properties = proxy->_proto->getPropertyNames(); v8::Local objectProperties; // V8TODO: this is done differently for variant proxy - use internal field of _v8Object instead? /*uint32_t propertiesLength = properties->Length(); if (info.This()->GetInternalField(2).As()->GetPropertyNames(context).ToLocal(&objectProperties)) { for (uint32_t n = 0; n < objectProperties->Length(); n++) { if(!properties->Set(context, propertiesLength+n, objectProperties->Get(context, n).ToLocalChecked()).FromMaybe(false)) { qDebug(scriptengine) << "ScriptObjectV8Proxy::v8GetPropertyNames: Cannot add member name"; } } }*/ info.GetReturnValue().Set(properties); } QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) { ScriptVariantV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQVariant() : QVariant(); // V8TODO } ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QList& metas, int numMaxParams) : _numMaxParams(numMaxParams), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) { } ScriptMethodV8Proxy::~ScriptMethodV8Proxy() { qDebug(scriptengine) << "ScriptMethodV8Proxy destroyed"; printf("ScriptMethodV8Proxy destroyed"); } V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QList& metas, int numMaxParams) { auto isolate = engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(engine->getContext()); auto methodDataTemplate = v8::ObjectTemplate::New(isolate); methodDataTemplate->SetInternalFieldCount(2); auto methodData = methodDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); methodData->SetAlignedPointerInInternalField(0, const_cast(internalPointsToMethodProxy)); // V8TODO it needs to be deleted somehow on object destruction methodData->SetAlignedPointerInInternalField(1, reinterpret_cast(new ScriptMethodV8Proxy(engine, object, lifetime, metas, numMaxParams))); auto v8Function = v8::Function::New(engine->getContext(), callback, methodData, numMaxParams).ToLocalChecked(); return V8ScriptValue(engine, v8Function); } QString ScriptMethodV8Proxy::fullName() const { Q_ASSERT(_object); if (!_object) return ""; Q_ASSERT(!_metas.isEmpty()); const QMetaMethod& firstMethod = _metas.front(); QString objectName = _object->objectName(); if (!objectName.isEmpty()) { return QString("%1.%2").arg(objectName, firstMethod.name()); } return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name()); } // V8TODO /*bool ScriptMethodV8Proxy::supportsExtension(Extension extension) const { switch (extension) { case Callable: return true; default: return false; } }*/ void ScriptMethodV8Proxy::callback(const v8::FunctionCallbackInfo& arguments) { v8::Locker locker(arguments.GetIsolate()); v8::Isolate::Scope isolateScope(arguments.GetIsolate()); v8::HandleScope handleScope(arguments.GetIsolate()); //Q_ASSERT(!arguments.GetIsolate()->GetCurrentContext().IsEmpty()); v8::Context::Scope contextScope(arguments.GetIsolate()->GetCurrentContext()); if (!arguments.Data()->IsObject()) { arguments.GetIsolate()->ThrowError("Method value is not an object"); return; } v8::Local data = v8::Local::Cast(arguments.Data()); /*if (!arguments.Data()->IsCallable()) { arguments.GetIsolate()->ThrowError("Method value is not callable"); return; }*/ if (data->InternalFieldCount() != 2) { arguments.GetIsolate()->ThrowError("Incorrect number of internal fields during method call"); return; } if (data->GetAlignedPointerFromInternalField(0) != internalPointsToMethodProxy) { arguments.GetIsolate()->ThrowError("Internal field 0 of ScriptMethodV8Proxy V8 object has wrong value"); return; } ScriptMethodV8Proxy *proxy = reinterpret_cast(data->GetAlignedPointerFromInternalField(1)); proxy->call(arguments); } void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& arguments) { v8::Isolate *isolate = arguments.GetIsolate(); Q_ASSERT(isolate == _engine->getIsolate()); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(_engine->getContext()); QObject* qobject = _object; if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } //v8::HandleScope handleScope(_engine->getIsolate()); int scriptNumArgs = arguments.Length(); int numArgs = std::min(scriptNumArgs, _numMaxParams); const int scriptValueTypeId = qMetaTypeId(); int parameterConversionFailureId = 0; int parameterConversionFailureCount = 0; int num_metas = _metas.size(); QVector< QList > qScriptArgLists; QVector< QVector > qGenArgsVectors; QVector< QList > qVarArgLists; qScriptArgLists.resize(num_metas); qGenArgsVectors.resize(num_metas); qVarArgLists.resize(num_metas); bool isValidMetaSelected = false; int bestMeta = 0; int bestConversionPenaltyScore = 0; for (int i = 0; i < num_metas; i++) { const QMetaMethod& meta = _metas[i]; int methodNumArgs = meta.parameterCount(); if (methodNumArgs != numArgs) { continue; } qGenArgsVectors[i].resize(10); int conversionPenaltyScore = 0; int conversionFailures = 0; for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); v8::Local argVal = arguments[arg]; if (methodArgTypeId == scriptValueTypeId) { qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, argVal)))); qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); } else if (methodArgTypeId == QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); } } else { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); const QVariant& converted = qVarArgLists[i].back(); conversionPenaltyScore = _engine->computeCastPenalty(V8ScriptValue(_engine, argVal), methodArgTypeId); // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant // A const_cast is needed because calling data() would detach the QVariant. qGenArgsVectors[i][arg] = QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); } } } if (conversionFailures) { if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { parameterConversionFailureCount = conversionFailures; parameterConversionFailureId = meta.methodIndex(); } continue; } if (!isValidMetaSelected) { isValidMetaSelected = true; bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } } if (isValidMetaSelected) { // V8TODO: is this the correct wrapper? ScriptContextV8Wrapper ourContext(_engine, &arguments, _engine->getContext(), _engine->currentContext()->parentContext()); ScriptContextGuard guard(&ourContext); const QMetaMethod& meta = _metas[bestMeta]; int returnTypeId = meta.returnType(); QVector &qGenArgs = qGenArgsVectors[bestMeta]; // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's // nothing in there for return values so these need to be explicitly runtime-registered! if (returnTypeId == QMetaType::UnknownType) { QString methodName = fullName(); qDebug(scriptengine) << "returnTypeId == QMetaType::UnknownType for method " << methodName; _engine->logBacktrace(""); //Q_ASSERT(false); } if (returnTypeId == QMetaType::UnknownType) { 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()); return; } else if (returnTypeId == QMetaType::Void) { bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); } return; } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result); arguments.GetReturnValue().Set(v8Result.get()); return; } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); QVariant qRetVal(returnTypeId, static_cast(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); bool success = meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal); arguments.GetReturnValue().Set(v8Result.get()); return; } } // we failed to convert the call to C++, try to create a somewhat sane error message if (parameterConversionFailureCount == 0) { isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return; } const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); int methodNumArgs = meta.parameterCount(); Q_ASSERT(methodNumArgs == numArgs); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); v8::Local argVal = arguments[arg]; if (methodArgTypeId != scriptValueTypeId) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); QByteArray argTypeName = _engine->valueType(V8ScriptValue(_engine, argVal)).toLatin1(); QString errorMessage = QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName); qDebug(scriptengine) << errorMessage << "\n Backtrace:" << _engine->currentContext()->backtrace(); isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); //context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") // .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); return; } } } QString errorMessage = QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()); qDebug(scriptengine) << errorMessage; isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); // V8TODO: it happens sometimes for some reason Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? return; } //V8TODO: was this used anywhere? /*QVariant ScriptMethodV8Proxy::extension(Extension extension, const QVariant& argument) { if (extension != Callable) return QVariant(); V8ScriptContext* context = qvariant_cast(argument); QObject* qobject = _object; if (!qobject) { context->throwError(V8ScriptContext::ReferenceError, "Referencing deleted native object"); return QVariant(); } int scriptNumArgs = context->argumentCount(); int numArgs = std::min(scriptNumArgs, _numMaxParams); const int scriptValueTypeId = qMetaTypeId(); int parameterConversionFailureId = 0; int parameterConversionFailureCount = 0; int num_metas = _metas.size(); QVector< QList > qScriptArgLists; QVector< QVector > qGenArgsVectors; QVector< QList > qVarArgLists; qScriptArgLists.resize(num_metas); qGenArgsVectors.resize(num_metas); qVarArgLists.resize(num_metas); bool isValidMetaSelected = false; int bestMeta = 0; int bestConversionPenaltyScore = 0; for (int i = 0; i < num_metas; i++) { const QMetaMethod& meta = _metas[i]; int methodNumArgs = meta.parameterCount(); if (methodNumArgs != numArgs) { continue; } qGenArgsVectors[i].resize(10); int conversionPenaltyScore = 0; int conversionFailures = 0; for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); V8ScriptValue argVal = context->argument(arg); if (methodArgTypeId == scriptValueTypeId) { qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, argVal))); qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); } else if (methodArgTypeId == QMetaType::QVariant) { qVarArgLists[i].append(argVal.toVariant()); qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); } else { QVariant varArgVal; if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); const QVariant& converted = qVarArgLists[i].back(); conversionPenaltyScore = _engine->computeCastPenalty(argVal, methodArgTypeId); // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant // A const_cast is needed because calling data() would detach the QVariant. qGenArgsVectors[i][arg] = QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); } } } if (conversionFailures) { if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { parameterConversionFailureCount = conversionFailures; parameterConversionFailureId = meta.methodIndex(); } continue; } if (!isValidMetaSelected) { isValidMetaSelected = true; bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { bestMeta = i; bestConversionPenaltyScore = conversionPenaltyScore; } } if (isValidMetaSelected) { ScriptContextV8Wrapper ourContext(_engine, context); ScriptContextGuard guard(&ourContext); const QMetaMethod& meta = _metas[bestMeta]; int returnTypeId = meta.returnType(); QVector &qGenArgs = qGenArgsVectors[bestMeta]; // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's // nothing in there for return values so these need to be explicitly runtime-registered! Q_ASSERT(returnTypeId != QMetaType::UnknownType); if (returnTypeId == QMetaType::UnknownType) { _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()); return QVariant(); } else if (returnTypeId == QMetaType::Void) { bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); } return QVariant(); } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return QVariant(); } V8ScriptValue qResult = ScriptValueV8Wrapper::fullUnwrap(_engine, result); return QVariant::fromValue(qResult); } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); QVariant qRetVal(returnTypeId, static_cast(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); bool success = meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return QVariant(); } V8ScriptValue qResult = _engine->castVariantToValue(qRetVal); return QVariant::fromValue(qResult); } } // we failed to convert the call to C++, try to create a somewhat sane error message if (parameterConversionFailureCount == 0) { _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); return QVariant(); } const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); int methodNumArgs = meta.parameterCount(); Q_ASSERT(methodNumArgs == numArgs); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); V8ScriptValue argVal = context->argument(arg); if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked()); context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); return QVariant(); } } } context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? return QVariant(); }*/ ScriptSignalV8Proxy::~ScriptSignalV8Proxy() { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); _v8Context.Reset(); } QString ScriptSignalV8Proxy::fullName() const { Q_ASSERT(_object); if (!_object) return ""; QString objectName = _object->objectName(); if (!objectName.isEmpty()) { return QString("%1.%2").arg(objectName, _meta.name()); } return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); } // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments); if (id != 0 || call != QMetaObject::InvokeMetaMethod) { return id; } _callCounter++; if (_callCounter % 10 == 0) { qDebug() << "Script engine: " << _engine->manager()->getFilename() << " Signal proxy " << fullName() << " call count: " << _callCounter << " total time: " << _totalCallTime_s; } QElapsedTimer callTimer; callTimer.start(); auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); //V8ScriptValueList args(isolate, v8::Null(isolate)); // Moved to inside of the lock - could it cause crashes if left here? /*v8::Local args[Q_METAMETHOD_INVOKE_MAX_ARGS]; int numArgs = _meta.parameterCount(); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QVariant argValue(methodArgTypeId, arguments[arg+1]); args[arg] = _engine->castVariantToValue(argValue).get(); }*/ QList connections; withReadLock([&]{ connections = _connections; }); // V8TODO: this may cause deadlocks on connect/disconnect, so the connect/disconnect procedure needs to be reworked. // It should probably add events to a separate list that would be processed before and after all the events for the signal. //withReadLock([&]{ { // V8TODO: check all other lambda functions to make sure they have handle scope - could they cause crashes otherwise? v8::HandleScope handleScope(isolate); // V8TODO: should we use function creation context, or context in which connect happened? auto context = _engine->getContext(); v8::Context::Scope contextScope(context); v8::Local args[Q_METAMETHOD_INVOKE_MAX_ARGS]; int numArgs = _meta.parameterCount(); for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); QVariant argValue(methodArgTypeId, arguments[arg + 1]); args[arg] = _engine->castVariantToValue(argValue).get(); } //for (ConnectionList::iterator iter = _connections.begin(); iter != _connections.end(); ++iter) { for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { Connection& conn = *iter; { /*if (!conn.callback.get()->IsFunction()) { auto stringV8 = conn.callback.get()->ToDetailString(context).ToLocalChecked(); QString error = *v8::String::Utf8Value(_engine->getIsolate(), stringV8); qDebug() << error; Q_ASSERT(false); } v8::Local callback = v8::Local::Cast(conn.callback.get()); auto functionContext = callback->CreationContext(); v8::Context::Scope functionContextScope(functionContext); _engine->pushContext(functionContext);*/ /*auto functionContext = _v8Context.Get(_engine->getIsolate()); _engine->pushContext(functionContext); v8::Context::Scope functionContextScope(functionContext);*/ auto functionContext = context; Q_ASSERT(!conn.callback.get().IsEmpty()); Q_ASSERT(!conn.callback.get()->IsUndefined()); if (conn.callback.get()->IsNull()) { qDebug() << "ScriptSignalV8Proxy::qt_metacall: Connection callback is Null"; _engine->popContext(); continue; } if (!conn.callback.get()->IsFunction()) { auto stringV8 = conn.callback.get()->ToDetailString(functionContext).ToLocalChecked(); QString error = *v8::String::Utf8Value(_engine->getIsolate(), stringV8); qDebug() << error; Q_ASSERT(false); } v8::Local callback = v8::Local::Cast(conn.callback.get()); v8::Local v8This; if (conn.thisValue.get()->IsObject()) { v8This = conn.thisValue.get(); } else { v8This = functionContext->Global(); } v8::TryCatch tryCatch(isolate); callback->Call(functionContext, v8This, numArgs, args); if (tryCatch.HasCaught()) { qCDebug(scriptengine) << "Signal proxy " << fullName() << " connection call failed: \"" << _engine->formatErrorMessageFromTryCatch(tryCatch) << "\nThis provided: " << conn.thisValue.get()->IsObject(); } //_engine->popContext(); } } } //}); _totalCallTime_s += callTimer.elapsed() / 1000.0f; return -1; } int ScriptSignalV8Proxy::discoverMetaCallIdx() { const QMetaObject* ourMeta = metaObject(); return ourMeta->methodCount(); } ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) { auto iterOut = resultWithReadLock([&]{ v8::Locker locker(_engine->getIsolate()); v8::Isolate::Scope isolateScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope contextScope(_engine->getContext()); ConnectionList::iterator iter; for (iter = _connections.begin(); iter != _connections.end(); ++iter) { Connection& conn = *iter; if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) { break; } } return iter; }); return iterOut; } /*void ScriptSignalV8Proxy::connect(ScriptValue arg0) { connect(arg0, ScriptValue(_engine->getIsolate(), v8::Undefined(_engine->getIsolate()))); }*/ void ScriptSignalV8Proxy::connect(ScriptValue arg0, ScriptValue arg1) { v8::Isolate *isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(_engine->getContext()); QObject* qobject = _object; if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } //v8::HandleScope handleScope(isolate); // untangle the arguments V8ScriptValue callback(_engine, v8::Null(isolate)); V8ScriptValue callbackThis(_engine, v8::Null(isolate)); if (arg1.isFunction()) { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); auto unwrappedArg1 = ScriptValueV8Wrapper::unwrap(arg1); if (!unwrappedArg0 || !unwrappedArg1) { Q_ASSERT(false); return; } callbackThis = unwrappedArg0->toV8Value(); callback = unwrappedArg1->toV8Value(); } else { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); if (!unwrappedArg0) { Q_ASSERT(false); return; } callback = unwrappedArg0->toV8Value(); } if (!callback.get()->IsFunction()) { isolate->ThrowError("Function expected as argument to 'connect'"); return; } // are we already connected? { ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup != _connections.end()) { return; // already exists } } // add a reference to ourselves to the destination callback Q_ASSERT(!callback.get().IsEmpty()); Q_ASSERT(!callback.get()->IsUndefined()); Q_ASSERT(callback.get()->IsFunction()); v8::Local destFunction = v8::Local::Cast(callback.get()); v8::Local destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); v8::Local destData; // V8TODO: I'm not sure which context to use here //auto destFunctionContext = destFunction->CreationContext(); auto destFunctionContext = _engine->getContext(); Q_ASSERT(thisObject().isObject()); V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)); ScriptSignalV8Proxy* thisProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)->toQObject()); Q_ASSERT(thisProxy); qDebug(scriptengine) << "ScriptSignalV8Proxy::connect: " << thisProxy->fullName() << " fullName: " << fullName(); //Q_ASSERT(destFunction->InternalFieldCount() == 4); //Q_ASSERT(destData.get()->IsArray()); //v8::Local destData = destFunction->GetInternalField(3); if (!destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData)) { Q_ASSERT(false); } if (destData->IsArray()) { v8::Local destArray = v8::Local::Cast(destData); int length = destArray->Length();//destData.property("length").toInteger(); // V8TODO: Maybe copying array is unnecessary? v8::Local newArray = v8::Array::New(isolate, length + 1); bool foundIt = false; for (int idx = 0; idx < length && !foundIt; ++idx) { v8::Local entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); { qDebug() << "ScriptSignalV8Proxy::connect: entry details: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, entry)); Q_ASSERT(entry->IsObject()); V8ScriptValue v8EntryObject(_engine, entry); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)); // For debugging ScriptSignalV8Proxy* entryProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)->toQObject()); Q_ASSERT(thisProxy); qDebug(scriptengine) << "ScriptSignalV8Proxy::connect: entry proxy: " << entryProxy->fullName(); } if (!newArray->Set(destFunctionContext, idx, entry).FromMaybe(false)) { Q_ASSERT(false); } } if (!newArray->Set(destFunctionContext, length, v8ThisObject.get()).FromMaybe(false)) { //if (!newArray->Set(destFunctionContext, length, v8ThisObject.get()).FromMaybe(false)) { Q_ASSERT(false); } if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } else { v8::Local newArray = v8::Array::New(isolate, 1); if (!newArray->Set(destFunctionContext, 0, v8ThisObject.get()).FromMaybe(false)) { Q_ASSERT(false); } if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } /*{ V8ScriptValueList args; args << thisObject(); destData.property("push").call(destData, args); }*/ // add this to our internal list of connections Connection newConnection(callbackThis, callback); //newConn.callback = callback; //newConn.thisValue = callbackThis; withWriteLock([&]{ _connections.append(newConnection); }); // inform Qt that we're connecting to this signal if (!_isConnected) { auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = true; } } /*void ScriptSignalV8Proxy::disconnect(ScriptValue arg0) { disconnect(arg0, V8ScriptValue(_engine->getIsolate(), v8::Undefined(_engine->getIsolate()))); }*/ void ScriptSignalV8Proxy::disconnect(ScriptValue arg0, ScriptValue arg1) { QObject* qobject = _object; v8::Isolate *isolate = _engine->getIsolate(); if (!qobject) { isolate->ThrowError("Referencing deleted native object"); return; } v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); v8::Context::Scope contextScope(_engine->getContext()); // untangle the arguments V8ScriptValue callback(_engine, v8::Null(isolate)); V8ScriptValue callbackThis(_engine, v8::Null(isolate)); if (arg1.isFunction()) { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); auto unwrappedArg1 = ScriptValueV8Wrapper::unwrap(arg1); if (!unwrappedArg0 || !unwrappedArg1) { Q_ASSERT(false); return; } callbackThis = unwrappedArg0->toV8Value(); callback = unwrappedArg1->toV8Value(); } else { auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); if (!unwrappedArg0) { Q_ASSERT(false); return; } callback = unwrappedArg0->toV8Value(); } if (!callback.get()->IsFunction()) { isolate->ThrowError("Function expected as argument to 'disconnect'"); return; } // locate this connection in our list of connections { ConnectionList::iterator lookup = findConnection(callbackThis, callback); if (lookup == _connections.end()) { return; // not here } // remove it from our internal list of connections withWriteLock([&]{ _connections.erase(lookup); }); } // remove a reference to ourselves from the destination callback v8::Local destFunction = v8::Local::Cast(callback.get()); v8::Local destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); v8::Local destData; //auto destFunctionContext = destFunction->CreationContext(); auto destFunctionContext = _engine->getContext(); Q_ASSERT(thisObject().isObject()); V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)); // For debugging ScriptSignalV8Proxy* thisProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)->toQObject()); Q_ASSERT(thisProxy); qDebug(scriptengine) << "ScriptSignalV8Proxy::disconnect: " << thisProxy->fullName() << " fullName: " << fullName(); //V8ScriptValue destData = callback.data(); //Q_ASSERT(destData->IsArray()); if (!destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData)) { Q_ASSERT(false); } if (destData->IsArray()) { v8::Local destArray = v8::Local::Cast(destData); int length = destArray->Length();//destData.property("length").toInteger(); v8::Local newArray = v8::Array::New(isolate, length - 1); bool foundIt = false; int newIndex = 0; //for (int idx = 0; idx < length && !foundIt; ++idx) { for (int idx = 0; idx < length; ++idx) { v8::Local entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); // For debugging: { _engine->logBacktrace("ScriptSignalV8Proxy::disconnect"); qDebug() << "ScriptSignalV8Proxy::disconnect: entry details: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, entry)) << " Array: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, destArray)); Q_ASSERT(entry->IsObject()); V8ScriptValue v8EntryObject(_engine, entry); Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)); // For debugging ScriptSignalV8Proxy* entryProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)->toQObject()); Q_ASSERT(thisProxy); qDebug(scriptengine) << "ScriptSignalV8Proxy::disconnect: entry proxy: " << entryProxy->fullName(); } if (entry->StrictEquals(v8ThisObject.get())) { //V8TODO: compare proxies instead? foundIt = true; qDebug() << "ScriptSignalV8Proxy::disconnect foundIt"; //V8ScriptValueList args; //args << idx << 1; //destData.property("splice").call(destData, args); } else { if (!newArray->Set(destFunctionContext, newIndex, entry).FromMaybe(false)) { Q_ASSERT(false); } newIndex++; } } Q_ASSERT(foundIt); if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { Q_ASSERT(false); } } else { Q_ASSERT(false); } // inform Qt that we're no longer connected to this signal if (_connections.empty()) { Q_ASSERT(_isConnected); bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); Q_ASSERT(result); _isConnected = false; } }