Fix mapping of JS objects/arrays to C++
authorSimon Hausmann <simon.hausmann@digia.com>
Wed, 10 Sep 2014 15:13:10 +0000 (17:13 +0200)
committerLars Knoll <lars.knoll@digia.com>
Wed, 17 Sep 2014 06:13:11 +0000 (08:13 +0200)
[ChangeLog][QtQml][Important Behavior Changes] When a JavaScript object/array
is passed to C++ through a QVariant, the engine no longer immediately converts
the object recursively into a QVariantMap or QVariantList but instead stores
a QJSValue in the QVariant. This prevents a loss of data when the JS object
contains non-primitive types such as function objects for example. Code that
expects the variant type to be exactly QVariant::Map or QVariant::List may
need to be adapted. Registered conversion functions however ensure that code
that merely calls toMap() or toList() continues to work.

Task-number: QTBUG-40431
Change-Id: I1dbc1d5f8e78ad28bb62db3681b9a0b34557e7f5
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
16 files changed:
src/qml/debugger/qqmlenginedebugservice.cpp
src/qml/debugger/qqmlenginedebugservice_p.h
src/qml/jsapi/qjsengine.cpp
src/qml/jsapi/qjsvalue.cpp
src/qml/jsapi/qjsvalue_p.h
src/qml/jsruntime/qv4qobjectwrapper.cpp
src/qml/jsruntime/qv4variantobject.cpp
src/qml/qml/v8/qv8engine.cpp
src/qml/qml/v8/qv8engine_p.h
src/qml/types/qqmllistmodel.cpp
src/qml/util/qqmllistaccessor.cpp
tests/auto/qml/qjsvalue/tst_qjsvalue.cpp
tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
tests/auto/qml/qqmllocale/tst_qqmllocale.cpp
tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp
tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp

index 399cc3e..cb533a0 100644 (file)
@@ -169,9 +169,13 @@ QQmlEngineDebugService::propertyData(QObject *obj, int propIdx)
     return rv;
 }
 
-QVariant QQmlEngineDebugService::valueContents(const QVariant &value) const
+QVariant QQmlEngineDebugService::valueContents(QVariant value) const
 {
-    int userType = value.userType();
+    // We can't send JS objects across the wire, so transform them to variant
+    // maps for serialization.
+    if (value.userType() == qMetaTypeId<QJSValue>())
+        value = value.value<QJSValue>().toVariant();
+    const int userType = value.userType();
 
     //QObject * is not streamable.
     //Convert all such instances to a String value
index 1bab51d..940ca7d 100644 (file)
@@ -111,7 +111,7 @@ private:
     void buildStatesList(bool cleanList, const QList<QPointer<QObject> > &instances);
     QQmlObjectData objectData(QObject *);
     QQmlObjectProperty propertyData(QObject *, int);
-    QVariant valueContents(const QVariant &defaultValue) const;
+    QVariant valueContents(QVariant defaultValue) const;
     bool setBinding(int objectId, const QString &propertyName, const QVariant &expression, bool isLiteralValue, QString filename = QString(), int line = -1, int column = 0);
     bool resetBinding(int objectId, const QString &propertyName);
     bool setMethodBody(int objectId, const QString &method, const QString &body);
index 58251fa..0d2b394 100644 (file)
@@ -420,17 +420,19 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr)
         QV4::ScopedValue v(scope, vp->getValue(engine->m_v4Engine));
         return engine->metaTypeFromJS(v, type, ptr);
     } else if (vp->value.isEmpty()) {
-        // have a string based value without engine. Do conversion manually
-        if (type == QMetaType::Bool) {
-            *reinterpret_cast<bool*>(ptr) = vp->string.length() != 0;
-            return true;
-        }
-        if (type == QMetaType::QString) {
-            *reinterpret_cast<QString*>(ptr) = vp->string;
-            return true;
-        }
-        double d = QV4::RuntimeHelpers::stringToNumber(vp->string);
-        switch (type) {
+        if (vp->unboundData.userType() == QMetaType::QString) {
+            QString string = vp->unboundData.toString();
+            // have a string based value without engine. Do conversion manually
+            if (type == QMetaType::Bool) {
+                *reinterpret_cast<bool*>(ptr) = string.length() != 0;
+                return true;
+            }
+            if (type == QMetaType::QString) {
+                *reinterpret_cast<QString*>(ptr) = string;
+                return true;
+            }
+            double d = QV4::RuntimeHelpers::stringToNumber(string);
+            switch (type) {
             case QMetaType::Int:
                 *reinterpret_cast<int*>(ptr) = QV4::Primitive::toInt32(d);
                 return true;
@@ -466,6 +468,9 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr)
                 return true;
             default:
                 return false;
+            }
+        } else {
+            return QMetaType::convert(&vp->unboundData.data_ptr(), vp->unboundData.userType(), ptr, type);
         }
     } else {
         switch (type) {
index 891f177..47a764e 100644 (file)
@@ -58,14 +58,14 @@ QV4::ReturnedValue QJSValuePrivate::getValue(QV4::ExecutionEngine *e)
     }
 
     if (value.isEmpty()) {
-        value = QV4::Encode(engine->newString(string));
+        value = QV4::Encode(engine->v8Engine->fromVariant(unboundData));
         PersistentValuePrivate **listRoot = &engine->memoryManager->m_persistentValues;
         prev = listRoot;
         next = *listRoot;
         *prev = this;
         if (next)
             next->prev = &this->next;
-        string = QString();
+        unboundData.clear();
     }
     return value.asReturnedValue();
 }
