1209 lines · 56.6 KB · C++
RawDownload
////  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 *)0x13373000; // 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());            }        }        // 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<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);            }            auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object);            if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) {                enginePtr->_qobjectWrapperMapV8.erase(lookupV8);            }        });    }     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()) {        //qDebug(scriptengine) << "Cannot unwrap proxy - value is not an object";        return nullptr;    }    v8::Local<v8::Object> v8Object = v8::Local<v8::Object>::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<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1));} QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) {    ScriptObjectV8Proxy* proxy = unwrapProxy(val);    return proxy ? proxy->toQObject() : nullptr;} ScriptObjectV8Proxy::~ScriptObjectV8Proxy() {    if(_object) qDebug(scriptengine) << "Deleting object proxy: " << name();    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());    v8::Context::Scope contextScope(_engine->getContext());        auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate());    objectTemplate->SetInternalFieldCount(3);    objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set));        const QMetaObject* metaObject = qobject->metaObject();     //qDebug(scriptengine) << "Investigate: " << metaObject->className();    if (QString("Vec3") == metaObject->className()) {        printf("Vec3");    }    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->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);        //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->getIsolate(), nameString);        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;                //qDebug(scriptengine) << "Utf8Value 1: " << QString(*v8::String::Utf8Value(const_cast<v8::Isolate*>(_engine->getIsolate()), nameString));                //qDebug(scriptengine) << "Utf8Value 2: " << QString(*v8::String::Utf8Value(const_cast<v8::Isolate*>(_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->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->SetInternalField(2, v8::Object::New(_engine->getIsolate()));     _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) {    v8::HandleScope handleScope(_engine->getIsolate());    // V8TODO: this might be inefficient when there's large number of properties    v8::Local<v8::Context> context = _engine->getContext();    v8::String::Utf8Value nameStr(_engine->getIsolate(), name.constGet());     // check for methods    for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) {        v8::String::Utf8Value methodNameStr(_engine->getIsolate(), 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<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {    v8::HandleScope handleScope(info.GetIsolate());    v8::String::Utf8Value utf8Value(info.GetIsolate(), name);    //qDebug(scriptengine) << "Get: " << *utf8Value;    V8ScriptValue object(info.GetIsolate(), info.This());    ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(object);    if (!proxy) {        qDebug(scriptengine) << "Proxy object not found when getting: " << *utf8Value;        return;    }    V8ScriptString nameString(info.GetIsolate(), v8::Local<v8::String>::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());    } else {        v8::Local<v8::Value> property;        if(info.This()->GetInternalField(2).As<v8::Object>()->Get(proxy->_engine->getContext(), name).ToLocal(&property)) {            info.GetReturnValue().Set(property);        } else {            qDebug(scriptengine) << "Value not found: " << *utf8Value;        }    }} void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) {    v8::HandleScope handleScope(info.GetIsolate());    v8::String::Utf8Value utf8Value(info.GetIsolate(), name);    qDebug(scriptengine) << "Set: " << *utf8Value;    V8ScriptValue object(info.GetIsolate(), info.This());    ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(object);    if (!proxy) {        qDebug(scriptengine) << "Proxy object not found when setting: " << *utf8Value;        return;    }    V8ScriptString nameString(info.GetIsolate(), v8::Local<v8::String>::Cast(name));    //V8ScriptString nameString(info.GetIsolate(), name->ToString(proxy->_engine->getContext()).ToLocalChecked());    uint id;    QueryFlags flags = proxy->queryProperty(object, nameString, HandlesWriteAccess, &id);    if (flags) {        proxy->setProperty(object, nameString, id, V8ScriptValue(info.GetIsolate(), value));        info.GetReturnValue().Set(value);    } else {        // V8TODO: Should it be v8::Object or v8::Local<v8::Object>?        if (info.This()->GetInternalField(2).As<v8::Object>()->Set(proxy->_engine->getContext(), name, value).FromMaybe(false)) {            info.GetReturnValue().Set(value);        } else {            qDebug(scriptengine) << "Set failed: " << *utf8Value;        }    }}  V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) {    v8::HandleScope handleScope(_engine->getIsolate());    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();                }            } //V8TODO: is new method created during every call? It needs to be cached instead            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) {    v8::HandleScope handleScope(_engine->getIsolate());    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;

Lines 1–500 of 1209.