From: Chris Adams Date: Fri, 30 Sep 2011 01:14:10 +0000 (+1000) Subject: Add JavaScript "var" property type to QML X-Git-Tag: qt-v5.0.0-alpha1~1461 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=752cd2aca42f6625f1cfc364937e0d39828cf954;p=profile%2Fivi%2Fqtdeclarative.git Add JavaScript "var" property type to QML This commit adds a new syntax which allows "var" type properties which can have JavaScript objects (as well as other basic types) assigned to them. Such JavaScript objects cannot be bound to. Task-number: QMLNG-18 Change-Id: If7f5045f4669e0d5c1b8d0891ed765128d0bc1c6 Reviewed-on: http://codereview.qt-project.org/1466 Reviewed-by: Aaron Kennedy --- diff --git a/doc/src/declarative/basictypes.qdoc b/doc/src/declarative/basictypes.qdoc index 0133ab5..1bc1373 100644 --- a/doc/src/declarative/basictypes.qdoc +++ b/doc/src/declarative/basictypes.qdoc @@ -422,14 +422,69 @@ \sa {QML Basic Types} */ + /*! + \qmlbasictype var + \ingroup qmlbasictypes + + \brief A var type is a generic property type. + + A var is a generic property type capable of storing any data type. + It is equivalent to a regular JavaScript variable. + For example, var properties can store numbers, strings, objects and + arrays: + + \qml + Item { + property var aNumber: 100 + property var aBool: false + property var aString: "Hello world!" + property var anotherString: String("#FF008800") + property var aColor: Qt.rgba(0.2, 0.3, 0.4, 0.5) + property var aRect: Qt.rect(10, 10, 10, 10) + property var aPoint: Qt.point(10, 10) + property var aSize: Qt.size(10, 10) + property var aVector3d: Qt.vector3d(100, 100, 100) + property var anArray: [1, 2, 3, "four", "five"] + property var anObject: { "foo": 10, "bar": 20 } + } + \endqml + + It is important to note that properties of JavaScript objects cannot + be bound to: + + \qml + Item { + property var car: new vehicle(4) + property int wheelCount: car.wheels + + function vehicle(wheels) { + this.wheels = wheels; + this.talk = function() { print("I have " + this.wheels + " wheels!"); } + } + + Component.onCompleted: { + car.wheels = 6; // wheelCount will _not_ be updated + } + } + \endqml + + \sa {QML Basic Types} +*/ + + /*! + \obsolete \qmlbasictype variant \ingroup qmlbasictypes \brief A variant type is a generic property type. - A variant is a generic property type. A variant type property can hold - any of the \l {QML Basic Types}{basic type} values: + A variant is a generic property type. It is obsolete and exists only to + support old applications; new applications should use "var" type + properties instead. + + A variant type property can hold any of the \l {QML Basic Types}{basic type} + values: \qml Item { diff --git a/doc/src/declarative/whatsnew.qdoc b/doc/src/declarative/whatsnew.qdoc index a9a1ecb..4efe0da 100644 --- a/doc/src/declarative/whatsnew.qdoc +++ b/doc/src/declarative/whatsnew.qdoc @@ -120,6 +120,9 @@ header and footer items). ListView section.labelPositioning property added to allow keeping the current section label at the start and/or next section label at the end of the view. +A new property type ("var") has been introduced which obsoletes "variant" properties in QML. +Properties of this type are equivalent to regular JavaScript variables. See the documentation +on \l{QML Basic Types} for more information about "var" properties. \section2 QtQuick 1 is now a separate library and module diff --git a/src/declarative/qml/qdeclarativecompiler.cpp b/src/declarative/qml/qdeclarativecompiler.cpp index dc992b5..c736c3f 100644 --- a/src/declarative/qml/qdeclarativecompiler.cpp +++ b/src/declarative/qml/qdeclarativecompiler.cpp @@ -380,26 +380,54 @@ void QDeclarativeCompiler::genLiteralAssignment(QDeclarativeScript::Property *pr if (v->value.isNumber()) { double n = v->value.asNumber(); if (double(int(n)) == n) { - Instruction::StoreVariantInteger instr; + if (prop->core.isVMEProperty()) { + Instruction::StoreVarInteger instr; + instr.propertyIndex = prop->index; + instr.value = int(n); + output->addInstruction(instr); + } else { + Instruction::StoreVariantInteger instr; + instr.propertyIndex = prop->index; + instr.value = int(n); + output->addInstruction(instr); + } + } else { + if (prop->core.isVMEProperty()) { + Instruction::StoreVarDouble instr; + instr.propertyIndex = prop->index; + instr.value = n; + output->addInstruction(instr); + } else { + Instruction::StoreVariantDouble instr; + instr.propertyIndex = prop->index; + instr.value = n; + output->addInstruction(instr); + } + } + } else if (v->value.isBoolean()) { + if (prop->core.isVMEProperty()) { + Instruction::StoreVarBool instr; instr.propertyIndex = prop->index; - instr.value = int(n); + instr.value = v->value.asBoolean(); output->addInstruction(instr); } else { - Instruction::StoreVariantDouble instr; + Instruction::StoreVariantBool instr; instr.propertyIndex = prop->index; - instr.value = n; + instr.value = v->value.asBoolean(); output->addInstruction(instr); } - } else if(v->value.isBoolean()) { - Instruction::StoreVariantBool instr; - instr.propertyIndex = prop->index; - instr.value = v->value.asBoolean(); - output->addInstruction(instr); } else { - Instruction::StoreVariant instr; - instr.propertyIndex = prop->index; - instr.value = output->indexForString(v->value.asString()); - output->addInstruction(instr); + if (prop->core.isVMEProperty()) { + Instruction::StoreVar instr; + instr.propertyIndex = prop->index; + instr.value = output->indexForString(v->value.asString()); + output->addInstruction(instr); + } else { + Instruction::StoreVariant instr; + instr.propertyIndex = prop->index; + instr.value = output->indexForString(v->value.asString()); + output->addInstruction(instr); + } } } break; @@ -1796,10 +1824,18 @@ void QDeclarativeCompiler::genPropertyAssignment(QDeclarativeScript::Property *p } else if (prop->type == QMetaType::QVariant) { - Instruction::StoreVariantObject store; - store.line = v->object->location.start.line; - store.propertyIndex = prop->index; - output->addInstruction(store); + if (prop->core.isVMEProperty()) { + Instruction::StoreVarObject store; + store.line = v->object->location.start.line; + store.propertyIndex = prop->index; + output->addInstruction(store); + } else { + Instruction::StoreVariantObject store; + store.line = v->object->location.start.line; + store.propertyIndex = prop->index; + output->addInstruction(store); + } + } else { @@ -2570,11 +2606,16 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn const Object::DynamicProperty *defaultProperty = 0; int aliasCount = 0; + int varPropCount = 0; + int totalPropCount = 0; + int firstPropertyVarIndex = 0; for (Object::DynamicProperty *p = obj->dynamicProperties.first(); p; p = obj->dynamicProperties.next(p)) { if (p->type == Object::DynamicProperty::Alias) aliasCount++; + if (p->type == Object::DynamicProperty::Var) + varPropCount++; if (p->isDefaultProperty && (resolveAlias || p->type != Object::DynamicProperty::Alias)) @@ -2628,6 +2669,7 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn int metaType; const char *cppType; } builtinTypes[] = { + { Object::DynamicProperty::Var, 0, "QVariant" }, { Object::DynamicProperty::Variant, 0, "QVariant" }, { Object::DynamicProperty::Int, QMetaType::Int, "int" }, { Object::DynamicProperty::Bool, QMetaType::Bool, "bool" }, @@ -2706,6 +2748,9 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn typeRef = p->typeRef; } + if (p->type == Object::DynamicProperty::Var) + continue; + if (buildData) { VMD *vmd = (QDeclarativeVMEMetaData *)dynamicData.data(); vmd->propertyCount++; @@ -2726,6 +2771,31 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn effectivePropertyIndex++; } + + if (varPropCount) { + VMD *vmd = (QDeclarativeVMEMetaData *)dynamicData.data(); + if (buildData) + vmd->varPropertyCount = varPropCount; + firstPropertyVarIndex = effectivePropertyIndex; + totalPropCount = varPropCount + effectivePropertyIndex; + for (Object::DynamicProperty *p = obj->dynamicProperties.first(); p; p = obj->dynamicProperties.next(p)) { + if (p->type == Object::DynamicProperty::Var) { + QFastMetaBuilder::StringRef typeRef = typeRefs[p->type]; + if (buildData) { + vmd->propertyCount++; + (vmd->propertyData() + effectivePropertyIndex)->propertyType = -1; + } + + builder.setProperty(effectivePropertyIndex, p->nameRef, typeRef, (QMetaType::Type)-1, + QFastMetaBuilder::Writable, effectivePropertyIndex); + + p->changedSignatureRef = builder.newString(p->name.utf8length() + strlen("Changed()")); + builder.setSignal(effectivePropertyIndex, p->changedSignatureRef); + + effectivePropertyIndex++; + } + } + } if (aliasCount) { int aliasIndex = 0; @@ -2913,9 +2983,19 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn if (obj->type != -1) { QDeclarativePropertyCache *cache = output->types[obj->type].createPropertyCache(engine)->copy(); - cache->append(engine, &obj->extObject, QDeclarativePropertyCache::Data::NoFlags, + cache->append(engine, &obj->extObject, + QDeclarativePropertyCache::Data::NoFlags, QDeclarativePropertyCache::Data::IsVMEFunction, QDeclarativePropertyCache::Data::IsVMESignal); + + // now we modify the flags appropriately for var properties. + int propertyOffset = obj->extObject.propertyOffset(); + QDeclarativePropertyCache::Data *currPropData = 0; + for (int pvi = firstPropertyVarIndex; pvi < totalPropCount; ++pvi) { + currPropData = cache->property(pvi + propertyOffset); + currPropData->setFlags(currPropData->getFlags() | QDeclarativePropertyCache::Data::IsVMEProperty); + } + obj->synthCache = cache; } @@ -3205,8 +3285,7 @@ int QDeclarativeCompiler::genValueTypeData(QDeclarativeScript::Property *valueTy int QDeclarativeCompiler::genPropertyData(QDeclarativeScript::Property *prop) { typedef QDeclarativePropertyPrivate QDPP; - QByteArray data = QDPP::saveProperty(prop->parent->metaObject(), prop->index, engine); - + QByteArray data = QDPP::saveProperty(&prop->core); return output->indexForByteArray(data); } diff --git a/src/declarative/qml/qdeclarativeinstruction_p.h b/src/declarative/qml/qdeclarativeinstruction_p.h index e64ca5d..b6efb19 100644 --- a/src/declarative/qml/qdeclarativeinstruction_p.h +++ b/src/declarative/qml/qdeclarativeinstruction_p.h @@ -73,6 +73,10 @@ QT_BEGIN_NAMESPACE F(StoreVariantInteger, storeInteger) \ F(StoreVariantDouble, storeDouble) \ F(StoreVariantBool, storeBool) \ + F(StoreVar, storeString) \ + F(StoreVarInteger, storeInteger) \ + F(StoreVarDouble, storeDouble) \ + F(StoreVarBool, storeBool) \ F(StoreString, storeString) \ F(StoreByteArray, storeByteArray) \ F(StoreUrl, storeUrl) \ @@ -109,6 +113,7 @@ QT_BEGIN_NAMESPACE F(StoreObjectQList, common) \ F(AssignObjectList, assignObjectList) \ F(StoreVariantObject, storeObject) \ + F(StoreVarObject, storeObject) \ F(StoreInterface, storeObject) \ F(FetchAttached, fetchAttached) \ F(FetchQList, fetchQmlList) \ diff --git a/src/declarative/qml/qdeclarativeproperty.cpp b/src/declarative/qml/qdeclarativeproperty.cpp index acc2cfb..6e5e712 100644 --- a/src/declarative/qml/qdeclarativeproperty.cpp +++ b/src/declarative/qml/qdeclarativeproperty.cpp @@ -1327,13 +1327,14 @@ bool QDeclarativePropertyPrivate::writeBinding(const QDeclarativeProperty &that, QDeclarativeDeleteWatcher watcher(expression); QVariant value; + bool isVmeProperty = pp->core.isVMEProperty(); if (isUndefined) { } else if (that.propertyTypeCategory() == QDeclarativeProperty::List) { value = engine->toVariant(result, qMetaTypeId >()); } else if (result->IsNull() && that.propertyTypeCategory() == QDeclarativeProperty::Object) { value = QVariant::fromValue((QObject *)0); - } else { + } else if (!isVmeProperty) { value = engine->toVariant(result, type); } @@ -1351,6 +1352,8 @@ bool QDeclarativePropertyPrivate::writeBinding(const QDeclarativeProperty &that, } else if (result->IsFunction()) { expression->error.setDescription(QLatin1String("Unable to assign a function to a property.")); return false; + } else if (isVmeProperty) { + static_cast(const_cast(that.object()->metaObject()))->setVMEProperty(that.index(), result); } else if (object && !QDeclarativePropertyPrivate::write(that, value, flags)) { if (watcher.wasDeleted()) @@ -1604,6 +1607,17 @@ QByteArray QDeclarativePropertyPrivate::saveProperty(const QMetaObject *metaObje return rv; } +QByteArray QDeclarativePropertyPrivate::saveProperty(QDeclarativePropertyCache::Data *core) +{ + SerializedData sd; + memset(&sd, 0, sizeof(sd)); + sd.isValueType = false; + sd.core = *core; + + QByteArray rv((const char *)&sd, sizeof(sd)); + return rv; +} + QDeclarativeProperty QDeclarativePropertyPrivate::restore(const QByteArray &data, QObject *object, QDeclarativeContextData *ctxt) { diff --git a/src/declarative/qml/qdeclarativeproperty_p.h b/src/declarative/qml/qdeclarativeproperty_p.h index d05e155..190cf2a 100644 --- a/src/declarative/qml/qdeclarativeproperty_p.h +++ b/src/declarative/qml/qdeclarativeproperty_p.h @@ -116,6 +116,7 @@ public: QDeclarativeEngine *); static QByteArray saveProperty(const QMetaObject *, int, QDeclarativeEngine *); + static QByteArray saveProperty(QDeclarativePropertyCache::Data *); static QDeclarativeProperty restore(const QByteArray &, QObject *, QDeclarativeContextData *); static QDeclarativeProperty restore(const QDeclarativePropertyCache::Data &, diff --git a/src/declarative/qml/qdeclarativepropertycache_p.h b/src/declarative/qml/qdeclarativepropertycache_p.h index fefcf7f..c1610f8 100644 --- a/src/declarative/qml/qdeclarativepropertycache_p.h +++ b/src/declarative/qml/qdeclarativepropertycache_p.h @@ -95,19 +95,20 @@ public: IsEnumType = 0x00000100, // Property type is an enum IsQList = 0x00000200, // Property type is a QML list IsQmlBinding = 0x00000400, // Property type is a QDeclarativeBinding* - IsQJSValue = 0x00000800, // Property type is a QScriptValue + IsQJSValue = 0x00000800, // Property type is a QScriptValue IsV8Handle = 0x00001000, // Property type is a QDeclarativeV8Handle + IsVMEProperty = 0x00002000, // Property type is a "var" property of VMEMO // Apply only to IsFunctions - IsVMEFunction = 0x00002000, // Function was added by QML - HasArguments = 0x00004000, // Function takes arguments - IsSignal = 0x00008000, // Function is a signal - IsVMESignal = 0x00010000, // Signal was added by QML - IsV8Function = 0x00020000, // Function takes QDeclarativeV8Function* args - IsSignalHandler = 0x00040000, // Function is a signal handler + IsVMEFunction = 0x00004000, // Function was added by QML + HasArguments = 0x00008000, // Function takes arguments + IsSignal = 0x00010000, // Function is a signal + IsVMESignal = 0x00020000, // Signal was added by QML + IsV8Function = 0x00040000, // Function takes QDeclarativeV8Function* args + IsSignalHandler = 0x00080000, // Function is a signal handler // Internal QDeclarativePropertyCache flags - NotFullyResolved = 0x00080000 // True if the type data is to be lazily resolved + NotFullyResolved = 0x00100000 // True if the type data is to be lazily resolved }; Q_DECLARE_FLAGS(Flags, Flag) @@ -129,6 +130,7 @@ public: bool isQmlBinding() const { return flags & IsQmlBinding; } bool isQJSValue() const { return flags & IsQJSValue; } bool isV8Handle() const { return flags & IsV8Handle; } + bool isVMEProperty() const { return flags & IsVMEProperty; } bool isVMEFunction() const { return flags & IsVMEFunction; } bool hasArguments() const { return flags & HasArguments; } bool isSignal() const { return flags & IsSignal; } diff --git a/src/declarative/qml/qdeclarativescript.cpp b/src/declarative/qml/qdeclarativescript.cpp index 8fb423c..2bc358a 100644 --- a/src/declarative/qml/qdeclarativescript.cpp +++ b/src/declarative/qml/qdeclarativescript.cpp @@ -920,7 +920,8 @@ bool ProcessAST::visit(AST::UiPublicMember *node) // { "time", strlen("time"), Object::DynamicProperty::Time, "QTime", strlen("QTime") }, // { "date", strlen("date"), Object::DynamicProperty::Date, "QDate", strlen("QDate") }, { "date", strlen("date"), Object::DynamicProperty::DateTime, "QDateTime", strlen("QDateTime") }, - { "variant", strlen("variant"), Object::DynamicProperty::Variant, "QVariant", strlen("QVariant") } + { "variant", strlen("variant"), Object::DynamicProperty::Variant, "QVariant", strlen("QVariant") }, + { "var", strlen("var"), Object::DynamicProperty::Var, "QVariant", strlen("QVariant") } }; static const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) / sizeof(propTypeNameToTypes[0]); diff --git a/src/declarative/qml/qdeclarativescript_p.h b/src/declarative/qml/qdeclarativescript_p.h index e270241..c5b9a7e 100644 --- a/src/declarative/qml/qdeclarativescript_p.h +++ b/src/declarative/qml/qdeclarativescript_p.h @@ -385,8 +385,8 @@ public: { DynamicProperty(); - enum Type { Variant, Int, Bool, Real, String, Url, Color, Time, - Date, DateTime, Alias, Custom, CustomList }; + enum Type { Var, Variant, Int, Bool, Real, String, Url, Color, + Time, Date, DateTime, Alias, Custom, CustomList }; bool isDefaultProperty; Type type; diff --git a/src/declarative/qml/qdeclarativevme.cpp b/src/declarative/qml/qdeclarativevme.cpp index 8cc8fa0..8f34fb5 100644 --- a/src/declarative/qml/qdeclarativevme.cpp +++ b/src/declarative/qml/qdeclarativevme.cpp @@ -251,6 +251,10 @@ QObject *QDeclarativeVME::run(QList *errors, QDeclarativeEngine *engine = states.at(0).context->engine; QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine); + // Need a v8 handle scope and execution context for StoreVar instructions. + v8::HandleScope handleScope; + v8::Context::Scope contextScope(ep->v8engine()->context()); + int status = -1; // needed for dbus QDeclarativePropertyPrivate::WriteFlags flags = QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::RemoveBindingOnAliasWrite; @@ -542,6 +546,41 @@ QObject *QDeclarativeVME::run(QList *errors, instr.propertyIndex, a); QML_END_INSTR(StoreVariantBool) + QML_BEGIN_INSTR(StoreVar) + QObject *target = objects.top(); + CLEAN_PROPERTY(target, instr.propertyIndex); + + // Note that we don't use QDeclarativeStringConverters::variantFromString() here, which + // means that automatic generation of value types from strings doesn't occur. + // This is a deliberate behaviour difference to variant properties. + v8::Handle v8Value = ep->v8engine()->fromVariant(PRIMITIVES.at(instr.value)); + static_cast(const_cast(target->metaObject()))->setVMEProperty(instr.propertyIndex, v8Value); + QML_END_INSTR(StoreVar) + + QML_BEGIN_INSTR(StoreVarInteger) + QObject *target = objects.top(); + CLEAN_PROPERTY(target, instr.propertyIndex); + + v8::Handle v8Value = v8::Integer::New(instr.value); + static_cast(const_cast(target->metaObject()))->setVMEProperty(instr.propertyIndex, v8Value); + QML_END_INSTR(StoreVarInteger) + + QML_BEGIN_INSTR(StoreVarDouble) + QObject *target = objects.top(); + CLEAN_PROPERTY(target, instr.propertyIndex); + + v8::Handle v8Value = v8::Number::New(instr.value); + static_cast(const_cast(target->metaObject()))->setVMEProperty(instr.propertyIndex, v8Value); + QML_END_INSTR(StoreVarDouble) + + QML_BEGIN_INSTR(StoreVarBool) + QObject *target = objects.top(); + CLEAN_PROPERTY(target, instr.propertyIndex); + + v8::Handle v8Value = v8::Boolean::New(instr.value); + static_cast(const_cast(target->metaObject()))->setVMEProperty(instr.propertyIndex, v8Value); + QML_END_INSTR(StoreVarBool) + QML_BEGIN_INSTR(StoreString) QObject *target = objects.top(); CLEAN_PROPERTY(target, instr.propertyIndex); @@ -974,6 +1013,15 @@ QObject *QDeclarativeVME::run(QList *errors, instr.propertyIndex, a); QML_END_INSTR(StoreVariantObject) + QML_BEGIN_INSTR(StoreVarObject) + QObject *assign = objects.pop(); + QObject *target = objects.top(); + CLEAN_PROPERTY(target, instr.propertyIndex); + + v8::Handle v8Value = ep->v8engine()->newQObject(assign); + static_cast(const_cast(target->metaObject()))->setVMEProperty(instr.propertyIndex, v8Value); + QML_END_INSTR(StoreVarObject) + QML_BEGIN_INSTR(StoreInterface) QObject *assign = objects.pop(); QObject *target = objects.top(); diff --git a/src/declarative/qml/qdeclarativevmemetaobject.cpp b/src/declarative/qml/qdeclarativevmemetaobject.cpp index bcd46f2..abf7349 100644 --- a/src/declarative/qml/qdeclarativevmemetaobject.cpp +++ b/src/declarative/qml/qdeclarativevmemetaobject.cpp @@ -382,8 +382,9 @@ QDeclarativeVMEMetaObject::QDeclarativeVMEMetaObject(QObject *obj, const QMetaObject *other, const QDeclarativeVMEMetaData *meta, QDeclarativeCompiledData *cdata) -: object(obj), compiledData(cdata), ctxt(QDeclarativeData::get(obj, true)->outerContext), - metaData(meta), data(0), v8methods(0), parent(0) +: QV8GCCallback::Node(GcPrologueCallback), object(obj), compiledData(cdata), + ctxt(QDeclarativeData::get(obj, true)->outerContext), metaData(meta), data(0), + firstVarPropertyIndex(-1), varPropertiesInitialized(false), v8methods(0), parent(0) { compiledData->addref(); @@ -398,19 +399,23 @@ QDeclarativeVMEMetaObject::QDeclarativeVMEMetaObject(QObject *obj, propOffset = QAbstractDynamicMetaObject::propertyOffset(); methodOffset = QAbstractDynamicMetaObject::methodOffset(); - data = new QDeclarativeVMEVariant[metaData->propertyCount]; + data = new QDeclarativeVMEVariant[metaData->propertyCount - metaData->varPropertyCount]; aConnected.resize(metaData->aliasCount); int list_type = qMetaTypeId >(); // ### Optimize - for (int ii = 0; ii < metaData->propertyCount; ++ii) { + for (int ii = 0; ii < metaData->propertyCount - metaData->varPropertyCount; ++ii) { int t = (metaData->propertyData() + ii)->propertyType; if (t == list_type) { listProperties.append(List(methodOffset + ii)); data[ii].setValue(listProperties.count() - 1); } } + + firstVarPropertyIndex = metaData->propertyCount - metaData->varPropertyCount; + if (metaData->varPropertyCount) + QV8GCCallback::addGcCallbackNode(this); } QDeclarativeVMEMetaObject::~QDeclarativeVMEMetaObject() @@ -422,6 +427,9 @@ QDeclarativeVMEMetaObject::~QDeclarativeVMEMetaObject() for (int ii = 0; v8methods && ii < metaData->methodCount; ++ii) { qPersistentDispose(v8methods[ii]); } + + if (metaData->varPropertyCount) + qPersistentDispose(varProperties); // if not weak, will not have been cleaned up by the callback. } int QDeclarativeVMEMetaObject::metaCall(QMetaObject::Call c, int _id, void **a) @@ -468,10 +476,29 @@ int QDeclarativeVMEMetaObject::metaCall(QMetaObject::Call c, int _id, void **a) if (t == -1) { - if (c == QMetaObject::ReadProperty) { - *reinterpret_cast(a[0]) = readVarPropertyAsVariant(id); - } else if (c == QMetaObject::WriteProperty) { - writeVarProperty(id, *reinterpret_cast(a[0])); + if (id >= firstVarPropertyIndex) { + // the context can be null if accessing var properties from cpp after re-parenting an item. + QDeclarativeEnginePrivate *ep = (ctxt == 0 || ctxt->engine == 0) ? 0 : QDeclarativeEnginePrivate::get(ctxt->engine); + QV8Engine *v8e = (ep == 0) ? 0 : ep->v8engine(); + if (v8e) { + v8::HandleScope handleScope; + v8::Context::Scope contextScope(v8e->context()); + if (c == QMetaObject::ReadProperty) { + *reinterpret_cast(a[0]) = readPropertyAsVariant(id); + } else if (c == QMetaObject::WriteProperty) { + writeProperty(id, *reinterpret_cast(a[0])); + } + } else if (c == QMetaObject::ReadProperty) { + // if the context was disposed, we just return an invalid variant from read. + *reinterpret_cast(a[0]) = QVariant(); + } + } else { + // don't need to set up v8 scope objects, since not accessing varProperties. + if (c == QMetaObject::ReadProperty) { + *reinterpret_cast(a[0]) = readPropertyAsVariant(id); + } else if (c == QMetaObject::WriteProperty) { + writeProperty(id, *reinterpret_cast(a[0])); + } } } else { @@ -716,54 +743,61 @@ v8::Handle QDeclarativeVMEMetaObject::method(int index) return v8methods[index]; } -#if 0 -QScriptValue QDeclarativeVMEMetaObject::readVarProperty(int id) +v8::Handle QDeclarativeVMEMetaObject::readVarProperty(int id) { - if (data[id].dataType() == qMetaTypeId()) - return data[id].asQJSValue(); - else if (data[id].dataType() == QMetaType::QObjectStar) - return QDeclarativeEnginePrivate::get(ctxt->engine)->objectClass->newQObject(data[id].asQObject()); - else - return QDeclarativeEnginePrivate::get(ctxt->engine)->scriptValueFromVariant(data[id].asQVariant()); + Q_ASSERT(id >= firstVarPropertyIndex); + + ensureVarPropertiesAllocated(); + return varProperties->Get(id - firstVarPropertyIndex); } -#endif -QVariant QDeclarativeVMEMetaObject::readVarPropertyAsVariant(int id) +QVariant QDeclarativeVMEMetaObject::readPropertyAsVariant(int id) { -#if 0 - if (data[id].dataType() == qMetaTypeId()) - return QDeclarativeEnginePrivate::get(ctxt->engine)->scriptValueToVariant(data[id].asQJSValue()); - else -#endif - if (data[id].dataType() == QMetaType::QObjectStar) - return QVariant::fromValue(data[id].asQObject()); - else - return data[id].asQVariant(); + if (id >= firstVarPropertyIndex) { + ensureVarPropertiesAllocated(); + return QDeclarativeEnginePrivate::get(ctxt->engine)->v8engine()->toVariant(varProperties->Get(id - firstVarPropertyIndex), -1); + } else { + if (data[id].dataType() == QMetaType::QObjectStar) { + return QVariant::fromValue(data[id].asQObject()); + } else { + return data[id].asQVariant(); + } + } } -#if 0 -void QDeclarativeVMEMetaObject::writeVarProperty(int id, const QScriptValue &value) +void QDeclarativeVMEMetaObject::writeVarProperty(int id, v8::Handle value) { - data[id].setValue(value); + Q_ASSERT(id >= firstVarPropertyIndex); + + ensureVarPropertiesAllocated(); + varProperties->Set(id - firstVarPropertyIndex, value); activate(object, methodOffset + id, 0); } -#endif -void QDeclarativeVMEMetaObject::writeVarProperty(int id, const QVariant &value) +void QDeclarativeVMEMetaObject::writeProperty(int id, const QVariant &value) { - bool needActivate = false; - if (value.userType() == QMetaType::QObjectStar) { - QObject *o = qvariant_cast(value); - needActivate = (data[id].dataType() != QMetaType::QObjectStar || data[id].asQObject() != o); - data[id].setValue(qvariant_cast(value)); + if (id >= firstVarPropertyIndex) { + ensureVarPropertiesAllocated(); + QVariant currentValue = readPropertyAsVariant(id); + varProperties->Set(id - firstVarPropertyIndex, QDeclarativeEnginePrivate::get(ctxt->engine)->v8engine()->fromVariant(value)); + if ((currentValue.userType() != value.userType() || currentValue != value)) + activate(object, methodOffset + id, 0); } else { - needActivate = (data[id].dataType() != qMetaTypeId() || - data[id].asQVariant().userType() != value.userType() || - data[id].asQVariant() != value); - data[id].setValue(value); + bool needActivate = false; + if (value.userType() == QMetaType::QObjectStar) { + QObject *o = qvariant_cast(value); + needActivate = (data[id].dataType() != QMetaType::QObjectStar || data[id].asQObject() != o); + data[id].setValue(qvariant_cast(value)); + } else { + needActivate = (data[id].dataType() != qMetaTypeId() || + data[id].asQVariant().userType() != value.userType() || + data[id].asQVariant() != value); + data[id].setValue(value); + } + + if (needActivate) + activate(object, methodOffset + id, 0); } - if (needActivate) - activate(object, methodOffset + id, 0); } void QDeclarativeVMEMetaObject::listChanged(int id) @@ -849,8 +883,7 @@ void QDeclarativeVMEMetaObject::setVmeMethod(int index, v8::Persistent QDeclarativeVMEMetaObject::vmeProperty(int index) { if (index < propOffset) { Q_ASSERT(parent); @@ -859,7 +892,7 @@ QScriptValue QDeclarativeVMEMetaObject::vmeProperty(int index) return readVarProperty(index - propOffset); } -void QDeclarativeVMEMetaObject::setVMEProperty(int index, const QScriptValue &v) +void QDeclarativeVMEMetaObject::setVMEProperty(int index, v8::Handle v) { if (index < propOffset) { Q_ASSERT(parent); @@ -867,7 +900,46 @@ void QDeclarativeVMEMetaObject::setVMEProperty(int index, const QScriptValue &v) } return writeVarProperty(index - propOffset, v); } -#endif + +void QDeclarativeVMEMetaObject::ensureVarPropertiesAllocated() +{ + if (!varPropertiesInitialized) + allocateVarPropertiesArray(); +} + +// see also: QV8GCCallback::garbageCollectorPrologueCallback() +void QDeclarativeVMEMetaObject::allocateVarPropertiesArray() +{ + v8::HandleScope handleScope; + v8::Context::Scope cs(QDeclarativeEnginePrivate::get(ctxt->engine)->v8engine()->context()); + varProperties = qPersistentNew(v8::Array::New(metaData->varPropertyCount)); + varProperties.MakeWeak(static_cast(this), VarPropertiesWeakReferenceCallback); + varPropertiesInitialized = true; +} + +/* + The "var" properties are stored in a v8::Array which will be strong persistent if the object has cpp-ownership + and the root QObject in the parent chain does not have JS-ownership. In the weak persistent handle case, + this callback will dispose the handle when the v8object which owns the lifetime of the var properties array + is cleared as a result of all other handles to that v8object being released. + See QV8GCCallback::garbageCollectorPrologueCallback() for more information. + */ +void QDeclarativeVMEMetaObject::VarPropertiesWeakReferenceCallback(v8::Persistent object, void* parameter) +{ + QDeclarativeVMEMetaObject *vmemo = static_cast(parameter); + Q_ASSERT(vmemo); + qPersistentDispose(object); + vmemo->varProperties.Clear(); +} + +void QDeclarativeVMEMetaObject::GcPrologueCallback(QV8GCCallback::Referencer *r, QV8GCCallback::Node *node) +{ + QDeclarativeVMEMetaObject *vmemo = static_cast(node); + Q_ASSERT(vmemo); + if (!vmemo->varPropertiesInitialized || vmemo->varProperties.IsEmpty()) + return; + r->addRelationship(vmemo->object, vmemo->varProperties); +} bool QDeclarativeVMEMetaObject::aliasTarget(int index, QObject **target, int *coreIndex, int *valueTypeIndex) const { diff --git a/src/declarative/qml/qdeclarativevmemetaobject_p.h b/src/declarative/qml/qdeclarativevmemetaobject_p.h index 991c79a..0c02b76 100644 --- a/src/declarative/qml/qdeclarativevmemetaobject_p.h +++ b/src/declarative/qml/qdeclarativevmemetaobject_p.h @@ -69,6 +69,8 @@ #include "private/qdeclarativecompiler_p.h" #include "private/qdeclarativecontext_p.h" +#include "private/qv8gccallback_p.h" + #include QT_BEGIN_NAMESPACE @@ -77,6 +79,7 @@ QT_BEGIN_NAMESPACE struct QDeclarativeVMEMetaData { + short varPropertyCount; short propertyCount; short aliasCount; short signalCount; @@ -131,9 +134,11 @@ struct QDeclarativeVMEMetaData } }; +class QV8QObjectWrapper; class QDeclarativeVMEVariant; class QDeclarativeRefCount; -class QDeclarativeVMEMetaObject : public QAbstractDynamicMetaObject +class Q_AUTOTEST_EXPORT QDeclarativeVMEMetaObject : public QAbstractDynamicMetaObject, + public QV8GCCallback::Node { public: QDeclarativeVMEMetaObject(QObject *obj, const QMetaObject *other, const QDeclarativeVMEMetaData *data, @@ -145,10 +150,8 @@ public: v8::Handle vmeMethod(int index); int vmeMethodLineNumber(int index); void setVmeMethod(int index, v8::Persistent); -#if 0 - QScriptValue vmeProperty(int index); - void setVMEProperty(int index, const QScriptValue &); -#endif + v8::Handle vmeProperty(int index); + void setVMEProperty(int index, v8::Handle v); void connectAliasSignal(int index); @@ -166,6 +169,14 @@ private: QDeclarativeVMEVariant *data; + v8::Persistent varProperties; + int firstVarPropertyIndex; + bool varPropertiesInitialized; + static void VarPropertiesWeakReferenceCallback(v8::Persistent object, void* parameter); + static void GcPrologueCallback(QV8GCCallback::Referencer *r, QV8GCCallback::Node *node); + inline void allocateVarPropertiesArray(); + inline void ensureVarPropertiesAllocated(); + void connectAlias(int aliasId); QBitArray aConnected; QBitArray aInterceptors; @@ -174,12 +185,10 @@ private: v8::Persistent *v8methods; v8::Handle method(int); -#if 0 - QScriptValue readVarProperty(int); - void writeVarProperty(int, const QScriptValue &); -#endif - QVariant readVarPropertyAsVariant(int); - void writeVarProperty(int, const QVariant &); + v8::Handle readVarProperty(int); + void writeVarProperty(int, v8::Handle); + QVariant readPropertyAsVariant(int); + void writeProperty(int, const QVariant &); QAbstractDynamicMetaObject *parent; @@ -196,6 +205,9 @@ private: static int list_count(QDeclarativeListProperty *); static QObject *list_at(QDeclarativeListProperty *, int); static void list_clear(QDeclarativeListProperty *); + + friend class QV8GCCallback; + friend class QV8QObjectWrapper; }; QT_END_NAMESPACE diff --git a/src/declarative/qml/v8/qv8engine.cpp b/src/declarative/qml/v8/qv8engine.cpp index 5259ab1..7012ae1 100644 --- a/src/declarative/qml/v8/qv8engine.cpp +++ b/src/declarative/qml/v8/qv8engine.cpp @@ -41,6 +41,7 @@ #include "qv8engine_p.h" +#include "qv8gccallback_p.h" #include "qv8contextwrapper_p.h" #include "qv8valuetypewrapper_p.h" #include "qv8gccallback_p.h" @@ -135,6 +136,8 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership) m_variantWrapper.init(this); m_valueTypeWrapper.init(this); + QV8GCCallback::registerGcPrologueCallback(); + { v8::Handle v = global()->Get(v8::String::New("Object"))->ToObject()->Get(v8::String::New("getOwnPropertyNames")); m_getOwnPropertyNames = qPersistentNew(v8::Handle::Cast(v)); diff --git a/src/declarative/qml/v8/qv8qobjectwrapper.cpp b/src/declarative/qml/v8/qv8qobjectwrapper.cpp index 9481bb5..0c0481f 100644 --- a/src/declarative/qml/v8/qv8qobjectwrapper.cpp +++ b/src/declarative/qml/v8/qv8qobjectwrapper.cpp @@ -522,6 +522,9 @@ v8::Handle QV8QObjectWrapper::GetProperty(QV8Engine *engine, QObject ep->capturedProperties << CapturedProperty(object, result->coreIndex, result->notifyIndex); } + if (result->isVMEProperty()) + return static_cast(const_cast(object->metaObject()))->vmeProperty(result->coreIndex); + if (result->isDirect()) { return LoadPropertyDirect(engine, object, *result); } else { @@ -589,6 +592,8 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QDeclarativ PROPERTY_STORE(double, double(value->ToNumber()->Value())); } else if (property->propType == QMetaType::QString && value->IsString()) { PROPERTY_STORE(QString, engine->toString(value->ToString())); + } else if (property->isVMEProperty()) { + static_cast(const_cast(object->metaObject()))->setVMEProperty(property->coreIndex, value); } else { QVariant v; if (property->isQList()) diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent.qml new file mode 100644 index 0000000..36c0254 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent.qml @@ -0,0 +1,23 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: first + property var vp: Item { + id: second + property var vp: Item { + id: third + property var vp: Item { + id: fourth + property var vp: Item { + id: fifth + property int fifthCanary: 5 + property var circ: third.vp + property MyScarceResourceObject srp; + srp: MyScarceResourceObject { id: scarceResourceProvider } + property variant memoryHog: scarceResourceProvider.newScarceResource() + } + } + } + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent2.qml new file mode 100644 index 0000000..6a49cb9 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent2.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +// Similar to PVCC.qml except that it has another var property +// It will have a different metaobject. +Item { + id: first + property var anotherVp: 6 + property var vp: Item { + id: second + property var vp: Item { + id: third + property var vp: Item { + id: fourth + property var vp: Item { + id: fifth + property int fifthCanary: 5 + property var circ: third.vp + property MyScarceResourceObject srp; + srp: MyScarceResourceObject { id: scarceResourceProvider } + property variant memoryHog2: scarceResourceProvider.newScarceResource() + } + } + } + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent3.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent3.qml new file mode 100644 index 0000000..a907250 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent3.qml @@ -0,0 +1,16 @@ +import QtQuick 2.0 + +Rectangle { + id: rectangle // will have JS ownership + objectName: "rectangle" + width: 10 + height: 10 + property var rectCanary: 5 + + Text { + id: text // will have Eventual-JS ownership + objectName: "text" + property var vp: rectangle + property var textCanary: 10 + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent4.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent4.qml new file mode 100644 index 0000000..9273a52 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent4.qml @@ -0,0 +1,28 @@ +import QtQuick 2.0 + +Rectangle { + id: rectangle // will have JS ownership + objectName: "rectangle" + width: 10 + height: 10 + property var rectCanary: 5 + + Text { + id: text // will have Eventual-JS ownership + objectName: "text" + property var vp + property var textCanary: 10 + + // The varProperties array of "text" is weak + // (due to eventual JS ownership since parent is JS owned) + // but nonetheless, the reference to the created QObject + // should cause that QObject to NOT be collected. + function constructQObject() { + var component = Qt.createComponent("PropertyVarCircularComponent5.qml"); + if (component.status == Component.Ready) { + text.vp = component.createObject(null); // has JavaScript ownership + } + gc(); + } + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent5.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent5.qml new file mode 100644 index 0000000..94ef338 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent5.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Image { + id: image + objectName: "image" + property var imageCanary: 13 +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarInheritanceComponent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarInheritanceComponent.qml new file mode 100644 index 0000000..b01cf6e --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarInheritanceComponent.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +PropertyVarCircularComponent { + id: inheritanceComponent + property int inheritanceIntProperty: 6 + property var inheritanceVarProperty + + function constructGarbage() { + var retn = 1; + var component = Qt.createComponent("PropertyVarCircularComponent2.qml"); + if (component.status == Component.Ready) { + retn = component.createObject(null); // has JavaScript ownership + } + return retn; + } + + Component.onCompleted: { + inheritanceVarProperty = constructGarbage(); + gc(); + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarOwnershipComponent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarOwnershipComponent.qml new file mode 100644 index 0000000..c1f73d3 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarOwnershipComponent.qml @@ -0,0 +1,37 @@ +import QtQuick 2.0 + +Rectangle { + id: rectangle // will have JS ownership + objectName: "rectangle" + width: 10 + height: 10 + property var rectCanary: 5 + + Text { + id: textOne // will have Eventual-JS ownership + objectName: "textOne" + property var textCanary: 11 + property var vp + } + + Text { + id: textTwo + objectName: "textTwo" + property var textCanary: 12 + property var vp + + function constructQObject() { + var component = Qt.createComponent("PropertyVarCircularComponent5.qml"); + if (component.status == Component.Ready) { + textTwo.vp = component.createObject(null); // has JavaScript ownership + } + gc(); + } + + function deassignVp() { + textTwo.textCanary = 22; + textTwo.vp = textTwo.textCanary; + gc(); + } + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml index 9c27653..8a06c30 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml +++ b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.1.qml @@ -18,8 +18,4 @@ Item { // NOTE: manually add reference from first to second // in unit test prior reparenting and gc. } - - function performGc() { - gc(); - } } diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml index dc19626..91edc44 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml +++ b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.handle.2.qml @@ -19,8 +19,4 @@ Item { // note: must manually reparent in unit test // after setting the handle references. } - - function performGc() { - gc(); - } } diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml index 4fd1311..70e8390 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml +++ b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.1.qml @@ -24,8 +24,4 @@ Item { first = cro; second = cro; } - - function performGc() { - gc(); - } } diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml index 3f8415c..2ddb925 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml +++ b/tests/auto/declarative/qdeclarativeecmascript/data/handleReferenceManagement.object.2.qml @@ -25,8 +25,4 @@ Item { first = cro; second = cro; } - - function performGc() { - gc(); - } } diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.1.qml new file mode 100644 index 0000000..219e61b --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.1.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + id: root + property bool test: false + + property var car: new vehicle(4); + property int wheelCount: car.wheels + + function vehicle(wheels) { + this.wheels = wheels; + } + + Component.onCompleted: { + car.wheels = 6; // not bindable, wheelCount shouldn't update + + if (car.wheels != 6) return; + if (wheelCount != 4) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.2.qml new file mode 100644 index 0000000..2ac4807 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.2.qml @@ -0,0 +1,24 @@ +import QtQuick 2.0 + +Item { + id: root + property bool test: false + + property var truck: new vehicle(8); + property int wheelCount: truck.wheels + + function vehicle(wheels) { + this.wheels = wheels; + } + + Component.onCompleted: { + if (wheelCount != 8) return; + + // not bindable, but wheelCount will update because truck itself changed. + truck = new vehicle(12); + + if (wheelCount != 12) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.3.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.3.qml new file mode 100644 index 0000000..cf6a651 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.3.qml @@ -0,0 +1,19 @@ +import QtQuick 2.0 + +Item { + id: root + property bool test: false + + property var jsint: 4 + property int bound: jsint + 5 + + Component.onCompleted: { + if (bound != 9) return; + + jsint = jsint + 1; + + if (bound != 10) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.4.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.4.qml new file mode 100644 index 0000000..82fc225 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.4.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + property bool test: false + + property var items: [1, 2, 3, "four", "five"] + property int bound: items[0] + + Component.onCompleted: { + if (bound != 1) return; + + items[0] = 10 // bound should remain 1 + + if (bound != 1) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.5.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.5.qml new file mode 100644 index 0000000..a5c7812 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.5.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + property bool test: false + + property var attributes: { 'color': 'red', 'width': 100 } + property int bound: attributes.width + + Component.onCompleted: { + if (bound != 100) return; + + attributes.width = 200 // bound should remain 100 + + if (bound != 100) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.6.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.6.qml new file mode 100644 index 0000000..5197aea --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.6.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + property bool test: false + + property var items: [1, 2, 3, "four", "five"] + property int bound: items[0] + + Component.onCompleted: { + if (bound != 1) return false; + + items = [10, 2, 3, "four", "five"] // bound should now be 10 + + if (bound != 10) return false; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.7.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.7.qml new file mode 100644 index 0000000..1d6c8c0 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.7.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 + +Item { + property bool test: false + + property var attributes: { 'color': 'red', 'width': 100 } + property int bound: attributes.width + + Component.onCompleted: { + if (bound != 100) return; + + attributes = { 'color': 'blue', 'width': 200 } // bound should now be 200 + + if (bound != 200) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.8.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.8.qml new file mode 100644 index 0000000..a9f73db --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.8.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 + +Item { + property bool test: false + + property var literalValue: 6 + + Component.onCompleted: { + if (literalValue != 6) return; + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.9.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.9.qml new file mode 100644 index 0000000..f5aca28 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.9.qml @@ -0,0 +1,19 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + property bool test: false + + MyQmlObject { + id: qmlobject + intProperty: 5 + } + property var qobjectVar: qmlobject + property int bound: qobjectVar.intProperty + + Component.onCompleted: { + if (bound != 5) return; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.circular.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.circular.2.qml new file mode 100644 index 0000000..93c44af --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.circular.2.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 + +Item { + id: root + objectName: "separateRootObject" + property var vp + + function constructGarbage() { + var retn = 1; + var component = Qt.createComponent("PropertyVarCircularComponent3.qml"); + if (component.status == Component.Ready) { + retn = component.createObject(null); // has JavaScript ownership + } + return retn; + } + + function assignCircular() { + vp = constructGarbage(); + gc(); + } + + function deassignCircular() { + vp = 2; + gc(); + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.circular.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.circular.qml new file mode 100644 index 0000000..171d774 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.circular.qml @@ -0,0 +1,44 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: testCircular + + property var varProperty + property variant canaryResource + property int canaryInt + + function constructGarbage() { + var retn = 1; + var component = Qt.createComponent("PropertyVarCircularComponent.qml"); + if (component.status == Component.Ready) { + retn = component.createObject(null); // has JavaScript ownership + } + return retn; + } + + function deassignCanaryResource() { + canaryResource = 1; + gc(); + } + + function assignCircular() { + varProperty = constructGarbage(); + canaryResource = varProperty.vp.vp.vp.vp.memoryHog; + canaryInt = varProperty.vp.vp.vp.vp.fifthCanary; // == 5 + gc(); + } + + function deassignCircular() { + canaryInt = 2; + varProperty = 2; + gc(); + } + + function assignThenDeassign() { + varProperty = constructGarbage(); + varProperty = 2; + gc(); + } +} + diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.inherit.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.inherit.qml new file mode 100644 index 0000000..abd0dd7 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.inherit.qml @@ -0,0 +1,34 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: testInheritance + + property var varProperty + + function constructGarbage() { + var retn = 1; + var component = Qt.createComponent("PropertyVarInheritanceComponent.qml"); + if (component.status == Component.Ready) { + retn = component.createObject(null); // has JavaScript ownership + } + return retn; + } + + function assignCircular() { + varProperty = constructGarbage(); + gc(); + } + + function deassignCircular() { + varProperty = 2; + gc(); + } + + function assignThenDeassign() { + varProperty = constructGarbage(); + varProperty = 2; + gc(); + } +} + diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.reparent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.reparent.qml new file mode 100644 index 0000000..7b3df67 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVar.reparent.qml @@ -0,0 +1,27 @@ +import QtQuick 2.0 + +Item { + id: root + objectName: "separateRootObject" + property var vp + + function constructGarbage() { + var retn = 1; + var component = Qt.createComponent("PropertyVarOwnershipComponent.qml"); + if (component.status == Component.Ready) { + retn = component.createObject(null); // has JavaScript ownership + } + return retn; + } + + function assignVarProp() { + vp = constructGarbage(); + gc(); + } + + function deassignVarProp() { + vp = 2; + gc(); + } +} + diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarCpp.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarCpp.qml new file mode 100644 index 0000000..cd3147f --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarCpp.qml @@ -0,0 +1,17 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: testOwnership + property int intProperty: 10 + property var varProperty: intProperty + property var varProperty2: false + property var varBound: varProperty + 5 + property int intBound: varProperty + 5 + property var jsobject: new vehicle(4) + + function vehicle(wheels) { + this.wheels = wheels; + } +} + diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarImplicitOwnership.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarImplicitOwnership.qml new file mode 100644 index 0000000..9cebded --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarImplicitOwnership.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 + +Item { + id: root + objectName: "separateRootObject" + property var vp + + function constructGarbage() { + var retn = 1; + var component = Qt.createComponent("PropertyVarCircularComponent4.qml"); + if (component.status == Component.Ready) { + retn = component.createObject(null); // has JavaScript ownership + } + return retn; + } + + function assignCircular() { + vp = constructGarbage(); + gc(); + } + + function deassignCircular() { + vp = 2; + gc(); + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.2.qml new file mode 100644 index 0000000..14d4f9f --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.2.qml @@ -0,0 +1,24 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: testOwnership + property bool test: false + + property int dummyProperty // Tests for non-interference of other properties + property var varProperty + + function runTest() { + if (varProperty != undefined) return; + varProperty = { a: 10, b: 11 } + if (varProperty.a != 10) return; + + gc(); // Shouldn't collect + + if (varProperty.a != 10) return; + + test = true; + } +} + + diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.3.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.3.qml new file mode 100644 index 0000000..d5b449c --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.3.qml @@ -0,0 +1,31 @@ +import QtQuick 2.0 + +Item { + property var object + + property bool test1: false + property bool test2: false + + // Test methods are executed in sequential order + + function runTest() { + var c = Qt.createComponent("propertyVarOwnership.3.type.qml"); + object = c.createObject(); + + if (object.dummy != 10) return; + test1 = true; + } + + // Run gc() from C++ + + function runTest2() { + if (object.dummy != 10) return; + + object = undefined; + if (object != undefined) return; + + test2 = true; + } + + // Run gc() from C++ - QObject should be collected +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.3.type.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.3.type.qml new file mode 100644 index 0000000..3406553 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.3.type.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +QtObject { + property int dummy: 10 +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.qml new file mode 100644 index 0000000..1eba36c --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.qml @@ -0,0 +1,25 @@ +import QtQuick 2.0 + +Item { + id: root + + property var object + + property bool test: false + + Component.onCompleted: { + var c = Qt.createComponent("propertyVarOwnership.4.type1.qml"); + object = c.createObject(); + + if (object.dummy != 10) return; + if (object.test != true) return; + + object.creatorRef = root; + + test = true; + } + + function runTest() { + object = null; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.type1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.type1.qml new file mode 100644 index 0000000..9a29b6e --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.type1.qml @@ -0,0 +1,23 @@ +import QtQuick 2.0 + +// Has a self reference in selfRef, and a reference to propertyVarOwnership.4.qml in creatorRef +Item { + id: root + + property var creatorRef + property var selfRef + property var object + + property int dummy: 10 + property bool test: false + + Component.onCompleted: { + selfRef = root; + + var c = Qt.createComponent("propertyVarOwnership.4.type2.qml"); + object = c.createObject(); + object.creatorRef = root; + + test = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.type2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.type2.qml new file mode 100644 index 0000000..f82b8a1 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.4.type2.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +// Has a reference to propertyVarOwnership.4.type1.qml in creatorRef +Item { + property var creatorRef +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.qml b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.qml new file mode 100644 index 0000000..7b99c4b --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/propertyVarOwnership.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 +import Qt.test 1.0 + +Item { + id: testOwnership + property bool test: false + + property var varProperty + + function runTest() { + if (varProperty != undefined) return; + varProperty = { a: 10, b: 11 } + if (varProperty.a != 10) return; + + gc(); // Shouldn't collect + + if (varProperty.a != 10) return; + + test = true; + } +} + diff --git a/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp b/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp index ac4cb30..5bd0287 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp @@ -164,6 +164,7 @@ void registerTypes() qmlRegisterType("Qt.test",1,1,"MyRevisionedClass"); // test scarce resource property binding post-evaluation optimisation + // and for testing memory usage in property var circular reference test qmlRegisterType("Qt.test", 1,0, "MyScarceResourceObject"); // Register the uncreatable base class diff --git a/tests/auto/declarative/qdeclarativeecmascript/testtypes.h b/tests/auto/declarative/qdeclarativeecmascript/testtypes.h index 3fd6eaf..5357a63 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/testtypes.h +++ b/tests/auto/declarative/qdeclarativeecmascript/testtypes.h @@ -98,9 +98,10 @@ class MyQmlObject : public QObject Q_PROPERTY(int resettableProperty READ resettableProperty WRITE setResettableProperty RESET resetProperty) Q_PROPERTY(QRegExp regExp READ regExp WRITE setRegExp) Q_PROPERTY(int nonscriptable READ nonscriptable WRITE setNonscriptable SCRIPTABLE false) + Q_PROPERTY(int intProperty READ intProperty WRITE setIntProperty NOTIFY intChanged) public: - MyQmlObject(): myinvokableObject(0), m_methodCalled(false), m_methodIntCalled(false), m_object(0), m_value(0), m_resetProperty(13) {} + MyQmlObject(): myinvokableObject(0), m_methodCalled(false), m_methodIntCalled(false), m_object(0), m_value(0), m_resetProperty(13), m_intProperty(0) {} enum MyEnum { EnumValue1 = 0, EnumValue2 = 1 }; enum MyEnum2 { EnumValue3 = 2, EnumValue4 = 3 }; @@ -161,6 +162,9 @@ public: int value; }; QVariant variant() const { return m_variant; } + + int intProperty() const { return m_intProperty; } + void setIntProperty(int i) { m_intProperty = i; emit intChanged(); } signals: void basicSignal(); @@ -171,6 +175,7 @@ signals: void thirdBasicSignal(); void signalWithUnknownType(const MyQmlObject::MyType &arg); void signalWithVariant(const QVariant &arg); + void intChanged(); public slots: void deleteMe() { delete this; } @@ -192,6 +197,7 @@ private: int m_resetProperty; QRegExp m_regExp; QVariant m_variant; + int m_intProperty; }; QML_DECLARE_TYPEINFO(MyQmlObject, QML_HAS_ATTACHED_PROPERTIES) @@ -928,12 +934,24 @@ public: bool scarceResourceIsDetached() const { return m_value.isDetached(); } + // this particular one returns a new one each time + // this means that every Scarce Resource Copy will + // consume resources (so that we can track disposal + // of v8 handles with circular references). + Q_INVOKABLE QPixmap newScarceResource() const + { + QPixmap retn(800, 600); + retn.fill(QColor(100, 110, 120, 45)); + return retn; + } + signals: void scarceResourceChanged(); private: QPixmap m_value; }; +QML_DECLARE_TYPE(ScarceResourceObject) class testQObjectApi : public QObject { diff --git a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp index 5739ec9..d6a2f0a 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp @@ -49,6 +49,8 @@ #include #include #include +#include +#include #include "testtypes.h" #include "testhttpserver.h" #include "../../../shared/util.h" @@ -151,6 +153,17 @@ private slots: void importScripts(); void scarceResources(); void propertyChangeSlots(); + void propertyVar_data(); + void propertyVar(); + void propertyVarCpp(); + void propertyVarOwnership(); + void propertyVarImplicitOwnership(); + void propertyVarReparent(); + void propertyVarReparentNullContext(); + void propertyVarCircular(); + void propertyVarCircular2(); + void propertyVarInheritance(); + void propertyVarInheritance2(); void elementAssign(); void objectPassThroughSignals(); void objectConversion(); @@ -205,6 +218,7 @@ private slots: void automaticSemicolon(); private: + static void propertyVarWeakRefCallback(v8::Persistent object, void* parameter); QDeclarativeEngine engine; }; @@ -3337,6 +3351,370 @@ void tst_qdeclarativeecmascript::propertyChangeSlots() delete object; } +void tst_qdeclarativeecmascript::propertyVar_data() +{ + QTest::addColumn("qmlFile"); + + // valid + QTest::newRow("non-bindable object subproperty changed") << TEST_FILE("propertyVar.1.qml"); + QTest::newRow("non-bindable object changed") << TEST_FILE("propertyVar.2.qml"); + QTest::newRow("primitive changed") << TEST_FILE("propertyVar.3.qml"); + QTest::newRow("javascript array modification") << TEST_FILE("propertyVar.4.qml"); + QTest::newRow("javascript map modification") << TEST_FILE("propertyVar.5.qml"); + QTest::newRow("javascript array assignment") << TEST_FILE("propertyVar.6.qml"); + QTest::newRow("javascript map assignment") << TEST_FILE("propertyVar.7.qml"); + QTest::newRow("literal property assignment") << TEST_FILE("propertyVar.8.qml"); + QTest::newRow("qobject property assignment") << TEST_FILE("propertyVar.9.qml"); +} + +void tst_qdeclarativeecmascript::propertyVar() +{ + QFETCH(QUrl, qmlFile); + + QDeclarativeComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + delete object; +} + +// Tests that we can write QVariant values to var properties from C++ +void tst_qdeclarativeecmascript::propertyVarCpp() +{ + QObject *object = 0; + + // ensure that writing to and reading from a var property from cpp works as required. + // Literal values stored in var properties can be read and written as QVariants + // of a specific type, whereas object values are read as QVariantMaps. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVarCpp.qml")); + object = component.create(); + QVERIFY(object != 0); + // assign int to property var that currently has int assigned + QVERIFY(object->setProperty("varProperty", QVariant::fromValue(10))); + QCOMPARE(object->property("varBound"), QVariant(15)); + QCOMPARE(object->property("intBound"), QVariant(15)); + QCOMPARE(object->property("varProperty").userType(), (int)QVariant::Int); + QCOMPARE(object->property("varBound").userType(), (int)QVariant::Int); + // assign string to property var that current has bool assigned + QCOMPARE(object->property("varProperty2").userType(), (int)QVariant::Bool); + QVERIFY(object->setProperty("varProperty2", QVariant(QLatin1String("randomString")))); + 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); + delete object; +} + +static void gc(QDeclarativeEngine &engine) +{ + engine.collectGarbage(); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); +} + +void tst_qdeclarativeecmascript::propertyVarOwnership() +{ + // Referenced JS objects are not collected + { + QDeclarativeComponent component(&engine, TEST_FILE("propertyVarOwnership.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QCOMPARE(object->property("test").toBool(), false); + QMetaObject::invokeMethod(object, "runTest"); + QCOMPARE(object->property("test").toBool(), true); + delete object; + } + // Referenced JS objects are not collected + { + QDeclarativeComponent component(&engine, TEST_FILE("propertyVarOwnership.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QCOMPARE(object->property("test").toBool(), false); + QMetaObject::invokeMethod(object, "runTest"); + QCOMPARE(object->property("test").toBool(), true); + delete object; + } + // Qt objects are not collected until they've been dereferenced + { + QDeclarativeComponent component(&engine, TEST_FILE("propertyVarOwnership.3.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test2").toBool(), false); + QCOMPARE(object->property("test2").toBool(), false); + + QMetaObject::invokeMethod(object, "runTest"); + QCOMPARE(object->property("test1").toBool(), true); + + QPointer referencedObject = object->property("object").value(); + QVERIFY(!referencedObject.isNull()); + gc(engine); + QVERIFY(!referencedObject.isNull()); + + QMetaObject::invokeMethod(object, "runTest2"); + QCOMPARE(object->property("test2").toBool(), true); + gc(engine); + QVERIFY(referencedObject.isNull()); + + delete object; + } + // Self reference does not prevent Qt object collection + { + QDeclarativeComponent component(&engine, TEST_FILE("propertyVarOwnership.4.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + QPointer referencedObject = object->property("object").value(); + QVERIFY(!referencedObject.isNull()); + gc(engine); + QVERIFY(!referencedObject.isNull()); + + QMetaObject::invokeMethod(object, "runTest"); + gc(engine); + QVERIFY(referencedObject.isNull()); + + delete object; + } +} + +void tst_qdeclarativeecmascript::propertyVarImplicitOwnership() +{ + // The childObject has a reference to a different QObject. We want to ensure + // that the different item will not be cleaned up until required. IE, the childObject + // has implicit ownership of the constructed QObject. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVarImplicitOwnership.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QObject *rootObject = object->property("vp").value(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild("text"); + QVERIFY(childObject != 0); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + QMetaObject::invokeMethod(childObject, "constructQObject"); // creates a reference to a constructed QObject. + QWeakPointer qobjectGuard(childObject->property("vp").value()); // get the pointer prior to processing deleteLater events. + QVERIFY(!qobjectGuard.isNull()); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(!qobjectGuard.isNull()); + QMetaObject::invokeMethod(object, "deassignCircular"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(qobjectGuard.isNull()); // should have been collected now. + delete object; +} + +void tst_qdeclarativeecmascript::propertyVarReparent() +{ + // ensure that nothing breaks if we re-parent objects + QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.reparent.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignVarProp"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QObject *rect = object->property("vp").value(); + QObject *text = rect->findChild("textOne"); + QObject *text2 = rect->findChild("textTwo"); + QWeakPointer rectGuard(rect); + QWeakPointer textGuard(text); + QWeakPointer text2Guard(text2); + QVERIFY(!rectGuard.isNull()); + QVERIFY(!textGuard.isNull()); + QVERIFY(!text2Guard.isNull()); + QCOMPARE(text->property("textCanary").toInt(), 11); + QCOMPARE(text2->property("textCanary").toInt(), 12); + // now construct an image which we will reparent. + QMetaObject::invokeMethod(text2, "constructQObject"); + QObject *image = text2->property("vp").value(); + QWeakPointer imageGuard(image); + QVERIFY(!imageGuard.isNull()); + QCOMPARE(image->property("imageCanary").toInt(), 13); + // now reparent the "Image" object (currently, it has JS ownership) + image->setParent(text); // shouldn't be collected after deassignVp now, since has a parent. + QMetaObject::invokeMethod(text2, "deassignVp"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QCOMPARE(text->property("textCanary").toInt(), 11); + QCOMPARE(text2->property("textCanary").toInt(), 22); + QVERIFY(!imageGuard.isNull()); // should still be alive. + QCOMPARE(image->property("imageCanary").toInt(), 13); // still able to access var properties + QMetaObject::invokeMethod(object, "deassignVarProp"); // now deassign the root-object's vp, causing gc of rect+text+text2 + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(imageGuard.isNull()); // should now have been deleted, due to parent being deleted. + delete object; +} + +void tst_qdeclarativeecmascript::propertyVarReparentNullContext() +{ + // sometimes reparenting can cause problems + // (eg, if the ctxt is collected, varproperties are no longer available) + // this test ensures that no crash occurs in that situation. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.reparent.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignVarProp"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QObject *rect = object->property("vp").value(); + QObject *text = rect->findChild("textOne"); + QObject *text2 = rect->findChild("textTwo"); + QWeakPointer rectGuard(rect); + QWeakPointer textGuard(text); + QWeakPointer text2Guard(text2); + QVERIFY(!rectGuard.isNull()); + QVERIFY(!textGuard.isNull()); + QVERIFY(!text2Guard.isNull()); + QCOMPARE(text->property("textCanary").toInt(), 11); + QCOMPARE(text2->property("textCanary").toInt(), 12); + // now construct an image which we will reparent. + QMetaObject::invokeMethod(text2, "constructQObject"); + QObject *image = text2->property("vp").value(); + QWeakPointer imageGuard(image); + QVERIFY(!imageGuard.isNull()); + QCOMPARE(image->property("imageCanary").toInt(), 13); + // now reparent the "Image" object (currently, it has JS ownership) + image->setParent(object); // reparented to base object. after deassignVarProp, the ctxt will be invalid. + QMetaObject::invokeMethod(object, "deassignVarProp"); // now deassign the root-object's vp, causing gc of rect+text+text2 + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(!imageGuard.isNull()); // should still be alive. + QVERIFY(!image->property("imageCanary").isValid()); // but varProperties won't be available (null context). + delete object; + QVERIFY(imageGuard.isNull()); // should now be dead. +} + +void tst_qdeclarativeecmascript::propertyVarCircular() +{ + // enforce behaviour regarding circular references - ensure qdvmemo deletion. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.circular.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); // cause assignment and gc + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QCOMPARE(object->property("canaryInt"), QVariant(5)); + QVariant canaryResourceVariant = object->property("canaryResource"); + QVERIFY(canaryResourceVariant.isValid()); + QPixmap canaryResourcePixmap = canaryResourceVariant.value(); + canaryResourceVariant = QVariant(); // invalidate it to remove one copy of the pixmap from memory. + QMetaObject::invokeMethod(object, "deassignCanaryResource"); // remove one copy of the pixmap from memory + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(!canaryResourcePixmap.isDetached()); // two copies extant - this and the propertyVar.vp.vp.vp.vp.memoryHog. + QMetaObject::invokeMethod(object, "deassignCircular"); // cause deassignment and gc + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QCOMPARE(object->property("canaryInt"), QVariant(2)); + QCOMPARE(object->property("canaryResource"), QVariant(1)); + QVERIFY(canaryResourcePixmap.isDetached()); // now detached, since orig copy was member of qdvmemo which was deleted. + delete object; +} + +void tst_qdeclarativeecmascript::propertyVarCircular2() +{ + // track deletion of JS-owned parent item with Cpp-owned child + // where the child has a var property referencing its parent. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.circular.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QObject *rootObject = object->property("vp").value(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild("text"); + QVERIFY(childObject != 0); + QWeakPointer rootObjectTracker(rootObject); + QVERIFY(!rootObjectTracker.isNull()); + QWeakPointer childObjectTracker(childObject); + QVERIFY(!childObjectTracker.isNull()); + gc(engine); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + QMetaObject::invokeMethod(object, "deassignCircular"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(rootObjectTracker.isNull()); // should have been collected + QVERIFY(childObjectTracker.isNull()); // should have been collected + delete object; +} + +void tst_qdeclarativeecmascript::propertyVarWeakRefCallback(v8::Persistent object, void* parameter) +{ + *(int*)(parameter) += 1; + qPersistentDispose(object); +} + +void tst_qdeclarativeecmascript::propertyVarInheritance() +{ + int propertyVarWeakRefCallbackCount = 0; + + // enforce behaviour regarding element inheritance - ensure handle disposal. + // The particular component under test here has a chain of references. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.inherit.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); // cause assignment and gc + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + // we want to be able to track when the varProperties array of the last metaobject is disposed + QObject *cco5 = object->property("varProperty").value()->property("vp").value()->property("vp").value()->property("vp").value()->property("vp").value(); + QObject *ico5 = object->property("varProperty").value()->property("inheritanceVarProperty").value()->property("vp").value()->property("vp").value()->property("vp").value()->property("vp").value(); + QDeclarativeVMEMetaObject *icovmemo = ((QDeclarativeVMEMetaObject *)(ico5->metaObject())); + QDeclarativeVMEMetaObject *ccovmemo = ((QDeclarativeVMEMetaObject *)(cco5->metaObject())); + v8::Persistent icoCanaryHandle; + v8::Persistent ccoCanaryHandle; + { + v8::HandleScope hs; + // XXX NOTE: this is very implementation dependent. QDVMEMO->vmeProperty() is the only + // public function which can return us a handle to something in the varProperties array. + icoCanaryHandle = qPersistentNew(icovmemo->vmeProperty(41)); + ccoCanaryHandle = qPersistentNew(ccovmemo->vmeProperty(41)); + // we make them weak and invoke the gc, but we should not hit the weak-callback yet + // as the varproperties array of each vmemo still references the resource. + icoCanaryHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback); + ccoCanaryHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback); + gc(engine); + QVERIFY(propertyVarWeakRefCallbackCount == 0); + } + // now we deassign the var prop, which should trigger collection of item subtrees. + QMetaObject::invokeMethod(object, "deassignCircular"); // cause deassignment and gc + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + // ensure that there are only weak handles to the underlying varProperties array remaining. + gc(engine); + QCOMPARE(propertyVarWeakRefCallbackCount, 2); // should have been called for both, since all refs should be weak. + delete object; + // since there are no parent vmemo's to keep implicit references alive, and the only handles + // to what remains are weak, all varProperties arrays must have been collected. +} + +void tst_qdeclarativeecmascript::propertyVarInheritance2() +{ + int propertyVarWeakRefCallbackCount = 0; + + // The particular component under test here does NOT have a chain of references; the + // only link between rootObject and childObject is that rootObject is the parent of childObject. + QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.circular.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QObject *rootObject = object->property("vp").value(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild("text"); + QVERIFY(childObject != 0); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + v8::Persistent childObjectVarArrayValueHandle; + { + v8::HandleScope hs; + propertyVarWeakRefCallbackCount = 0; // reset callback count. + childObjectVarArrayValueHandle = qPersistentNew(((QDeclarativeVMEMetaObject *)(childObject->metaObject()))->vmeProperty(58)); + childObjectVarArrayValueHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback); + gc(engine); + QVERIFY(propertyVarWeakRefCallbackCount == 0); // should not have been collected yet. + QCOMPARE(childObject->property("textCanary").toInt(), 10); + } + QMetaObject::invokeMethod(object, "deassignCircular"); + QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper. + QVERIFY(propertyVarWeakRefCallbackCount == 1); // should have been collected now. + delete object; +} + // Ensure that QObject type conversion works on binding assignment void tst_qdeclarativeecmascript::elementAssign() { @@ -3412,8 +3790,7 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() CircularReferenceObject *cro = object->findChild("cro"); cro->setDtorCount(&dtorCount); QMetaObject::invokeMethod(object, "createReference"); - QMetaObject::invokeMethod(object, "performGc"); - QCoreApplication::processEvents(QEventLoop::DeferredDeletion); + gc(engine); QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference delete object; hrmEngine.collectGarbage(); @@ -3431,8 +3808,7 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() CircularReferenceObject *cro = object->findChild("cro"); cro->setDtorCount(&dtorCount); QMetaObject::invokeMethod(object, "circularReference"); - QMetaObject::invokeMethod(object, "performGc"); - QCoreApplication::processEvents(QEventLoop::DeferredDeletion); + gc(engine); QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive. delete object; hrmEngine.collectGarbage(); @@ -3459,8 +3835,7 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() // now we have to reparent second and make second owned by JS. second->setParent(0); QDeclarativeEngine::setObjectOwnership(second, QDeclarativeEngine::JavaScriptOwnership); - QMetaObject::invokeMethod(object, "performGc"); - QCoreApplication::processEvents(QEventLoop::DeferredDeletion); + gc(engine); QCOMPARE(dtorCount, 0); // due to reference from first to second, second shouldn't be collected. delete object; hrmEngine.collectGarbage(); @@ -3490,8 +3865,7 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() second->setParent(0); QDeclarativeEngine::setObjectOwnership(first, QDeclarativeEngine::JavaScriptOwnership); QDeclarativeEngine::setObjectOwnership(second, QDeclarativeEngine::JavaScriptOwnership); - QMetaObject::invokeMethod(object, "performGc"); - QCoreApplication::processEvents(QEventLoop::DeferredDeletion); + gc(engine); QCOMPARE(dtorCount, 2); // despite circular references, both will be collected. delete object; hrmEngine.collectGarbage(); @@ -3530,8 +3904,7 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() // now we have to reparent second2 and make second2 owned by JS. second2->setParent(0); QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership); - QMetaObject::invokeMethod(object1, "performGc"); - QMetaObject::invokeMethod(object2, "performGc"); + gc(engine); QCoreApplication::processEvents(QEventLoop::DeferredDeletion); QCOMPARE(dtorCount, 0); // due to reference from first1 to second2, second2 shouldn't be collected. delete object1; @@ -3582,8 +3955,7 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() QDeclarativeEngine::setObjectOwnership(second1, QDeclarativeEngine::JavaScriptOwnership); QDeclarativeEngine::setObjectOwnership(first2, QDeclarativeEngine::JavaScriptOwnership); QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership); - QMetaObject::invokeMethod(object1, "performGc"); - QMetaObject::invokeMethod(object2, "performGc"); + gc(engine); QCoreApplication::processEvents(QEventLoop::DeferredDeletion); QCOMPARE(dtorCount, 4); // circular references shouldn't keep them alive. delete object1; @@ -3632,13 +4004,10 @@ void tst_qdeclarativeecmascript::handleReferenceManagement() QDeclarativeEngine::setObjectOwnership(second1, QDeclarativeEngine::JavaScriptOwnership); QDeclarativeEngine::setObjectOwnership(first2, QDeclarativeEngine::JavaScriptOwnership); QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership); - QMetaObject::invokeMethod(object1, "performGc"); - QMetaObject::invokeMethod(object2, "performGc"); - QCoreApplication::processEvents(QEventLoop::DeferredDeletion); + gc(engine); QCOMPARE(dtorCount, 0); delete hrmEngine2; - QMetaObject::invokeMethod(object1, "performGc"); - QCoreApplication::processEvents(QEventLoop::DeferredDeletion); + gc(engine); QCOMPARE(dtorCount, 0); delete object1; delete object2; diff --git a/tests/auto/declarative/qdeclarativelanguage/data/assignLiteralToVar.qml b/tests/auto/declarative/qdeclarativelanguage/data/assignLiteralToVar.qml new file mode 100644 index 0000000..65826dc --- /dev/null +++ b/tests/auto/declarative/qdeclarativelanguage/data/assignLiteralToVar.qml @@ -0,0 +1,32 @@ +// This tests assigning literals to "var" properties. +// These properties store JavaScript object references. + +import QtQuick 1.0 + +QtObject { + property var test1: 1 + property var test2: 1.7 + property var test3: "Hello world!" + property var test4: "#FF008800" + property var test5: "10,10,10x10" + property var test6: "10,10" + property var test7: "10x10" + property var test8: "100,100,100" + property var test9: String("#FF008800") + property var test10: true + property var test11: false + + property variant variantTest1Bound: test1 + 4 // 1 + 4 + 4 = 9 + + property var test12: Qt.rgba(0.2, 0.3, 0.4, 0.5) + property var test13: Qt.rect(10, 10, 10, 10) + property var test14: Qt.point(10, 10) + property var test15: Qt.size(10, 10) + property var test16: Qt.vector3d(100, 100, 100) + + property var test1Bound: test1 + 6 // 1 + 4 + 6 = 11 + + Component.onCompleted: { + test1 = test1 + 4; + } +} diff --git a/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp b/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp index f7e74e8..a06be02 100644 --- a/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp +++ b/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp @@ -121,6 +121,7 @@ private slots: void assignTypeExtremes(); void assignCompositeToType(); void assignLiteralToVariant(); + void assignLiteralToVar(); void customParserTypes(); void rootAsQmlComponent(); void inlineQmlComponents(); @@ -648,6 +649,57 @@ void tst_qdeclarativelanguage::assignLiteralToVariant() delete object; } +// Test that literals are stored correctly in "var" properties +// Note that behaviour differs from "variant" properties in that +// no conversion from "special strings" to QVariants is performed. +void tst_qdeclarativelanguage::assignLiteralToVar() +{ + QDeclarativeComponent component(&engine, TEST_FILE("assignLiteralToVar.qml")); + VERIFY_ERRORS(0); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test1").userType(), (int)QMetaType::Int); + QCOMPARE(object->property("test2").userType(), (int)QMetaType::Double); + QCOMPARE(object->property("test3").userType(), (int)QVariant::String); + QCOMPARE(object->property("test4").userType(), (int)QVariant::String); + QCOMPARE(object->property("test5").userType(), (int)QVariant::String); + QCOMPARE(object->property("test6").userType(), (int)QVariant::String); + QCOMPARE(object->property("test7").userType(), (int)QVariant::String); + QCOMPARE(object->property("test8").userType(), (int)QVariant::String); + QCOMPARE(object->property("test9").userType(), (int)QVariant::String); + QCOMPARE(object->property("test10").userType(), (int)QVariant::Bool); + QCOMPARE(object->property("test11").userType(), (int)QVariant::Bool); + QCOMPARE(object->property("test12").userType(), (int)QVariant::Color); + QCOMPARE(object->property("test13").userType(), (int)QVariant::RectF); + QCOMPARE(object->property("test14").userType(), (int)QVariant::PointF); + QCOMPARE(object->property("test15").userType(), (int)QVariant::SizeF); + QCOMPARE(object->property("test16").userType(), (int)QVariant::Vector3D); + QCOMPARE(object->property("variantTest1Bound").userType(), (int)QMetaType::Int); + QCOMPARE(object->property("test1Bound").userType(), (int)QMetaType::Int); + + QCOMPARE(object->property("test1"), QVariant(5)); + QCOMPARE(object->property("test2"), QVariant((double)1.7)); + QCOMPARE(object->property("test3"), QVariant(QString(QLatin1String("Hello world!")))); + QCOMPARE(object->property("test4"), QVariant(QString(QLatin1String("#FF008800")))); + QCOMPARE(object->property("test5"), QVariant(QString(QLatin1String("10,10,10x10")))); + QCOMPARE(object->property("test6"), QVariant(QString(QLatin1String("10,10")))); + QCOMPARE(object->property("test7"), QVariant(QString(QLatin1String("10x10")))); + QCOMPARE(object->property("test8"), QVariant(QString(QLatin1String("100,100,100")))); + QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800")))); + QCOMPARE(object->property("test10"), QVariant(bool(true))); + QCOMPARE(object->property("test11"), QVariant(bool(false))); + QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5))); + QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10))); + QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10))); + QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10))); + QCOMPARE(object->property("test16"), QVariant(QVector3D(100, 100, 100))); + QCOMPARE(object->property("variantTest1Bound"), QVariant(9)); + QCOMPARE(object->property("test1Bound"), QVariant(11)); + + delete object; +} + // Tests that custom parser types can be instantiated void tst_qdeclarativelanguage::customParserTypes() {