diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 915543b128..f2408651be 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -127,7 +127,7 @@ public: virtual void updateMemoryCost(const qint64& deltaSize) = 0; virtual void requestCollectGarbage() = 0; virtual void compileTest() = 0; - virtual QString scriptValueDebugDetails(ScriptValue &value) = 0; + virtual QString scriptValueDebugDetails(const ScriptValue &value) = 0; public: // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index 16b129dd9b..3852708e27 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -1422,6 +1422,7 @@ ScriptValue ScriptManager::instantiateModule(const ScriptValue& module, const QS closure.setProperty("require", module.property("require")); closure.setProperty("__filename", modulePath, READONLY_HIDDEN_PROP_FLAGS); closure.setProperty("__dirname", QString(modulePath).replace(QRegExp("/[^/]*$"), ""), READONLY_HIDDEN_PROP_FLAGS); + _engine->scriptValueDebugDetails(module); result = _engine->evaluateInClosure(closure, _engine->newProgram( sourceCode, modulePath )); } _engine->maybeEmitUncaughtException(__FUNCTION__); diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index 14a0f412a7..53fe2647e2 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -800,20 +800,24 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, Q_ASSERT(false); return nullValue(); } + //qDebug() << "Closure global details:" << scriptValueDebugDetailsV8(V8ScriptValue(_v8Isolate, closureGlobal)); } //_v8Context.Get(_v8Isolate)->DetachGlobal(); //oldGlobal = _v8Context.Get(_v8Isolate)->Global(); v8::Local closureContext; - if (closureGlobal->IsObject()) { + // V8TODO + /*if (closureGlobal->IsObject()) { #ifdef DEBUG_JS qCDebug(shared) << " setting global = closure.global" << shortName; #endif + closureContext = v8::Context::New(_v8Isolate, nullptr, v8::Local(), closureGlobal); closureContext = v8::Context::New(_v8Isolate, nullptr, v8::Local(), closureGlobal); //setGlobalObject(global); } else { closureContext = v8::Context::New(_v8Isolate); - } + }*/ + closureContext = v8::Context::New(_v8Isolate, nullptr, v8::Local(), closureObject); ScriptValue result; //auto context = pushContext(); @@ -828,23 +832,25 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, const V8ScriptProgram& program = unwrappedProgram->toV8Value(); v8::Local thiz; - // V8TODO: not sure if "this" doesn't exist or is empty in some cases - if (!closureObject->Get(closure.constGetContext(), v8::String::NewFromUtf8(_v8Isolate, "this").ToLocalChecked()) + // V8TODO: not sure if "this" is used at all here + /*if (!closureObject->Get(closure.constGetContext(), v8::String::NewFromUtf8(_v8Isolate, "this").ToLocalChecked()) .ToLocal(&thiz)) { _evaluatingCounter--; qDebug(scriptengine) << "Empty this object in closure"; Q_ASSERT(false); return nullValue(); - } + }*/ //thiz = closure.property("this"); - if (thiz->IsObject()) { + //qDebug() << "Closure this details:" << scriptValueDebugDetailsV8(V8ScriptValue(_v8Isolate, thiz)); + // V8TODO: + /*if (thiz->IsObject()) { #ifdef DEBUG_JS qCDebug(shared) << " setting this = closure.this" << shortName; #endif //V8TODO I don't know how to do this in V8, will adding "this" to global object work? closureContext->Global()->Set(closureContext, v8::String::NewFromUtf8(_v8Isolate, "this").ToLocalChecked(), thiz); //context->setThisObject(thiz); - } + }*/ //context->pushScope(closure); #ifdef DEBUG_JS @@ -852,12 +858,15 @@ ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, #endif { v8::TryCatch tryCatch(getIsolate()); + //qDebug(scriptengine) << "Closure before run:" << scriptValueDebugDetailsV8(closure); auto maybeResult = program.constGet()->Run(closureContext); + //qDebug(scriptengine) << "Closure after run:" << scriptValueDebugDetailsV8(closure); v8::Local v8Result; if (!maybeResult.ToLocal(&v8Result)) { v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); QString errorMessage = QString(*utf8Value); qWarning(scriptengine) << __FUNCTION__ << "---------- hasCaught:" << errorMessage; + qWarning(scriptengine) << __FUNCTION__ << "---------- tryCatch details:" << formatErrorMessageFromTryCatch(tryCatch); //V8TODO: better error reporting } @@ -1513,7 +1522,7 @@ void ScriptEngineV8::compileTest() { } } -QString ScriptEngineV8::scriptValueDebugDetails(ScriptValue &value) { +QString ScriptEngineV8::scriptValueDebugDetails(const ScriptValue &value) { V8ScriptValue v8Value = ScriptValueV8Wrapper::fullUnwrap(this, value); return scriptValueDebugDetailsV8(v8Value); } diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.h b/libraries/script-engine/src/v8/ScriptEngineV8.h index 0e8a939aea..2c0ecad40a 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.h +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -127,7 +127,7 @@ public: // ScriptEngine implementation virtual void updateMemoryCost(const qint64& deltaSize) override; virtual void requestCollectGarbage() override { while(!_v8Isolate->IdleNotificationDeadline(getV8Platform()->MonotonicallyIncreasingTime() + GARBAGE_COLLECTION_TIME_LIMIT_S)) {}; } virtual void compileTest() override; - virtual QString scriptValueDebugDetails(ScriptValue &value) override; + virtual QString scriptValueDebugDetails(const ScriptValue &value) override; QString scriptValueDebugDetailsV8(const V8ScriptValue &value); // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways @@ -171,6 +171,9 @@ public: // not for public use, but I don't like how Qt strings this along with p ScriptEngine::DemarshalFunction demarshalFunc) override; int computeCastPenalty(const V8ScriptValue& val, int destTypeId); bool castValueToVariant(const V8ScriptValue& val, QVariant& dest, int destTypeId); + + // Converts JS objects created in V8 to variants. Iterates over all properties and converts them to variants. + bool convertJSObjectToVariant(v8::Local object, QVariant &dest); V8ScriptValue castVariantToValue(const QVariant& val); QString valueType(const V8ScriptValue& val); v8::Isolate* getIsolate() {return _v8Isolate;} diff --git a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp index 9d5120c7df..ca505278fc 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -383,6 +383,12 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de break; } } + // This is for generic JS objects + if (val->IsObject()) { + if (convertJSObjectToVariant(v8::Local::Cast(val), dest)) { + break; + } + } // V8TODO errorMessage = QString() + "Conversion failure: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) + "to variant. Destination type: " + QMetaType::typeName(destTypeId) +" details: "+ scriptValueDebugDetailsV8(v8Val); @@ -487,6 +493,12 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de return true; } } + if (val->IsObject()) { + if (convertJSObjectToVariant(v8::Local::Cast(val), dest)) { + return true; + } + } + //V8TODO is v8::Array to QVariant conversion used anywhere? errorMessage = QString() + "Conversion to variant failed: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) + " Destination type: " + QMetaType::typeName(destTypeId) + " Value details: " + scriptValueDebugDetailsV8(v8Val); qDebug() << errorMessage; @@ -532,6 +544,34 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de return destTypeId == QMetaType::UnknownType || dest.userType() == destTypeId || dest.convert(destTypeId); } +bool ScriptEngineV8::convertJSObjectToVariant(v8::Local object, QVariant &dest) { + auto context = getContext(); + v8::Local names; + if(object->GetPropertyNames(context).ToLocal(&names)) { + qDebug() << "ScriptEngineV8::convertJSObjectToVariant could not get property names"; + return false; + } + int length = names->Length(); + QHash properties; + for (int i = 0; i < length; i++) { + v8::Local v8Property; + QString name = *v8::String::Utf8Value(_v8Isolate, names->Get(context, i).ToLocalChecked()); + if (!object->Get(context, names->Get(context, i).ToLocalChecked()).ToLocal(&v8Property)) { + qDebug() << "ScriptEngineV8::convertJSObjectToVariant could not get property: " + name; + continue; + } + QVariant property; + // Maybe QMetaType::QVariant? + if (castValueToVariant(V8ScriptValue(_v8Isolate, v8Property), property, QMetaType::UnknownType)) { + properties.insert( name, property); + } else { + qDebug() << "ScriptEngineV8::convertJSObjectToVariant could cast property to variant: " + name; + ; + } + } + dest = QVariant(properties); +} + QString ScriptEngineV8::valueType(const V8ScriptValue& v8Val) { // V8TODO v8::HandleScope handleScope(const_cast(v8Val.constGetIsolate()));