//// 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 <QElapsedTimer>#include <QtCore/QList>#include <QtCore/QSharedPointer> #include "../ScriptEngineLogging.h" #include "ScriptContextV8Wrapper.h"#include "ScriptValueV8Wrapper.h"#include "ScriptEngineLoggingV8.h" Q_DECLARE_METATYPE(ScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>)Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>) // These values are put into internal fields of V8 objects, to signalize what kind of data is the pointer in another// internal field pointing to. Proxy unwrapping functions recognize proxies by checking for these values in internal field 0// of V8 object. // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1static const void *internalPointsToQObjectProxy = (void *)0x13370000;// Internal field value of object pointing to ScriptObjectV8Proxy is changed to this value upon proxy's deletionstatic const void *internalPointsToDeletedQObjectProxy = (void *)0x13370010;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 prototypestatic const void *internalPointsToQVariantInProxy = (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 neededclass 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(); } // Name of the file in which message was generated. Empty string when no file name is available. virtual int currentLineNumber() const override { return _parent->currentLineNumber(); } // Number of the line on which message was generated. -1 if there line number is not available. virtual QString currentFileName() const override { return _parent->currentFileName(); } 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) { 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<ScriptObjectV8Proxy> proxy = lookup.value().lock(); if (proxy) { return V8ScriptValue(engine, proxy.get()->toV8Value()); } } } 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; qCCritical(scriptengine_v8) << "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 V8ScriptValue(engine, proxy.get()->toV8Value()); } } // register the wrapper with the engine and make sure it cleans itself up engine->_qobjectWrapperMap.insert(object, proxy); engine->_qobjectWrapperMapV8.insert(object, proxy); QPointer<ScriptEngineV8> enginePtr = engine; object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { if (!enginePtr) return; // Lookup needs to be done twice, because _qobjectWrapperMapProtect must not be locked when disconnecting signals QSharedPointer<ScriptObjectV8Proxy> proxy; { QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object); if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) { proxy = lookupV8.value(); } } if (proxy) { for (auto signal : proxy->_signalInstances) { if (signal) { signal->disconnectAllScriptSignalProxies(); } } } 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());} 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<v8::Context> context = val.constGetContext(); v8::Context::Scope contextScope(context); auto v8Value = val.constGet(); Q_ASSERT(!v8Value.IsEmpty()); if (v8Value->IsNullOrUndefined()) { return nullptr; } if (!v8Value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(v8Value); if (v8Object->InternalFieldCount() != 3) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy"; return nullptr; } return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));} ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local<v8::Value> &value) { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); if (value->IsNullOrUndefined()) { return nullptr; } if (!value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(value); if (v8Object->InternalFieldCount() != 3) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy"; return nullptr; } ScriptObjectV8Proxy* proxy = reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); if (proxy) { Q_ASSERT(!proxy->_wasDestroyed); } return proxy;} QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { ScriptObjectV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQObject() : nullptr;} ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { for (auto signal : _signalInstances) { if (signal) { signal->disconnectAllScriptSignalProxies(); } } _wasDestroyed = true; if (_ownsObject) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); _v8Object.Reset(); QObject* qobject = _object; if(qobject) qobject->deleteLater(); } else { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); if (_object) qCDebug(scriptengine_v8) << "Deleting object proxy: " << name(); Q_ASSERT(!_v8Object.Get(isolate)->IsNullOrUndefined()); // This prevents unwrap function from unwrapping proxy that was deleted _v8Object.Get(isolate)->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToDeletedQObjectProxy)); _v8Object.Reset(); }} void ScriptObjectV8Proxy::investigate() { QObject* qobject = _object; if (!qobject) { QStringList backtrace = _engine->currentContext()->backtrace(); qCDebug(scriptengine_v8) << "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()); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); const QMetaObject* metaObject = qobject->metaObject(); auto objectTemplate = _engine->getObjectProxyTemplate(); 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; // 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; } } PropertyDef& propDef = _props.insert(idx, PropertyDef(prop.name(), idx)).value(); _propNameMap.insert(prop.name(), &propDef); 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<QString, 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::Private) continue; bool isSignal = false; QByteArray szName = method.name(); switch (method.methodType()) { case QMetaMethod::Constructor: continue; case QMetaMethod::Signal: if (szName == "destroyed") { continue; } if (szName == "objectNameChanged") { continue; } 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(szName); if (isSignal) { if (nameLookup == methodNames.end()) { SignalDef& signalDef = _signals.insert(idx, SignalDef(szName, idx)).value(); signalDef.name = szName; signalDef.signal = method; _signalNameMap.insert(szName, &signalDef); methodNames.insert(szName, 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) { qCCritical(scriptengine_v8) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value"; } for (int i = 0; i < method.parameterCount(); i++) { if (method.parameterType(i) == QMetaType::UnknownType) { qCCritical(scriptengine_v8) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType"; } } if (nameLookup == methodNames.end()) { MethodDef& methodDef = _methods.insert(idx, MethodDef(szName, idx)).value(); methodDef.name = szName; methodDef.numMaxParams = parameterCount; methodDef.methods.append(method); _methodNameMap.insert(szName, &methodDef); methodNames.insert(szName, idx); } else { int originalMethodId = nameLookup.value(); MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); Q_ASSERT(methodLookup != _methods.end()); MethodDef& methodDef = methodLookup.value(); if(methodDef.numMaxParams < parameterCount) methodDef.numMaxParams = parameterCount; methodDef.methods.append(method); } } } v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(context).ToLocalChecked(); v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy)); v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this)); _v8Object.Reset(_engine->getIsolate(), v8Object); if (_ownsObject) { _v8Object.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); } // Properties added later will be stored in this object v8::Local<v8::Object> propertiesObject = v8::Object::New(_engine->getIsolate()); v8Object->SetInternalField(2, propertiesObject); // Add all the methods objects as properties - this allows adding properties to a given method later. Is used by Script.request. for (auto i = _methods.begin(); i != _methods.end(); i++) { V8ScriptValue method = ScriptMethodV8Proxy::newMethod(_engine, qobject, V8ScriptValue(_engine, v8Object), i.value().methods, i.value().numMaxParams); if(!propertiesObject->Set(context, v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(), method.get()).FromMaybe(false)) { Q_ASSERT(false); } }} void ScriptObjectV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo<ScriptObjectV8Proxy>& info) { auto proxy = info.GetParameter(); proxy->_v8Object.Reset(); info.GetParameter()->_object->deleteLater();} 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); v8::Local<v8::Context> context = _engine->getContext(); v8::Context::Scope contextScope(context); QString nameStr(*v8::String::Utf8Value(isolate, name.constGet())); // V8TODO: registering methods on V8 side would make API calls a lot faster // check for methods MethodNameMap::const_iterator method = _methodNameMap.find(nameStr); if (method != _methodNameMap.cend()) { const MethodDef* methodDef = method.value(); *id = methodDef->_id | METHOD_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for properties PropertyNameMap::const_iterator prop = _propNameMap.find(nameStr); if (prop != _propNameMap.cend()) { const PropertyDef* propDef = prop.value(); *id = propDef->_id | PROPERTY_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for signals // V8TODO: this should use _signalNameMap QHash for faster search for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { if (!(trans.value().name == nameStr)) 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) { v8::HandleScope handleScope(info.GetIsolate()); v8::String::Utf8Value utf8Value(info.GetIsolate(), name); v8::Local<v8::Value> objectV8 = info.This(); ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qCDebug(scriptengine_v8) << "Proxy object not found when getting: " << *utf8Value; return; } V8ScriptValue object(proxy->_engine, objectV8);Lines 1–500 of 1613. 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-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 <QElapsedTimer> #include <QtCore/QList> #include <QtCore/QSharedPointer> #include "../ScriptEngineLogging.h" #include "ScriptContextV8Wrapper.h" #include "ScriptValueV8Wrapper.h" #include "ScriptEngineLoggingV8.h" Q_DECLARE_METATYPE(ScriptValue) Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>) Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>) // These values are put into internal fields of V8 objects, to signalize what kind of data is the pointer in another // internal field pointing to. Proxy unwrapping functions recognize proxies by checking for these values in internal field 0 // of V8 object. // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1 static const void *internalPointsToQObjectProxy = (void *)0x13370000; // Internal field value of object pointing to ScriptObjectV8Proxy is changed to this value upon proxy's deletion static const void *internalPointsToDeletedQObjectProxy = (void *)0x13370010; 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 *internalPointsToQVariantInProxy = (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(); } // Name of the file in which message was generated. Empty string when no file name is available. virtual int currentLineNumber() const override { return _parent->currentLineNumber(); } // Number of the line on which message was generated. -1 if there line number is not available. virtual QString currentFileName() const override { return _parent->currentFileName(); } 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) { 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<ScriptObjectV8Proxy> proxy = lookup.value().lock(); if (proxy) { return V8ScriptValue(engine, proxy.get()->toV8Value()); } } } 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; qCCritical(scriptengine_v8) << "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 V8ScriptValue(engine, proxy.get()->toV8Value()); } } // register the wrapper with the engine and make sure it cleans itself up engine->_qobjectWrapperMap.insert(object, proxy); engine->_qobjectWrapperMapV8.insert(object, proxy); QPointer<ScriptEngineV8> enginePtr = engine; object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { if (!enginePtr) return; // Lookup needs to be done twice, because _qobjectWrapperMapProtect must not be locked when disconnecting signals QSharedPointer<ScriptObjectV8Proxy> proxy; { QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object); if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) { proxy = lookupV8.value(); } } if (proxy) { for (auto signal : proxy->_signalInstances) { if (signal) { signal->disconnectAllScriptSignalProxies(); } } } 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()); } 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<v8::Context> context = val.constGetContext(); v8::Context::Scope contextScope(context); auto v8Value = val.constGet(); Q_ASSERT(!v8Value.IsEmpty()); if (v8Value->IsNullOrUndefined()) { return nullptr; } if (!v8Value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(v8Value); if (v8Object->InternalFieldCount() != 3) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy"; return nullptr; } return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); } ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local<v8::Value> &value) { v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); if (value->IsNullOrUndefined()) { return nullptr; } if (!value->IsObject()) { return nullptr; } v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::Cast(value); if (v8Object->InternalFieldCount() != 3) { return nullptr; } if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy"; return nullptr; } ScriptObjectV8Proxy* proxy = reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); if (proxy) { Q_ASSERT(!proxy->_wasDestroyed); } return proxy; } QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { ScriptObjectV8Proxy* proxy = unwrapProxy(val); return proxy ? proxy->toQObject() : nullptr; } ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { for (auto signal : _signalInstances) { if (signal) { signal->disconnectAllScriptSignalProxies(); } } _wasDestroyed = true; if (_ownsObject) { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); _v8Object.Reset(); QObject* qobject = _object; if(qobject) qobject->deleteLater(); } else { auto isolate = _engine->getIsolate(); v8::Locker locker(isolate); v8::Isolate::Scope isolateScope(isolate); v8::HandleScope handleScope(isolate); if (_object) qCDebug(scriptengine_v8) << "Deleting object proxy: " << name(); Q_ASSERT(!_v8Object.Get(isolate)->IsNullOrUndefined()); // This prevents unwrap function from unwrapping proxy that was deleted _v8Object.Get(isolate)->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToDeletedQObjectProxy)); _v8Object.Reset(); } } void ScriptObjectV8Proxy::investigate() { QObject* qobject = _object; if (!qobject) { QStringList backtrace = _engine->currentContext()->backtrace(); qCDebug(scriptengine_v8) << "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()); auto context = _engine->getContext(); v8::Context::Scope contextScope(context); const QMetaObject* metaObject = qobject->metaObject(); auto objectTemplate = _engine->getObjectProxyTemplate(); 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; // 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; } } PropertyDef& propDef = _props.insert(idx, PropertyDef(prop.name(), idx)).value(); _propNameMap.insert(prop.name(), &propDef); 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<QString, 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::Private) continue; bool isSignal = false; QByteArray szName = method.name(); switch (method.methodType()) { case QMetaMethod::Constructor: continue; case QMetaMethod::Signal: if (szName == "destroyed") { continue; } if (szName == "objectNameChanged") { continue; } 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(szName); if (isSignal) { if (nameLookup == methodNames.end()) { SignalDef& signalDef = _signals.insert(idx, SignalDef(szName, idx)).value(); signalDef.name = szName; signalDef.signal = method; _signalNameMap.insert(szName, &signalDef); methodNames.insert(szName, 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) { qCCritical(scriptengine_v8) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value"; } for (int i = 0; i < method.parameterCount(); i++) { if (method.parameterType(i) == QMetaType::UnknownType) { qCCritical(scriptengine_v8) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType"; } } if (nameLookup == methodNames.end()) { MethodDef& methodDef = _methods.insert(idx, MethodDef(szName, idx)).value(); methodDef.name = szName; methodDef.numMaxParams = parameterCount; methodDef.methods.append(method); _methodNameMap.insert(szName, &methodDef); methodNames.insert(szName, idx); } else { int originalMethodId = nameLookup.value(); MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); Q_ASSERT(methodLookup != _methods.end()); MethodDef& methodDef = methodLookup.value(); if(methodDef.numMaxParams < parameterCount) methodDef.numMaxParams = parameterCount; methodDef.methods.append(method); } } } v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(context).ToLocalChecked(); v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy)); v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this)); _v8Object.Reset(_engine->getIsolate(), v8Object); if (_ownsObject) { _v8Object.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); } // Properties added later will be stored in this object v8::Local<v8::Object> propertiesObject = v8::Object::New(_engine->getIsolate()); v8Object->SetInternalField(2, propertiesObject); // Add all the methods objects as properties - this allows adding properties to a given method later. Is used by Script.request. for (auto i = _methods.begin(); i != _methods.end(); i++) { V8ScriptValue method = ScriptMethodV8Proxy::newMethod(_engine, qobject, V8ScriptValue(_engine, v8Object), i.value().methods, i.value().numMaxParams); if(!propertiesObject->Set(context, v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(), method.get()).FromMaybe(false)) { Q_ASSERT(false); } } } void ScriptObjectV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo<ScriptObjectV8Proxy>& info) { auto proxy = info.GetParameter(); proxy->_v8Object.Reset(); info.GetParameter()->_object->deleteLater(); } 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); v8::Local<v8::Context> context = _engine->getContext(); v8::Context::Scope contextScope(context); QString nameStr(*v8::String::Utf8Value(isolate, name.constGet())); // V8TODO: registering methods on V8 side would make API calls a lot faster // check for methods MethodNameMap::const_iterator method = _methodNameMap.find(nameStr); if (method != _methodNameMap.cend()) { const MethodDef* methodDef = method.value(); *id = methodDef->_id | METHOD_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for properties PropertyNameMap::const_iterator prop = _propNameMap.find(nameStr); if (prop != _propNameMap.cend()) { const PropertyDef* propDef = prop.value(); *id = propDef->_id | PROPERTY_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for signals // V8TODO: this should use _signalNameMap QHash for faster search for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { if (!(trans.value().name == nameStr)) 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) { v8::HandleScope handleScope(info.GetIsolate()); v8::String::Utf8Value utf8Value(info.GetIsolate(), name); v8::Local<v8::Value> objectV8 = info.This(); ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); if (!proxy) { qCDebug(scriptengine_v8) << "Proxy object not found when getting: " << *utf8Value; return; } V8ScriptValue object(proxy->_engine, objectV8);