@@ -353,8 +353,21 @@ bool QJSValue::isVariant() const
 */
 QString QJSValue::toString() const
 {
-    if (d->value.isEmpty())
-        return d->string;
+    if (d->value.isEmpty()) {
+        if (d->unboundData.type() == QVariant::Map)
+            return QStringLiteral("[object Object]");
+        else if (d->unboundData.type() == QVariant::List) {
+            const QVariantList list = d->unboundData.toList();
+            QString result;
+            for (int i = 0; i < list.count(); ++i) {
+                if (i > 0)
+                    result.append(QLatin1Char(','));
+                result.append(list.at(i).toString());
+            }
+            return result;
+        }
+        return d->unboundData.toString();
+    }
     return d->value.toQStringNoThrow();
 }
 
@@ -372,8 +385,14 @@ QString QJSValue::toString() const
 */
 double QJSValue::toNumber() const
 {
-    if (d->value.isEmpty())
-        return RuntimeHelpers::stringToNumber(d->string);
+    if (d->value.isEmpty()) {
+        if (d->unboundData.type() == QVariant::String)
+            return RuntimeHelpers::stringToNumber(d->unboundData.toString());
+        else if (d->unboundData.canConvert<double>())
+            return d->unboundData.value<double>();
+        else
+            return std::numeric_limits<double>::quiet_NaN();
+    }
 
     QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
     double dbl = d->value.toNumber();
@@ -398,8 +417,12 @@ double QJSValue::toNumber() const
 */
 bool QJSValue::toBool() const
 {
-    if (d->value.isEmpty())
-        return d->string.length() > 0;
+    if (d->value.isEmpty()) {
+        if (d->unboundData.userType() == QMetaType::QString)
+            return d->unboundData.toString().length() > 0;
+        else
+            return d->unboundData.toBool();
+    }
 
     QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
     bool b = d->value.toBoolean();
@@ -424,8 +447,12 @@ bool QJSValue::toBool() const
 */
 qint32 QJSValue::toInt() const
 {
-    if (d->value.isEmpty())
-        return QV4::Primitive::toInt32(RuntimeHelpers::stringToNumber(d->string));
+    if (d->value.isEmpty()) {
+        if (d->unboundData.userType() == QMetaType::QString)
+            return QV4::Primitive::toInt32(RuntimeHelpers::stringToNumber(d->unboundData.toString()));
+        else
+            return d->unboundData.toInt();
+    }
 
     QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
     qint32 i = d->value.toInt32();
@@ -450,8 +477,12 @@ qint32 QJSValue::toInt() const
 */
 quint32 QJSValue::toUInt() const
 {
-    if (d->value.isEmpty())
-        return QV4::Primitive::toUInt32(RuntimeHelpers::stringToNumber(d->string));
+    if (d->value.isEmpty()) {
+        if (d->unboundData.userType() == QMetaType::QString)
+            return QV4::Primitive::toUInt32(RuntimeHelpers::stringToNumber(d->unboundData.toString()));
+        else
+            return d->unboundData.toUInt();
+    }
 
     QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
     quint32 u = d->value.toUInt32();
@@ -487,7 +518,7 @@ quint32 QJSValue::toUInt() const
 QVariant QJSValue::toVariant() const
 {
     if (d->value.isEmpty())
-        return QVariant(d->string);
+        return d->unboundData;
 
     return QV4::VariantObject::toVariant(d->value);
 }
@@ -775,8 +806,10 @@ bool QJSValue::equals(const QJSValue& other) const
 {
     if (d->value.isEmpty()) {
         if (other.d->value.isEmpty())
-            return d->string == other.d->string;
-        return js_equal(d->string, QV4::ValueRef(other.d->value));
+            return d->unboundData == other.d->unboundData;
+        if (d->unboundData.type() == QVariant::Map || d->unboundData.type() == QVariant::List)
+            return false;
+        return js_equal(d->unboundData.toString(), QV4::ValueRef(other.d->value));
     }
     if (other.d->value.isEmpty())
         return other.equals(*this);
@@ -810,9 +843,11 @@ bool QJSValue::strictlyEquals(const QJSValue& other) const
 {
     if (d->value.isEmpty()) {
         if (other.d->value.isEmpty())
-            return d->string == other.d->string;
+            return d->unboundData == other.d->unboundData;
+        if (d->unboundData.type() == QVariant::Map || d->unboundData.type() == QVariant::List)
+            return false;
         if (other.d->value.isString())
-            return d->string == other.d->value.stringValue()->toQString();
+            return d->unboundData.toString() == other.d->value.stringValue()->toQString();
         return false;
     }
     if (other.d->value.isEmpty())
index e66c1bc..43a3a74 100644 (file)
@@ -51,6 +51,7 @@
 #include <private/qv4string_p.h>
 #include <private/qv4engine_p.h>
 #include <private/qv4object_p.h>
+#include <private/qflagpointer_p.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -72,8 +73,8 @@ public:
         Q_ASSERT(!value.isEmpty());
     }
     QJSValuePrivate(const QString &s)
-        : PersistentValuePrivate(QV4::Primitive::emptyValue().asReturnedValue())
-        , string(s)
+        : PersistentValuePrivate(QV4::Primitive::emptyValue().asReturnedValue()),
+          unboundData(s)
     {
     }
 
@@ -81,7 +82,7 @@ public:
 
     static QJSValuePrivate *get(const QJSValue &v) { return v.d; }
 
-    QString string;
+    QVariant unboundData;
 };
 
 QT_END_NAMESPACE
index ff51ee6..32379f7 100644 (file)
@@ -1665,17 +1665,13 @@ void CallArgument::fromValue(int callType, QV8Engine *engine, const QV4::ValueRe
         type = -1;
 
         QQmlEnginePrivate *ep = engine->engine() ? QQmlEnginePrivate::get(engine->engine()) : 0;
-        QVariant v = engine->toVariant(value, -1); // why -1 instead of callType?
+        QVariant v = engine->toVariant(value, callType);
 
         if (v.userType() == callType) {
             *qvariantPtr = v;
         } else if (v.canConvert(callType)) {
             *qvariantPtr = v;
             qvariantPtr->convert(callType);
-        } else if (QV4::SequencePrototype::isSequenceType(callType) && v.userType() == qMetaTypeId<QVariantList>()) {
-            // convert the JS array to a sequence of the correct type.
-            QVariant seqV = engine->toVariant(value, callType);
-            *qvariantPtr = seqV;
         } else {
             QQmlMetaObject mo = ep ? ep->rawMetaObjectForType(callType) : QQmlMetaObject();
             if (!mo.isNull()) {
index 3465750..68b08fb 100644 (file)
@@ -59,7 +59,7 @@ VariantObject::Data::Data(ExecutionEngine *engine, const QVariant &value)
 QVariant VariantObject::toVariant(const QV4::ValueRef v)
 {
     if (v->asObject())
-        return v->engine()->v8Engine->variantFromJS(v);
+        return v->engine()->v8Engine->toVariant(v, /*typeHint*/ -1, /*createJSValueForObjects*/ false);
 
     if (v->isString())
         return QVariant(v->stringValue()->toQString());
index b56af2e..993cf96 100644 (file)
@@ -79,6 +79,37 @@ Q_DECLARE_METATYPE(QList<int>)
 // QQmlEngine is not available
 QT_BEGIN_NAMESPACE
 
+template <typename ReturnType>
+ReturnType convertJSValueToVariantType(const QJSValue &value)
+{
+    return value.toVariant().value<ReturnType>();
+}
+
+static void saveJSValue(QDataStream &stream, const void *data)
+{
+    const QJSValue *jsv = reinterpret_cast<const QJSValue *>(data);
+    const quint32 isNullOrUndefined = jsv->isNull() | (jsv->isUndefined() << 1);
+    stream << isNullOrUndefined;
+    if (!isNullOrUndefined)
+        reinterpret_cast<const QJSValue*>(data)->toVariant().save(stream);
+}
+
+static void restoreJSValue(QDataStream &stream, void *data)
+{
+    QJSValue *jsv = reinterpret_cast<QJSValue*>(data);
+    QJSValuePrivate *d = QJSValuePrivate::get(*jsv);
+
+    quint32 isNullOrUndefined;
+    stream >> isNullOrUndefined;
+    if (isNullOrUndefined & 0x1) {
+        d->value = QV4::Primitive::nullValue().asReturnedValue();
+    } else if (isNullOrUndefined & 0x2) {
+        d->value = QV4::Primitive::undefinedValue().asReturnedValue();
+    } else {
+        d->value = QV4::Primitive::emptyValue().asReturnedValue();
+        d->unboundData.load(stream);
+    }
+}
 
 QV8Engine::QV8Engine(QJSEngine* qq)
     : q(qq)
@@ -96,6 +127,14 @@ QV8Engine::QV8Engine(QJSEngine* qq)
     qMetaTypeId<QJSValue>();
     qMetaTypeId<QList<int> >();
 
+    if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantMap>())
+        QMetaType::registerConverter<QJSValue, QVariantMap>(convertJSValueToVariantType<QVariantMap>);
+    if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantList>())
+        QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>);
+    if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>())
+        QMetaType::registerConverter<QJSValue, QStringList>(convertJSValueToVariantType<QStringList>);
+    QMetaType::registerStreamOperators(qMetaTypeId<QJSValue>(), saveJSValue, restoreJSValue);
+
     m_v4Engine = new QV4::ExecutionEngine;
     m_v4Engine->v8Engine = this;
 
@@ -116,7 +155,7 @@ QV8Engine::~QV8Engine()
     delete m_v4Engine;
 }
 
-QVariant QV8Engine::toVariant(const QV4::ValueRef value, int typeHint)
+QVariant QV8Engine::toVariant(const QV4::ValueRef value, int typeHint, bool createJSValueForObjects, V8ObjectSet *visitedObjects)
 {
     Q_ASSERT (!value->isEmpty());
     QV4::Scope scope(m_v4Engine);
@@ -178,7 +217,88 @@ QVariant QV8Engine::toVariant(const QV4::ValueRef value, int typeHint)
             return retn;
     }
 
-    return toBasicVariant(value);
+    if (value->isUndefined())
+        return QVariant();
+    if (value->isNull())
+        return QVariant(QMetaType::VoidStar, (void *)0);
+    if (value->isBoolean())
+        return value->booleanValue();
+    if (value->isInteger())
+        return value->integerValue();
+    if (value->isNumber())
+        return value->asDouble();
+    if (value->isString())
+        return value->stringValue()->toQString();
+    if (QQmlLocaleData *ld = value->as<QQmlLocaleData>())
+        return ld->d()->locale;
+    if (QV4::DateObject *d = value->asDateObject())
+        return d->toQDateTime();
+    // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)!
+
+    QV4::ScopedObject o(scope, value);
+    Q_ASSERT(o);
+
+    if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>())
+        return re->toQRegExp();
+
+    if (createJSValueForObjects)
+        return QVariant::fromValue(QJSValue(new QJSValuePrivate(o->asReturnedValue())));
+
+    return objectToVariant(o, visitedObjects);
+}
+
+QVariant QV8Engine::objectToVariant(QV4::Object *o, V8ObjectSet *visitedObjects)
+{
+    Q_ASSERT(o);
+
+    V8ObjectSet recursionGuardSet;
+    if (!visitedObjects) {
+        visitedObjects = &recursionGuardSet;
+    } else if (visitedObjects->contains(o)) {
+        // Avoid recursion.
+        // For compatibility with QVariant{List,Map} conversion, we return an
+        // empty object (and no error is thrown).
+        if (o->asArrayObject())
+            return QVariantList();
+        return QVariantMap();
+    }
+    visitedObjects->insert(o);
+
+    QVariant result;
+
+    if (o->asArrayObject()) {
+        QV4::Scope scope(m_v4Engine);
+        QV4::ScopedArrayObject a(scope, o->asReturnedValue());
+        QV4::ScopedValue v(scope);
+        QVariantList list;
+
+        int length = a->getLength();
+        for (int ii = 0; ii < length; ++ii) {
+            v = a->getIndexed(ii);
+            list << toVariant(v, -1, /*createJSValueForObjects*/false, visitedObjects);
+        }
+
+        result = list;
+    } else if (!o->asFunctionObject()) {
+        QVariantMap map;
+        QV4::Scope scope(m_v4Engine);
+        QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
+        QV4::ScopedValue name(scope);
+        QV4::ScopedValue val(scope);
+        while (1) {
+            name = it.nextPropertyNameAsString(val);
+            if (name->isNull())
+                break;
+
+            QString key = name->toQStringNoThrow();
+            map.insert(key, toVariant(val, /*type hint*/-1, /*createJSValueForObjects*/false, visitedObjects));
+        }
+
+        result = map;
+    }
+
+    visitedObjects->remove(o);
+    return result;
 }
 
 static QV4::ReturnedValue arrayFromStringList(QV8Engine *engine, const QStringList &list)
@@ -370,61 +490,6 @@ QQmlContextData *QV8Engine::callingContext()
     return QV4::QmlContextWrapper::callingContext(m_v4Engine);
 }
 
