diff options
author | Chris Adams <christopher.adams@nokia.com> | 2011-09-30 11:14:10 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2011-10-06 05:29:00 +0200 |
commit | 752cd2aca42f6625f1cfc364937e0d39828cf954 (patch) | |
tree | dcb8891d7ff0d99d7bcbf948ed6339c4cce6b257 /tests/auto/declarative | |
parent | 6bd1704c42f564980677682e1d47e91129d94e5c (diff) |
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 <aaron.kennedy@nokia.com>
Diffstat (limited to 'tests/auto/declarative')
38 files changed, 1127 insertions, 34 deletions
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/PropertyVarCircularComponent.qml new file mode 100644 index 0000000000..36c025401f --- /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 0000000000..6a49cb9317 --- /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 0000000000..a90725016e --- /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 0000000000..9273a52f54 --- /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 0000000000..94ef338792 --- /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 0000000000..b01cf6ed84 --- /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 0000000000..c1f73d3bac --- /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 9c27653b34..8a06c30d8c 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 dc196263b4..91edc447e2 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 4fd1311c29..70e8390677 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 3f8415ce0f..2ddb9253eb 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 0000000000..219e61bf91 --- /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 0000000000..2ac4807ec5 --- /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 0000000000..cf6a651639 --- /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 0000000000..82fc225e71 --- /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 0000000000..a5c7812289 --- /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 0000000000..5197aeada7 --- /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 0000000000..1d6c8c0a37 --- /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 0000000000..a9f73db402 --- /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 0000000000..f5aca28417 --- /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 0000000000..93c44afcc9 --- /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 0000000000..171d7747cd --- /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 0000000000..abd0dd7c04 --- /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 0000000000..7b3df674f1 --- /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 0000000000..cd3147f565 --- /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 0000000000..9cebded932 --- /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 0000000000..14d4f9fd27 --- /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 0000000000..d5b449c938 --- /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 0000000000..3406553b91 --- /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 0000000000..1eba36ce81 --- /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 0000000000..9a29b6e17f --- /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 0000000000..f82b8a1c1e --- /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 0000000000..7b99c4b6ad --- /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 ac4cb308d1..5bd0287c1b 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/testtypes.cpp @@ -164,6 +164,7 @@ void registerTypes() qmlRegisterType<MyRevisionedClass,1>("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<ScarceResourceObject>("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 3fd6eaf02d..5357a63678 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 5739ec9817..d6a2f0a5bd 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp @@ -49,6 +49,8 @@ #include <QtCore/qdir.h> #include <QtCore/qnumeric.h> #include <private/qdeclarativeengine_p.h> +#include <private/qv8gccallback_p.h> +#include <private/qdeclarativevmemetaobject_p.h> #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<v8::Value> object, void* parameter); QDeclarativeEngine engine; }; @@ -3337,6 +3351,370 @@ void tst_qdeclarativeecmascript::propertyChangeSlots() delete object; } +void tst_qdeclarativeecmascript::propertyVar_data() +{ + QTest::addColumn<QUrl>("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<QObject> referencedObject = object->property("object").value<QObject*>(); + 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<QObject> referencedObject = object->property("object").value<QObject*>(); + 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<QObject*>(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild<QObject*>("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<QObject> qobjectGuard(childObject->property("vp").value<QObject*>()); // 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*>(); + QObject *text = rect->findChild<QObject*>("textOne"); + QObject *text2 = rect->findChild<QObject*>("textTwo"); + QWeakPointer<QObject> rectGuard(rect); + QWeakPointer<QObject> textGuard(text); + QWeakPointer<QObject> 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<QObject*>(); + QWeakPointer<QObject> 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*>(); + QObject *text = rect->findChild<QObject*>("textOne"); + QObject *text2 = rect->findChild<QObject*>("textTwo"); + QWeakPointer<QObject> rectGuard(rect); + QWeakPointer<QObject> textGuard(text); + QWeakPointer<QObject> 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<QObject*>(); + QWeakPointer<QObject> 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<QPixmap>(); + 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<QObject*>(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild<QObject*>("text"); + QVERIFY(childObject != 0); + QWeakPointer<QObject> rootObjectTracker(rootObject); + QVERIFY(!rootObjectTracker.isNull()); + QWeakPointer<QObject> 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<v8::Value> 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<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>(); + QObject *ico5 = object->property("varProperty").value<QObject*>()->property("inheritanceVarProperty").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>(); + QDeclarativeVMEMetaObject *icovmemo = ((QDeclarativeVMEMetaObject *)(ico5->metaObject())); + QDeclarativeVMEMetaObject *ccovmemo = ((QDeclarativeVMEMetaObject *)(cco5->metaObject())); + v8::Persistent<v8::Value> icoCanaryHandle; + v8::Persistent<v8::Value> 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<QObject*>(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild<QObject*>("text"); + QVERIFY(childObject != 0); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + v8::Persistent<v8::Value> 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<CircularReferenceObject*>("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<CircularReferenceObject*>("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 0000000000..65826dcc87 --- /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 f7e74e80f7..a06be0250b 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() { |