Showing the changes to libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp (1 of 40 files). Show all
Unified · Split
| @@ -0,0 +1,1105 @@ | |||
| 1 | // | ||
| 2 | // ScriptObjectV8Proxy.cpp | ||
| 3 | // libraries/script-engine/src/v8 | ||
| 4 | // | ||
| 5 | // Created by Heather Anderson on 12/5/21. | ||
| 6 | // Modified for V8 by dr Karol Suprynowicz on 2022/10/08 | ||
| 7 | // Copyright 2021 Vircadia contributors. | ||
| 8 | // Copyright 2022 Overte e.V. | ||
| 9 | // | ||
| 10 | // Distributed under the Apache License, Version 2.0. | ||
| 11 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 12 | // | ||
| 13 | |||
| 14 | #include "ScriptObjectV8Proxy.h" | ||
| 15 | |||
| 16 | #include <QtCore/QList> | ||
| 17 | #include <QtCore/QSharedPointer> | ||
| 18 | |||
| 19 | #include "../ScriptEngineLogging.h" | ||
| 20 | |||
| 21 | #include "ScriptContextV8Wrapper.h" | ||
| 22 | #include "ScriptValueV8Wrapper.h" | ||
| 23 | |||
| 24 | Q_DECLARE_METATYPE(V8ScriptContext*) | ||
| 25 | Q_DECLARE_METATYPE(ScriptValue) | ||
| 26 | //V8TODO? | ||
| 27 | //Q_DECLARE_METATYPE(V8ScriptValue) | ||
| 28 | |||
| 29 | Q_DECLARE_METATYPE(QSharedPointer<ScriptObjectV8Proxy>) | ||
| 30 | Q_DECLARE_METATYPE(QSharedPointer<ScriptVariantV8Proxy>) | ||
| 31 | |||
| 32 | // Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1 | ||
| 33 | static const void *internalPointsToQObjectProxy = (void *)0x13370000; | ||
| 34 | static const void *internalPointsToQVariantProxy = (void *)0x13371000; | ||
| 35 | static const void *internalPointsToSignalProxy = (void *)0x13372000; | ||
| 36 | static const void *internalPointsToMethodProxy = (void *)0x13372000; | ||
| 37 | |||
| 38 | // Used strictly to replace the "this" object value for property access. May expand to a full context element | ||
| 39 | // if we find it necessary to, but hopefully not needed | ||
| 40 | class ScriptPropertyContextQtWrapper final : public ScriptContext { | ||
| 41 | public: // construction | ||
| 42 | inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) : | ||
| 43 | _parent(parentContext), _object(object) {} | ||
| 44 | |||
| 45 | public: // ScriptContext implementation | ||
| 46 | virtual int argumentCount() const override { return _parent->argumentCount(); } | ||
| 47 | virtual ScriptValue argument(int index) const override { return _parent->argument(index); } | ||
| 48 | virtual QStringList backtrace() const override { return _parent->backtrace(); } | ||
| 49 | virtual ScriptValue callee() const override { return _parent->callee(); } | ||
| 50 | virtual ScriptEnginePointer engine() const override { return _parent->engine(); } | ||
| 51 | virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } | ||
| 52 | virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); } | ||
| 53 | virtual ScriptValue thisObject() const override { return _object; } | ||
| 54 | virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); } | ||
| 55 | virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } | ||
| 56 | |||
| 57 | private: // storage | ||
| 58 | ScriptContext* _parent; | ||
| 59 | const ScriptValue& _object; | ||
| 60 | }; | ||
| 61 | |||
| 62 | ScriptObjectV8Proxy::ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) : | ||
| 63 | _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object), | ||
| 64 | _v8ObjectTemplate(engine->getIsolate(), v8::ObjectTemplate::New(engine->getIsolate())) { | ||
| 65 | investigate(); | ||
| 66 | } | ||
| 67 | |||
| 68 | V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* object, | ||
| 69 | ScriptEngine::ValueOwnership ownership, | ||
| 70 | const ScriptEngine::QObjectWrapOptions& options) { | ||
| 71 | // do we already have a valid wrapper for this QObject? | ||
| 72 | { | ||
| 73 | QMutexLocker guard(&engine->_qobjectWrapperMapProtect); | ||
| 74 | ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); | ||
| 75 | if (lookup != engine->_qobjectWrapperMap.end()) { | ||
| 76 | QSharedPointer<ScriptObjectV8Proxy> proxy = lookup.value().lock(); | ||
| 77 | // V8TODO: is conversion to QVariant and back needed? | ||
| 78 | if (proxy) { | ||
| 79 | return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); | ||
| 80 | } | ||
| 81 | //if (proxy) return engine->newObject(proxy.get(), engine->newVariant(QVariant::fromValue(proxy)));; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | bool ownsObject; | ||
| 86 | switch (ownership) { | ||
| 87 | case ScriptEngine::QtOwnership: | ||
| 88 | ownsObject = false; | ||
| 89 | break; | ||
| 90 | case ScriptEngine::ScriptOwnership: | ||
| 91 | ownsObject = true; | ||
| 92 | break; | ||
| 93 | case ScriptEngine::AutoOwnership: | ||
| 94 | ownsObject = !object->parent(); | ||
| 95 | break; | ||
| 96 | default: | ||
| 97 | ownsObject = false; | ||
| 98 | qCritical() << "Wrong ScriptEngine::ValueOwnership value: " << ownership; | ||
| 99 | break; | ||
| 100 | } | ||
| 101 | |||
| 102 | // create the wrapper | ||
| 103 | auto proxy = QSharedPointer<ScriptObjectV8Proxy>::create(engine, object, ownsObject, options); | ||
| 104 | |||
| 105 | { | ||
| 106 | QMutexLocker guard(&engine->_qobjectWrapperMapProtect); | ||
| 107 | |||
| 108 | // check again to see if someone else created the wrapper while we were busy | ||
| 109 | ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); | ||
| 110 | if (lookup != engine->_qobjectWrapperMap.end()) { | ||
| 111 | QSharedPointer<ScriptObjectV8Proxy> proxy = lookup.value().lock(); | ||
| 112 | //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; | ||
| 113 | if (proxy) { | ||
| 114 | return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | // register the wrapper with the engine and make sure it cleans itself up | ||
| 119 | engine->_qobjectWrapperMap.insert(object, proxy); | ||
| 120 | QPointer<ScriptEngineV8> enginePtr = engine; | ||
| 121 | object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { | ||
| 122 | if (!enginePtr) return; | ||
| 123 | QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); | ||
| 124 | ScriptEngineV8::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); | ||
| 125 | if (lookup != enginePtr->_qobjectWrapperMap.end()) { | ||
| 126 | enginePtr->_qobjectWrapperMap.erase(lookup); | ||
| 127 | } | ||
| 128 | }); | ||
| 129 | } | ||
| 130 | |||
| 131 | return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); | ||
| 132 | //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); | ||
| 133 | } | ||
| 134 | |||
| 135 | ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) { | ||
| 136 | auto v8Value = val.constGet(); | ||
| 137 | if (!v8Value->IsObject()) { | ||
| 138 | return nullptr; | ||
| 139 | } | ||
| 140 | v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); | ||
| 141 | if (v8Object->InternalFieldCount() != 2) { | ||
| 142 | return nullptr; | ||
| 143 | } | ||
| 144 | if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQObjectProxy) { | ||
| 145 | return nullptr; | ||
| 146 | } | ||
| 147 | return reinterpret_cast<ScriptObjectV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); | ||
| 148 | } | ||
| 149 | |||
| 150 | QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { | ||
| 151 | ScriptObjectV8Proxy* proxy = unwrapProxy(val); | ||
| 152 | return proxy ? proxy->toQObject() : nullptr; | ||
| 153 | } | ||
| 154 | |||
| 155 | ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { | ||
| 156 | if (_ownsObject) { | ||
| 157 | QObject* qobject = _object; | ||
| 158 | if(qobject) qobject->deleteLater(); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | void ScriptObjectV8Proxy::investigate() { | ||
| 163 | QObject* qobject = _object; | ||
| 164 | Q_ASSERT(qobject); | ||
| 165 | if (!qobject) return; | ||
| 166 | |||
| 167 | auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate()); | ||
| 168 | objectTemplate->SetInternalFieldCount(3); | ||
| 169 | objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set)); | ||
| 170 | |||
| 171 | const QMetaObject* metaObject = qobject->metaObject(); | ||
| 172 | |||
| 173 | // discover properties | ||
| 174 | int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; | ||
| 175 | int num = metaObject->propertyCount(); | ||
| 176 | for (int idx = startIdx; idx < num; ++idx) { | ||
| 177 | QMetaProperty prop = metaObject->property(idx); | ||
| 178 | if (!prop.isScriptable()) continue; | ||
| 179 | |||
| 180 | // always exclude child objects (at least until we decide otherwise) | ||
| 181 | int metaTypeId = prop.userType(); | ||
| 182 | if (metaTypeId != QMetaType::UnknownType) { | ||
| 183 | QMetaType metaType(metaTypeId); | ||
| 184 | if (metaType.flags() & QMetaType::PointerToQObject) { | ||
| 185 | continue; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | auto v8Name = v8::String::NewFromUtf8(_engine->getIsolate(), prop.name()).ToLocalChecked(); | ||
| 190 | PropertyDef& propDef = _props.insert(idx, PropertyDef(_engine->getIsolate(), v8Name)).value(); | ||
| 191 | propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter | | ||
| 192 | ScriptValue::QObjectMember; | ||
| 193 | if (prop.isConstant()) propDef.flags |= ScriptValue::ReadOnly; | ||
| 194 | } | ||
| 195 | |||
| 196 | // discover methods | ||
| 197 | startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0; | ||
| 198 | num = metaObject->methodCount(); | ||
| 199 | QHash<V8ScriptString, int> methodNames; | ||
| 200 | for (int idx = startIdx; idx < num; ++idx) { | ||
| 201 | QMetaMethod method = metaObject->method(idx); | ||
| 202 | |||
| 203 | // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible | ||
| 204 | /* if (method.access() != QMetaMethod::Public) continue;*/ | ||
| 205 | |||
| 206 | bool isSignal = false; | ||
| 207 | QByteArray szName = method.name(); | ||
| 208 | |||
| 209 | switch (method.methodType()) { | ||
| 210 | case QMetaMethod::Constructor: | ||
| 211 | continue; | ||
| 212 | case QMetaMethod::Signal: | ||
| 213 | isSignal = true; | ||
| 214 | break; | ||
| 215 | case QMetaMethod::Slot: | ||
| 216 | if (_wrapOptions & ScriptEngine::ExcludeSlots) { | ||
| 217 | continue; | ||
| 218 | } | ||
| 219 | if (szName == "deleteLater") { | ||
| 220 | continue; | ||
| 221 | } | ||
| 222 | break; | ||
| 223 | default: | ||
| 224 | break; | ||
| 225 | } | ||
| 226 | |||
| 227 | V8ScriptString name(_engine->getIsolate(), v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked()); | ||
| 228 | auto nameLookup = methodNames.find(name); | ||
| 229 | if (isSignal) { | ||
| 230 | if (nameLookup == methodNames.end()) { | ||
| 231 | SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine->getIsolate(), name.get())).value(); | ||
| 232 | signalDef.name = name; | ||
| 233 | signalDef.signal = method; | ||
| 234 | methodNames.insert(name, idx); | ||
| 235 | } else { | ||
| 236 | int originalMethodId = nameLookup.value(); | ||
| 237 | SignalDefMap::iterator signalLookup = _signals.find(originalMethodId); | ||
| 238 | Q_ASSERT(signalLookup != _signals.end()); | ||
| 239 | SignalDef& signalDef = signalLookup.value(); | ||
| 240 | Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount()); | ||
| 241 | if (signalDef.signal.parameterCount() < method.parameterCount()) { | ||
| 242 | signalDef.signal = method; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | } else { | ||
| 246 | int parameterCount = method.parameterCount(); | ||
| 247 | if(method.returnType() == QMetaType::UnknownType) { | ||
| 248 | qCritical(scriptengine) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value"; | ||
| 249 | } | ||
| 250 | for (int i = 0; i < method.parameterCount(); i++) { | ||
| 251 | if (method.parameterType(i) == QMetaType::UnknownType) { | ||
| 252 | qCritical(scriptengine) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType"; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | if (nameLookup == methodNames.end()) { | ||
| 256 | MethodDef& methodDef = _methods.insert(idx, MethodDef(_engine->getIsolate(), name.get())).value(); | ||
| 257 | methodDef.name = name; | ||
| 258 | methodDef.numMaxParms = parameterCount; | ||
| 259 | methodDef.methods.append(method); | ||
| 260 | methodNames.insert(name, idx); | ||
| 261 | } else { | ||
| 262 | int originalMethodId = nameLookup.value(); | ||
| 263 | MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); | ||
| 264 | Q_ASSERT(methodLookup != _methods.end()); | ||
| 265 | MethodDef& methodDef = methodLookup.value(); | ||
| 266 | if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount; | ||
| 267 | methodDef.methods.append(method); | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | v8::Local<v8::Object> v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked(); | ||
| 273 | v8Object->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQObjectProxy)); | ||
| 274 | v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(this)); | ||
| 275 | |||
| 276 | _v8Object.Reset(_engine->getIsolate(), v8Object); | ||
| 277 | } | ||
| 278 | |||
| 279 | QString ScriptObjectV8Proxy::name() const { | ||
| 280 | Q_ASSERT(_object); | ||
| 281 | if (!_object) return ""; | ||
| 282 | return _object ? _object->objectName() : ""; | ||
| 283 | QString objectName = _object->objectName(); | ||
| 284 | if (!objectName.isEmpty()) return objectName; | ||
| 285 | return _object->metaObject()->className(); | ||
| 286 | } | ||
| 287 | |||
| 288 | ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) { | ||
| 289 | // check for properties | ||
| 290 | for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { | ||
| 291 | const PropertyDef& propDef = trans.value(); | ||
| 292 | if (propDef.name.constGet() != name.constGet()) continue; | ||
| 293 | *id = trans.key() | PROPERTY_TYPE; | ||
| 294 | return flags & (HandlesReadAccess | HandlesWriteAccess); | ||
| 295 | } | ||
| 296 | |||
| 297 | // check for methods | ||
| 298 | for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { | ||
| 299 | if (trans.value().name.constGet() != name.constGet()) continue; | ||
| 300 | *id = trans.key() | METHOD_TYPE; | ||
| 301 | return flags & (HandlesReadAccess | HandlesWriteAccess); | ||
| 302 | } | ||
| 303 | |||
| 304 | // check for signals | ||
| 305 | for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { | ||
| 306 | if (trans.value().name.constGet() != name.constGet()) continue; | ||
| 307 | *id = trans.key() | SIGNAL_TYPE; | ||
| 308 | return flags & (HandlesReadAccess | HandlesWriteAccess); | ||
| 309 | } | ||
| 310 | |||
| 311 | return QueryFlags(); | ||
| 312 | } | ||
| 313 | |||
| 314 | ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) { | ||
| 315 | QObject* qobject = _object; | ||
| 316 | if (!qobject) { | ||
| 317 | return ScriptValue::PropertyFlags(); | ||
| 318 | } | ||
| 319 | |||
| 320 | switch (id & TYPE_MASK) { | ||
| 321 | case PROPERTY_TYPE: { | ||
| 322 | PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); | ||
| 323 | if (lookup == _props.cend()) return ScriptValue::PropertyFlags(); | ||
| 324 | const PropertyDef& propDef = lookup.value(); | ||
| 325 | return propDef.flags; | ||
| 326 | } | ||
| 327 | case METHOD_TYPE: { | ||
| 328 | MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK); | ||
| 329 | if (lookup == _methods.cend()) return ScriptValue::PropertyFlags(); | ||
| 330 | return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; | ||
| 331 | } | ||
| 332 | case SIGNAL_TYPE: { | ||
| 333 | SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); | ||
| 334 | if (lookup == _signals.cend()) return ScriptValue::PropertyFlags(); | ||
| 335 | return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; | ||
| 336 | } | ||
| 337 | } | ||
| 338 | return ScriptValue::PropertyFlags(); | ||
| 339 | } | ||
| 340 | |||
| 341 | void ScriptObjectV8Proxy::v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) { | ||
| 342 | // V8TODO | ||
| 343 | //info.GetReturnValue().Set(); | ||
| 344 | } | ||
| 345 | |||
| 346 | void ScriptObjectV8Proxy::v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<v8::Value>& info) { | ||
| 347 | // V8TODO | ||
| 348 | info.GetReturnValue().Set(value); | ||
| 349 | } | ||
| 350 | |||
| 351 | |||
| 352 | V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { | ||
| 353 | QObject* qobject = _object; | ||
| 354 | if (!qobject) { | ||
| 355 | _engine->getIsolate()->ThrowError("Referencing deleted native object"); | ||
| 356 | return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); | ||
| 357 | } | ||
| 358 | |||
| 359 | const QMetaObject* metaObject = qobject->metaObject(); | ||
| 360 | |||
| 361 | switch (id & TYPE_MASK) { | ||
| 362 | case PROPERTY_TYPE: { | ||
| 363 | int propId = id & ~TYPE_MASK; | ||
| 364 | PropertyDefMap::const_iterator lookup = _props.find(propId); | ||
| 365 | if (lookup == _props.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); | ||
| 366 | |||
| 367 | QMetaProperty prop = metaObject->property(propId); | ||
| 368 | ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); | ||
| 369 | ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); | ||
| 370 | ScriptContextGuard guard(&ourContext); | ||
| 371 | |||
| 372 | QVariant varValue = prop.read(qobject); | ||
| 373 | return _engine->castVariantToValue(varValue); | ||
| 374 | } | ||
| 375 | case METHOD_TYPE: { | ||
| 376 | int methodId = id & ~TYPE_MASK; | ||
| 377 | MethodDefMap::const_iterator lookup = _methods.find(methodId); | ||
| 378 | if (lookup == _methods.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); | ||
| 379 | const MethodDef& methodDef = lookup.value(); | ||
| 380 | for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) { | ||
| 381 | if((*iter).returnType() == QMetaType::UnknownType) { | ||
| 382 | qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name(); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | return ScriptMethodV8Proxy::newMethod(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms); | ||
| 386 | } | ||
| 387 | case SIGNAL_TYPE: { | ||
| 388 | int signalId = id & ~TYPE_MASK; | ||
| 389 | SignalDefMap::const_iterator defLookup = _signals.find(signalId); | ||
| 390 | if (defLookup == _signals.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); | ||
| 391 | |||
| 392 | InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); | ||
| 393 | if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { | ||
| 394 | instLookup = _signalInstances.insert(signalId, | ||
| 395 | new ScriptSignalV8Proxy(_engine, qobject, object, defLookup.value().signal)); | ||
| 396 | Q_ASSERT(instLookup != _signalInstances.cend()); | ||
| 397 | } | ||
| 398 | ScriptSignalV8Proxy* proxy = instLookup.value(); | ||
| 399 | |||
| 400 | ScriptEngine::QObjectWrapOptions options = ScriptEngine::ExcludeSuperClassContents | | ||
| 401 | //V8TODO ScriptEngine::ExcludeDeleteLater | | ||
| 402 | ScriptEngine::PreferExistingWrapperObject; | ||
| 403 | return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options); | ||
| 404 | //return _engine->newQObject(proxy, ScriptEngine::ScriptOwnership, options); | ||
| 405 | } | ||
| 406 | } | ||
| 407 | return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); | ||
| 408 | } | ||
| 409 | |||
| 410 | void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { | ||
| 411 | if (!(id & PROPERTY_TYPE)) return; | ||
| 412 | QObject* qobject = _object; | ||
| 413 | if (!qobject) { | ||
| 414 | _engine->getIsolate()->ThrowError("Referencing deleted native object"); | ||
| 415 | return; | ||
| 416 | } | ||
| 417 | |||
| 418 | int propId = id & ~TYPE_MASK; | ||
| 419 | PropertyDefMap::const_iterator lookup = _props.find(propId); | ||
| 420 | if (lookup == _props.cend()) return; | ||
| 421 | const PropertyDef& propDef = lookup.value(); | ||
| 422 | if (propDef.flags & ScriptValue::ReadOnly) return; | ||
| 423 | |||
| 424 | const QMetaObject* metaObject = qobject->metaObject(); | ||
| 425 | QMetaProperty prop = metaObject->property(propId); | ||
| 426 | |||
| 427 | ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); | ||
| 428 | ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); | ||
| 429 | ScriptContextGuard guard(&ourContext); | ||
| 430 | |||
| 431 | int propTypeId = prop.userType(); | ||
| 432 | Q_ASSERT(propTypeId != QMetaType::UnknownType); | ||
| 433 | QVariant varValue; | ||
| 434 | if(!_engine->castValueToVariant(value, varValue, propTypeId)) { | ||
| 435 | QByteArray propTypeName = QMetaType(propTypeId).name(); | ||
| 436 | QByteArray valTypeName = _engine->valueType(value).toLatin1(); | ||
| 437 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName).toStdString().c_str()).ToLocalChecked()); | ||
| 438 | return; | ||
| 439 | } | ||
| 440 | prop.write(qobject, varValue); | ||
| 441 | } | ||
| 442 | |||
| 443 | ScriptVariantV8Proxy::ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto) : | ||
| 444 | _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { | ||
| 445 | _name = QString::fromLatin1(variant.typeName()); | ||
| 446 | } | ||
| 447 | |||
| 448 | V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) { | ||
| 449 | ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto); | ||
| 450 | if (!protoProxy) { | ||
| 451 | Q_ASSERT(protoProxy); | ||
| 452 | //return engine->newVariant(variant); | ||
| 453 | return V8ScriptValue(engine->getIsolate(), v8::Undefined(engine->getIsolate())); | ||
| 454 | } | ||
| 455 | // V8TODO probably needs connection to be deleted | ||
| 456 | // V8TODO what to do with proto variable? | ||
| 457 | auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy); | ||
| 458 | auto variantDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); | ||
| 459 | variantDataTemplate->SetInternalFieldCount(2); | ||
| 460 | auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); | ||
| 461 | variantData->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToQVariantProxy)); | ||
| 462 | variantData->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(proxy)); | ||
| 463 | return V8ScriptValue(engine->getIsolate(), variantData); | ||
| 464 | } | ||
| 465 | |||
| 466 | ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) { | ||
| 467 | auto v8Value = val.constGet(); | ||
| 468 | if (!v8Value->IsObject()) { | ||
| 469 | return nullptr; | ||
| 470 | } | ||
| 471 | v8::Local<v8::Object> v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); | ||
| 472 | if (v8Object->InternalFieldCount() != 2) { | ||
| 473 | return nullptr; | ||
| 474 | } | ||
| 475 | if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQVariantProxy) { | ||
| 476 | return nullptr; | ||
| 477 | } | ||
| 478 | return reinterpret_cast<ScriptVariantV8Proxy*>(v8Object->GetAlignedPointerFromInternalField(1)); | ||
| 479 | } | ||
| 480 | |||
| 481 | QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) { | ||
| 482 | ScriptVariantV8Proxy* proxy = unwrapProxy(val); | ||
| 483 | return proxy ? proxy->toQVariant() : QVariant(); | ||
| 484 | } | ||
| 485 | |||
| 486 | ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, | ||
| 487 | const QList<QMetaMethod>& metas, int numMaxParms) : | ||
| 488 | _numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) { | ||
| 489 | } | ||
| 490 | |||
| 491 | V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, | ||
| 492 | const QList<QMetaMethod>& metas, int numMaxParams) { | ||
| 493 | auto methodDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); | ||
| 494 | methodDataTemplate->SetInternalFieldCount(2); | ||
| 495 | auto methodData = methodDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); | ||
| 496 | methodData->SetAlignedPointerInInternalField(0, const_cast<void*>(internalPointsToMethodProxy)); | ||
| 497 | // V8TODO it needs to be deleted somehow on object destruction | ||
| 498 | methodData->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(new ScriptMethodV8Proxy(engine, object, lifetime, metas, numMaxParams))); | ||
| 499 | auto v8Function = v8::Function::New(engine->getContext(), callback, methodData, numMaxParams).ToLocalChecked(); | ||
| 500 | return V8ScriptValue(engine->getIsolate(), v8Function); | ||
| 501 | } | ||
| 502 | |||
| 503 | QString ScriptMethodV8Proxy::fullName() const { | ||
| 504 | Q_ASSERT(_object); | ||
| 505 | if (!_object) return ""; | ||
| 506 | Q_ASSERT(!_metas.isEmpty()); | ||
| 507 | const QMetaMethod& firstMethod = _metas.front(); | ||
| 508 | QString objectName = _object->objectName(); | ||
| 509 | if (!objectName.isEmpty()) { | ||
| 510 | return QString("%1.%2").arg(objectName, firstMethod.name()); | ||
| 511 | } | ||
| 512 | return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name()); | ||
| 513 | } | ||
| 514 | |||
| 515 | // V8TODO | ||
| 516 | /*bool ScriptMethodV8Proxy::supportsExtension(Extension extension) const { | ||
| 517 | switch (extension) { | ||
| 518 | case Callable: | ||
| 519 | return true; | ||
| 520 | default: | ||
| 521 | return false; | ||
| 522 | } | ||
| 523 | }*/ | ||
| 524 | |||
| 525 | void ScriptMethodV8Proxy::callback(const v8::FunctionCallbackInfo<v8::Value>& arguments) { | ||
| 526 | if (!arguments.This()->IsObject()) { | ||
| 527 | arguments.GetIsolate()->ThrowError("Method value is not an object"); | ||
| 528 | return; | ||
| 529 | } | ||
| 530 | if (!arguments.This()->IsCallable()) { | ||
| 531 | arguments.GetIsolate()->ThrowError("Method value is not callable"); | ||
| 532 | return; | ||
| 533 | } | ||
| 534 | if (arguments.This()->InternalFieldCount() != 2) { | ||
| 535 | arguments.GetIsolate()->ThrowError("Incorrect number of internal fields during method call"); | ||
| 536 | return; | ||
| 537 | } | ||
| 538 | if (arguments.This()->GetAlignedPointerFromInternalField(0) == internalPointsToMethodProxy) { | ||
| 539 | arguments.GetIsolate()->ThrowError("Internal field 0 of ScriptMethodV8Proxy V8 object has wrong value"); | ||
| 540 | return; | ||
| 541 | } | ||
| 542 | ScriptMethodV8Proxy *proxy = reinterpret_cast<ScriptMethodV8Proxy*>(arguments.This()->GetAlignedPointerFromInternalField(1)); | ||
| 543 | proxy->call(arguments); | ||
| 544 | } | ||
| 545 | |||
| 546 | void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo<v8::Value>& arguments) { | ||
| 547 | QObject* qobject = _object; | ||
| 548 | v8::Isolate *isolate = arguments.GetIsolate(); | ||
| 549 | if (!qobject) { | ||
| 550 | isolate->ThrowError("Referencing deleted native object"); | ||
| 551 | return; | ||
| 552 | } | ||
| 553 | |||
| 554 | int scriptNumArgs = arguments.Length(); | ||
| 555 | int numArgs = std::min(scriptNumArgs, _numMaxParms); | ||
| 556 | |||
| 557 | const int scriptValueTypeId = qMetaTypeId<ScriptValue>(); | ||
| 558 | |||
| 559 | int parameterConversionFailureId = 0; | ||
| 560 | int parameterConversionFailureCount = 0; | ||
| 561 | |||
| 562 | int num_metas = _metas.size(); | ||
| 563 | QVector< QList<ScriptValue> > qScriptArgLists; | ||
| 564 | QVector< QVector <QGenericArgument> > qGenArgsVectors; | ||
| 565 | QVector< QList<QVariant> > qVarArgLists; | ||
| 566 | qScriptArgLists.resize(num_metas); | ||
| 567 | qGenArgsVectors.resize(num_metas); | ||
| 568 | qVarArgLists.resize(num_metas); | ||
| 569 | bool isValidMetaSelected = false; | ||
| 570 | int bestMeta = 0; | ||
| 571 | int bestConversionPenaltyScore = 0; | ||
| 572 | |||
| 573 | for (int i = 0; i < num_metas; i++) { | ||
| 574 | const QMetaMethod& meta = _metas[i]; | ||
| 575 | int methodNumArgs = meta.parameterCount(); | ||
| 576 | if (methodNumArgs != numArgs) { | ||
| 577 | continue; | ||
| 578 | } | ||
| 579 | |||
| 580 | qGenArgsVectors[i].resize(10); | ||
| 581 | int conversionPenaltyScore = 0; | ||
| 582 | int conversionFailures = 0; | ||
| 583 | |||
| 584 | for (int arg = 0; arg < numArgs; ++arg) { | ||
| 585 | int methodArgTypeId = meta.parameterType(arg); | ||
| 586 | Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | ||
| 587 | v8::Local<v8::Value> argVal = arguments[arg]; | ||
| 588 | if (methodArgTypeId == scriptValueTypeId) { | ||
| 589 | qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(isolate, argVal)))); | ||
| 590 | qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); | ||
| 591 | } else if (methodArgTypeId == QMetaType::QVariant) { | ||
| 592 | QVariant varArgVal; | ||
| 593 | if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) { | ||
| 594 | conversionFailures++; | ||
| 595 | } else { | ||
| 596 | qVarArgLists[i].append(varArgVal); | ||
| 597 | qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); | ||
| 598 | } | ||
| 599 | } else { | ||
| 600 | QVariant varArgVal; | ||
| 601 | if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) { | ||
| 602 | conversionFailures++; | ||
| 603 | } else { | ||
| 604 | qVarArgLists[i].append(varArgVal); | ||
| 605 | const QVariant& converted = qVarArgLists[i].back(); | ||
| 606 | conversionPenaltyScore = _engine->computeCastPenalty(V8ScriptValue(isolate, argVal), methodArgTypeId); | ||
| 607 | |||
| 608 | // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | ||
| 609 | // A const_cast is needed because calling data() would detach the QVariant. | ||
| 610 | qGenArgsVectors[i][arg] = | ||
| 611 | QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); | ||
| 612 | } | ||
| 613 | } | ||
| 614 | } | ||
| 615 | if (conversionFailures) { | ||
| 616 | if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { | ||
| 617 | parameterConversionFailureCount = conversionFailures; | ||
| 618 | parameterConversionFailureId = meta.methodIndex(); | ||
| 619 | } | ||
| 620 | continue; | ||
| 621 | } | ||
| 622 | |||
| 623 | if (!isValidMetaSelected) { | ||
| 624 | isValidMetaSelected = true; | ||
| 625 | bestMeta = i; | ||
| 626 | bestConversionPenaltyScore = conversionPenaltyScore; | ||
| 627 | } | ||
| 628 | if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { | ||
| 629 | bestMeta = i; | ||
| 630 | bestConversionPenaltyScore = conversionPenaltyScore; | ||
| 631 | } | ||
| 632 | } | ||
| 633 | |||
| 634 | if (isValidMetaSelected) { | ||
| 635 | //ScriptContextV8Wrapper ourContext(_engine, context); | ||
| 636 | //ScriptContextGuard guard(&ourContext); | ||
| 637 | const QMetaMethod& meta = _metas[bestMeta]; | ||
| 638 | int returnTypeId = meta.returnType(); | ||
| 639 | QVector <QGenericArgument> &qGenArgs = qGenArgsVectors[bestMeta]; | ||
| 640 | |||
| 641 | // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's | ||
| 642 | // nothing in there for return values so these need to be explicitly runtime-registered! | ||
| 643 | Q_ASSERT(returnTypeId != QMetaType::UnknownType); | ||
| 644 | if (returnTypeId == QMetaType::UnknownType) { | ||
| 645 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 646 | return; | ||
| 647 | } else if (returnTypeId == QMetaType::Void) { | ||
| 648 | bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], | ||
| 649 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 650 | if (!success) { | ||
| 651 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 652 | } | ||
| 653 | return; | ||
| 654 | } else if (returnTypeId == scriptValueTypeId) { | ||
| 655 | ScriptValue result; | ||
| 656 | bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], | ||
| 657 | qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], | ||
| 658 | qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 659 | if (!success) { | ||
| 660 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 661 | return; | ||
| 662 | } | ||
| 663 | V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result); | ||
| 664 | arguments.GetReturnValue().Set(v8Result.get()); | ||
| 665 | return; | ||
| 666 | } else { | ||
| 667 | // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | ||
| 668 | const char* typeName = meta.typeName(); | ||
| 669 | QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); | ||
| 670 | QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); | ||
| 671 | |||
| 672 | bool success = | ||
| 673 | meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], | ||
| 674 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 675 | if (!success) { | ||
| 676 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 677 | return; | ||
| 678 | } | ||
| 679 | V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal); | ||
| 680 | arguments.GetReturnValue().Set(v8Result.get()); | ||
| 681 | return; | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | // we failed to convert the call to C++, try to create a somewhat sane error message | ||
| 686 | if (parameterConversionFailureCount == 0) { | ||
| 687 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 688 | return; | ||
| 689 | } | ||
| 690 | |||
| 691 | const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); | ||
| 692 | int methodNumArgs = meta.parameterCount(); | ||
| 693 | Q_ASSERT(methodNumArgs == numArgs); | ||
| 694 | |||
| 695 | for (int arg = 0; arg < numArgs; ++arg) { | ||
| 696 | int methodArgTypeId = meta.parameterType(arg); | ||
| 697 | Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | ||
| 698 | v8::Local<v8::Value> argVal = arguments[arg]; | ||
| 699 | if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { | ||
| 700 | QVariant varArgVal; | ||
| 701 | if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) { | ||
| 702 | QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); | ||
| 703 | QByteArray argTypeName = _engine->valueType(V8ScriptValue(isolate, argVal)).toLatin1(); | ||
| 704 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") | ||
| 705 | .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked()); | ||
| 706 | //context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") | ||
| 707 | // .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); | ||
| 708 | return; | ||
| 709 | } | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 713 | isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 714 | Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? | ||
| 715 | return; | ||
| 716 | } | ||
| 717 | |||
| 718 | /*QVariant ScriptMethodV8Proxy::extension(Extension extension, const QVariant& argument) { | ||
| 719 | if (extension != Callable) return QVariant(); | ||
| 720 | V8ScriptContext* context = qvariant_cast<V8ScriptContext*>(argument); | ||
| 721 | |||
| 722 | QObject* qobject = _object; | ||
| 723 | if (!qobject) { | ||
| 724 | context->throwError(V8ScriptContext::ReferenceError, "Referencing deleted native object"); | ||
| 725 | return QVariant(); | ||
| 726 | } | ||
| 727 | |||
| 728 | int scriptNumArgs = context->argumentCount(); | ||
| 729 | int numArgs = std::min(scriptNumArgs, _numMaxParms); | ||
| 730 | |||
| 731 | const int scriptValueTypeId = qMetaTypeId<ScriptValue>(); | ||
| 732 | |||
| 733 | int parameterConversionFailureId = 0; | ||
| 734 | int parameterConversionFailureCount = 0; | ||
| 735 | |||
| 736 | int num_metas = _metas.size(); | ||
| 737 | QVector< QList<ScriptValue> > qScriptArgLists; | ||
| 738 | QVector< QVector <QGenericArgument> > qGenArgsVectors; | ||
| 739 | QVector< QList<QVariant> > qVarArgLists; | ||
| 740 | qScriptArgLists.resize(num_metas); | ||
| 741 | qGenArgsVectors.resize(num_metas); | ||
| 742 | qVarArgLists.resize(num_metas); | ||
| 743 | bool isValidMetaSelected = false; | ||
| 744 | int bestMeta = 0; | ||
| 745 | int bestConversionPenaltyScore = 0; | ||
| 746 | |||
| 747 | for (int i = 0; i < num_metas; i++) { | ||
| 748 | const QMetaMethod& meta = _metas[i]; | ||
| 749 | int methodNumArgs = meta.parameterCount(); | ||
| 750 | if (methodNumArgs != numArgs) { | ||
| 751 | continue; | ||
| 752 | } | ||
| 753 | |||
| 754 | qGenArgsVectors[i].resize(10); | ||
| 755 | int conversionPenaltyScore = 0; | ||
| 756 | int conversionFailures = 0; | ||
| 757 | |||
| 758 | for (int arg = 0; arg < numArgs; ++arg) { | ||
| 759 | int methodArgTypeId = meta.parameterType(arg); | ||
| 760 | Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | ||
| 761 | V8ScriptValue argVal = context->argument(arg); | ||
| 762 | if (methodArgTypeId == scriptValueTypeId) { | ||
| 763 | qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, argVal))); | ||
| 764 | qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); | ||
| 765 | } else if (methodArgTypeId == QMetaType::QVariant) { | ||
| 766 | qVarArgLists[i].append(argVal.toVariant()); | ||
| 767 | qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); | ||
| 768 | } else { | ||
| 769 | QVariant varArgVal; | ||
| 770 | if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { | ||
| 771 | conversionFailures++; | ||
| 772 | } else { | ||
| 773 | qVarArgLists[i].append(varArgVal); | ||
| 774 | const QVariant& converted = qVarArgLists[i].back(); | ||
| 775 | conversionPenaltyScore = _engine->computeCastPenalty(argVal, methodArgTypeId); | ||
| 776 | |||
| 777 | // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | ||
| 778 | // A const_cast is needed because calling data() would detach the QVariant. | ||
| 779 | qGenArgsVectors[i][arg] = | ||
| 780 | QGenericArgument(QMetaType::typeName(converted.userType()), const_cast<void*>(converted.constData())); | ||
| 781 | } | ||
| 782 | } | ||
| 783 | } | ||
| 784 | if (conversionFailures) { | ||
| 785 | if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { | ||
| 786 | parameterConversionFailureCount = conversionFailures; | ||
| 787 | parameterConversionFailureId = meta.methodIndex(); | ||
| 788 | } | ||
| 789 | continue; | ||
| 790 | } | ||
| 791 | |||
| 792 | if (!isValidMetaSelected) { | ||
| 793 | isValidMetaSelected = true; | ||
| 794 | bestMeta = i; | ||
| 795 | bestConversionPenaltyScore = conversionPenaltyScore; | ||
| 796 | } | ||
| 797 | if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { | ||
| 798 | bestMeta = i; | ||
| 799 | bestConversionPenaltyScore = conversionPenaltyScore; | ||
| 800 | } | ||
| 801 | } | ||
| 802 | |||
| 803 | if (isValidMetaSelected) { | ||
| 804 | ScriptContextV8Wrapper ourContext(_engine, context); | ||
| 805 | ScriptContextGuard guard(&ourContext); | ||
| 806 | const QMetaMethod& meta = _metas[bestMeta]; | ||
| 807 | int returnTypeId = meta.returnType(); | ||
| 808 | QVector <QGenericArgument> &qGenArgs = qGenArgsVectors[bestMeta]; | ||
| 809 | |||
| 810 | // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's | ||
| 811 | // nothing in there for return values so these need to be explicitly runtime-registered! | ||
| 812 | Q_ASSERT(returnTypeId != QMetaType::UnknownType); | ||
| 813 | if (returnTypeId == QMetaType::UnknownType) { | ||
| 814 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 815 | return QVariant(); | ||
| 816 | } else if (returnTypeId == QMetaType::Void) { | ||
| 817 | bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], | ||
| 818 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 819 | if (!success) { | ||
| 820 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 821 | } | ||
| 822 | return QVariant(); | ||
| 823 | } else if (returnTypeId == scriptValueTypeId) { | ||
| 824 | ScriptValue result; | ||
| 825 | bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], | ||
| 826 | qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], | ||
| 827 | qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 828 | if (!success) { | ||
| 829 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 830 | return QVariant(); | ||
| 831 | } | ||
| 832 | V8ScriptValue qResult = ScriptValueV8Wrapper::fullUnwrap(_engine, result); | ||
| 833 | return QVariant::fromValue(qResult); | ||
| 834 | } else { | ||
| 835 | // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant | ||
| 836 | const char* typeName = meta.typeName(); | ||
| 837 | QVariant qRetVal(returnTypeId, static_cast<void*>(NULL)); | ||
| 838 | QGenericReturnArgument sRetVal(typeName, const_cast<void*>(qRetVal.constData())); | ||
| 839 | |||
| 840 | bool success = | ||
| 841 | meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], | ||
| 842 | qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); | ||
| 843 | if (!success) { | ||
| 844 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 845 | return QVariant(); | ||
| 846 | } | ||
| 847 | V8ScriptValue qResult = _engine->castVariantToValue(qRetVal); | ||
| 848 | return QVariant::fromValue(qResult); | ||
| 849 | } | ||
| 850 | } | ||
| 851 | |||
| 852 | // we failed to convert the call to C++, try to create a somewhat sane error message | ||
| 853 | if (parameterConversionFailureCount == 0) { | ||
| 854 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); | ||
| 855 | return QVariant(); | ||
| 856 | } | ||
| 857 | |||
| 858 | const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); | ||
| 859 | int methodNumArgs = meta.parameterCount(); | ||
| 860 | Q_ASSERT(methodNumArgs == numArgs); | ||
| 861 | |||
| 862 | for (int arg = 0; arg < numArgs; ++arg) { | ||
| 863 | int methodArgTypeId = meta.parameterType(arg); | ||
| 864 | Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | ||
| 865 | V8ScriptValue argVal = context->argument(arg); | ||
| 866 | if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { | ||
| 867 | QVariant varArgVal; | ||
| 868 | if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { | ||
| 869 | QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); | ||
| 870 | QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); | ||
| 871 | _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") | ||
| 872 | .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked()); | ||
| 873 | context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") | ||
| 874 | .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); | ||
| 875 | return QVariant(); | ||
| 876 | } | ||
| 877 | } | ||
| 878 | } | ||
| 879 | |||
| 880 | context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); | ||
| 881 | Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? | ||
| 882 | return QVariant(); | ||
| 883 | }*/ | ||
| 884 | |||
| 885 | QString ScriptSignalV8Proxy::fullName() const { | ||
| 886 | Q_ASSERT(_object); | ||
| 887 | if (!_object) return ""; | ||
| 888 | QString objectName = _object->objectName(); | ||
| 889 | if (!objectName.isEmpty()) { | ||
| 890 | return QString("%1.%2").arg(objectName, _meta.name()); | ||
| 891 | } | ||
| 892 | return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); | ||
| 893 | } | ||
| 894 | |||
| 895 | // Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it | ||
| 896 | int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { | ||
| 897 | id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments); | ||
| 898 | if (id != 0 || call != QMetaObject::InvokeMetaMethod) { | ||
| 899 | return id; | ||
| 900 | } | ||
| 901 | |||
| 902 | //V8ScriptValueList args(isolate, v8::Null(isolate)); | ||
| 903 | v8::Local<v8::Value> args[Q_METAMETHOD_INVOKE_MAX_ARGS]; | ||
| 904 | int numArgs = _meta.parameterCount(); | ||
| 905 | for (int arg = 0; arg < numArgs; ++arg) { | ||
| 906 | int methodArgTypeId = _meta.parameterType(arg); | ||
| 907 | Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); | ||
| 908 | QVariant argValue(methodArgTypeId, arguments[arg+1]); | ||
| 909 | args[arg] = _engine->castVariantToValue(argValue).get(); | ||
| 910 | } | ||
| 911 | |||
| 912 | QList<Connection> connections; | ||
| 913 | withReadLock([&]{ | ||
| 914 | connections = _connections; | ||
| 915 | }); | ||
| 916 | |||
| 917 | for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { | ||
| 918 | Connection& conn = *iter; | ||
| 919 | v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(conn.callback.get()); | ||
| 920 | v8::Local<v8::Value> v8This; | ||
| 921 | if (conn.thisValue.get()->IsNull()) { | ||
| 922 | v8This = _engine->getContext()->Global(); | ||
| 923 | } else { | ||
| 924 | v8This = conn.thisValue.get(); | ||
| 925 | } | ||
| 926 | //V8TODO: should there be a trycatch here? | ||
| 927 | callback->Call(_engine->getContext(), v8This, numArgs, args); | ||
| 928 | } | ||
| 929 | |||
| 930 | return -1; | ||
| 931 | } | ||
| 932 | |||
| 933 | int ScriptSignalV8Proxy::discoverMetaCallIdx() { | ||
| 934 | const QMetaObject* ourMeta = metaObject(); | ||
| 935 | return ourMeta->methodCount(); | ||
| 936 | } | ||
| 937 | |||
| 938 | ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) { | ||
| 939 | auto iterOut = resultWithReadLock<ScriptSignalV8Proxy::ConnectionList::iterator>([&]{ | ||
| 940 | ConnectionList::iterator iter; | ||
| 941 | for (iter = _connections.begin(); iter != _connections.end(); ++iter) { | ||
| 942 | Connection& conn = *iter; | ||
| 943 | if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) { | ||
| 944 | break; | ||
| 945 | } | ||
| 946 | } | ||
| 947 | return iter; | ||
| 948 | }); | ||
| 949 | return iterOut; | ||
| 950 | } | ||
| 951 | |||
| 952 | |||
| 953 | void ScriptSignalV8Proxy::connect(V8ScriptValue arg0, V8ScriptValue arg1) { | ||
| 954 | QObject* qobject = _object; | ||
| 955 | if (!qobject) { | ||
| 956 | _engine->getIsolate()->ThrowError("Referencing deleted native object"); | ||
| 957 | return; | ||
| 958 | } | ||
| 959 | |||
| 960 | v8::Isolate *isolate = _engine->getIsolate(); | ||
| 961 | // untangle the arguments | ||
| 962 | V8ScriptValue callback(isolate, v8::Null(isolate)); | ||
| 963 | V8ScriptValue callbackThis(isolate, v8::Null(isolate)); | ||
| 964 | if (arg1.get()->IsFunction()) { | ||
| 965 | callbackThis = arg0; | ||
| 966 | callback = arg1; | ||
| 967 | } else { | ||
| 968 | callback = arg0; | ||
| 969 | } | ||
| 970 | if (!callback.get()->IsFunction()) { | ||
| 971 | _engine->getIsolate()->ThrowError("Function expected as argument to 'connect'"); | ||
| 972 | return; | ||
| 973 | } | ||
| 974 | |||
| 975 | // are we already connected? | ||
| 976 | { | ||
| 977 | ConnectionList::iterator lookup = findConnection(callbackThis, callback); | ||
| 978 | if (lookup != _connections.end()) { | ||
| 979 | return; // already exists | ||
| 980 | } | ||
| 981 | } | ||
| 982 | |||
| 983 | // add a reference to ourselves to the destination callback | ||
| 984 | v8::Local<v8::Function> destFunction = v8::Local<v8::Function>::Cast(callback.get()); | ||
| 985 | v8::Local<v8::String> destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); | ||
| 986 | v8::Local<v8::Value> destData; | ||
| 987 | auto destFunctionContext = destFunction->CreationContext(); | ||
| 988 | V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); | ||
| 989 | //Q_ASSERT(destFunction->InternalFieldCount() == 4); | ||
| 990 | //Q_ASSERT(destData.get()->IsArray()); | ||
| 991 | //v8::Local<v8::Value> destData = destFunction->GetInternalField(3); | ||
| 992 | if (destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData) && destData->IsArray()) { | ||
| 993 | v8::Local<v8::Array> destArray = v8::Local<v8::Array>::Cast(destData); | ||
| 994 | int length = destArray->Length();//destData.property("length").toInteger(); | ||
| 995 | v8::Local<v8::Array> newArray = v8::Array::New(isolate, length + 1); | ||
| 996 | bool foundIt = false; | ||
| 997 | for (int idx = 0; idx < length && !foundIt; ++idx) { | ||
| 998 | v8::Local<v8::Value> entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); | ||
| 999 | newArray->Set(destFunctionContext, idx, entry); | ||
| 1000 | } | ||
| 1001 | newArray->Set(destFunctionContext, length, v8ThisObject.get()); | ||
| 1002 | destFunction->Set(destFunctionContext, destDataName, newArray); | ||
| 1003 | } else { | ||
| 1004 | v8::Local<v8::Array> newArray = v8::Array::New(isolate, 1); | ||
| 1005 | newArray->Set(destFunctionContext, 0, v8ThisObject.get()); | ||
| 1006 | destFunction->Set(destFunctionContext, destDataName, newArray); | ||
| 1007 | } | ||
| 1008 | /*{ | ||
| 1009 | V8ScriptValueList args; | ||
| 1010 | args << thisObject(); | ||
| 1011 | destData.property("push").call(destData, args); | ||
| 1012 | }*/ | ||
| 1013 | |||
| 1014 | // add this to our internal list of connections | ||
| 1015 | Connection newConnection(callbackThis, callback); | ||
| 1016 | //newConn.callback = callback; | ||
| 1017 | //newConn.thisValue = callbackThis; | ||
| 1018 | |||
| 1019 | withWriteLock([&]{ | ||
| 1020 | _connections.append(newConnection); | ||
| 1021 | }); | ||
| 1022 | |||
| 1023 | // inform Qt that we're connecting to this signal | ||
| 1024 | if (!_isConnected) { | ||
| 1025 | auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); | ||
| 1026 | Q_ASSERT(result); | ||
| 1027 | _isConnected = true; | ||
| 1028 | } | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | void ScriptSignalV8Proxy::disconnect(V8ScriptValue arg0, V8ScriptValue arg1) { | ||
| 1032 | QObject* qobject = _object; | ||
| 1033 | if (!qobject) { | ||
| 1034 | _engine->getIsolate()->ThrowError("Referencing deleted native object"); | ||
| 1035 | return; | ||
| 1036 | } | ||
| 1037 | v8::Isolate *isolate = _engine->getIsolate(); | ||
| 1038 | |||
| 1039 | // untangle the arguments | ||
| 1040 | V8ScriptValue callback(isolate, v8::Null(isolate)); | ||
| 1041 | V8ScriptValue callbackThis(isolate, v8::Null(isolate)); | ||
| 1042 | if (arg1.get()->IsFunction()) { | ||
| 1043 | callbackThis = arg0; | ||
| 1044 | callback = arg1; | ||
| 1045 | } else { | ||
| 1046 | callback = arg0; | ||
| 1047 | } | ||
| 1048 | if (!callback.get()->IsFunction()) { | ||
| 1049 | _engine->getIsolate()->ThrowError("Function expected as argument to 'disconnect'"); | ||
| 1050 | return; | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | // locate this connection in our list of connections | ||
| 1054 | { | ||
| 1055 | ConnectionList::iterator lookup = findConnection(callbackThis, callback); | ||
| 1056 | if (lookup == _connections.end()) { | ||
| 1057 | return; // not here | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | // remove it from our internal list of connections | ||
| 1061 | withWriteLock([&]{ | ||
| 1062 | _connections.erase(lookup); | ||
| 1063 | }); | ||
| 1064 | } | ||
| 1065 | |||
| 1066 | // remove a reference to ourselves from the destination callback | ||
| 1067 | v8::Local<v8::Function> destFunction = v8::Local<v8::Function>::Cast(callback.get()); | ||
| 1068 | v8::Local<v8::String> destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); | ||
| 1069 | v8::Local<v8::Value> destData; | ||
| 1070 | auto destFunctionContext = destFunction->CreationContext(); | ||
| 1071 | V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); | ||
| 1072 | //V8ScriptValue destData = callback.data(); | ||
| 1073 | //Q_ASSERT(destData->IsArray()); | ||
| 1074 | if (destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData) && destData->IsArray()) { | ||
| 1075 | v8::Local<v8::Array> destArray = v8::Local<v8::Array>::Cast(destData); | ||
| 1076 | int length = destArray->Length();//destData.property("length").toInteger(); | ||
| 1077 | v8::Local<v8::Array> newArray = v8::Array::New(isolate, length - 1); | ||
| 1078 | bool foundIt = false; | ||
| 1079 | int newIndex = 0; | ||
| 1080 | for (int idx = 0; idx < length && !foundIt; ++idx) { | ||
| 1081 | v8::Local<v8::Value> entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); | ||
| 1082 | if (entry->StrictEquals(v8ThisObject.get())) { | ||
| 1083 | foundIt = true; | ||
| 1084 | //V8ScriptValueList args; | ||
| 1085 | //args << idx << 1; | ||
| 1086 | //destData.property("splice").call(destData, args); | ||
| 1087 | } else { | ||
| 1088 | newArray->Set(destFunctionContext, newIndex, entry); | ||
| 1089 | newIndex++; | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | Q_ASSERT(foundIt); | ||
| 1093 | destFunction->Set(destFunctionContext, destDataName, newArray); | ||
| 1094 | } else { | ||
| 1095 | Q_ASSERT(false); | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | // inform Qt that we're no longer connected to this signal | ||
| 1099 | if (_connections.empty()) { | ||
| 1100 | Q_ASSERT(_isConnected); | ||
| 1101 | bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); | ||
| 1102 | Q_ASSERT(result); | ||
| 1103 | _isConnected = false; | ||
| 1104 | } | ||
| 1105 | } | ||