-// Converts a JS value to a QVariant.
-// Null, Undefined -> QVariant() (invalid)
-// Boolean -> QVariant(bool)
-// Number -> QVariant(double)
-// String -> QVariant(QString)
-// Array -> QVariantList(...)
-// Date -> QVariant(QDateTime)
-// RegExp -> QVariant(QRegExp)
-// [Any other object] -> QVariantMap(...)
-QVariant QV8Engine::toBasicVariant(const QV4::ValueRef value)
-{
-    if (value->isUndefined())
-        return QVariant();
-    if (value->isNull())
-        return QVariant(QMetaType::VoidStar, (void *)0);
-    if (value->isBoolean())
-        return value->booleanValue();
-    if (value->isInteger())
-        return value->integerValue();
-    if (value->isNumber())
-        return value->asDouble();
-    if (value->isString())
-        return value->stringValue()->toQString();
-    if (QQmlLocaleData *ld = value->as<QQmlLocaleData>())
-        return ld->d()->locale;
-    if (QV4::DateObject *d = value->asDateObject())
-        return d->toQDateTime();
-    // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)!
-
-    QV4::Scope scope(value->engine());
-    QV4::ScopedObject o(scope, value);
-    Q_ASSERT(o);
-
-    if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>())
-        return re->toQRegExp();
-    if (o->asArrayObject()) {
-        QV4::ScopedArrayObject a(scope, o);
-        QV4::ScopedValue v(scope);
-        QVariantList rv;
-
-        int length = a->getLength();
-        for (int ii = 0; ii < length; ++ii) {
-            v = a->getIndexed(ii);
-            rv << toVariant(v, -1);
-        }
-        return rv;
-    }
-    if (!value->asFunctionObject())
-        return variantMapFromJS(o);
-
-    return QVariant();
-}
-
-
-
 void QV8Engine::initializeGlobal()
 {
     QV4::Scope scope(m_v4Engine);
@@ -547,36 +612,6 @@ QV4::ReturnedValue QV8Engine::variantListToJS(const QVariantList &lst)
     return a.asReturnedValue();
 }
 
