diff options
author | Simon Hausmann <simon.hausmann@digia.com> | 2014-09-10 17:13:10 +0200 |
---|---|---|
committer | Lars Knoll <lars.knoll@digia.com> | 2014-09-17 08:13:11 +0200 |
commit | 3dbe05f6bf3fd51ce8097c35f6c7f12b39acb0f6 (patch) | |
tree | 444ed433aa02085357b589b19b28f4bc1c243320 | |
parent | cfe1a8152c948a4586ffa1fe79b47f9a0e88beb5 (diff) |
Fix mapping of JS objects/arrays to C++
[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>
-rw-r--r-- | src/qml/debugger/qqmlenginedebugservice.cpp | 8 | ||||
-rw-r--r-- | src/qml/debugger/qqmlenginedebugservice_p.h | 2 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 27 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue.cpp | 69 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue_p.h | 7 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 6 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4variantobject.cpp | 2 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8engine.cpp | 299 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8engine_p.h | 20 | ||||
-rw-r--r-- | src/qml/types/qqmllistmodel.cpp | 11 | ||||
-rw-r--r-- | src/qml/util/qqmllistaccessor.cpp | 7 | ||||
-rw-r--r-- | tests/auto/qml/qjsvalue/tst_qjsvalue.cpp | 50 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 20 | ||||
-rw-r--r-- | tests/auto/qml/qqmllocale/tst_qqmllocale.cpp | 4 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp | 12 | ||||
-rw-r--r-- | tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp | 5 |
16 files changed, 296 insertions, 253 deletions
diff --git a/src/qml/debugger/qqmlenginedebugservice.cpp b/src/qml/debugger/qqmlenginedebugservice.cpp index 399cc3e07d..cb533a0459 100644 --- a/src/qml/debugger/qqmlenginedebugservice.cpp +++ b/src/qml/debugger/qqmlenginedebugservice.cpp @@ -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 diff --git a/src/qml/debugger/qqmlenginedebugservice_p.h b/src/qml/debugger/qqmlenginedebugservice_p.h index 1bab51d17b..940ca7d99c 100644 --- a/src/qml/debugger/qqmlenginedebugservice_p.h +++ b/src/qml/debugger/qqmlenginedebugservice_p.h @@ -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); diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 58251fac96..0d2b394cd6 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -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) { diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index 891f17762c..47a764e641 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -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()) diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h index e66c1bcde4..43a3a74e38 100644 --- a/src/qml/jsapi/qjsvalue_p.h +++ b/src/qml/jsapi/qjsvalue_p.h @@ -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 diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index ff51ee6c6f..32379f7f1e 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -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()) { diff --git a/src/qml/jsruntime/qv4variantobject.cpp b/src/qml/jsruntime/qv4variantobject.cpp index 34657501e8..68b08fb3ca 100644 --- a/src/qml/jsruntime/qv4variantobject.cpp +++ b/src/qml/jsruntime/qv4variantobject.cpp @@ -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()); diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp index b56af2e7f6..993cf96104 100644 --- a/src/qml/qml/v8/qv8engine.cpp +++ b/src/qml/qml/v8/qv8engine.cpp @@ -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('*')) diff --git a/src/qml/qml/v8/qv8engine_p.h b/src/qml/qml/v8/qv8engine_p.h index e2c96ffc87..51e857c8a2 100644 --- a/src/qml/qml/v8/qv8engine_p.h +++ b/src/qml/qml/v8/qv8engine_p.h @@ -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) }; diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 25879972ca..142625d7ae 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -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); diff --git a/src/qml/util/qqmllistaccessor.cpp b/src/qml/util/qqmllistaccessor.cpp index e434d6cef4..5a199abf44 100644 --- a/src/qml/util/qqmllistaccessor.cpp +++ b/src/qml/util/qqmllistaccessor.cpp @@ -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>()) { diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp index 837403d391..9c615cfc15 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp @@ -34,10 +34,6 @@ #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 diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index c246647325..196f6b96f9 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -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"); diff --git a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp index 320333f889..4ee75f8df5 100644 --- a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp +++ b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp @@ -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(); diff --git a/tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp b/tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp index 510a76cc06..b6e7a43c46 100644 --- a/tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp +++ b/tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp @@ -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); diff --git a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp index 66ddb392f9..801707f2ec 100644 --- a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp +++ b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp @@ -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; |