diff options
24 files changed, 1915 insertions, 21 deletions
diff --git a/doc/src/declarative/extending.qdoc b/doc/src/declarative/extending.qdoc index 0563f65cc0..bed31ab88a 100644 --- a/doc/src/declarative/extending.qdoc +++ b/doc/src/declarative/extending.qdoc @@ -236,6 +236,36 @@ The \c guest property declaration looks like this: \l {Extending QML - Object and List Property Types Example} shows the complete code used to create the \c BirthdayParty type. +\section1 Sequence Types + +Certain C++ sequence types are supported transparently in QML as JavaScript Array types. +In particular, QML currently supports: +\list + \o \c {QList<int>} + \o \c {QList<qreal>} + \o \c {QList<bool>} + \o \c {QList<QString>} + \o \c {QList<QUrl>} +\endlist + +These sequence types are implemented directly in terms of the underlying C++ sequence. +There are two ways in which such sequences can be exposed to QML: as a Q_PROPERTY of +the given sequence type; or as the return type of a Q_INVOKABLE method. There are some +differences in the way these are implemented, which are important to note. + +If the sequence is exposed as a Q_PROPERTY, accessing any value in the sequence by index +will cause the sequence data to be read from the QObject's property, then a read to occur. +Similarly, modifying any value in the sequence will cause the sequence data to be read, +and then the modification will be performed and the modified sequence will be written back +to the QObject's property. + +If the sequence is returned from a Q_INVOKABLE function, access and mutation is much cheaper, +as no QObject property read or write occurs; instead, the C++ sequence data is accessed and +modified directly. + +Other sequence types are not supported transparently, and instead an instance of any other +sequence type will be passed between QML and C++ as an opaque QVariantList. + \section1 Inheritance and Coercion \snippet examples/declarative/cppextensions/referenceexamples/coercion/example.qml 0 diff --git a/src/declarative/qml/qdeclarativevaluetype.cpp b/src/declarative/qml/qdeclarativevaluetype.cpp index 9f43d0ef72..9a941e63f9 100644 --- a/src/declarative/qml/qdeclarativevaluetype.cpp +++ b/src/declarative/qml/qdeclarativevaluetype.cpp @@ -92,7 +92,7 @@ QDeclarativeValueTypeFactory::~QDeclarativeValueTypeFactory() bool QDeclarativeValueTypeFactory::isValueType(int idx) { - if ((uint)idx < QVariant::UserType) + if ((uint)idx < QVariant::UserType && (uint)idx != QVariant::StringList) return true; return false; } diff --git a/src/declarative/qml/v8/qv8engine.cpp b/src/declarative/qml/v8/qv8engine.cpp index 62abbbe59b..d239f30734 100644 --- a/src/declarative/qml/v8/qv8engine.cpp +++ b/src/declarative/qml/v8/qv8engine.cpp @@ -45,6 +45,7 @@ #include "qv8contextwrapper_p.h" #include "qv8valuetypewrapper_p.h" #include "qv8gccallback_p.h" +#include "qv8sequencewrapper_p.h" #include "qv8include_p.h" #include "../../../3rdparty/javascriptcore/DateMath.h" @@ -82,6 +83,7 @@ static bool ObjectComparisonCallback(v8::Local<v8::Object> lhs, v8::Local<v8::Ob switch (lhst) { case QV8ObjectResource::ValueTypeType: + // a value type might be equal to a variant or another value type if (rhst == QV8ObjectResource::ValueTypeType) { return lhsr->engine->valueTypeWrapper()->isEqual(lhsr, lhsr->engine->valueTypeWrapper()->toVariant(rhsr)); } else if (rhst == QV8ObjectResource::VariantType) { @@ -89,6 +91,7 @@ static bool ObjectComparisonCallback(v8::Local<v8::Object> lhs, v8::Local<v8::Ob } break; case QV8ObjectResource::VariantType: + // a variant might be equal to a value type or other variant. if (rhst == QV8ObjectResource::VariantType) { return lhsr->engine->variantWrapper()->toVariant(lhsr) == lhsr->engine->variantWrapper()->toVariant(rhsr); @@ -96,6 +99,12 @@ static bool ObjectComparisonCallback(v8::Local<v8::Object> lhs, v8::Local<v8::Ob return rhsr->engine->valueTypeWrapper()->isEqual(rhsr, rhsr->engine->variantWrapper()->toVariant(lhsr)); } break; + case QV8ObjectResource::SequenceType: + // a sequence might be equal to itself. + if (rhst == QV8ObjectResource::SequenceType) { + return lhsr->engine->sequenceWrapper()->isEqual(lhsr, rhsr); + } + break; default: break; } @@ -135,6 +144,7 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership) m_listWrapper.init(this); m_variantWrapper.init(this); m_valueTypeWrapper.init(this); + m_sequenceWrapper.init(this); QV8GCCallback::registerGcPrologueCallback(); @@ -164,6 +174,7 @@ QV8Engine::~QV8Engine() invalidateAllValues(); clearExceptions(); + m_sequenceWrapper.destroy(); m_valueTypeWrapper.destroy(); m_variantWrapper.destroy(); m_listWrapper.destroy(); @@ -226,25 +237,33 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint) return m_variantWrapper.toVariant(r); case QV8ObjectResource::ValueTypeType: return m_valueTypeWrapper.toVariant(r); + case QV8ObjectResource::SequenceType: + return m_sequenceWrapper.toVariant(r); } } } - if (typeHint == qMetaTypeId<QList<QObject *> >() && value->IsArray()) { + if (value->IsArray()) { v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(value); - - QList<QObject *> list; - uint32_t length = array->Length(); - for (uint32_t ii = 0; ii < length; ++ii) { - v8::Local<v8::Value> arrayItem = array->Get(ii); - if (arrayItem->IsObject()) { - list << toQObject(arrayItem->ToObject()); - } else { - list << 0; + if (typeHint == qMetaTypeId<QList<QObject *> >()) { + QList<QObject *> list; + uint32_t length = array->Length(); + for (uint32_t ii = 0; ii < length; ++ii) { + v8::Local<v8::Value> arrayItem = array->Get(ii); + if (arrayItem->IsObject()) { + list << toQObject(arrayItem->ToObject()); + } else { + list << 0; + } } + + return qVariantFromValue<QList<QObject*> >(list); } - return qVariantFromValue<QList<QObject*> >(list); + bool succeeded = false; + QVariant retn = m_sequenceWrapper.toVariant(array, typeHint, &succeeded); + if (succeeded) + return retn; } return toBasicVariant(value); @@ -325,8 +344,14 @@ v8::Handle<v8::Value> QV8Engine::fromVariant(const QVariant &variant) case QMetaType::QObjectStar: case QMetaType::QWidgetStar: return newQObject(*reinterpret_cast<QObject* const *>(ptr)); - case QMetaType::QStringList: + case QMetaType::QStringList: + { + bool succeeded = false; + v8::Handle<v8::Value> retn = m_sequenceWrapper.fromVariant(variant, &succeeded); + if (succeeded) + return retn; return arrayFromStringList(this, *reinterpret_cast<const QStringList *>(ptr)); + } case QMetaType::QVariantList: return arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr)); case QMetaType::QVariantMap: @@ -369,6 +394,11 @@ v8::Handle<v8::Value> QV8Engine::fromVariant(const QVariant &variant) QObject *obj = QDeclarativeMetaType::toQObject(variant, &objOk); if (objOk) return newQObject(obj); + + bool succeeded = false; + v8::Handle<v8::Value> retn = m_sequenceWrapper.fromVariant(variant, &succeeded); + if (succeeded) + return retn; } // XXX TODO: To be compatible, we still need to handle: @@ -459,7 +489,6 @@ QVariant QV8Engine::toBasicVariant(v8::Handle<v8::Value> value) int length = array->Length(); for (int ii = 0; ii < length; ++ii) rv << toVariant(array->Get(ii), -1); - return rv; } if (!value->IsFunction()) { diff --git a/src/declarative/qml/v8/qv8engine_p.h b/src/declarative/qml/v8/qv8engine_p.h index 79a77c2ac1..92e6123ca1 100644 --- a/src/declarative/qml/v8/qv8engine_p.h +++ b/src/declarative/qml/v8/qv8engine_p.h @@ -77,6 +77,7 @@ #include "qv8listwrapper_p.h" #include "qv8variantwrapper_p.h" #include "qv8valuetypewrapper_p.h" +#include "qv8sequencewrapper_p.h" QT_BEGIN_NAMESPACE @@ -136,7 +137,8 @@ public: enum ResourceType { ContextType, QObjectType, TypeType, ListType, VariantType, ValueTypeType, XMLHttpRequestType, DOMNodeType, SQLDatabaseType, ListModelType, Context2DType, Context2DStyleType, Context2DPixelArrayType, - ParticleDataType, SignalHandlerType, IncubatorType, VisualDataItemType }; + ParticleDataType, SignalHandlerType, IncubatorType, VisualDataItemType, + SequenceType }; virtual ResourceType resourceType() const = 0; QV8Engine *engine; @@ -279,6 +281,7 @@ public: QV8ListWrapper *listWrapper() { return &m_listWrapper; } QV8VariantWrapper *variantWrapper() { return &m_variantWrapper; } QV8ValueTypeWrapper *valueTypeWrapper() { return &m_valueTypeWrapper; } + QV8SequenceWrapper *sequenceWrapper() { return &m_sequenceWrapper; } void *xmlHttpRequestData() { return m_xmlHttpRequestData; } void *sqlDatabaseData() { return m_sqlDatabaseData; } @@ -326,6 +329,9 @@ public: inline v8::Handle<v8::Value> newValueType(QObject *, int coreIndex, QDeclarativeValueType *); inline v8::Handle<v8::Value> newValueType(const QVariant &, QDeclarativeValueType *); + // Create a new sequence type object + inline v8::Handle<v8::Value> newSequence(int sequenceType, QObject *, int coreIndex, bool *succeeded); + // Create a new QVariant object. This doesn't examine the type of the variant, but always returns // a QVariant wrapper inline v8::Handle<v8::Value> newQVariant(const QVariant &); @@ -415,6 +421,7 @@ protected: QV8ListWrapper m_listWrapper; QV8VariantWrapper m_variantWrapper; QV8ValueTypeWrapper m_valueTypeWrapper; + QV8SequenceWrapper m_sequenceWrapper; v8::Persistent<v8::Function> m_getOwnPropertyNames; v8::Persistent<v8::Function> m_freezeObject; @@ -545,6 +552,11 @@ v8::Handle<v8::Value> QV8Engine::newValueType(const QVariant &value, QDeclarativ return m_valueTypeWrapper.newValueType(value, type); } +v8::Handle<v8::Value> QV8Engine::newSequence(int sequenceType, QObject *object, int property, bool *succeeded) +{ + return m_sequenceWrapper.newSequence(sequenceType, object, property, succeeded); +} + // XXX Can this be made more optimal? It is called prior to resolving each and every // unqualified name in QV8ContextWrapper. bool QV8Engine::startsWithUpper(v8::Handle<v8::String> string) diff --git a/src/declarative/qml/v8/qv8qobjectwrapper.cpp b/src/declarative/qml/v8/qv8qobjectwrapper.cpp index c2ee429d5c..40cb021512 100644 --- a/src/declarative/qml/v8/qv8qobjectwrapper.cpp +++ b/src/declarative/qml/v8/qv8qobjectwrapper.cpp @@ -385,7 +385,13 @@ static v8::Handle<v8::Value> LoadProperty(QV8Engine *engine, QObject *object, QDeclarativeValueType *valueType = ep->valueTypes[property.propType]; if (valueType) return engine->newValueType(object, property.coreIndex, valueType); - } + } else { + // see if it's a sequence type + bool succeeded = false; + v8::Handle<v8::Value> retn = engine->newSequence(property.propType, object, property.coreIndex, &succeeded); + if (succeeded) + return retn; + } QVariant var = object->metaObject()->property(property.coreIndex).read(object); return engine->fromVariant(var); @@ -425,13 +431,18 @@ static v8::Handle<v8::Value> LoadPropertyDirect(QV8Engine *engine, QObject *obje void *args[] = { &handle, 0 }; object->qt_metacall(QMetaObject::ReadProperty, property.coreIndex, args); return handle.toHandle(); - } else if (QDeclarativeValueTypeFactory::isValueType((uint)property.propType) - && engine->engine()) { + } else if (engine->engine() && QDeclarativeValueTypeFactory::isValueType((uint)property.propType)) { QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine->engine()); QDeclarativeValueType *valueType = ep->valueTypes[property.propType]; if (valueType) return engine->newValueType(object, property.coreIndex, valueType); - } + } else { + // see if it's a sequence type + bool success = false; + v8::Handle<v8::Value> retn = engine->newSequence(property.propType, object, property.coreIndex, &success); + if (success) + return retn; + } QVariant var = object->metaObject()->property(property.coreIndex).read(object); return engine->fromVariant(var); @@ -601,7 +612,6 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QDeclarativ v = engine->toVariant(value, property->propType); QDeclarativeContextData *context = engine->callingContext(); - if (!QDeclarativePropertyPrivate::write(object, *property, v, context)) { const char *valueType = 0; if (v.userType() == QVariant::Invalid) valueType = "null"; diff --git a/src/declarative/qml/v8/qv8sequencewrapper.cpp b/src/declarative/qml/v8/qv8sequencewrapper.cpp new file mode 100644 index 0000000000..61fb993d8c --- /dev/null +++ b/src/declarative/qml/v8/qv8sequencewrapper.cpp @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtDeclarative/qdeclarative.h> + +#include "qv8sequencewrapper_p.h" +#include "qv8sequencewrapper_p_p.h" +#include "qv8engine_p.h" + +QT_BEGIN_NAMESPACE + +QV8SequenceWrapper::QV8SequenceWrapper() + : m_engine(0) +{ +} + +QV8SequenceWrapper::~QV8SequenceWrapper() +{ +} + +#define REGISTER_QML_SEQUENCE_METATYPE(unused, unused2, SequenceType, unused3) qRegisterMetaType<SequenceType>(); +void QV8SequenceWrapper::init(QV8Engine *engine) +{ + FOREACH_QML_SEQUENCE_TYPE(REGISTER_QML_SEQUENCE_METATYPE) + + m_engine = engine; + m_toString = qPersistentNew<v8::Function>(v8::FunctionTemplate::New(ToString)->GetFunction()); + m_valueOf = qPersistentNew<v8::Function>(v8::FunctionTemplate::New(ValueOf)->GetFunction()); + v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(); + ft->InstanceTemplate()->SetFallbackPropertyHandler(Getter, Setter); + ft->InstanceTemplate()->SetIndexedPropertyHandler(IndexedGetter, IndexedSetter, 0, 0, IndexedEnumerator); + ft->InstanceTemplate()->SetAccessor(v8::String::New("length"), LengthGetter, 0, + v8::Handle<v8::Value>(), v8::DEFAULT, + v8::PropertyAttribute(v8::ReadOnly | v8::DontDelete | v8::DontEnum)); + ft->InstanceTemplate()->SetAccessor(v8::String::New("toString"), ToStringGetter, 0, + m_toString, v8::DEFAULT, + v8::PropertyAttribute(v8::ReadOnly | v8::DontDelete | v8::DontEnum)); + ft->InstanceTemplate()->SetAccessor(v8::String::New("valueOf"), ValueOfGetter, 0, + m_valueOf, v8::DEFAULT, + v8::PropertyAttribute(v8::ReadOnly | v8::DontDelete | v8::DontEnum)); + ft->InstanceTemplate()->SetHasExternalResource(true); + ft->InstanceTemplate()->MarkAsUseUserObjectComparison(); + m_constructor = qPersistentNew<v8::Function>(ft->GetFunction()); +} +#undef REGISTER_QML_SEQUENCE_METATYPE + +void QV8SequenceWrapper::destroy() +{ + qPersistentDispose(m_toString); + qPersistentDispose(m_valueOf); + qPersistentDispose(m_constructor); +} + +bool QV8SequenceWrapper::isEqual(QV8ObjectResource *lhs, const QVariant &rhs) +{ + Q_ASSERT(lhs->resourceType() == QV8ObjectResource::SequenceType); + QV8SequenceResource *r = static_cast<QV8SequenceResource *>(lhs); + Q_ASSERT(r); + return r->isEqual(rhs); +} + +bool QV8SequenceWrapper::isEqual(QV8ObjectResource *lhs, QV8ObjectResource *rhs) +{ + QV8SequenceResource *lr = static_cast<QV8SequenceResource *>(lhs); + QV8SequenceResource *rr = static_cast<QV8SequenceResource *>(rhs); + Q_ASSERT(lr && rr); + return lr->isEqual(rr); +} + +quint32 QV8SequenceWrapper::sequenceLength(QV8ObjectResource *r) +{ + Q_ASSERT(r->resourceType() == QV8ObjectResource::SequenceType); + QV8SequenceResource *sr = static_cast<QV8SequenceResource *>(r); + Q_ASSERT(sr); + return sr->lengthGetter(); +} + +#define NEW_REFERENCE_SEQUENCE(ElementType, ElementTypeName, SequenceType, unused) \ + if (sequenceType == qMetaTypeId<SequenceType>()) { \ + r = new QV8##ElementTypeName##SequenceResource(m_engine, object, propertyIndex); \ + } else + +v8::Local<v8::Object> QV8SequenceWrapper::newSequence(int sequenceType, QObject *object, int propertyIndex, bool *succeeded) +{ + // This function is called when the property is a QObject Q_PROPERTY of + // the given sequence type. Internally we store a typed-sequence + // (as well as object ptr + property index for updated-read and write-back) + // and so access/mutate avoids variant conversion. + *succeeded = true; + QV8SequenceResource *r = 0; + FOREACH_QML_SEQUENCE_TYPE(NEW_REFERENCE_SEQUENCE) { /* else */ *succeeded = false; return v8::Local<v8::Object>(); } + + v8::Local<v8::Object> rv = m_constructor->NewInstance(); + rv->SetExternalResource(r); + rv->SetPrototype(v8::Array::New(1)->GetPrototype()); + return rv; +} +#undef NEW_REFERENCE_SEQUENCE + +#define NEW_COPY_SEQUENCE(ElementType, ElementTypeName, SequenceType, unused) \ + if (sequenceType == qMetaTypeId<SequenceType>()) { \ + r = new QV8##ElementTypeName##SequenceResource(m_engine, v.value<SequenceType>()); \ + } else + +v8::Local<v8::Object> QV8SequenceWrapper::fromVariant(const QVariant& v, bool *succeeded) +{ + // This function is called when assigning a sequence value to a normal JS var + // in a JS block. Internally, we store a sequence of the specified type. + // Access and mutation is extremely fast since it will not need to modify any + // QObject property. + int sequenceType = v.userType(); + *succeeded = true; + QV8SequenceResource *r = 0; + FOREACH_QML_SEQUENCE_TYPE(NEW_COPY_SEQUENCE) { /* else */ *succeeded = false; return v8::Local<v8::Object>(); } + + v8::Local<v8::Object> rv = m_constructor->NewInstance(); + rv->SetExternalResource(r); + rv->SetPrototype(v8::Array::New(1)->GetPrototype()); + return rv; +} +#undef NEW_COPY_SEQUENCE + +QVariant QV8SequenceWrapper::toVariant(QV8ObjectResource *r) +{ + Q_ASSERT(r->resourceType() == QV8ObjectResource::SequenceType); + QV8SequenceResource *resource = static_cast<QV8SequenceResource *>(r); + return resource->toVariant(); +} + +#define SEQUENCE_TO_VARIANT(ElementType, ElementTypeName, SequenceType, unused) \ + if (typeHint == qMetaTypeId<SequenceType>()) { \ + return QV8##ElementTypeName##SequenceResource::toVariant(m_engine, array, length, succeeded); \ + } else + +QVariant QV8SequenceWrapper::toVariant(v8::Handle<v8::Array> array, int typeHint, bool *succeeded) +{ + *succeeded = true; + QV8SequenceResource *sr = static_cast<QV8SequenceResource *>(array->GetExternalResource()); + if (sr) + return sr->toVariant(); + + // if no typehint is given it's just a sequence of arbitrary variants + uint32_t length = array->Length(); + if (typeHint == -1) { + QVariantList list; + for (uint32_t ii = 0; ii < length; ++ii) { + v8::Local<v8::Value> arrayItem = array->Get(ii); + list.append(m_engine->toVariant(arrayItem, -1)); + } + + return list; + } + + // otherwise, try to build a sequence of the correct type + FOREACH_QML_SEQUENCE_TYPE(SEQUENCE_TO_VARIANT) { /* else */ *succeeded = false; return QVariant(); } +} +#undef SEQUENCE_TO_VARIANT + +v8::Handle<v8::Value> QV8SequenceWrapper::IndexedSetter(quint32 index, v8::Local<v8::Value> value, const v8::AccessorInfo &info) +{ + QV8SequenceResource *sr = v8_resource_cast<QV8SequenceResource>(info.This()); + Q_ASSERT(sr); + return sr->indexedSetter(index, value); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::IndexedGetter(quint32 index, const v8::AccessorInfo &info) +{ + QV8SequenceResource *sr = v8_resource_cast<QV8SequenceResource>(info.This()); + Q_ASSERT(sr); + return sr->indexedGetter(index); +} + +v8::Handle<v8::Array> QV8SequenceWrapper::IndexedEnumerator(const v8::AccessorInfo &info) +{ + QV8SequenceResource *sr = v8_resource_cast<QV8SequenceResource>(info.This()); + Q_ASSERT(sr); + return sr->indexedEnumerator(); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::LengthGetter(v8::Local<v8::String> property, const v8::AccessorInfo &info) +{ + Q_UNUSED(property); + QV8SequenceResource *sr = v8_resource_cast<QV8SequenceResource>(info.This()); + Q_ASSERT(sr); + return v8::Integer::NewFromUnsigned(sr->lengthGetter()); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::ToStringGetter(v8::Local<v8::String> property, const v8::AccessorInfo &info) +{ + Q_UNUSED(property); + return info.Data(); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::ValueOfGetter(v8::Local<v8::String> property, + const v8::AccessorInfo &info) +{ + Q_UNUSED(property); + return info.Data(); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::ToString(const v8::Arguments &args) +{ + QV8SequenceResource *sr = v8_resource_cast<QV8SequenceResource>(args.This()); + Q_ASSERT(sr); + return sr->toString(); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::ValueOf(const v8::Arguments &args) +{ + QV8SequenceResource *sr = v8_resource_cast<QV8SequenceResource>(args.This()); + Q_ASSERT(sr); + v8::Handle<v8::Value> tostringValue = sr->toString(); + if (!tostringValue.IsEmpty()) + return tostringValue; + return v8::Integer::NewFromUnsigned(sr->lengthGetter()); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::Getter(v8::Local<v8::String> property, + const v8::AccessorInfo &info) +{ + Q_UNUSED(property); + Q_UNUSED(info); + return v8::Handle<v8::Value>(); +} + +v8::Handle<v8::Value> QV8SequenceWrapper::Setter(v8::Local<v8::String> property, + v8::Local<v8::Value> value, + const v8::AccessorInfo &info) +{ + Q_UNUSED(property); + Q_UNUSED(info); + return value; +} + +QT_END_NAMESPACE diff --git a/src/declarative/qml/v8/qv8sequencewrapper_p.h b/src/declarative/qml/v8/qv8sequencewrapper_p.h new file mode 100644 index 0000000000..da0f7eacca --- /dev/null +++ b/src/declarative/qml/v8/qv8sequencewrapper_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV8SEQUENCEWRAPPER_P_H +#define QV8SEQUENCEWRAPPER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtCore/qvariant.h> +#include <private/qv8_p.h> + +QT_BEGIN_NAMESPACE + +class QV8Engine; +class QV8ObjectResource; +class QV8SequenceWrapper +{ +public: + QV8SequenceWrapper(); + ~QV8SequenceWrapper(); + + void init(QV8Engine *); + void destroy(); + + bool isEqual(QV8ObjectResource *lhs, const QVariant &rhs); + bool isEqual(QV8ObjectResource *lhs, QV8ObjectResource *rhs); + quint32 sequenceLength(QV8ObjectResource *); + + v8::Local<v8::Object> newSequence(int sequenceTypeId, QObject *object, int propertyIndex, bool *succeeded); + v8::Local<v8::Object> fromVariant(const QVariant& v, bool *succeeded); + QVariant toVariant(QV8ObjectResource *); + QVariant toVariant(v8::Handle<v8::Array> array, int typeHint, bool *succeeded); + +private: + QV8Engine *m_engine; + + v8::Persistent<v8::Function> m_constructor; + v8::Persistent<v8::Function> m_toString; + v8::Persistent<v8::Function> m_valueOf; + + static v8::Handle<v8::Value> IndexedGetter(quint32 index, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> IndexedSetter(quint32 index, v8::Local<v8::Value> value, const v8::AccessorInfo &info); + static v8::Handle<v8::Array> IndexedEnumerator(const v8::AccessorInfo &info); + static v8::Handle<v8::Value> LengthGetter(v8::Local<v8::String> property, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> ToStringGetter(v8::Local<v8::String> property, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> ToString(const v8::Arguments &args); + static v8::Handle<v8::Value> ValueOfGetter(v8::Local<v8::String> property, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> ValueOf(const v8::Arguments &args); + static v8::Handle<v8::Value> Getter(v8::Local<v8::String> property, const v8::AccessorInfo &info); + static v8::Handle<v8::Value> Setter(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::AccessorInfo &info); +}; + + +QT_END_NAMESPACE + +#endif // QV8SEQUENCEWRAPPER_P_H diff --git a/src/declarative/qml/v8/qv8sequencewrapper_p_p.h b/src/declarative/qml/v8/qv8sequencewrapper_p_p.h new file mode 100644 index 0000000000..f609aff223 --- /dev/null +++ b/src/declarative/qml/v8/qv8sequencewrapper_p_p.h @@ -0,0 +1,387 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV8SEQUENCEWRAPPER_P_P_H +#define QV8SEQUENCEWRAPPER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qdeclarativeengine_p.h> +#include <private/qdeclarativemetatype_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QV8SequenceResource + \brief The abstract base class of the external resource used in sequence type objects + + Every sequence type object returned by QV8SequenceWrapper::fromVariant() or + QV8SequenceWrapper::newSequence() has a type-specific QV8SequenceResource which + contains the type name, the meta type ids of the sequence and sequence element + types, as well as either the sequence data (copy) or object pointer and property + index (reference) data associated with the sequence. + */ +class QV8SequenceResource : public QV8ObjectResource +{ + V8_RESOURCE_TYPE(SequenceType); + +public: + virtual ~QV8SequenceResource() {} + + enum ObjectType { Reference, Copy }; + + virtual QVariant toVariant() = 0; + virtual bool isEqual(const QVariant &v) = 0; + virtual bool isEqual(const QV8SequenceResource *v) = 0; + + virtual quint32 lengthGetter() = 0; + virtual v8::Handle<v8::Value> indexedSetter(quint32 index, v8::Handle<v8::Value> value) = 0; + virtual v8::Handle<v8::Value> indexedGetter(quint32 index) = 0; + virtual v8::Handle<v8::Array> indexedEnumerator() = 0; + virtual v8::Handle<v8::Value> toString() = 0; + + ObjectType objectType; + QByteArray typeName; + int sequenceMetaTypeId; + int elementMetaTypeId; + +protected: + QV8SequenceResource(QV8Engine *engine, ObjectType type, const QByteArray &name, int sequenceId, int elementId) + : QV8ObjectResource(engine), objectType(type), typeName(name), sequenceMetaTypeId(sequenceId), elementMetaTypeId(elementId) + { + } +}; + +static int convertV8ValueToInt(QV8Engine *, v8::Handle<v8::Value> v) +{ + return v->Int32Value(); +} + +static v8::Handle<v8::Value> convertIntToV8Value(QV8Engine *, int v) +{ + return v8::Integer::New(v); +} + +static QString convertIntToString(QV8Engine *, int v) +{ + return QString::number(v); +} + +static qreal convertV8ValueToReal(QV8Engine *, v8::Handle<v8::Value> v) +{ + return v->NumberValue(); +} + +static v8::Handle<v8::Value> convertRealToV8Value(QV8Engine *, qreal v) +{ + return v8::Number::New(v); +} + +static QString convertRealToString(QV8Engine *, qreal v) +{ + return QString::number(v); +} + +static bool convertV8ValueToBool(QV8Engine *, v8::Handle<v8::Value> v) +{ + return v->BooleanValue(); +} + +static v8::Handle<v8::Value> convertBoolToV8Value(QV8Engine *, bool v) +{ + return v8::Boolean::New(v); +} + +static QString convertBoolToString(QV8Engine *, bool v) +{ + if (v) + return QLatin1String("true"); + return QLatin1String("false"); +} + +static QString convertV8ValueToString(QV8Engine *e, v8::Handle<v8::Value> v) +{ + return e->toString(v->ToString()); +} + +static v8::Handle<v8::Value> convertStringToV8Value(QV8Engine *e, const QString &v) +{ + return e->toString(v); +} + +static QString convertStringToString(QV8Engine *, const QString &v) +{ + return v; +} + +static QString convertV8ValueToQString(QV8Engine *e, v8::Handle<v8::Value> v) +{ + return e->toString(v->ToString()); +} + +static v8::Handle<v8::Value> convertQStringToV8Value(QV8Engine *e, const QString &v) +{ + return e->toString(v); +} + +static QString convertQStringToString(QV8Engine *, const QString &v) +{ + return v; +} + +static QUrl convertV8ValueToUrl(QV8Engine *e, v8::Handle<v8::Value> v) +{ + return QUrl(e->toString(v->ToString())); +} + +static v8::Handle<v8::Value> convertUrlToV8Value(QV8Engine *e, const QUrl &v) +{ + return e->toString(v.toString()); +} + +static QString convertUrlToString(QV8Engine *, const QUrl &v) +{ + return v.toString(); +} + + +/* + \internal + \class QV8<Type>SequenceResource + \brief The external resource used in sequence type objects + + Every sequence type object returned by QV8SequenceWrapper::newSequence() has + a QV8<Type>SequenceResource which contains a property index and a pointer + to the object which contains the property. + + Every sequence type object returned by QV8SequenceWrapper::fromVariant() has + a QV8<Type>SequenceResource which contains a copy of the sequence value. + Operations on the sequence are implemented directly in terms of that sequence data. + + There exists one QV8<Type>SequenceResource instance for every JavaScript Object + (sequence) instance returned from QV8SequenceWrapper::newSequence() or + QV8SequenceWrapper::fromVariant(). + */ + +// F(elementType, elementTypeName, sequenceType, defaultValue) +#define FOREACH_QML_SEQUENCE_TYPE(F) \ + F(int, Int, QList<int>, 0) \ + F(qreal, Real, QList<qreal>, 0.0) \ + F(bool, Bool, QList<bool>, false) \ + F(QString, String, QList<QString>, QString()) \ + F(QString, QString, QStringList, QString()) \ + F(QUrl, Url, QList<QUrl>, QUrl()) + +#define QML_SEQUENCE_TYPE_RESOURCE(SequenceElementType, SequenceElementTypeName, SequenceType, DefaultValue, ConversionToV8fn, ConversionFromV8fn, ToStringfn) \ + Q_DECLARE_METATYPE(SequenceType) \ + class QV8##SequenceElementTypeName##SequenceResource : public QV8SequenceResource { \ + public:\ + QV8##SequenceElementTypeName##SequenceResource(QV8Engine *engine, QObject *obj, int propIdx) \ + : QV8SequenceResource(engine, QV8SequenceResource::Reference, #SequenceType, qMetaTypeId<SequenceType>(), qMetaTypeId<SequenceElementType>()) \ + , object(obj), propertyIndex(propIdx) \ + { \ + } \ + QV8##SequenceElementTypeName##SequenceResource(QV8Engine *engine, const SequenceType &value) \ + : QV8SequenceResource(engine, QV8SequenceResource::Copy, #SequenceType, qMetaTypeId<SequenceType>(), qMetaTypeId<SequenceElementType>()) \ + , object(0), propertyIndex(-1), c(value) \ + { \ + } \ + ~QV8##SequenceElementTypeName##SequenceResource() \ + { \ + } \ + static QVariant toVariant(QV8Engine *e, v8::Handle<v8::Array> array, uint32_t length, bool *succeeded) \ + { \ + SequenceType list; \ + for (uint32_t ii = 0; ii < length; ++ii) { \ + list.append(ConversionFromV8fn(e, array->Get(ii))); \ + } \ + *succeeded = true; \ + return QVariant::fromValue<SequenceType>(list); \ + } \ + QVariant toVariant() \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return QVariant(); \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + return QVariant::fromValue<SequenceType>(c); \ + } \ + bool isEqual(const QVariant &v) \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return false; \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + return (c == v.value<SequenceType>()); \ + } \ + bool isEqual(const QV8SequenceResource *v) \ + { \ + /* Note: two different sequences can never be equal (even if they */ \ + /* contain the same elements in the same order) in order to */ \ + /* maintain JavaScript semantics. However, if they both reference */ \ + /* the same QObject+propertyIndex, they are equal. */ \ + if (objectType == QV8SequenceResource::Reference && v->objectType == QV8SequenceResource::Reference) { \ + if (sequenceMetaTypeId == v->sequenceMetaTypeId) { \ + const QV8##SequenceElementTypeName##SequenceResource *rhs = static_cast<const QV8##SequenceElementTypeName##SequenceResource *>(v); \ + return (object != 0 && object == rhs->object && propertyIndex == rhs->propertyIndex); \ + } \ + } else if (objectType == QV8SequenceResource::Copy && v->objectType == QV8SequenceResource::Copy) { \ + if (sequenceMetaTypeId == v->sequenceMetaTypeId) { \ + const QV8##SequenceElementTypeName##SequenceResource *rhs = static_cast<const QV8##SequenceElementTypeName##SequenceResource *>(v); \ + return (this == rhs); \ + } \ + } \ + return false; \ + } \ + quint32 lengthGetter() \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return 0; \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + return c.count(); \ + } \ + v8::Handle<v8::Value> indexedSetter(quint32 index, v8::Handle<v8::Value> value) \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return v8::Undefined(); \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + /* modify the sequence */ \ + SequenceElementType elementValue = ConversionFromV8fn(engine, value); \ + quint32 count = c.count(); \ + if (index == count) { \ + c.append(elementValue); \ + } else if (index < count) { \ + c[index] = elementValue; \ + } else { \ + /* according to ECMA262r3 we need to insert */ \ + /* the value at the given index, increasing length to index+1. */ \ + while (index > count++) { \ + c.append(DefaultValue); \ + } \ + c.append(elementValue); \ + } \ + if (objectType == QV8SequenceResource::Reference) { \ + /* write back. already checked that object is non-null, so skip that check here. */ \ + int status = -1; \ + QDeclarativePropertyPrivate::WriteFlags flags = QDeclarativePropertyPrivate::DontRemoveBinding; \ + void *a[] = { &c, 0, &status, &flags }; \ + QMetaObject::metacall(object, QMetaObject::WriteProperty, propertyIndex, a); \ + } \ + return value; \ + } \ + v8::Handle<v8::Value> indexedGetter(quint32 index) \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return v8::Undefined(); \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + quint32 count = c.count(); \ + if (index < count) \ + return ConversionToV8fn(engine, c.at(index)); \ + return v8::Undefined(); \ + } \ + v8::Handle<v8::Array> indexedEnumerator() \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return v8::Handle<v8::Array>(); \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + quint32 count = c.count(); \ + v8::Local<v8::Array> retn = v8::Array::New(count); \ + for (quint32 i = 0; i < count; ++i) { \ + retn->Set(i, v8::Integer::NewFromUnsigned(i)); \ + } \ + return retn; \ + } \ + v8::Handle<v8::Value> toString() \ + { \ + if (objectType == QV8SequenceResource::Reference) { \ + if (!object) \ + return v8::Undefined(); \ + void *a[] = { &c, 0 }; \ + QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a); \ + } \ + QString str; \ + quint32 count = c.count(); \ + for (quint32 i = 0; i < count; ++i) { \ + str += QString(QLatin1String("%1,")).arg(ToStringfn(engine, c[i])); \ + } \ + str.chop(1); \ + return engine->toString(str); \ + } \ + private: \ + QDeclarativeGuard<QObject> object; \ + int propertyIndex; \ + SequenceType c; \ + }; + +#define GENERATE_QML_SEQUENCE_TYPE_RESOURCE(ElementType, ElementTypeName, SequenceType, DefaultValue) \ + QML_SEQUENCE_TYPE_RESOURCE(ElementType, ElementTypeName, SequenceType, DefaultValue, convert##ElementTypeName##ToV8Value, convertV8ValueTo##ElementTypeName, convert##ElementTypeName##ToString) + +FOREACH_QML_SEQUENCE_TYPE(GENERATE_QML_SEQUENCE_TYPE_RESOURCE) +#undef GENERATE_QML_SEQUENCE_TYPE_RESOURCE +#undef QML_SEQUENCE_TYPE_RESOURCE + +#endif // QV8SEQUENCEWRAPPER_P_P_H diff --git a/src/declarative/qml/v8/qv8worker.cpp b/src/declarative/qml/v8/qv8worker.cpp index d6998849e2..16e5abfb06 100644 --- a/src/declarative/qml/v8/qv8worker.cpp +++ b/src/declarative/qml/v8/qv8worker.cpp @@ -74,7 +74,8 @@ enum Type { WorkerNumber, WorkerDate, WorkerRegexp, - WorkerListModel + WorkerListModel, + WorkerSequence }; static inline quint32 valueheader(Type type, quint32 size = 0) @@ -252,6 +253,31 @@ void QV8Worker::serialize(QByteArray &data, v8::Handle<v8::Value> v, QV8Engine * // No other QObject's are allowed to be sent push(data, valueheader(WorkerUndefined)); } else { + // we can convert sequences, but not other types with external data. + if (v->IsObject()) { + v8::Handle<v8::Object> seqObj = v->ToObject(); + QV8ObjectResource *r = static_cast<QV8ObjectResource *>(seqObj->GetExternalResource()); + QVariant sequenceVariant = engine->sequenceWrapper()->toVariant(r); + if (!sequenceVariant.isNull()) { + // valid sequence. we generate a length (sequence length + 1 for the sequence type) + uint32_t seqLength = engine->sequenceWrapper()->sequenceLength(r); + uint32_t length = seqLength + 1; + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + reserve(data, sizeof(quint32) + length * sizeof(quint32)); + push(data, valueheader(WorkerSequence, length)); + serialize(data, v8::Integer::New(sequenceVariant.userType()), engine); // sequence type + for (uint32_t ii = 0; ii < seqLength; ++ii) { + serialize(data, seqObj->Get(ii), engine); // sequence elements + } + + return; + } + } + + // not a sequence. push(data, valueheader(WorkerUndefined)); } } @@ -330,6 +356,18 @@ v8::Handle<v8::Value> QV8Worker::deserialize(const char *&data, QV8Engine *engin agent->setV8Engine(engine); return rv; } + case WorkerSequence: + { + bool succeeded = false; + quint32 length = headersize(header); + quint32 seqLength = length - 1; + int sequenceType = deserialize(data, engine)->Int32Value(); + v8::Local<v8::Array> array = v8::Array::New(seqLength); + for (quint32 ii = 0; ii < seqLength; ++ii) + array->Set(ii, deserialize(data, engine)); + QVariant seqVariant = engine->sequenceWrapper()->toVariant(array, sequenceType, &succeeded); + return engine->sequenceWrapper()->fromVariant(seqVariant, &succeeded); + } } Q_ASSERT(!"Unreachable"); return v8::Undefined(); diff --git a/src/declarative/qml/v8/v8.pri b/src/declarative/qml/v8/v8.pri index c4372ab82a..924602ec39 100644 --- a/src/declarative/qml/v8/v8.pri +++ b/src/declarative/qml/v8/v8.pri @@ -9,6 +9,8 @@ HEADERS += \ $$PWD/qv8stringwrapper_p.h \ $$PWD/qv8engine_p.h \ $$PWD/qv8gccallback_p.h \ + $$PWD/qv8sequencewrapper_p.h \ + $$PWD/qv8sequencewrapper_p_p.h \ $$PWD/qv8contextwrapper_p.h \ $$PWD/qv8qobjectwrapper_p.h \ $$PWD/qv8typewrapper_p.h \ @@ -26,6 +28,7 @@ HEADERS += \ SOURCES += \ $$PWD/qv8stringwrapper.cpp \ $$PWD/qv8engine.cpp \ + $$PWD/qv8sequencewrapper.cpp \ $$PWD/qv8contextwrapper.cpp \ $$PWD/qv8qobjectwrapper.cpp \ $$PWD/qv8typewrapper.cpp \ diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/SequenceConversionComponent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/SequenceConversionComponent.qml new file mode 100644 index 0000000000..0c7f60b062 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/SequenceConversionComponent.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +MySequenceConversionObject { + id: sccmsco + objectName: "sccmsco" +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.array.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.array.qml new file mode 100644 index 0000000000..5eaa225708 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.array.qml @@ -0,0 +1,152 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + property bool success: false + + property variant intList + property variant qrealList + property variant boolList + property variant stringList + + function indexedAccess() { + intList = msco.intListProperty; + var jsIntList = msco.intListProperty; + qrealList = msco.qrealListProperty; + var jsQrealList = msco.qrealListProperty; + boolList = msco.boolListProperty; + var jsBoolList = msco.boolListProperty; + stringList = msco.stringListProperty; + var jsStringList = msco.stringListProperty; + + // Three cases: direct property modification, variant copy modification, js var reference modification. + // Only the first and third should "write back" to the original QObject Q_PROPERTY; the second one + // should have no effect whatsoever to maintain "property variant" semantics (see e.g., valuetype). + success = true; + + msco.intListProperty[1] = 33; + if (msco.intListProperty[1] != 33) success = false; // ensure write back + intList[1] = 44; + if (intList[1] == 44) success = false; // ensure no effect + jsIntList[1] = 55; + if (jsIntList[1] != 55 + || jsIntList[1] != msco.intListProperty[1]) success = false; // ensure write back + + msco.qrealListProperty[1] = 33.3; + if (msco.qrealListProperty[1] != 33.3) success = false; // ensure write back + qrealList[1] = 44.4; + if (qrealList[1] == 44.4) success = false; // ensure no effect + jsQrealList[1] = 55.5; + if (jsQrealList[1] != 55.5 + || jsQrealList[1] != msco.qrealListProperty[1]) success = false; // ensure write back + + msco.boolListProperty[1] = true; + if (msco.boolListProperty[1] != true) success = false; // ensure write back + boolList[1] = true; + if (boolList[1] != false) success = false; // ensure no effect + jsBoolList[1] = false; + if (jsBoolList[1] != false + || jsBoolList[1] != msco.boolListProperty[1]) success = false; // ensure write back + + msco.stringListProperty[1] = "changed"; + if (msco.stringListProperty[1] != "changed") success = false; // ensure write back + stringList[1] = "changed"; + if (stringList[1] != "second") success = false; // ensure no effect + jsStringList[1] = "different"; + if (jsStringList[1] != "different" + || jsStringList[1] != msco.stringListProperty[1]) success = false; // ensure write back + } + + function arrayOperations() { + success = true; + var expected = 0; + var expectedStr = ""; + + // ecma262r3 defines array as implementing Length and Put. Test put here. + msco.intListProperty.asdf = 5; // shouldn't work, only indexes are valid names. + if (msco.intListProperty.asdf == 5) success = false; + msco.intListProperty[3] = 38; // should work. + if (msco.intListProperty[3] != 38) success = false; + msco.intListProperty[199] = 200; // should work, and should set length to 200. + if (msco.intListProperty[199] != 200) success = false; + if (msco.intListProperty.length != 200) success = false; + + // other operations are defined on the array prototype; see if they work. + msco.intListProperty = [ 0, 1, 2, 3, 4, 5, 6, 7 ]; + msco.intListProperty.splice(1,3, 33, 44, 55, 66); + expected = [ 0, 33, 44, 55, 66, 4, 5, 6, 7 ]; + if (msco.intListProperty.toString() != expected.toString()) success = false; + + msco.qrealListProperty = [ 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1 ]; + msco.qrealListProperty.splice(1,3, 33.33, 44.44, 55.55, 66.66); + expected = [ 0.1, 33.33, 44.44, 55.55, 66.66, 4.1, 5.1, 6.1, 7.1 ]; + if (msco.qrealListProperty.toString() != expected.toString()) success = false; + + msco.boolListProperty = [ false, true, true, false, false, true, false, true ]; + msco.boolListProperty.splice(1,3, false, true, false, false); + expected = [ false, false, true, false, false, false, true, false, true ]; + if (msco.boolListProperty.toString() != expected.toString()) success = false; + + msco.stringListProperty = [ "one", "two", "three", "four", "five", "six", "seven", "eight" ]; + msco.stringListProperty.splice(1,3, "nine", "ten", "eleven", "twelve"); + expected = [ "one", "nine", "ten", "eleven", "twelve", "five", "six", "seven", "eight" ]; + if (msco.stringListProperty.toString() != expected.toString()) success = false; + } + + property variant variantList: [ 1, 2, 3, 4, 5 ]; + property variant variantList2: [ 1, 2, 3, 4, 5 ]; + function testEqualitySemantics() { + // ensure equality semantics match JS array equality semantics + success = true; + + msco.intListProperty = [ 1, 2, 3, 4, 5 ]; + msco.intListProperty2 = [ 1, 2, 3, 4, 5 ]; + var jsIntList = [ 1, 2, 3, 4, 5 ]; + var jsIntList2 = [ 1, 2, 3, 4, 5 ]; + + if (jsIntList != jsIntList) success = false; + if (jsIntList == jsIntList2) success = false; + if (jsIntList == msco.intListProperty) success = false; + if (jsIntList == variantList) success = false; + + if (msco.intListProperty != msco.intListProperty) success = false; + if (msco.intListProperty == msco.intListProperty2) success = false; + if (msco.intListProperty == jsIntList) success = false; + if (msco.intListProperty == variantList) success = false; + + if (variantList == variantList) return false; + if (variantList == variantList2) return false; + if (variantList == msco.intListProperty) return false; + if (variantList == jsIntList) return false; + + if ((jsIntList == jsIntList2) != (jsIntList == msco.intListProperty)) success = false; + if ((jsIntList == jsIntList2) != (msco.intListProperty == msco.intListProperty2)) success = false; + if ((jsIntList == jsIntList) != (msco.intListProperty == msco.intListProperty)) success = false; + if ((jsIntList == variantList) != (msco.intListProperty == variantList)) success = false; + if ((variantList == jsIntList) != (variantList == msco.intListProperty)) success = false; + if ((msco.intListProperty == variantList) != (variantList == msco.intListProperty)) success = false; + } + + property bool referenceDeletion: false + function testReferenceDeletion() { + referenceDeletion = true; + var testObj = msco.generateTestObject(); + testObj.intListProperty = [1, 2, 3, 4, 5]; + var testSequence = testObj.intListProperty; + var prevString = testSequence.toString(); + var prevValueOf = testSequence.valueOf(); + var prevLength = testSequence.length; + msco.deleteTestObject(testObj); // delete referenced object. + if (testSequence.toString() == prevString) referenceDeletion = false; + if (testSequence.valueOf() == prevValueOf) referenceDeletion = false; + if (testSequence.length == prevLength) referenceDeletion = false; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.bindings.error.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.bindings.error.qml new file mode 100644 index 0000000000..9c87dd293e --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.bindings.error.qml @@ -0,0 +1,19 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + intListProperty: [ 1, 2, 3, 6, 7 ] + } + + MySequenceConversionObject { + id: mscoTwo + objectName: "mscoTwo" + boolListProperty: msco.intListProperty + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.bindings.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.bindings.qml new file mode 100644 index 0000000000..8d83e9f9f5 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.bindings.qml @@ -0,0 +1,28 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + intListProperty: [ 1, 2, 3, 6, 7 ] + } + + MySequenceConversionObject { + id: mscoTwo + objectName: "mscoTwo" + intListProperty: msco.intListProperty + } + + property variant boundSequence: msco.intListProperty + property int boundElement: msco.intListProperty[3] + property variant boundSequenceTwo: mscoTwo.intListProperty + + Component.onCompleted: { + msco.intListProperty[3] = 12; + mscoTwo.intListProperty[4] = 14; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.copy.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.copy.qml new file mode 100644 index 0000000000..f6614dad0c --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.copy.qml @@ -0,0 +1,160 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + property bool success: true + + property variant intList + property variant qrealList + property variant boolList + property variant stringList + property variant urlList + property variant qstringList + + // this test ensures that the "copy resource" codepaths work + function testCopySequences() { + success = true; + + // create "copy resource" sequences + var jsIntList = msco.generateIntSequence(); + var jsQrealList = msco.generateQrealSequence(); + var jsBoolList = msco.generateBoolSequence(); + var jsStringList = msco.generateStringSequence(); + var jsUrlList = msco.generateUrlSequence(); + var jsQStringList = msco.generateQStringSequence(); + + if (jsIntList.toString() != [1, 2, 3].toString()) + success = false; + if (jsQrealList.toString() != [1.1, 2.2, 3.3].toString()) + success = false; + if (jsBoolList.toString() != [true, false, true].toString()) + success = false; + if (jsStringList.toString() != ["one", "two", "three"].toString()) + success = false; + if (jsUrlList.toString() != ["http://www.example1.com", "http://www.example2.com", "http://www.example3.com"].toString()) + success = false; + if (jsQStringList.toString() != ["one", "two", "three"].toString()) + success = false; + + // copy the sequence; should result in a new copy + intList = jsIntList; + qrealList = jsQrealList; + boolList = jsBoolList; + stringList = jsStringList; + urlList = jsUrlList; + qstringList = jsQStringList; + + // these operations shouldn't modify either variables - because + // we don't handle writing to the intermediate variant at list[index] + // for variant properties. + intList[1] = 8; + qrealList[1] = 8.8; + boolList[1] = true; + stringList[1] = "eight"; + urlList[1] = "http://www.example8.com"; + qstringList[1] = "eight"; + + if (jsIntList[1] == 8) + success = false; + if (jsQrealList[1] == 8.8) + success = false; + if (jsBoolList[1] == true) + success = false; + if (jsStringList[1] == "eight") + success = false; + if (jsUrlList[1] == "http://www.example8.com") + success = false; + if (jsQStringList[1] == "eight") + success = false; + + // assign a "copy resource" sequence to a QObject Q_PROPERTY + msco.intListProperty = intList; + msco.qrealListProperty = qrealList; + msco.boolListProperty = boolList; + msco.stringListProperty = stringList; + msco.urlListProperty = urlList; + msco.qstringListProperty = qstringList; + + if (msco.intListProperty.toString() != [1, 2, 3].toString()) + success = false; + if (msco.qrealListProperty.toString() != [1.1, 2.2, 3.3].toString()) + success = false; + if (msco.boolListProperty.toString() != [true, false, true].toString()) + success = false; + if (msco.stringListProperty.toString() != ["one", "two", "three"].toString()) + success = false; + if (msco.urlListProperty.toString() != ["http://www.example1.com", "http://www.example2.com", "http://www.example3.com"].toString()) + success = false; + if (msco.qstringListProperty.toString() != ["one", "two", "three"].toString()) + success = false; + + // now modify the QObject Q_PROPERTY (reference resource) sequences - shouldn't modify the copy resource sequences. + msco.intListProperty[2] = 9; + msco.qrealListProperty[2] = 9.9; + msco.boolListProperty[2] = false; + msco.stringListProperty[2] = "nine"; + msco.urlListProperty[2] = "http://www.example9.com"; + msco.qstringListProperty[2] = "nine"; + + if (intList[2] == 9) + success = false; + if (qrealList[2] == 9.9) + success = false; + if (boolList[2] == false) + success = false; + if (stringList[2] == "nine") + success = false; + if (urlList[2] == "http://www.example9.com") + success = false; + if (qstringList[2] == "nine") + success = false; + } + + property int intVal + property real qrealVal + property bool boolVal + property string stringVal + + // this test ensures that indexed access works for copy resource sequences. + function readSequenceCopyElements() { + success = true; + + var jsIntList = msco.generateIntSequence(); + var jsQrealList = msco.generateQrealSequence(); + var jsBoolList = msco.generateBoolSequence(); + var jsStringList = msco.generateStringSequence(); + + intVal = jsIntList[1]; + qrealVal = jsQrealList[1]; + boolVal = jsBoolList[1]; + stringVal = jsStringList[1]; + + if (intVal != 2) + success = false; + if (qrealVal != 2.2) + success = false; + if (boolVal != false) + success = false; + if (stringVal != "two") + success = false; + } + + // this test ensures that equality works for copy resource sequences. + function testEqualitySemantics() { + success = true; + + var jsIntList = msco.generateIntSequence(); + var jsIntList2 = msco.generateIntSequence(); + + if (jsIntList == jsIntList2) success = false; + if (jsIntList != jsIntList) success = false; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.read.error.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.read.error.qml new file mode 100644 index 0000000000..12a76d7e7d --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.read.error.qml @@ -0,0 +1,21 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + property int pointListLength: 0 + property variant pointList + + function performTest() { + // we have NOT registered QList<QPoint> as a type + pointListLength = msco.pointListProperty.length; + pointList = msco.pointListProperty; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.read.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.read.qml new file mode 100644 index 0000000000..4a8a4a17b2 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.read.qml @@ -0,0 +1,105 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + property int intListLength: 0 + property variant intList + property int qrealListLength: 0 + property variant qrealList + property int boolListLength: 0 + property variant boolList + property int stringListLength: 0 + property variant stringList + property int urlListLength: 0 + property variant urlList + property int qstringListLength: 0 + property variant qstringList + + function readSequences() { + intListLength = msco.intListProperty.length; + intList = msco.intListProperty; + qrealListLength = msco.qrealListProperty.length; + qrealList = msco.qrealListProperty; + boolListLength = msco.boolListProperty.length; + boolList = msco.boolListProperty; + stringListLength = msco.stringListProperty.length; + stringList = msco.stringListProperty; + urlListLength = msco.urlListProperty.length; + urlList = msco.urlListProperty; + qstringListLength = msco.qstringListProperty.length; + qstringList = msco.qstringListProperty; + } + + property int intVal + property real qrealVal + property bool boolVal + property string stringVal + property url urlVal + property string qstringVal + + function readSequenceElements() { + intVal = msco.intListProperty[1]; + qrealVal = msco.qrealListProperty[1]; + boolVal = msco.boolListProperty[1]; + stringVal = msco.stringListProperty[1]; + urlVal = msco.urlListProperty[1]; + qstringVal = msco.qstringListProperty[1]; + } + + property bool enumerationMatches + function enumerateSequenceElements() { + var jsIntList = [1, 2, 3, 4, 5]; + msco.intListProperty = [1, 2, 3, 4, 5]; + + var jsIntListProps = [] + var seqIntListProps = [] + + enumerationMatches = true; + for (var i in jsIntList) { + jsIntListProps.push(i); + if (jsIntList[i] != msco.intListProperty[i]) { + enumerationMatches = false; + } + } + for (var j in msco.intListProperty) { + seqIntListProps.push(j); + if (jsIntList[j] != msco.intListProperty[j]) { + enumerationMatches = false; + } + } + + if (jsIntListProps.length != seqIntListProps.length) { + enumerationMatches = false; + } + + var emptyList = []; + msco.stringListProperty = [] + if (emptyList.toString() != msco.stringListProperty.toString()) { + enumerationMatches = false; + } + if (emptyList.valueOf() != msco.stringListProperty.valueOf()) { + enumerationMatches = false; + } + } + + property bool referenceDeletion: false + function testReferenceDeletion() { + referenceDeletion = true; + var testObj = msco.generateTestObject(); + testObj.intListProperty = [1, 2, 3, 4, 5]; + var testSequence = testObj.intListProperty; + if (testSequence[4] != 5) + referenceDeletion = false; + msco.deleteTestObject(testObj); // delete referenced object. + if (testSequence[4] == 5) + referenceDeletion = false; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.threads.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.threads.qml new file mode 100644 index 0000000000..5c4afe0574 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.threads.qml @@ -0,0 +1,69 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + property bool success: false + property bool finished: false + + function testIntSequence() { + msco.intListProperty = [ 0, 1, 2, 3, 4, 5, 6, 7 ]; + worker.sendSequence(msco.intListProperty); + } + + function testQrealSequence() { + msco.qrealListProperty = [ 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1 ]; + worker.sendSequence(msco.qrealListProperty); + } + + function testBoolSequence() { + msco.boolListProperty = [ false, true, true, false, false, true, false, true ]; + worker.sendSequence(msco.boolListProperty); + } + + function testStringSequence() { + msco.stringListProperty = [ "one", "two", "three", "four" ]; + worker.sendSequence(msco.stringListProperty); + } + + function testQStringSequence() { + msco.qstringListProperty = [ "one", "two", "three", "four" ]; + worker.sendSequence(msco.qstringListProperty); + } + + function testUrlSequence() { + msco.urlListProperty = [ "www.example1.com", "www.example2.com", "www.example3.com", "www.example4.com" ]; + worker.sendSequence(msco.urlListProperty); + } + + WorkerScript { + id: worker + source: "threadScript.js" + + property variant expected + property variant response + + function sendSequence(seq) { + root.success = false; + root.finished = false; + worker.expected = seq; + worker.sendMessage(seq); + } + + onMessage: { + worker.response = messageObject; + if (worker.response.toString() == worker.expected.toString()) + root.success = true; + else + root.success = false; + root.finished = true; + } + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.write.error.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.write.error.qml new file mode 100644 index 0000000000..75beafd1ee --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.write.error.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + function performTest() { + // we have NOT registered QList<QPoint> as a type + var pointList = [ Qt.point(7,7), Qt.point(8,8), Qt.point(9,9) ]; + msco.pointListProperty = pointList; // error. + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.write.qml b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.write.qml new file mode 100644 index 0000000000..812de043b7 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/sequenceConversion.write.qml @@ -0,0 +1,109 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: root + objectName: "root" + + MySequenceConversionObject { + id: msco + objectName: "msco" + } + + property bool success + + function writeSequences() { + success = true; + + var intList = [ 9, 8, 7, 6 ]; + msco.intListProperty = intList; + var qrealList = [ 9.9, 8.8, 7.7, 6.6 ]; + msco.qrealListProperty = qrealList; + var boolList = [ false, false, false, true ]; + msco.boolListProperty = boolList; + var stringList = [ "nine", "eight", "seven", "six" ] + msco.stringListProperty = stringList; + var urlList = [ "http://www.example9.com", "http://www.example8.com", "http://www.example7.com", "http://www.example6.com" ] + msco.urlListProperty = urlList; + var qstringList = [ "nine", "eight", "seven", "six" ] + msco.qstringListProperty = qstringList; + + if (msco.intListProperty[0] != 9 || msco.intListProperty[1] != 8 || msco.intListProperty[2] != 7 || msco.intListProperty[3] != 6) + success = false; + if (msco.qrealListProperty[0] != 9.9 || msco.qrealListProperty[1] != 8.8 || msco.qrealListProperty[2] != 7.7 || msco.qrealListProperty[3] != 6.6) + success = false; + if (msco.boolListProperty[0] != false || msco.boolListProperty[1] != false || msco.boolListProperty[2] != false || msco.boolListProperty[3] != true) + success = false; + if (msco.stringListProperty[0] != "nine" || msco.stringListProperty[1] != "eight" || msco.stringListProperty[2] != "seven" || msco.stringListProperty[3] != "six") + success = false; + if (msco.urlListProperty[0] != "http://www.example9.com" || msco.urlListProperty[1] != "http://www.example8.com" || msco.urlListProperty[2] != "http://www.example7.com" || msco.urlListProperty[3] != "http://www.example6.com") + success = false; + if (msco.qstringListProperty[0] != "nine" || msco.qstringListProperty[1] != "eight" || msco.qstringListProperty[2] != "seven" || msco.qstringListProperty[3] != "six") + success = false; + } + + function writeSequenceElements() { + // set up initial conditions. + writeSequences(); + success = true; + + // element set. + msco.intListProperty[3] = 2; + msco.qrealListProperty[3] = 2.2; + msco.boolListProperty[3] = false; + msco.stringListProperty[3] = "changed"; + msco.urlListProperty[3] = "http://www.examplechanged.com"; + msco.qstringListProperty[3] = "changed"; + + if (msco.intListProperty[0] != 9 || msco.intListProperty[1] != 8 || msco.intListProperty[2] != 7 || msco.intListProperty[3] != 2) + success = false; + if (msco.qrealListProperty[0] != 9.9 || msco.qrealListProperty[1] != 8.8 || msco.qrealListProperty[2] != 7.7 || msco.qrealListProperty[3] != 2.2) + success = false; + if (msco.boolListProperty[0] != false || msco.boolListProperty[1] != false || msco.boolListProperty[2] != false || msco.boolListProperty[3] != false) + success = false; + if (msco.stringListProperty[0] != "nine" || msco.stringListProperty[1] != "eight" || msco.stringListProperty[2] != "seven" || msco.stringListProperty[3] != "changed") + success = false; + if (msco.urlListProperty[0] != "http://www.example9.com" || msco.urlListProperty[1] != "http://www.example8.com" || msco.urlListProperty[2] != "http://www.example7.com" || msco.urlListProperty[3] != "http://www.examplechanged.com") + success = false; + if (msco.qstringListProperty[0] != "nine" || msco.qstringListProperty[1] != "eight" || msco.qstringListProperty[2] != "seven" || msco.qstringListProperty[3] != "changed") + success = false; + } + + function writeOtherElements() { + success = true; + var jsIntList = [1, 2, 3, 4, 5]; + msco.intListProperty = [1, 2, 3, 4, 5]; + + jsIntList[8] = 8; + msco.intListProperty[8] = 8; + if (jsIntList[8] != msco.intListProperty[8]) + success = false; + if (jsIntList.length != msco.intListProperty.length) + success = false; + + // NOTE: we can't exactly match the spec here -- we fill the sequence with a default (rather than empty) value + if (msco.intListProperty[5] != 0 || msco.intListProperty[6] != 0 || msco.intListProperty[7] != 0) + success = false; + + // should have no effect + var currLength = jsIntList.length; + jsIntList.someThing = 9; + msco.intListProperty.someThing = 9; + if (msco.intListProperty.length != currLength) + success = false; + } + + property bool referenceDeletion: false + function testReferenceDeletion() { + referenceDeletion = true; + var testObj = msco.generateTestObject(); + testObj.intListProperty = [1, 2, 3, 4, 5]; + var testSequence = testObj.intListProperty; + if (testSequence[4] != 5) + referenceDeletion = false; + msco.deleteTestObject(testObj); // delete referenced object. + testSequence[4] = 5; // shouldn't work, since referenced object no longer exists. + if (testSequence[4] == 5) + referenceDeletion = false; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/threadScript.js b/tests/auto/declarative/qdeclarativeecmascript/data/threadScript.js new file mode 100644 index 0000000000..9f94de1bc1 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/threadScript.js @@ -0,0 +1,4 @@ +WorkerScript.onMessage = function(msg) { + WorkerScript.sendMessage(msg); +} + diff --git a/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp b/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp index 513705d697..781b78663f 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp @@ -196,6 +196,8 @@ void registerTypes() qmlRegisterType<MyDynamicCreationDestructionObject>("Qt.test", 1, 0, "MyDynamicCreationDestructionObject"); qmlRegisterType<WriteCounter>("Qt.test", 1, 0, "WriteCounter"); + + qmlRegisterType<MySequenceConversionObject>("Qt.test", 1, 0, "MySequenceConversionObject"); } #include "testtypes.moc" diff --git a/tests/auto/declarative/qdeclarativeecmascript/testtypes.h b/tests/auto/declarative/qdeclarativeecmascript/testtypes.h index 7684ddd438..af4225c8da 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/testtypes.h +++ b/tests/auto/declarative/qdeclarativeecmascript/testtypes.h @@ -1148,6 +1148,87 @@ private: int m_count; }; +class MySequenceConversionObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY (QList<int> intListProperty READ intListProperty WRITE setIntListProperty NOTIFY intListPropertyChanged) + Q_PROPERTY (QList<int> intListProperty2 READ intListProperty2 WRITE setIntListProperty2 NOTIFY intListProperty2Changed) + Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged) + Q_PROPERTY (QList<bool> boolListProperty READ boolListProperty WRITE setBoolListProperty NOTIFY boolListPropertyChanged) + Q_PROPERTY (QList<QString> stringListProperty READ stringListProperty WRITE setStringListProperty NOTIFY stringListPropertyChanged) + Q_PROPERTY (QList<QUrl> urlListProperty READ urlListProperty WRITE setUrlListProperty NOTIFY urlListPropertyChanged) + + Q_PROPERTY (QStringList qstringListProperty READ qstringListProperty WRITE setQStringListProperty NOTIFY qstringListPropertyChanged) + Q_PROPERTY (QList<QPoint> pointListProperty READ pointListProperty WRITE setPointListProperty NOTIFY pointListPropertyChanged) + +public: + MySequenceConversionObject() + { + m_intList << 1 << 2 << 3 << 4; + m_intList2 << 1 << 2 << 3 << 4; + m_qrealList << 1.1 << 2.2 << 3.3 << 4.4; + m_boolList << true << false << true << false; + m_stringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth"); + m_urlList << QUrl("http://www.example1.com") << QUrl("http://www.example2.com") << QUrl("http://www.example3.com"); + + m_qstringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth"); + m_pointList << QPoint(1, 2) << QPoint(3, 4) << QPoint(5, 6); + } + + ~MySequenceConversionObject() {} + + QList<int> intListProperty() const { return m_intList; } + void setIntListProperty(const QList<int> &list) { m_intList = list; emit intListPropertyChanged(); } + QList<int> intListProperty2() const { return m_intList2; } + void setIntListProperty2(const QList<int> &list) { m_intList2 = list; emit intListProperty2Changed(); } + QList<qreal> qrealListProperty() const { return m_qrealList; } + void setQrealListProperty(const QList<qreal> &list) { m_qrealList = list; emit qrealListPropertyChanged(); } + QList<bool> boolListProperty() const { return m_boolList; } + void setBoolListProperty(const QList<bool> &list) { m_boolList = list; emit boolListPropertyChanged(); } + QList<QString> stringListProperty() const { return m_stringList; } + void setStringListProperty(const QList<QString> &list) { m_stringList = list; emit stringListPropertyChanged(); } + QList<QUrl> urlListProperty() const { return m_urlList; } + void setUrlListProperty(const QList<QUrl> &list) { m_urlList = list; emit urlListPropertyChanged(); } + QStringList qstringListProperty() const { return m_qstringList; } + void setQStringListProperty(const QStringList &list) { m_qstringList = list; emit qstringListPropertyChanged(); } + QList<QPoint> pointListProperty() const { return m_pointList; } + void setPointListProperty(const QList<QPoint> &list) { m_pointList = list; emit pointListPropertyChanged(); } + + // now for "copy resource" sequences: + Q_INVOKABLE QList<int> generateIntSequence() const { QList<int> retn; retn << 1 << 2 << 3; return retn; } + Q_INVOKABLE QList<qreal> generateQrealSequence() const { QList<qreal> retn; retn << 1.1 << 2.2 << 3.3; return retn; } + Q_INVOKABLE QList<bool> generateBoolSequence() const { QList<bool> retn; retn << true << false << true; return retn; } + Q_INVOKABLE QList<QString> generateStringSequence() const { QList<QString> retn; retn << "one" << "two" << "three"; return retn; } + Q_INVOKABLE QList<QUrl> generateUrlSequence() const { QList<QUrl> retn; retn << QUrl("http://www.example1.com") << QUrl("http://www.example2.com") << QUrl("http://www.example3.com"); return retn; } + Q_INVOKABLE QStringList generateQStringSequence() const { QStringList retn; retn << "one" << "two" << "three"; return retn; } + + // "reference resource" underlying qobject deletion test: + Q_INVOKABLE MySequenceConversionObject *generateTestObject() const { return new MySequenceConversionObject; } + Q_INVOKABLE void deleteTestObject(QObject *object) const { delete object; } + +signals: + void intListPropertyChanged(); + void intListProperty2Changed(); + void qrealListPropertyChanged(); + void boolListPropertyChanged(); + void stringListPropertyChanged(); + void urlListPropertyChanged(); + void qstringListPropertyChanged(); + void pointListPropertyChanged(); + +private: + QList<int> m_intList; + QList<int> m_intList2; + QList<qreal> m_qrealList; + QList<bool> m_boolList; + QList<QString> m_stringList; + QList<QUrl> m_urlList; + + QStringList m_qstringList; // not a supported sequence type, but QStringList support is hardcoded. + QList<QPoint> m_pointList; // not a supported sequence type +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp index c92dc809e9..ee228ec3b8 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp @@ -170,6 +170,13 @@ private slots: void handleReferenceManagement(); void stringArg(); void readonlyDeclaration(); + void sequenceConversionRead(); + void sequenceConversionWrite(); + void sequenceConversionArray(); + void sequenceConversionThreads(); + void sequenceConversionBindings(); + void sequenceConversionCopy(); + void bug1(); void bug2(); void dynamicCreationCrash(); @@ -4048,6 +4055,232 @@ void tst_qdeclarativeecmascript::readonlyDeclaration() delete object; } +Q_DECLARE_METATYPE(QList<int>) +Q_DECLARE_METATYPE(QList<qreal>) +Q_DECLARE_METATYPE(QList<bool>) +Q_DECLARE_METATYPE(QList<QString>) +Q_DECLARE_METATYPE(QList<QUrl>) +void tst_qdeclarativeecmascript::sequenceConversionRead() +{ + { + QUrl qmlFile = TEST_FILE("sequenceConversion.read.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + QMetaObject::invokeMethod(object, "readSequences"); + QList<int> intList; intList << 1 << 2 << 3 << 4; + QCOMPARE(object->property("intListLength").toInt(), intList.length()); + QCOMPARE(object->property("intList").value<QList<int> >(), intList); + QList<qreal> qrealList; qrealList << 1.1 << 2.2 << 3.3 << 4.4; + QCOMPARE(object->property("qrealListLength").toInt(), qrealList.length()); + QCOMPARE(object->property("qrealList").value<QList<qreal> >(), qrealList); + QList<bool> boolList; boolList << true << false << true << false; + QCOMPARE(object->property("boolListLength").toInt(), boolList.length()); + QCOMPARE(object->property("boolList").value<QList<bool> >(), boolList); + QList<QString> stringList; stringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth"); + QCOMPARE(object->property("stringListLength").toInt(), stringList.length()); + QCOMPARE(object->property("stringList").value<QList<QString> >(), stringList); + QList<QUrl> urlList; urlList << QUrl("http://www.example1.com") << QUrl("http://www.example2.com") << QUrl("http://www.example3.com"); + QCOMPARE(object->property("urlListLength").toInt(), urlList.length()); + QCOMPARE(object->property("urlList").value<QList<QUrl> >(), urlList); + QStringList qstringList; qstringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth"); + QCOMPARE(object->property("qstringListLength").toInt(), qstringList.length()); + QCOMPARE(object->property("qstringList").value<QStringList>(), qstringList); + + QMetaObject::invokeMethod(object, "readSequenceElements"); + QCOMPARE(object->property("intVal").toInt(), 2); + QCOMPARE(object->property("qrealVal").toReal(), 2.2); + QCOMPARE(object->property("boolVal").toBool(), false); + QCOMPARE(object->property("stringVal").toString(), QString(QLatin1String("second"))); + QCOMPARE(object->property("urlVal").toUrl(), QUrl("http://www.example2.com")); + QCOMPARE(object->property("qstringVal").toString(), QString(QLatin1String("second"))); + + QMetaObject::invokeMethod(object, "enumerateSequenceElements"); + QCOMPARE(object->property("enumerationMatches").toBool(), true); + + intList.clear(); intList << 1 << 2 << 3 << 4 << 5; // set by the enumerateSequenceElements test. + QDeclarativeProperty seqProp(seq, "intListProperty"); + QCOMPARE(seqProp.read().value<QList<int> >(), intList); + QDeclarativeProperty seqProp2(seq, "intListProperty", &engine); + QCOMPARE(seqProp2.read().value<QList<int> >(), intList); + + QMetaObject::invokeMethod(object, "testReferenceDeletion"); + QCOMPARE(object->property("referenceDeletion").toBool(), true); + + delete object; + } + + { + QUrl qmlFile = TEST_FILE("sequenceConversion.read.error.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + // we haven't registered QList<QPoint> as a sequence type. + QString warningOne = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'QList<QPoint>' for property 'MySequenceConversionObject::pointListProperty'"); + QString warningTwo = qmlFile.toString() + QLatin1String(":18: TypeError: Cannot read property 'length' of undefined"); + QTest::ignoreMessage(QtWarningMsg, warningOne.toAscii().constData()); + QTest::ignoreMessage(QtWarningMsg, warningTwo.toAscii().constData()); + + QMetaObject::invokeMethod(object, "performTest"); + + // QList<QPoint> has not been registered as a sequence type. + QCOMPARE(object->property("pointListLength").toInt(), 0); + QVERIFY(!object->property("pointList").isValid()); + QTest::ignoreMessage(QtWarningMsg, "QMetaProperty::read: Unable to handle unregistered datatype 'QList<QPoint>' for property 'MySequenceConversionObject::pointListProperty'"); + QDeclarativeProperty seqProp(seq, "pointListProperty", &engine); + QVERIFY(!seqProp.read().isValid()); // not a valid/known sequence type + + delete object; + } +} + +void tst_qdeclarativeecmascript::sequenceConversionWrite() +{ + { + QUrl qmlFile = TEST_FILE("sequenceConversion.write.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + QMetaObject::invokeMethod(object, "writeSequences"); + QCOMPARE(object->property("success").toBool(), true); + + QMetaObject::invokeMethod(object, "writeSequenceElements"); + QCOMPARE(object->property("success").toBool(), true); + + QMetaObject::invokeMethod(object, "writeOtherElements"); + QCOMPARE(object->property("success").toBool(), true); + + QMetaObject::invokeMethod(object, "testReferenceDeletion"); + QCOMPARE(object->property("referenceDeletion").toBool(), true); + + delete object; + } + + { + QUrl qmlFile = TEST_FILE("sequenceConversion.write.error.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + 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 void"); + QTest::ignoreMessage(QtWarningMsg, warningOne.toAscii().constData()); + + QMetaObject::invokeMethod(object, "performTest"); + + QList<QPoint> pointList; pointList << QPoint(1, 2) << QPoint(3, 4) << QPoint(5, 6); // original values, shouldn't have changed + QCOMPARE(seq->pointListProperty(), pointList); + + delete object; + } +} + +void tst_qdeclarativeecmascript::sequenceConversionArray() +{ + // ensure that in JS the returned sequences act just like normal JS Arrays. + QUrl qmlFile = TEST_FILE("sequenceConversion.array.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + //QMetaObject::invokeMethod(object, "indexedAccess"); + //QVERIFY(object->property("success").toBool()); + //QMetaObject::invokeMethod(object, "arrayOperations"); + //QVERIFY(object->property("success").toBool()); + QMetaObject::invokeMethod(object, "testEqualitySemantics"); + QVERIFY(object->property("success").toBool()); + //QMetaObject::invokeMethod(object, "testReferenceDeletion"); + //QCOMPARE(object->property("referenceDeletion").toBool(), true); + delete object; +} + +void tst_qdeclarativeecmascript::sequenceConversionThreads() +{ + // ensure that sequence conversion operations work correctly in a worker thread + // and that serialisation between the main and worker thread succeeds. + QUrl qmlFile = TEST_FILE("sequenceConversion.threads.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + + QMetaObject::invokeMethod(object, "testIntSequence"); + QTRY_VERIFY(object->property("finished").toBool()); + QVERIFY(object->property("success").toBool()); + + QMetaObject::invokeMethod(object, "testQrealSequence"); + QTRY_VERIFY(object->property("finished").toBool()); + QVERIFY(object->property("success").toBool()); + + QMetaObject::invokeMethod(object, "testBoolSequence"); + QTRY_VERIFY(object->property("finished").toBool()); + QVERIFY(object->property("success").toBool()); + + QMetaObject::invokeMethod(object, "testStringSequence"); + QTRY_VERIFY(object->property("finished").toBool()); + QVERIFY(object->property("success").toBool()); + + QMetaObject::invokeMethod(object, "testQStringSequence"); + QTRY_VERIFY(object->property("finished").toBool()); + QVERIFY(object->property("success").toBool()); + + QMetaObject::invokeMethod(object, "testUrlSequence"); + QTRY_VERIFY(object->property("finished").toBool()); + QVERIFY(object->property("success").toBool()); + + delete object; +} + +void tst_qdeclarativeecmascript::sequenceConversionBindings() +{ + { + QUrl qmlFile = TEST_FILE("sequenceConversion.bindings.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + QList<int> intList; intList << 1 << 2 << 3 << 12 << 7; + QCOMPARE(object->property("boundSequence").value<QList<int> >(), intList); + QCOMPARE(object->property("boundElement").toInt(), intList.at(3)); + QList<int> intListTwo; intListTwo << 1 << 2 << 3 << 12 << 14; + QCOMPARE(object->property("boundSequenceTwo").value<QList<int> >(), intListTwo); + delete object; + } + + { + QUrl qmlFile = TEST_FILE("sequenceConversion.bindings.error.qml"); + QString warning = QString(QLatin1String("%1:17: Unable to assign QList<int> to QList<bool>")).arg(qmlFile.toString()); + QTest::ignoreMessage(QtWarningMsg, warning.toAscii().constData()); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + delete object; + } +} + +void tst_qdeclarativeecmascript::sequenceConversionCopy() +{ + QUrl qmlFile = TEST_FILE("sequenceConversion.copy.qml"); + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "testCopySequences"); + QCOMPARE(object->property("success").toBool(), true); + QMetaObject::invokeMethod(object, "readSequenceCopyElements"); + QCOMPARE(object->property("success").toBool(), true); + QMetaObject::invokeMethod(object, "testEqualitySemantics"); + QCOMPARE(object->property("success").toBool(), true); + delete object; +} + // Test that assigning a null object works // Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4 void tst_qdeclarativeecmascript::nullObjectBinding() |