-// Converts a JS Array object to a QVariantList.
-// The result is a QVariantList with length equal to the length
-// of the JS Array, and elements being the JS Array's elements
-// converted to QVariants, recursively.
-QVariantList QV8Engine::variantListFromJS(QV4::ArrayObject *a, V8ObjectSet &visitedObjects)
-{
-    QVariantList result;
-    if (!a)
-        return result;
-
-    if (visitedObjects.contains(a))
-        // Avoid recursion.
-        return result;
-
-    visitedObjects.insert(a);
-
-    QV4::Scope scope(a->engine());
-    QV4::ScopedValue v(scope);
-
-    quint32 length = a->getLength();
-    for (quint32 i = 0; i < length; ++i) {
-        v = a->getIndexed(i);
-        result.append(variantFromJS(v, visitedObjects));
-    }
-
-    visitedObjects.remove(a);
-
-    return result;
-}
-
 // Converts a QVariantMap to JS.
 // The result is a new Object object with property names being
 // the keys of the QVariantMap, and values being the values of
@@ -600,43 +635,6 @@ QV4::ReturnedValue QV8Engine::variantMapToJS(const QVariantMap &vmap)
     return o.asReturnedValue();
 }
 
-// Converts a JS Object to a QVariantMap.
-// The result is a QVariantMap with keys being the property names
-// of the object, and values being the values of the JS object's
-// properties converted to QVariants, recursively.
-QVariantMap QV8Engine::variantMapFromJS(QV4::Object *o, V8ObjectSet &visitedObjects)
-{
-    QVariantMap result;
-
-    if (!o || o->asFunctionObject())
-        return result;
-
-    if (visitedObjects.contains(o)) {
-        // Avoid recursion.
-        // For compatibility with QVariant{List,Map} conversion, we return an
-        // empty object (and no error is thrown).
-        return result;
-    }
-    QV4::Scope scope(o->engine());
-
-    visitedObjects.insert(o);
-
-    QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
-    QV4::ScopedValue name(scope);
-    QV4::ScopedValue val(scope);
-    while (1) {
-        name = it.nextPropertyNameAsString(val);
-        if (name->isNull())
-            break;
-
-        QString key = name->toQStringNoThrow();
-        result.insert(key, variantFromJS(val, visitedObjects));
-    }
-
-    visitedObjects.remove(o);
-    return result;
-}
-
 // Converts the meta-type defined by the given type and data to JS.
 // Returns the value if conversion succeeded, an empty handle otherwise.
 QV4::ReturnedValue QV8Engine::metaTypeToJS(int type, const void *data)
