//// 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 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// #include "ScriptObjectV8Proxy.h" #include <QtCore/QList>#include <QtCore/QSharedPointer> #include "../ScriptEngineLogging.h" #include "ScriptContextV8Wrapper.h"#include "ScriptValueV8Wrapper.h" Q_DECLARE_METATYPE(V8ScriptContext*)Q_DECLARE_METATYPE(ScriptValue)//V8TODO?//Q_DECLARE_METATYPE(V8ScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>)Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>) // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1static const void *internalPointsToQObjectProxy = (void *)0x13370000;static const void *internalPointsToQVariantProxy = (void *)0x13371000;static const void *internalPointsToSignalProxy = (void *)0x13372000;static const void *internalPointsToMethodProxy = (void *)0x13372000; // 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 neededclass ScriptPropertyContextQtWrapper final : public ScriptContext {public: // construction inline ScriptPropertyContextQtWrapper(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())) { 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<ScriptObjectV8Proxy> proxy = lookup.value().lock(); // V8TODO: is conversion to QVariant and back needed? if (proxy) { return V8ScriptValue(engine->getIsolate(), 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<ScriptObjectV8Proxy>::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<ScriptObjectV8Proxy> proxy = lookup.value().lock(); //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; if (proxy) { return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); } } // register the wrapper with the engine and make sure it cleans itself up engine->_qobjectWrapperMap.insert(object, proxy); QPointer<ScriptEngineV8> 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); } }); } return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));} ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) { //V8TODO This shouldn't cause problems but I'm not sure if it's ok v8::HandleScope handleScope(const_cast<v8::Isolate*>(val.constGetIsolate())); auto v8Value = val.constGet(); if (!v8Value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQObjectProxy) { return nullptr; } return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));} QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { ScriptObjectV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQObject() : nullptr;} ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { if (_ownsObject) { QObject* qobject = _object; if(qobject) qobject->deleteLater(); }} void ScriptObjectV8Proxy::investigate() { QObject* qobject = _object; Q_ASSERT(qobject); if (!qobject) return; v8::HandleScope handleScope(_engine->getIsolate()); auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate()); objectTemplate->SetInternalFieldCount(3); objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set)); const QMetaObject* metaObject = qobject->metaObject(); // 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; // 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->getIsolate(), 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<V8ScriptString, int> methodNames; for (int idx = startIdx; idx < num; ++idx) { QMetaMethod method = metaObject->method(idx); // 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; } V8ScriptString name(_engine->getIsolate(), v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked()); auto nameLookup = methodNames.find(name); if (isSignal) { if (nameLookup == methodNames.end()) { SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine->getIsolate(), name.get())).value(); signalDef.name = name; signalDef.signal = method; 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->getIsolate(), 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<v8::Object> v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked(); v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy)); v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this)); _v8Object.Reset(_engine->getIsolate(), v8Object);} 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) { // check for properties for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { const PropertyDef& propDef = trans.value(); if (propDef.name.constGet() != name.constGet()) continue; *id = trans.key() | PROPERTY_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for methods for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { if (trans.value().name.constGet() != name.constGet()) continue; *id = trans.key() | METHOD_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for signals for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { if (trans.value().name.constGet() != name.constGet()) 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<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { // V8TODO //info.GetReturnValue().Set();} void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) { // V8TODO info.GetReturnValue().Set(value);} V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QObject* qobject = _object; if (!qobject) { _engine->getIsolate()->ThrowError("Referencing deleted native object"); return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); } 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->getIsolate(), v8::Null(_engine->getIsolate())); QMetaProperty prop = metaObject->property(propId); ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); ScriptPropertyContextQtWrapper 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->getIsolate(), v8::Null(_engine->getIsolate())); 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(); } } 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->getIsolate(), v8::Null(_engine->getIsolate())); 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; return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options); //return _engine->newQObject(proxy, ScriptEngine::ScriptOwnership, options); } } return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate()));} void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { 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)); ScriptPropertyContextQtWrapper 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(); _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), 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) { _name = QString::fromLatin1(variant.typeName());} V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) { ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto); if (!protoProxy) { Q_ASSERT(protoProxy); //return engine->newVariant(variant); return V8ScriptValue(engine->getIsolate(), v8::Undefined(engine->getIsolate())); } // V8TODO probably needs connection to be deleted // V8TODO what to do with proto variable? auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy); auto variantDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); variantDataTemplate->SetInternalFieldCount(2); auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); variantData->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQVariantProxy)); variantData->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(proxy)); return V8ScriptValue(engine->getIsolate(), variantData);} ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) { // V8TODO v8::HandleScope handleScope(const_cast<v8::Isolate*>(val.constGetIsolate())); auto v8Value = val.constGet(); if (!v8Value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQVariantProxy) { return nullptr; } return reinterpret_cast<ScriptVariantV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));} QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) { ScriptVariantV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQVariant() : QVariant();} ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QList<QMetaMethod>& metas, int numMaxParms) : _numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) {} V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QList<QMetaMethod>& metas, int numMaxParams) { auto methodDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); methodDataTemplate->SetInternalFieldCount(2);Lines 1–500 of 1118. Load more
// // 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 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 // #include "ScriptObjectV8Proxy.h" #include <QtCore/QList> #include <QtCore/QSharedPointer> #include "../ScriptEngineLogging.h" #include "ScriptContextV8Wrapper.h" #include "ScriptValueV8Wrapper.h" Q_DECLARE_METATYPE(V8ScriptContext*) Q_DECLARE_METATYPE(ScriptValue) //V8TODO? //Q_DECLARE_METATYPE(V8ScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>) Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>) // 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 *)0x13372000; // 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 ScriptPropertyContextQtWrapper final : public ScriptContext { public: // construction inline ScriptPropertyContextQtWrapper(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())) { 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<ScriptObjectV8Proxy> proxy = lookup.value().lock(); // V8TODO: is conversion to QVariant and back needed? if (proxy) { return V8ScriptValue(engine->getIsolate(), 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<ScriptObjectV8Proxy>::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<ScriptObjectV8Proxy> proxy = lookup.value().lock(); //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; if (proxy) { return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); } } // register the wrapper with the engine and make sure it cleans itself up engine->_qobjectWrapperMap.insert(object, proxy); QPointer<ScriptEngineV8> 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); } }); } return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); } ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) { //V8TODO This shouldn't cause problems but I'm not sure if it's ok v8::HandleScope handleScope(const_cast<v8::Isolate*>(val.constGetIsolate())); auto v8Value = val.constGet(); if (!v8Value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQObjectProxy) { return nullptr; } return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); } QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { ScriptObjectV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQObject() : nullptr; } ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { if (_ownsObject) { QObject* qobject = _object; if(qobject) qobject->deleteLater(); } } void ScriptObjectV8Proxy::investigate() { QObject* qobject = _object; Q_ASSERT(qobject); if (!qobject) return; v8::HandleScope handleScope(_engine->getIsolate()); auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate()); objectTemplate->SetInternalFieldCount(3); objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set)); const QMetaObject* metaObject = qobject->metaObject(); // 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; // 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->getIsolate(), 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<V8ScriptString, int> methodNames; for (int idx = startIdx; idx < num; ++idx) { QMetaMethod method = metaObject->method(idx); // 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; } V8ScriptString name(_engine->getIsolate(), v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked()); auto nameLookup = methodNames.find(name); if (isSignal) { if (nameLookup == methodNames.end()) { SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine->getIsolate(), name.get())).value(); signalDef.name = name; signalDef.signal = method; 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->getIsolate(), 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<v8::Object> v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked(); v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy)); v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this)); _v8Object.Reset(_engine->getIsolate(), v8Object); } 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) { // check for properties for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { const PropertyDef& propDef = trans.value(); if (propDef.name.constGet() != name.constGet()) continue; *id = trans.key() | PROPERTY_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for methods for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { if (trans.value().name.constGet() != name.constGet()) continue; *id = trans.key() | METHOD_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for signals for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { if (trans.value().name.constGet() != name.constGet()) 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<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { // V8TODO //info.GetReturnValue().Set(); } void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) { // V8TODO info.GetReturnValue().Set(value); } V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QObject* qobject = _object; if (!qobject) { _engine->getIsolate()->ThrowError("Referencing deleted native object"); return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); } 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->getIsolate(), v8::Null(_engine->getIsolate())); QMetaProperty prop = metaObject->property(propId); ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); ScriptPropertyContextQtWrapper 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->getIsolate(), v8::Null(_engine->getIsolate())); 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(); } } 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->getIsolate(), v8::Null(_engine->getIsolate())); 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; return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options); //return _engine->newQObject(proxy, ScriptEngine::ScriptOwnership, options); } } return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); } void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { 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)); ScriptPropertyContextQtWrapper 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(); _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), 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) { _name = QString::fromLatin1(variant.typeName()); } V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) { ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto); if (!protoProxy) { Q_ASSERT(protoProxy); //return engine->newVariant(variant); return V8ScriptValue(engine->getIsolate(), v8::Undefined(engine->getIsolate())); } // V8TODO probably needs connection to be deleted // V8TODO what to do with proto variable? auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy); auto variantDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); variantDataTemplate->SetInternalFieldCount(2); auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); variantData->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQVariantProxy)); variantData->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(proxy)); return V8ScriptValue(engine->getIsolate(), variantData); } ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) { // V8TODO v8::HandleScope handleScope(const_cast<v8::Isolate*>(val.constGetIsolate())); auto v8Value = val.constGet(); if (!v8Value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); if (v8Object->InternalFieldCount() != 2) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQVariantProxy) { return nullptr; } return reinterpret_cast<ScriptVariantV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); } QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) { ScriptVariantV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQVariant() : QVariant(); } ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QList<QMetaMethod>& metas, int numMaxParms) : _numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) { } V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QList<QMetaMethod>& metas, int numMaxParams) { auto methodDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); methodDataTemplate->SetInternalFieldCount(2);