diff options
30 files changed, 674 insertions, 35 deletions
diff --git a/.qmake.conf b/.qmake.conf index a0e2de0f37..703f7df915 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -4,4 +4,4 @@ CONFIG += warning_clean DEFINES += QT_NO_LINKED_LIST DEFINES += QT_NO_JAVA_STYLE_ITERATORS -MODULE_VERSION = 5.15.11 +MODULE_VERSION = 5.15.12 diff --git a/examples/quick/tutorials/dynamicview/dynamicview1/dynamicview.qml b/examples/quick/tutorials/dynamicview/dynamicview1/dynamicview.qml index 60167aa813..3db55a51a3 100644 --- a/examples/quick/tutorials/dynamicview/dynamicview1/dynamicview.qml +++ b/examples/quick/tutorials/dynamicview/dynamicview1/dynamicview.qml @@ -63,7 +63,11 @@ Rectangle { Rectangle { id: content - anchors { left: parent.left; right: parent.right } + anchors { + left: parent ? parent.left : undefined + right: parent ? parent.right : undefined + } + height: column.implicitHeight + 4 border.width: 1 diff --git a/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp b/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp index d54b7e9492..b5c5f6a2b0 100644 --- a/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp +++ b/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp @@ -225,7 +225,7 @@ void OSAllocator::commit(void* address, size_t bytes, bool writable, bool execut while (madvise(address, bytes, MADV_WILLNEED)) { if (errno != EAGAIN) - CRASH(); + break; // We don't have to crash here. MADV_WILLNEED is only advisory } #elif HAVE(MADV_FREE_REUSE) diff --git a/src/imports/layouts/qquickstacklayout.cpp b/src/imports/layouts/qquickstacklayout.cpp index 1d67d2058d..d127488b82 100644 --- a/src/imports/layouts/qquickstacklayout.cpp +++ b/src/imports/layouts/qquickstacklayout.cpp @@ -171,6 +171,7 @@ void QQuickStackLayout::itemChange(QQuickItem::ItemChange change, const QQuickIt QQuickLayout::itemChange(change, value); if (change == ItemChildRemovedChange) { + m_cachedItemSizeHints.remove(value.item); invalidate(); } else if (change == ItemChildAddedChange) { invalidate(); @@ -254,11 +255,12 @@ void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*alig void QQuickStackLayout::invalidate(QQuickItem *childItem) { - const int indexOfChild = indexOf(childItem); - if (indexOfChild >= 0 && indexOfChild < m_cachedItemSizeHints.count()) { - m_cachedItemSizeHints[indexOfChild].min() = QSizeF(); - m_cachedItemSizeHints[indexOfChild].pref() = QSizeF(); - m_cachedItemSizeHints[indexOfChild].max() = QSizeF(); + ensureLayoutItemsUpdated(); + if (childItem) { + SizeHints &hints = m_cachedItemSizeHints[childItem]; + hints.min() = QSizeF(); + hints.pref() = QSizeF(); + hints.max() = QSizeF(); } for (int i = 0; i < Qt::NSizeHints; ++i) @@ -284,7 +286,6 @@ void QQuickStackLayout::updateLayoutItems() if (count != d->count) { d->count = count; emit countChanged(); - m_cachedItemSizeHints.resize(count); } for (int i = 0; i < count; ++i) { QQuickItem *child = itemAt(i); @@ -293,10 +294,13 @@ void QQuickStackLayout::updateLayoutItems() } } -QQuickStackLayout::SizeHints &QQuickStackLayout::cachedItemSizeHints(int index) const { - SizeHints &hints = m_cachedItemSizeHints[index]; +QQuickStackLayout::SizeHints &QQuickStackLayout::cachedItemSizeHints(int index) const +{ + QQuickItem *item = itemAt(index); + Q_ASSERT(item); + SizeHints &hints = m_cachedItemSizeHints[item]; // will create an entry if it doesn't exist if (!hints.min().isValid()) - QQuickStackLayout::collectItemSizeHints(itemAt(index), hints.array); + QQuickStackLayout::collectItemSizeHints(item, hints.array); return hints; } diff --git a/src/imports/layouts/qquickstacklayout_p.h b/src/imports/layouts/qquickstacklayout_p.h index 5349adfa36..d8feecc11b 100644 --- a/src/imports/layouts/qquickstacklayout_p.h +++ b/src/imports/layouts/qquickstacklayout_p.h @@ -95,7 +95,7 @@ private: QSizeF array[Qt::NSizeHints]; } SizeHints; - mutable QVector<SizeHints> m_cachedItemSizeHints; + mutable QHash<QQuickItem*, SizeHints> m_cachedItemSizeHints; mutable QSizeF m_cachedSizeHints[Qt::NSizeHints]; SizeHints &cachedItemSizeHints(int index) const; }; diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index f5dca60559..0a564fc6d5 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1892,6 +1892,25 @@ int ExecutionEngine::maxGCStackSize() const return m_maxGCStackSize; } +/*! + \internal + Returns \a length converted to int if its safe to + pass to \c Scope::alloc. + Otherwise it throws a RangeError, and returns 0. + */ +int ExecutionEngine::safeForAllocLength(qint64 len64) +{ + if (len64 < 0ll || len64 > qint64(std::numeric_limits<int>::max())) { + this->throwRangeError(QStringLiteral("Invalid array length.")); + return 0; + } + if (len64 > qint64(this->jsStackLimit - this->jsStackTop)) { + this->throwRangeError(QStringLiteral("Array too large for apply().")); + return 0; + } + return len64; +} + ReturnedValue ExecutionEngine::global() { return globalObject->asReturnedValue(); diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index efe44a324c..dd6e6ed279 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -655,6 +655,7 @@ public: int maxGCStackSize() const; bool checkStackLimits(); + int safeForAllocLength(qint64 len64); bool canJIT(Function *f = nullptr) { @@ -723,6 +724,9 @@ public: QQmlRefPointer<ExecutableCompilationUnit> loadModule(const QUrl &_url, const ExecutableCompilationUnit *referrer = nullptr); private: + template<int Frames> + friend struct ExecutionEngineCallDepthRecorder; + #if QT_CONFIG(qml_debug) QScopedPointer<QV4::Debugging::Debugger> m_debugger; QScopedPointer<QV4::Profiling::Profiler> m_profiler; @@ -753,14 +757,17 @@ private: }; #define CHECK_STACK_LIMITS(v4) if ((v4)->checkStackLimits()) return Encode::undefined(); \ - ExecutionEngineCallDepthRecorder _executionEngineCallDepthRecorder(v4); + ExecutionEngineCallDepthRecorder<1> _executionEngineCallDepthRecorder(v4); +template<int Frames = 1> struct ExecutionEngineCallDepthRecorder { ExecutionEngine *ee; - ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) { ++ee->callDepth; } - ~ExecutionEngineCallDepthRecorder() { --ee->callDepth; } + ExecutionEngineCallDepthRecorder(ExecutionEngine *e): ee(e) { ee->callDepth += Frames; } + ~ExecutionEngineCallDepthRecorder() { ee->callDepth -= Frames; } + + bool hasOverflow() const { return ee->callDepth >= ExecutionEngine::maxCallDepth; } }; inline bool ExecutionEngine::checkStackLimits() diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index cdb3b8942b..8dafe251e0 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -364,15 +364,10 @@ ReturnedValue FunctionPrototype::method_apply(const QV4::FunctionObject *b, cons if (!arr) return v4->throwTypeError(); - const qint64 len64 = arr->getLength(); - if (len64 < 0ll || len64 > qint64(std::numeric_limits<int>::max())) - return v4->throwRangeError(QStringLiteral("Invalid array length.")); - if (len64 > qint64(v4->jsStackLimit - v4->jsStackTop)) - return v4->throwRangeError(QStringLiteral("Array too large for apply().")); - - const uint len = uint(len64); - Scope scope(v4); + const uint len = v4->safeForAllocLength(arr->getLength()); + CHECK_EXCEPTION(); + Value *arguments = scope.alloc<Scope::Uninitialized>(len); if (len) { if (ArgumentsObject::isNonStrictArgumentsObject(arr) && !arr->cast<ArgumentsObject>()->fullyCreated()) { diff --git a/src/qml/jsruntime/qv4reflect.cpp b/src/qml/jsruntime/qv4reflect.cpp index 2a6c61f044..39a0b03dec 100644 --- a/src/qml/jsruntime/qv4reflect.cpp +++ b/src/qml/jsruntime/qv4reflect.cpp @@ -76,7 +76,10 @@ struct CallArgs { static CallArgs createListFromArrayLike(Scope &scope, const Object *o) { - int len = o->getLength(); + int len = scope.engine->safeForAllocLength(o->getLength()); + if (scope.engine->hasException) + return {nullptr, 0}; + Value *arguments = scope.alloc(len); for (int i = 0; i < len; ++i) { diff --git a/src/qml/qml/qqmlincubator.cpp b/src/qml/qml/qqmlincubator.cpp index 244f9ad292..3cbd5e17d2 100644 --- a/src/qml/qml/qqmlincubator.cpp +++ b/src/qml/qml/qqmlincubator.cpp @@ -279,6 +279,20 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) // get a copy of the engine pointer as it might get reset; QQmlEnginePrivate *enginePriv = this->enginePriv; + // Incubating objects takes quite a bit more stack space than our usual V4 function + enum { EstimatedSizeInV4Frames = 2 }; + QV4::ExecutionEngineCallDepthRecorder<EstimatedSizeInV4Frames> callDepthRecorder( + compilationUnit->engine); + if (callDepthRecorder.hasOverflow()) { + QQmlError error; + error.setMessageType(QtCriticalMsg); + error.setUrl(compilationUnit->url()); + error.setDescription(QQmlComponent::tr("Maximum call stack size exceeded.")); + errors << error; + progress = QQmlIncubatorPrivate::Completed; + goto finishIncubate; + } + if (!vmeGuard.isOK()) { QQmlError error; error.setMessageType(QtInfoMsg); diff --git a/src/qml/qml/qqmlpropertycachecreator.cpp b/src/qml/qml/qqmlpropertycachecreator.cpp index 1579fd3336..de02636d95 100644 --- a/src/qml/qml/qqmlpropertycachecreator.cpp +++ b/src/qml/qml/qqmlpropertycachecreator.cpp @@ -115,8 +115,10 @@ bool QQmlBindingInstantiationContext::resolveInstantiatingProperty() return true; } + if (!referencingObjectPropertyCache) + return false; + Q_ASSERT(referencingObjectIndex >= 0); - Q_ASSERT(referencingObjectPropertyCache); Q_ASSERT(instantiatingBinding->propertyNameIndex != 0); bool notInRevision = false; diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index 7232f0bb2f..16cf2c8100 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -543,6 +543,9 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) The translation of the gesture \l centroid. It is \c (0, 0) when the gesture begins. + + \note On some touchpads, such as on a \macos trackpad, native gestures do + not generate any translation values, and this property stays at \c (0, 0). */ QT_END_NAMESPACE diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index f8ad168a17..29afaae93b 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -290,8 +290,6 @@ void QQuickItemView::setDelegate(QQmlComponent *delegate) if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) { int oldCount = dataModel->count(); dataModel->setDelegate(delegate); - if (isComponentComplete()) - d->applyDelegateChange(); if (oldCount != dataModel->count()) emit countChanged(); } diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 50b7a333c7..8ab69603ad 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -1494,7 +1494,11 @@ QQuickWindow::QQuickWindow(QQuickWindowPrivate &dd, QWindow *parent) } /*! - \internal + Constructs a window for displaying a QML scene, whose rendering will + be controlled by the \a control object. + Please refer to QQuickRenderControl's documentation for more information. + + \since 5.4 */ QQuickWindow::QQuickWindow(QQuickRenderControl *control) : QWindow(*(new QQuickWindowPrivate), nullptr) diff --git a/src/quick/scenegraph/coreapi/qsggeometry.h b/src/quick/scenegraph/coreapi/qsggeometry.h index d17915a842..685ac1cd95 100644 --- a/src/quick/scenegraph/coreapi/qsggeometry.h +++ b/src/quick/scenegraph/coreapi/qsggeometry.h @@ -202,6 +202,7 @@ public: void setLineWidth(float w); private: + Q_DISABLE_COPY_MOVE(QSGGeometry) friend class QSGGeometryData; int m_drawing_mode; diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp index 89c9ea4d5b..c35d5b0705 100644 --- a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp +++ b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp @@ -233,7 +233,10 @@ void QSGRhiTextureGlyphCache::endFillTexture() int QSGRhiTextureGlyphCache::glyphPadding() const { - return 1; + if (m_format == QFontEngine::Format_Mono) + return 8; + else + return 1; } int QSGRhiTextureGlyphCache::maxTextureWidth() const diff --git a/src/quickshapes/qquickshape.cpp b/src/quickshapes/qquickshape.cpp index bdfd5e3f76..f9c0968c7e 100644 --- a/src/quickshapes/qquickshape.cpp +++ b/src/quickshapes/qquickshape.cpp @@ -1522,6 +1522,7 @@ static void generateGradientColorTable(const QQuickShapeGradientCacheKey &gradie { int pos = 0; const QGradientStops &s = gradient.stops; + Q_ASSERT(!s.isEmpty()); const bool colorInterpolation = true; uint alpha = qRound(opacity * 256); @@ -1559,8 +1560,6 @@ static void generateGradientColorTable(const QQuickShapeGradientCacheKey &gradie current_color = next_color; } - Q_ASSERT(s.size() > 0); - uint last_color = ARGB2RGBA(qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha))); for ( ; pos < size; ++pos) colorTable[pos] = last_color; @@ -1595,7 +1594,10 @@ QSGTexture *QQuickShapeGradientCache::get(const QQuickShapeGradientCacheKey &gra if (!tx) { static const int W = 1024; // texture size is 1024x1 QImage gradTab(W, 1, QImage::Format_RGBA8888_Premultiplied); - generateGradientColorTable(grad, reinterpret_cast<uint *>(gradTab.bits()), W, 1.0f); + if (!grad.stops.isEmpty()) + generateGradientColorTable(grad, reinterpret_cast<uint *>(gradTab.bits()), W, 1.0f); + else + gradTab.fill(Qt::black); tx = new QSGPlainTexture; tx->setImage(gradTab); switch (grad.spread) { diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index c66e2dccf3..363070d7f8 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -260,6 +260,7 @@ private slots: void sortNonStringArray(); void iterateInvalidProxy(); void applyOnHugeArray(); + void reflectApplyOnHugeArray(); void tostringRecursionCheck(); void arrayIncludesWithLargeArray(); @@ -5174,6 +5175,22 @@ void tst_QJSEngine::applyOnHugeArray() QCOMPARE(value.toString(), "RangeError: Array too large for apply()."); } + +void tst_QJSEngine::reflectApplyOnHugeArray() +{ + QQmlEngine engine; + const QJSValue value = engine.evaluate(R"( +(function(){ +const v1 = []; +const v3 = []; +v3.length = 3900000000; +Reflect.apply(v1.reverse,v1,v3); +})() + )"); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), QLatin1String("RangeError: Invalid array length.")); +} + void tst_QJSEngine::typedArraySet() { QJSEngine engine; diff --git a/tests/auto/qml/qqmllanguage/data/alias.15a.qml b/tests/auto/qml/qqmllanguage/data/alias.15a.qml index ba8097c997..b5f96e15c1 100644 --- a/tests/auto/qml/qqmllanguage/data/alias.15a.qml +++ b/tests/auto/qml/qqmllanguage/data/alias.15a.qml @@ -9,4 +9,25 @@ Item { Item { id: symbol } + + Rectangle { + id: txtElevationValue + + property Rectangle background: Rectangle { } + + state: "ValidatorInvalid" + + states: [ + State { + name: "ValidatorInvalid" + PropertyChanges { + target: txtElevationValue + background.border.color: "red" // this line caused the segfault in qtbug107795 + } + }, + State { + name: "ValidatorAcceptable" + } + ] + } } diff --git a/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml b/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml new file mode 100644 index 0000000000..34dcbf96fa --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml @@ -0,0 +1,10 @@ +import QtQml 2.15 + +QtObject { + id: testItem + property rect rect + onComplete { + rect.x: 2 + rect.width: 22 + } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 1be1533b63..ac6634290a 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -341,6 +341,7 @@ private slots: void ambiguousContainingType(); void staticConstexprMembers(); void bindingAliasToComponentUrl(); + void badGroupedProperty(); private: QQmlEngine engine; @@ -5939,6 +5940,17 @@ void tst_qqmllanguage::bindingAliasToComponentUrl() } } +void tst_qqmllanguage::badGroupedProperty() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("badGroupedProperty.qml"); + QQmlComponent c(&engine, url); + QVERIFY(c.isError()); + QCOMPARE(c.errorString(), + QStringLiteral("%1:6 Cannot assign to non-existent property \"onComplete\"\n") + .arg(url.toString())); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/quick/qquickanimations/BLACKLIST b/tests/auto/quick/qquickanimations/BLACKLIST new file mode 100644 index 0000000000..6c1bec6520 --- /dev/null +++ b/tests/auto/quick/qquickanimations/BLACKLIST @@ -0,0 +1,3 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format +[reparent] +ci macos # QTBUG-108880 diff --git a/tests/auto/quick/qquickbehaviors/BLACKLIST b/tests/auto/quick/qquickbehaviors/BLACKLIST new file mode 100644 index 0000000000..d2a11a6a32 --- /dev/null +++ b/tests/auto/quick/qquickbehaviors/BLACKLIST @@ -0,0 +1,3 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format +[currentValue] +ci macos # QTBUG-108258 diff --git a/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml b/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml index de335386cf..2536b9789f 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_stacklayout.qml @@ -51,6 +51,7 @@ import QtQuick 2.2 import QtTest 1.0 import QtQuick.Layouts 1.3 +import QtQuick.Window 2.1 Item { id: container @@ -153,5 +154,417 @@ Item { compare(layout.secondItem.width, 200) // width should not be -1 layout.destroy() } + + function geometry(item) { + return [item.x, item.y, item.width, item.height] + } + + Component { + id: countGeometryChanges_Component + StackLayout { + id: stack + property alias col: _col + property alias row: _row + width: 100 + ColumnLayout { + id: _col + property alias r1: _r1 + property alias r2: _r2 + property alias r3: _r3 + spacing: 0 + property int counter : 0 + onWidthChanged: { ++counter; } + Rectangle { + id: _r1 + implicitWidth: 20 + implicitHeight: 20 + Layout.fillWidth: true + property int counter : 0 + onWidthChanged: { ++counter; } + } + Rectangle { + id: _r2 + implicitWidth: 50 + implicitHeight: 50 + Layout.fillWidth: true + property int counter : 0 + onWidthChanged: { ++counter; } + } + Rectangle { + id: _r3 + implicitWidth: 40 + implicitHeight: 40 + Layout.fillWidth: true + property int counter : 0 + onWidthChanged: { ++counter; } + } + } + RowLayout { + id: _row + property alias r5: _r5 + spacing: 0 + property int counter : 0 + onWidthChanged: { ++counter; } + Rectangle { + id: _r5 + implicitWidth: 100 + implicitHeight: 100 + Layout.fillWidth: true + property int counter : 0 + onWidthChanged: { ++counter; } + } + } + } + } + + function test_countGeometryChanges() { + + var stack = countGeometryChanges_Component.createObject(container) + compare(stack.currentIndex, 0) + compare(stack.col.width, 100) + compare(stack.col.height, 110) + compare(stack.row.width, 100) + compare(stack.row.height, 100) + verify(stack.col.r1.counter <= 2) + compare(stack.col.r2.counter, 1) + verify(stack.col.r3.counter <= 2) + verify(stack.col.counter <= 2) + compare(stack.row.counter, 1) // not visible, will only receive the initial geometry change + compare(stack.row.r5.counter, 0) + stack.destroy() + } + + + Component { + id: layoutItem_Component + Rectangle { + implicitWidth: 20 + implicitHeight: 20 + } + } + + Component { + id: emtpy_StackLayout_Component + StackLayout { + property int num_onCountChanged: 0 + property int num_onCurrentIndexChanged: 0 + onCountChanged: { ++num_onCountChanged; } + onCurrentIndexChanged: { ++num_onCurrentIndexChanged; } + } + } + + function test_addAndRemoveItems() + { + var stack = emtpy_StackLayout_Component.createObject(container) + stack.currentIndex = 2 + compare(stack.implicitWidth, 0) + compare(stack.implicitHeight, 0) + + var rect0 = layoutItem_Component.createObject(stack) + verify(waitForItemPolished(stack)) + compare(stack.implicitWidth, 20) + compare(stack.implicitHeight, 20) + compare(rect0.visible, false) + + var rect1 = layoutItem_Component.createObject(stack) + rect1.Layout.preferredWidth = 30 + rect1.Layout.preferredHeight = 10 + verify(waitForItemPolished(stack)) + compare(stack.implicitWidth, 30) + compare(stack.implicitHeight, 20) + compare(rect0.visible, false) + compare(rect1.visible, false) + + var rect2 = layoutItem_Component.createObject(stack) + rect2.x = 42 // ### items in a stacklayout will have their x and y positions discarded. + rect2.y = 42 + rect2.Layout.preferredWidth = 80 + rect2.Layout.preferredHeight = 30 + rect2.Layout.fillWidth = true + verify(waitForItemPolished(stack)) + compare(stack.implicitWidth, 80) + compare(stack.implicitHeight, 30) + compare(rect0.visible, false) + compare(rect1.visible, false) + compare(rect2.visible, true) + compare(geometry(rect2), geometry(stack)) + + rect2.destroy() + wait(0) + verify(waitForItemPolished(stack)) + compare(stack.implicitWidth, 30) + compare(stack.implicitHeight, 20) + + rect0.destroy() + wait(0) + verify(waitForItemPolished(stack)) + compare(stack.implicitWidth, 30) + compare(stack.implicitHeight, 10) + + rect1.destroy() + wait(0) + verify(waitForItemPolished(stack)) + compare(stack.implicitWidth, 0) + compare(stack.implicitHeight, 0) + + stack.destroy() + } + + function test_sizeHint_data() { + return [ + { tag: "propagateNone", layoutHints: [10, 20, 30], childHints: [11, 21, 31], expected:[10, 20, Number.POSITIVE_INFINITY]}, + { tag: "propagateMinimumWidth", layoutHints: [-1, 20, 30], childHints: [10, 21, 31], expected:[10, 20, Number.POSITIVE_INFINITY]}, + { tag: "propagatePreferredWidth", layoutHints: [10, -1, 30], childHints: [11, 20, 31], expected:[10, 20, Number.POSITIVE_INFINITY]}, + { tag: "propagateMaximumWidth", layoutHints: [10, 20, -1], childHints: [11, 21, 30], expected:[10, 20, Number.POSITIVE_INFINITY]}, + { tag: "propagateAll", layoutHints: [-1, -1, -1], childHints: [10, 20, 30], expected:[10, 20, Number.POSITIVE_INFINITY]}, + { tag: "propagateCrazy", layoutHints: [-1, -1, -1], childHints: [40, 21, 30], expected:[30, 30, Number.POSITIVE_INFINITY]}, + { tag: "expandMinToExplicitPref", layoutHints: [-1, 1, -1], childHints: [11, 21, 31], expected:[ 1, 1, Number.POSITIVE_INFINITY]}, + { tag: "expandMaxToExplicitPref", layoutHints: [-1, 99, -1], childHints: [11, 21, 31], expected:[11, 99, Number.POSITIVE_INFINITY]}, + { tag: "expandAllToExplicitMin", layoutHints: [99, -1, -1], childHints: [11, 21, 31], expected:[99, 99, Number.POSITIVE_INFINITY]}, + { tag: "expandPrefToExplicitMin", layoutHints: [24, -1, -1], childHints: [11, 21, 31], expected:[24, 24, Number.POSITIVE_INFINITY]}, + { tag: "boundPrefToExplicitMax", layoutHints: [-1, -1, 19], childHints: [11, 21, 31], expected:[11, 19, Number.POSITIVE_INFINITY]}, + { tag: "boundAllToExplicitMax", layoutHints: [-1, -1, 9], childHints: [11, 21, 31], expected:[ 9, 9, Number.POSITIVE_INFINITY]}, + ]; + } + + function itemSizeHints(item) { + return [item.Layout.minimumWidth, item.implicitWidth, item.Layout.maximumWidth] + } + Component { + id: stacklayout_sizeHint_Component + StackLayout { + property int implicitWidthChangedCount : 0 + onImplicitWidthChanged: { ++implicitWidthChangedCount } + ColumnLayout { + Rectangle { + id: r1 + color: "red" + Layout.minimumWidth: 1 + Layout.preferredWidth: 2 + Layout.maximumWidth: 3 + + Layout.minimumHeight: 20 + Layout.preferredHeight: 20 + Layout.maximumHeight: 20 + Layout.fillWidth: true + } + } + } + } + + function test_sizeHint(data) { + var layout = stacklayout_sizeHint_Component.createObject(container) + + var col = layout.children[0] + col.Layout.minimumWidth = data.layoutHints[0] + col.Layout.preferredWidth = data.layoutHints[1] + col.Layout.maximumWidth = data.layoutHints[2] + + var child = col.children[0] + if (data.implicitWidth !== undefined) { + child.implicitWidth = data.implicitWidth + } + child.Layout.minimumWidth = data.childHints[0] + child.Layout.preferredWidth = data.childHints[1] + child.Layout.maximumWidth = data.childHints[2] + + verify(waitForItemPolished(layout)) + var effectiveSizeHintResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth] + compare(effectiveSizeHintResult, data.expected) + layout.destroy() + } + + Component { + id: stacklayout_addIgnoredItem_Component + StackLayout { + Repeater { + id: rep + model: 1 + Rectangle { + id: r + } + } + } + } + + // Items with no size information is ignored. + function test_addIgnoredItem() + { + var stack = stacklayout_addIgnoredItem_Component.createObject(container) + compare(stack.count, 1) + compare(stack.implicitWidth, 0) + compare(stack.implicitHeight, 0) + var r = stack.children[0] + r.Layout.preferredWidth = 20 + r.Layout.preferredHeight = 30 + verify(waitForItemPolished(stack)) + compare(stack.count, 1) + compare(stack.implicitWidth, 20) + compare(stack.implicitHeight, 30) + stack.destroy(); + } + + function test_dontCrashWhenAnchoredToAWindow() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Window 2.1; \ + import QtQuick.Layouts 1.3; \ + Window { \ + visible: true; \ + width: stack.implicitWidth; \ + height: stack.implicitHeight; \ + StackLayout { \ + id: stack; \ + currentIndex: 0; \ + anchors.fill: parent; \ + Rectangle { \ + color: "red"; \ + implicitWidth: 300; \ + implicitHeight: 200; \ + } \ + } \ + } ' + + var win = Qt.createQmlObject(test_layoutStr, container, ''); + if (win.visibility === Window.Windowed) { + // on single-window systems (such as Android), the window geometry will be + // fullscreen, and most likely it will be set to screen size. Avoid this test for + // those systems, as the size of the window will not be determined by the layout + tryCompare(win, 'width', 300); + } + win.destroy() + } + + Component { + id: test_dontCrashWhenChildIsResizedToNull_Component + StackLayout { + property alias rect : _rect + Rectangle { + id: _rect; + color: "red" + implicitWidth: 200 + implicitHeight: 200 + } + } + } + + function test_dontCrashWhenChildIsResizedToNull() { + var layout = test_dontCrashWhenChildIsResizedToNull_Component.createObject(container) + layout.rect.width = 0 + layout.width = 222 // trigger a rearrange with a valid size + layout.height = 222 + } + + Component { + id: test_currentIndex_Component + StackLayout { + currentIndex: 1 + Text { + text: "0" + } + Text { + text: "1" + } + } + } + + function test_currentIndex() { + var layout = test_currentIndex_Component.createObject(container) + var c0 = layout.children[0] + var c1 = layout.children[1] + compare(layout.currentIndex, 1) + tryCompare(layout, 'visible', true) + compare(c0.visible, false) + compare(c1.visible, true) + layout.currentIndex = 0 + compare(c0.visible, true) + compare(c1.visible, false) + var c2 = layoutItem_Component.createObject(layout) + compare(c2.visible, false) + + /* + * destroy the current item and check if visibility advances to next + */ + c0.destroy() + tryCompare(c1, 'visible', true) + compare(c2.visible, false) + c1.destroy() + tryCompare(c2, 'visible', true) + c2.destroy() + tryCompare(layout, 'currentIndex', 0) + + layout.destroy() + + /* + * Test the default/implicit value of currentIndex, either -1 (if empty) or 0: + */ + layout = emtpy_StackLayout_Component.createObject(container) + tryCompare(layout, 'visible', true) + compare(layout.currentIndex, -1) + compare(layout.num_onCurrentIndexChanged, 0) + // make it non-empty + c0 = layoutItem_Component.createObject(layout) + compare(layout.currentIndex, 0) + compare(layout.num_onCurrentIndexChanged, 1) + compare(c0.visible, true) + // make it empty again + c0.destroy() + wait(0) + compare(layout.currentIndex, -1) + //tryCompare(layout, 'currentIndex', -1) + compare(layout.num_onCurrentIndexChanged, 2) + + /* + * Check that explicit value doesn't change, + * and that no items are visible if the index is invalid/out of range + */ + layout.currentIndex = 2 + compare(layout.currentIndex, 2) + compare(layout.num_onCurrentIndexChanged, 3) + c0 = layoutItem_Component.createObject(layout) + compare(layout.currentIndex, 2) + compare(c0.visible, false) + + c1 = layoutItem_Component.createObject(layout) + compare(layout.currentIndex, 2) + compare(c0.visible, false) + compare(c1.visible, false) + + c2 = layoutItem_Component.createObject(layout) + compare(layout.currentIndex, 2) + compare(c0.visible, false) + compare(c1.visible, false) + compare(c2.visible, true) + + c2.destroy() + wait(0) + compare(layout.currentIndex, 2) + compare(c0.visible, false) + compare(c1.visible, false) + c1.destroy() + wait(0) + compare(layout.currentIndex, 2) + compare(c0.visible, false) + c0.destroy() + wait(0) + compare(layout.currentIndex, 2) + compare(layout.num_onCurrentIndexChanged, 3) + } + + function test_count() { + var layout = emtpy_StackLayout_Component.createObject(container) + tryCompare(layout, 'visible', true) + compare(layout.count, 0) + compare(layout.currentIndex, -1) + compare(layout.num_onCountChanged, 0) + compare(layout.num_onCurrentIndexChanged, 0) + var c0 = layoutItem_Component.createObject(layout) + compare(layout.count, 1) + compare(layout.currentIndex, 0) + compare(layout.num_onCurrentIndexChanged, 1) + compare(layout.num_onCountChanged, 1) + } + + } } diff --git a/tests/auto/quick/qquickloader/data/overflow.qml b/tests/auto/quick/qquickloader/data/overflow.qml new file mode 100644 index 0000000000..8ca196e2ed --- /dev/null +++ b/tests/auto/quick/qquickloader/data/overflow.qml @@ -0,0 +1,5 @@ +import QtQuick 2.15 + +Loader { + source: "overflow.qml" +} diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index db678ae5a1..4539a89cb3 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -134,6 +134,8 @@ private slots: void setSourceAndCheckStatus(); void asyncLoaderRace(); void noEngine(); + + void stackOverflow(); }; Q_DECLARE_METATYPE(QList<QQmlError>) @@ -1530,6 +1532,17 @@ void tst_QQuickLoader::noEngine() QTRY_COMPARE(o->property("changes").toInt(), 1); } +void tst_QQuickLoader::stackOverflow() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("overflow.qml"); + QQmlComponent component(&engine, url); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + const QString message = url.toString() + QStringLiteral(": Maximum call stack size exceeded."); + QTest::ignoreMessage(QtCriticalMsg, qPrintable(message)); + QScopedPointer<QObject> o(component.create()); +} + QTEST_MAIN(tst_QQuickLoader) #include "tst_qquickloader.moc" diff --git a/tests/auto/quick/qquickvisualdatamodel/data/setDelegateNoDoubleChange.qml b/tests/auto/quick/qquickvisualdatamodel/data/setDelegateNoDoubleChange.qml new file mode 100644 index 0000000000..05623fbc5b --- /dev/null +++ b/tests/auto/quick/qquickvisualdatamodel/data/setDelegateNoDoubleChange.qml @@ -0,0 +1,43 @@ +import QtQuick 2.0 + +Item { + width: 200 + height: 200 + id: root + property int creationCount: 0 + property bool testStarted: false + + ListModel { + id: mymodel + ListElement {message: "This is my alarm"} + } + + Component { + id: aDelegate + Rectangle { + color: "blue" + width: 100 + height: 100 + } + } + + Component { + id: bDelegate + Rectangle { + color: "red" + width: 100 + height: 100 + Text {text: model.message } + Component.onCompleted: root.creationCount++ + } + } + + + ListView { + width: 200 + height: 200 + id: myListView + model: mymodel + delegate: testStarted ? bDelegate : aDelegate + } +} diff --git a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp index 27bd8aae49..83bdb1a5f8 100644 --- a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp +++ b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp @@ -434,6 +434,7 @@ private slots: void invalidContext(); void externalManagedModel(); void delegateModelChangeDelegate(); + void noDoubleDelegateUpdate(); void checkFilterGroupForDelegate(); void readFromProxyObject(); @@ -4345,6 +4346,20 @@ void tst_qquickvisualdatamodel::delegateModelChangeDelegate() QCOMPARE(visualModel->count(), 3); } +void tst_qquickvisualdatamodel::noDoubleDelegateUpdate() +{ + // changing a delegate only refreshes its instances once + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("setDelegateNoDoubleChange.qml")); + + QScopedPointer<QObject> root(component.create()); + QVERIFY(root); + + bool ok = root->setProperty("testStarted", true); + QVERIFY(ok); + QCOMPARE(root->property("creationCount").toInt(), 1); +} + void tst_qquickvisualdatamodel::checkFilterGroupForDelegate() { QQuickView view; diff --git a/tests/baseline/scenegraph/data/text/text_nativerendering_no_antialiasing.qml b/tests/baseline/scenegraph/data/text/text_nativerendering_no_antialiasing.qml new file mode 100644 index 0000000000..92598eb490 --- /dev/null +++ b/tests/baseline/scenegraph/data/text/text_nativerendering_no_antialiasing.qml @@ -0,0 +1,25 @@ +import QtQuick 2.0 + +//vary font style, native rendering without antialiasing + +Item { + id: topLevel + width: 320 + height: 580 + + Repeater { + model: [Text.Normal, Text.Outline, Text.Raised, Text.Sunken] + Text { + y: 20 * index + clip: true + renderType: Text.NativeRendering + width: parent.width + wrapMode: Text.Wrap + font.pointSize: 10 + style: modelData + styleColor: "green" + antialiasing: false + text: "The quick fox jumps in style " + modelData + } + } +} diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 4222d9ef46..37368bb794 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -182,11 +182,11 @@ QVariantMap pluginsForModulePath(const QString &modulePath) { } else if (sections.size() == 2 && sectionType != "module" && sectionType != "typeinfo") { componentFiles.append(modulePath + QLatin1Char('/') - + QString::fromUtf8(sections.at(1))); + + QString::fromUtf8(sections.at(1)).trimmed()); } else if (sections.size() == 3 || (sectionType == "singleton" && sections.size() == 4)) { int fileNameIndex = (sectionType == "singleton") ? 3 : 2; - const QString fileName = QString::fromUtf8(sections.at(fileNameIndex)); + const QString fileName = QString::fromUtf8(sections.at(fileNameIndex)).trimmed(); const QString filePath = modulePath + QLatin1Char('/') + fileName; if (fileName.endsWith(QLatin1String(".js")) || fileName.endsWith(QLatin1String(".mjs"))) |