@@ -811,7 +809,7 @@ bool QV8Engine::metaTypeFromJS(const QV4::ValueRef value, int type, void *data)
     case QMetaType::QVariantList: {
         QV4::ScopedArrayObject a(scope, value);
         if (a) {
-            *reinterpret_cast<QVariantList *>(data) = variantListFromJS(a);
+            *reinterpret_cast<QVariantList *>(data) = toVariant(a, /*typeHint*/-1, /*createJSValueForObjects*/false).toList();
             return true;
         }
         break;
@@ -825,7 +823,7 @@ bool QV8Engine::metaTypeFromJS(const QV4::ValueRef value, int type, void *data)
         break;
     }
     case QMetaType::QVariant:
-        *reinterpret_cast<QVariant*>(data) = variantFromJS(value);
+        *reinterpret_cast<QVariant*>(data) = toVariant(value, /*typeHint*/-1, /*createJSValueForObjects*/false);
         return true;
     case QMetaType::QJsonValue:
         *reinterpret_cast<QJsonValue *>(data) = QV4::JsonObject::toJsonValue(value);
@@ -921,55 +919,6 @@ QV4::ReturnedValue QV8Engine::variantToJS(const QVariant &value)
     return metaTypeToJS(value.userType(), value.constData());
 }
 
-// Converts a JS value to a QVariant.
-// Undefined -> QVariant() (invalid)
-// Null -> QVariant((void*)0)
-// Boolean -> QVariant(bool)
-// Number -> QVariant(double)
-// String -> QVariant(QString)
-// Array -> QVariantList(...)
-// Date -> QVariant(QDateTime)
-// RegExp -> QVariant(QRegExp)
-// [Any other object] -> QVariantMap(...)
-QVariant QV8Engine::variantFromJS(const QV4::ValueRef value,
-                                  V8ObjectSet &visitedObjects)
-{
-    Q_ASSERT(!value->isEmpty());
-    if (value->isUndefined())
-        return QVariant();
-    if (value->isNull())
-        return QVariant(QMetaType::VoidStar, 0);
-    if (value->isBoolean())
-        return value->booleanValue();
-    if (value->isInteger())
-        return value->integerValue();
-    if (value->isNumber())
-        return value->asDouble();
-    if (value->isString())
-        return value->stringValue()->toQString();
-
-    Q_ASSERT(value->isObject());
-    QV4::Scope scope(value->engine());
-
-    if (value->asArrayObject()) {
-        QV4::ScopedArrayObject a(scope, value);
-        return variantListFromJS(a, visitedObjects);
-    }
-    if (QV4::DateObject *d = value->asDateObject())
-        return d->toQDateTime();
-    if (QV4::RegExpObject *re = value->as<QV4::RegExpObject>())
-        return re->toQRegExp();
-    if (QV4::VariantObject *v = value->as<QV4::VariantObject>())
-        return v->d()->data;
-    if (value->as<QV4::QObjectWrapper>())
-        return qVariantFromValue(qtObjectFromJS(value));
-    if (QV4::QmlValueTypeWrapper *v = value->as<QV4::QmlValueTypeWrapper>())
-        return v->toVariant();
-    QV4::ScopedObject o(scope, value);
-    return variantMapFromJS(o, visitedObjects);
-}
-
-
 bool QV8Engine::convertToNativeQObject(const QV4::ValueRef value, const QByteArray &targetType, void **result)
 {
     if (!targetType.endsWith('*'))
index e2c96ff..51e857c 100644 (file)
@@ -197,9 +197,13 @@ public:
 
     void freezeObject(const QV4::ValueRef value);
 
-    QVariant toVariant(const QV4::ValueRef value, int typeHint);
+    QVariant toVariant(const QV4::ValueRef value, int typeHint, bool createJSValueForObjects = true, V8ObjectSet *visitedObjects = 0);
+    QVariant objectToVariant(QV4::Object *o, V8ObjectSet *visitedObjects = 0);
     QV4::ReturnedValue fromVariant(const QVariant &);
 
+    QVariantMap variantMapFromJS(QV4::Object *o)
+    { return objectToVariant(o).toMap(); }
+
     // Return a JS string for the given QString \a string
     QV4::ReturnedValue toString(const QString &string);
 
@@ -218,16 +222,8 @@ public:
     void setExtensionData(int, Deletable *);
 
     QV4::ReturnedValue variantListToJS(const QVariantList &lst);
-    inline QVariantList variantListFromJS(QV4::ArrayObject *array)
-    { V8ObjectSet visitedObjects; return variantListFromJS(array, visitedObjects); }
-
     QV4::ReturnedValue variantMapToJS(const QVariantMap &vmap);
-    inline QVariantMap variantMapFromJS(QV4::Object *object)
-    { V8ObjectSet visitedObjects; return variantMapFromJS(object, visitedObjects); }
-
     QV4::ReturnedValue variantToJS(const QVariant &value);
-    inline QVariant variantFromJS(const QV4::ValueRef value)
-    { V8ObjectSet visitedObjects; return variantFromJS(value, visitedObjects); }
 
     QV4::ReturnedValue metaTypeToJS(int type, const void *data);
     bool metaTypeFromJS(const QV4::ValueRef value, int type, void *data);
@@ -265,15 +261,9 @@ protected:
 
     QHash<QString, quint32> m_consoleCount;
 
-    QVariant toBasicVariant(const QV4::ValueRef);
-
     void initializeGlobal();
 
 private:
-    QVariantList variantListFromJS(QV4::ArrayObject *array, V8ObjectSet &visitedObjects);
-    QVariantMap variantMapFromJS(QV4::Object *object, V8ObjectSet &visitedObjects);
-    QVariant variantFromJS(const QV4::ValueRef value, V8ObjectSet &visitedObjects);
-
     Q_DISABLE_COPY(QV8Engine)
 };
 
