aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@digia.com>2014-09-10 17:13:10 +0200
committerLars Knoll <lars.knoll@digia.com>2014-09-17 08:13:11 +0200
commit3dbe05f6bf3fd51ce8097c35f6c7f12b39acb0f6 (patch)
tree444ed433aa02085357b589b19b28f4bc1c243320
parentcfe1a8152c948a4586ffa1fe79b47f9a0e88beb5 (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.cpp8
-rw-r--r--src/qml/debugger/qqmlenginedebugservice_p.h2
-rw-r--r--src/qml/jsapi/qjsengine.cpp27
-rw-r--r--src/qml/jsapi/qjsvalue.cpp69
-rw-r--r--src/qml/jsapi/qjsvalue_p.h7
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper.cpp6
-rw-r--r--src/qml/jsruntime/qv4variantobject.cpp2
-rw-r--r--src/qml/qml/v8/qv8engine.cpp299
-rw-r--r--src/qml/qml/v8/qv8engine_p.h20
-rw-r--r--src/qml/types/qqmllistmodel.cpp11
-rw-r--r--src/qml/util/qqmllistaccessor.cpp7
-rw-r--r--tests/auto/qml/qjsvalue/tst_qjsvalue.cpp50
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp20
-rw-r--r--tests/auto/qml/qqmllocale/tst_qqmllocale.cpp4
-rw-r--r--tests/auto/qml/qqmlmetaobject/tst_qqmlmetaobject.cpp12
-rw-r--r--tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp5
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;