index 2587997..142625d 100644 (file)
@@ -1330,6 +1330,11 @@ void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int>
 
         QVariant value = object[key];
 
+        // A JS array/object is translated into a (hierarchical) QQmlListModel,
+        // so translate to a variant map/list first with toVariant().
+        if (value.userType() == qMetaTypeId<QJSValue>())
+            value = value.value<QJSValue>().toVariant();
+
         if (value.type() == QVariant::List) {
             QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);
 
@@ -1392,6 +1397,12 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
     QQmlListModel *parentModel = m_owner->m_owner;
 
     QVariant v = value(index);
+
+    // A JS array/object is translated into a (hierarchical) QQmlListModel,
+    // so translate to a variant map/list first with toVariant().
+    if (v.userType() == qMetaTypeId<QJSValue>())
+        v= v.value<QJSValue>().toVariant();
+
     if (v.type() == QVariant::List) {
         QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);
 
index e434d6c..5a199ab 100644 (file)
@@ -61,6 +61,11 @@ void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine)
 {
     d = v;
 
+    // An incoming JS array as model is treated as a variant list, so we need to
+    // convert it first with toVariant().
+    if (d.userType() == qMetaTypeId<QJSValue>())
+        d = d.value<QJSValue>().toVariant();
+
     QQmlEnginePrivate *enginePrivate = engine?QQmlEnginePrivate::get(engine):0;
 
     if (!d.isValid()) {
@@ -73,7 +78,7 @@ void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine)
         m_type = Integer;
     } else if ((!enginePrivate && QQmlMetaType::isQObject(d.userType())) ||
                (enginePrivate && enginePrivate->isQObject(d.userType()))) {
-        QObject *data = enginePrivate?enginePrivate->toQObject(v):QQmlMetaType::toQObject(v);
+        QObject *data = enginePrivate?enginePrivate->toQObject(d):QQmlMetaType::toQObject(d);
         d = QVariant::fromValue(data);
         m_type = Instance;
     } else if (d.userType() == qMetaTypeId<QQmlListReference>()) {
index 837403d..9c615cf 100644 (file)
 #include "tst_qjsvalue.h"
 #include <QtWidgets/QPushButton>
 
-QT_BEGIN_NAMESPACE
-extern bool qt_script_isJITEnabled();
-QT_END_NAMESPACE
-
 tst_QJSValue::tst_QJSValue()
     : engine(0)
 {
@@ -45,8 +41,7 @@ tst_QJSValue::tst_QJSValue()
 
 tst_QJSValue::~tst_QJSValue()
 {
-    if (engine)
-        delete engine;
+    delete engine;
 }
 
 void tst_QJSValue::ctor_invalid()
@@ -308,6 +303,19 @@ void tst_QJSValue::ctor_copyAndAssign()
     QCOMPARE(v5.toNumber(), 1.0);
 }
 
+static QJSValue createUnboundValue(const QJSValue &value)
+{
+    QVariant variant = QVariant::fromValue(value);
+    QBuffer buffer;
+    buffer.open(QIODevice::ReadWrite);
+    QDataStream stream(&buffer);
+    variant.save(stream);
+    buffer.seek(0);
+    QVariant resultVariant;
+    resultVariant.load(stream);
+    return resultVariant.value<QJSValue>();
+}
+
 void tst_QJSValue::toString()
 {
     QJSEngine eng;
@@ -406,6 +414,28 @@ void tst_QJSValue::toString()
     variant = eng.toScriptValue(QUrl());
     QVERIFY(variant.isVariant());
     QVERIFY(variant.toString().isEmpty());
+
+    {
+        QJSValue o = eng.newObject();
+        o.setProperty(QStringLiteral("test"), 42);
+        QCOMPARE(o.toString(), QStringLiteral("[object Object]"));
+
+        o = createUnboundValue(o);
+        QVERIFY(!o.engine());
+        QCOMPARE(o.toString(), QStringLiteral("[object Object]"));
+    }
+
+    {
+        QJSValue o = eng.newArray();
+        o.setProperty(0, 1);
+        o.setProperty(1, 2);
+        o.setProperty(2, 3);
+        QCOMPARE(o.toString(), QStringLiteral("1,2,3"));
+
+        o = createUnboundValue(o);
+        QVERIFY(!o.engine());
+        QCOMPARE(o.toString(), QStringLiteral("1,2,3"));
+    }
 }
 
 void tst_QJSValue::toNumber()
@@ -419,35 +449,43 @@ void tst_QJSValue::toNumber()
     QJSValue null = eng.evaluate("null");
     QCOMPARE(null.toNumber(), 0.0);
     QCOMPARE(qjsvalue_cast<qreal>(null), 0.0);
+    QCOMPARE(createUnboundValue(null).toNumber(), 0.0);
 
     {
         QJSValue falskt = eng.toScriptValue(false);
         QCOMPARE(falskt.toNumber(), 0.0);
+        QCOMPARE(createUnboundValue(falskt).toNumber(), 0.0);
         QCOMPARE(qjsvalue_cast<qreal>(falskt), 0.0);
 
         QJSValue sant = eng.toScriptValue(true);
         QCOMPARE(sant.toNumber(), 1.0);
+        QCOMPARE(createUnboundValue(sant).toNumber(), 1.0);
         QCOMPARE(qjsvalue_cast<qreal>(sant), 1.0);
 
         QJSValue number = eng.toScriptValue(123.0);
         QCOMPARE(number.toNumber(), 123.0);
         QCOMPARE(qjsvalue_cast<qreal>(number), 123.0);
+        QCOMPARE(createUnboundValue(number).toNumber(), 123.0);
 
         QJSValue str = eng.toScriptValue(QString("ciao"));
         QCOMPARE(qIsNaN(str.toNumber()), true);
         QCOMPARE(qIsNaN(qjsvalue_cast<qreal>(str)), true);
+        QCOMPARE(qIsNaN(createUnboundValue(str).toNumber()), true);
 
         QJSValue str2 = eng.toScriptValue(QString("123"));
         QCOMPARE(str2.toNumber(), 123.0);
         QCOMPARE(qjsvalue_cast<qreal>(str2), 123.0);
+        QCOMPARE(createUnboundValue(str2).toNumber(), 123.0);
     }
 
     QJSValue object = eng.newObject();
     QCOMPARE(qIsNaN(object.toNumber()), true);
+    QCOMPARE(qIsNaN(createUnboundValue(object).toNumber()), true);
     QCOMPARE(qIsNaN(qjsvalue_cast<qreal>(object)), true);
 
     QJSValue inv = QJSValue();
     QVERIFY(qIsNaN(inv.toNumber()));
+    QCOMPARE(qIsNaN(createUnboundValue(inv).toNumber()), true);
     QVERIFY(qIsNaN(qjsvalue_cast<qreal>(inv)));
 
     // V2 constructors
index c246647..196f6b9 100644 (file)
@@ -746,13 +746,13 @@ void tst_qqmlecmascript::arrayExpressions()
 
     MyExpression expr(&context, "[a, b, c, 10]");
     QVariant result = expr.evaluate();
-    QCOMPARE(result.userType(), qMetaTypeId<QVariantList>());
-    QVariantList list = qvariant_cast<QVariantList>(result);
-    QCOMPARE(list.count(), 4);
-    QCOMPARE(list.at(0).value<QObject*>(), &obj1);
-    QCOMPARE(list.at(1).value<QObject*>(), &obj2);
-    QCOMPARE(list.at(2).value<QObject*>(), &obj3);
-    QCOMPARE(list.at(3).value<int>(), 10);
+    QCOMPARE(result.userType(), qMetaTypeId<QJSValue>());
+    QJSValue list = qvariant_cast<QJSValue>(result);
+    QCOMPARE(list.property("length").toInt(), 4);
+    QCOMPARE(list.property(0).toQObject(), &obj1);
+    QCOMPARE(list.property(1).toQObject(), &obj2);
+    QCOMPARE(list.property(2).toQObject(), &obj3);
+    QCOMPARE(list.property(3).toInt(), 10);
 }
 
 // Tests that modifying a context property will reevaluate expressions
@@ -4811,7 +4811,7 @@ void tst_qqmlecmascript::propertyVarCpp()
     QCOMPARE(object->property("varProperty2"), QVariant(QLatin1String("randomString")));
     QCOMPARE(object->property("varProperty2").userType(), (int)QVariant::String);
     // now enforce behaviour when accessing JavaScript objects from cpp.
-    QCOMPARE(object->property("jsobject").userType(), (int)QVariant::Map);
+    QCOMPARE(object->property("jsobject").userType(), qMetaTypeId<QJSValue>());
     delete object;
 }
 
@@ -5166,7 +5166,7 @@ void tst_qqmlecmascript::objectConversion()
     QVERIFY(object != 0);
     QVariant retn;
     QMetaObject::invokeMethod(object, "circularObject", Q_RETURN_ARG(QVariant, retn));
-    QCOMPARE(retn.value<QVariantMap>().value("test"), QVariant(100));
+    QCOMPARE(retn.value<QJSValue>().property("test").toInt(), int(100));
 
     delete object;
 }
@@ -5434,7 +5434,7 @@ void tst_qqmlecmascript::sequenceConversionWrite()
         QVERIFY(seq != 0);
 
         // we haven't registered QList<QPoint> as a sequence type, so writing shouldn't work.
-        QString warningOne = qmlFile.toString() + QLatin1String(":16: Error: Cannot assign QVariantList to an unregistered type");
+        QString warningOne = qmlFile.toString() + QLatin1String(":16: Error: Cannot assign QJSValue to an unregistered type");
         QTest::ignoreMessage(QtWarningMsg, warningOne.toLatin1().constData());
 
         QMetaObject::invokeMethod(object, "performTest");
index 320333f..4ee75f8 100644 (file)
@@ -486,7 +486,7 @@ void tst_qqmllocale::weekDays()
         Q_ARG(QVariant, QVariant(locale)));
 
     QVariant val = obj->property("weekDays");
-    QVERIFY(val.type() == QVariant::List);
+    QVERIFY(val.userType() == qMetaTypeId<QJSValue>());
 
     QList<QVariant> qmlDays = val.toList();
     QList<Qt::DayOfWeek> days = QLocale(locale).weekdays();
@@ -528,7 +528,7 @@ void tst_qqmllocale::uiLanguages()
         Q_ARG(QVariant, QVariant(locale)));
 
     QVariant val = obj->property("uiLanguages");
-    QVERIFY(val.type() == QVariant::List);
+    QVERIFY(val.userType() == qMetaTypeId<QJSValue>());
 
     QList<QVariant> qmlLangs = val.toList();
     QStringList langs = QLocale(locale).uiLanguages();
index 510a76c..b6e7a43 100644 (file)
@@ -249,16 +249,22 @@ void tst_QQmlMetaObject::property()
     QSignalSpy changedSpy(object, SIGNAL(testChanged()));
     QObject::connect(object, SIGNAL(testChanged()), object, SLOT(deleteLater()));
 
+    QVariant value = prop.read(object);
+    if (value.userType() == qMetaTypeId<QJSValue>())
+        value = value.value<QJSValue>().toVariant();
     if (expectedValue.isValid())
-        QCOMPARE(prop.read(object), expectedValue);
+        QCOMPARE(value, expectedValue);
     else
-        QVERIFY(prop.read(object).isValid());
+        QVERIFY(value.isValid());
     QCOMPARE(changedSpy.count(), 0);
 
     if (isWritable) {
         QVERIFY(prop.write(object, newValue));
         QCOMPARE(changedSpy.count(), 1);
-        QCOMPARE(prop.read(object), newValue);
+        QVariant value = prop.read(object);
+        if (value.userType() == qMetaTypeId<QJSValue>())
+            value = value.value<QJSValue>().toVariant();
+        QCOMPARE(value, newValue);
     } else {
         QVERIFY(!prop.write(object, prop.read(object)));
         QCOMPARE(changedSpy.count(), 0);
index 66ddb39..801707f 100644 (file)
@@ -115,7 +115,10 @@ void tst_QQuickWorkerScript::messaging()
     waitForEchoMessage(worker);
 
     const QMetaObject *mo = worker->metaObject();
-    QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker).value<QVariant>(), value);
+    QVariant response = mo->property(mo->indexOfProperty("response")).read(worker).value<QVariant>();
+    if (response.userType() == qMetaTypeId<QJSValue>())
+        response = response.value<QJSValue>().toVariant();
+    QCOMPARE(response, value);
 
     qApp->processEvents();
     delete worker;