diff options
79 files changed, 1982 insertions, 188 deletions
diff --git a/.qmake.conf b/.qmake.conf index 71ee0ceb5a..4feec109d3 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,4 +1,4 @@ load(qt_build_config) CONFIG += warning_clean -MODULE_VERSION = 5.12.8 +MODULE_VERSION = 5.12.12 diff --git a/dist/changes-5.12.10 b/dist/changes-5.12.10 new file mode 100644 index 0000000000..2f3bad1943 --- /dev/null +++ b/dist/changes-5.12.10 @@ -0,0 +1,32 @@ +Qt 5.12.10 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.9. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + https://doc.qt.io/qt-5.12/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* QtQml * +**************************************************************************** + + - [QTBUG-78044] Fixed an index out of range bug in the interactive mode of + qmlprofiler. + +**************************************************************************** +* QtQuick * +**************************************************************************** + + - [QTBUG-65170] Fixed a crash when QSGTexture is deleted in rc->endSync(). + diff --git a/dist/changes-5.12.8 b/dist/changes-5.12.8 new file mode 100644 index 0000000000..b6b5d0f2e1 --- /dev/null +++ b/dist/changes-5.12.8 @@ -0,0 +1,36 @@ +Qt 5.12.8 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.7. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* QtQml * +**************************************************************************** + + - [QTBUG-48809] Fixed a warning about non-relative paths in qmldir files. + We now permit the use of resource urls in qmldir files to allow a plugin + to include its .qml files in resources, optionally compiled ahead of time. + - [QTBUG-81123] Fixed a crash when objects were deleted from a QML list property. + +**************************************************************************** +* QtQuick * +**************************************************************************** + + - [QTBUG-76362] Fixed behavior of ListView with PullBackHeader positioning + mode in combination with snapMode. + - [QTBUG-64138] Particle effects can now run continuously over longer + periods of time. diff --git a/dist/changes-5.12.9 b/dist/changes-5.12.9 new file mode 100644 index 0000000000..7c705e8fc9 --- /dev/null +++ b/dist/changes-5.12.9 @@ -0,0 +1,46 @@ +Qt 5.12.9 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.8. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* QtQml * +**************************************************************************** + + - [Coverity 175402] Refactored some code so that Coverity and future human + readers can understand it. + - [Coverity 190701] Standardized QV4_SHOW_BYTECODE output for JS classes. + - [Coverity 193545] Fixed a dangling pointer dereference when getting + QML property values from dynamic accessors. + - [QTBUG-81581][QTBUG-83384] Fixed various crashes related to the JavaScript + accumulator register. + - JavaScript's isSafeInteger() covers a sensible range now. Before it + flagged anything greater than 55 as unsafe. + - [QTBUG-84095] Fixed a crash on QML context destruction. + +**************************************************************************** +* QtQuick * +**************************************************************************** + + - [Coverity 218729] If setting a new root item on QQuickView fails, the + old one is no longer deleted. + - [QTBUG-73929][QTBUG-82474] Fixed a crash by releasing resources while + closing a window. + - [QTBUG-40220][QTBUG-83856] Recursive loops caused by items that call + polish() within updatePolish() are now detected and terminated earlier. + - [QTBUG-55879][QTBUG-79339] Fixed QQuickItem::grabToImage() with + QQuickWidget and QQuickRenderControl. diff --git a/src/3rdparty/masm/wtf/PageBlock.cpp b/src/3rdparty/masm/wtf/PageBlock.cpp index e715ed262a..bc0e8d6f2d 100644 --- a/src/3rdparty/masm/wtf/PageBlock.cpp +++ b/src/3rdparty/masm/wtf/PageBlock.cpp @@ -64,6 +64,7 @@ inline size_t systemPageSize() #endif +inline namespace hidden { size_t pageSize() { if (!s_pageSize) @@ -78,5 +79,6 @@ size_t pageMask() s_pageMask = ~(pageSize() - 1); return s_pageMask; } +} } // namespace WTF diff --git a/src/3rdparty/masm/wtf/PageBlock.h b/src/3rdparty/masm/wtf/PageBlock.h index 4d408e1c91..9e2e561073 100644 --- a/src/3rdparty/masm/wtf/PageBlock.h +++ b/src/3rdparty/masm/wtf/PageBlock.h @@ -28,8 +28,13 @@ namespace WTF { +// avoid false positive detection by apple +// by putting the function inside an inline namespace +// to obtain different name mangling +inline namespace hidden { WTF_EXPORT_PRIVATE size_t pageSize(); WTF_EXPORT_PRIVATE size_t pageMask(); +} inline bool isPageAligned(void* address) { return !(reinterpret_cast<intptr_t>(address) & (pageSize() - 1)); } inline bool isPageAligned(size_t size) { return !(size & (pageSize() - 1)); } inline bool isPowerOfTwo(size_t size) { return !(size & (size - 1)); } diff --git a/src/qml/animations/qanimationgroupjob.cpp b/src/qml/animations/qanimationgroupjob.cpp index 66599561fc..60b3003d0a 100644 --- a/src/qml/animations/qanimationgroupjob.cpp +++ b/src/qml/animations/qanimationgroupjob.cpp @@ -47,9 +47,45 @@ QAnimationGroupJob::QAnimationGroupJob() m_isGroup = true; } +void QAnimationGroupJob::ungroupChild(QAbstractAnimationJob *animation) +{ + Q_ASSERT(animation); + Q_ASSERT(animation->m_group == this); + QAbstractAnimationJob *prev = animation->previousSibling(); + QAbstractAnimationJob *next = animation->nextSibling(); + + if (prev) + prev->m_nextSibling = next; + else + m_firstChild = next; + + if (next) + next->m_previousSibling = prev; + else + m_lastChild = prev; + + animation->m_previousSibling = nullptr; + animation->m_nextSibling = nullptr; + + animation->m_group = nullptr; +} + +void QAnimationGroupJob::handleAnimationRemoved(QAbstractAnimationJob *animation) +{ + resetUncontrolledAnimationFinishTime(animation); + if (!firstChild()) { + m_currentTime = 0; + stop(); + } +} + QAnimationGroupJob::~QAnimationGroupJob() { - clear(); + while (QAbstractAnimationJob *animation = firstChild()) { + ungroupChild(animation); + handleAnimationRemoved(animation); + delete animation; + } } void QAnimationGroupJob::topLevelAnimationLoopChanged() @@ -96,25 +132,9 @@ void QAnimationGroupJob::prependAnimation(QAbstractAnimationJob *animation) void QAnimationGroupJob::removeAnimation(QAbstractAnimationJob *animation) { - Q_ASSERT(animation); - Q_ASSERT(animation->m_group == this); QAbstractAnimationJob *prev = animation->previousSibling(); QAbstractAnimationJob *next = animation->nextSibling(); - - if (prev) - prev->m_nextSibling = next; - else - m_firstChild = next; - - if (next) - next->m_previousSibling = prev; - else - m_lastChild = prev; - - animation->m_previousSibling = nullptr; - animation->m_nextSibling = nullptr; - - animation->m_group = nullptr; + ungroupChild(animation); animationRemoved(animation, prev, next); } @@ -154,11 +174,7 @@ void QAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *an void QAnimationGroupJob::animationRemoved(QAbstractAnimationJob* anim, QAbstractAnimationJob* , QAbstractAnimationJob* ) { - resetUncontrolledAnimationFinishTime(anim); - if (!firstChild()) { - m_currentTime = 0; - stop(); - } + handleAnimationRemoved(anim); } void QAnimationGroupJob::debugChildren(QDebug d) const diff --git a/src/qml/animations/qanimationgroupjob_p.h b/src/qml/animations/qanimationgroupjob_p.h index b01b2f3b36..6a908ccece 100644 --- a/src/qml/animations/qanimationgroupjob_p.h +++ b/src/qml/animations/qanimationgroupjob_p.h @@ -91,6 +91,9 @@ protected: void debugChildren(QDebug d) const; private: + void ungroupChild(QAbstractAnimationJob *animation); + void handleAnimationRemoved(QAbstractAnimationJob *animation); + //definition QAbstractAnimationJob *m_firstChild = nullptr; QAbstractAnimationJob *m_lastChild = nullptr; diff --git a/src/qml/animations/qanimationjobutil_p.h b/src/qml/animations/qanimationjobutil_p.h index 83cf3b246f..2b7bda3123 100644 --- a/src/qml/animations/qanimationjobutil_p.h +++ b/src/qml/animations/qanimationjobutil_p.h @@ -70,7 +70,7 @@ struct SelfDeletable { // \param func statements or functions that to be executed under test. // \param action post process if p was deleted under test. #define ACTION_IF_DELETED(p, func, action) \ -{ \ +do { \ static_assert(std::is_same<decltype((p)->m_selfDeletable), SelfDeletable>::value, "m_selfDeletable must be SelfDeletable");\ bool *prevWasDeleted = (p)->m_selfDeletable.m_wasDeleted; \ bool wasDeleted = false; \ @@ -82,7 +82,7 @@ struct SelfDeletable { {action;} \ } \ (p)->m_selfDeletable.m_wasDeleted = prevWasDeleted; \ -} +} while (false) #define RETURN_IF_DELETED(func) \ ACTION_IF_DELETED(this, func, return) diff --git a/src/qml/animations/qsequentialanimationgroupjob.cpp b/src/qml/animations/qsequentialanimationgroupjob.cpp index 22e20d9268..89adf0d64a 100644 --- a/src/qml/animations/qsequentialanimationgroupjob.cpp +++ b/src/qml/animations/qsequentialanimationgroupjob.cpp @@ -329,7 +329,7 @@ void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimat if (m_direction == Forward) { // set the current animation to be the next one if (m_currentAnimation->nextSibling()) - setCurrentAnimation(m_currentAnimation->nextSibling()); + RETURN_IF_DELETED(setCurrentAnimation(m_currentAnimation->nextSibling())); for (QAbstractAnimationJob *a = animation->nextSibling(); a; a = a->nextSibling()) { int dur = a->duration(); @@ -344,7 +344,7 @@ void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimat } else { // set the current animation to be the previous one if (m_currentAnimation->previousSibling()) - setCurrentAnimation(m_currentAnimation->previousSibling()); + RETURN_IF_DELETED(setCurrentAnimation(m_currentAnimation->previousSibling())); for (QAbstractAnimationJob *a = animation->previousSibling(); a; a = a->previousSibling()) { int dur = a->duration(); @@ -365,12 +365,12 @@ void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimat void QSequentialAnimationGroupJob::animationInserted(QAbstractAnimationJob *anim) { if (m_currentAnimation == nullptr) - setCurrentAnimation(firstChild()); // initialize the current animation + RETURN_IF_DELETED(setCurrentAnimation(firstChild())); // initialize the current animation if (m_currentAnimation == anim->nextSibling() && m_currentAnimation->currentTime() == 0 && m_currentAnimation->currentLoop() == 0) { //in this case we simply insert the animation before the current one has actually started - setCurrentAnimation(anim); + RETURN_IF_DELETED(setCurrentAnimation(anim)); } // TODO @@ -389,11 +389,11 @@ void QSequentialAnimationGroupJob::animationRemoved(QAbstractAnimationJob *anim, bool removingCurrent = anim == m_currentAnimation; if (removingCurrent) { if (next) - setCurrentAnimation(next); //let's try to take the next one + RETURN_IF_DELETED(setCurrentAnimation(next)); //let's try to take the next one else if (prev) - setCurrentAnimation(prev); + RETURN_IF_DELETED(setCurrentAnimation(prev)); else// case all animations were removed - setCurrentAnimation(nullptr); + RETURN_IF_DELETED(setCurrentAnimation(nullptr)); } // duration of the previous animations up to the current animation diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index 9fb91e9140..adef147ccd 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -62,6 +62,7 @@ #include <QStandardPaths> #include <QDir> #include <private/qv4identifiertable_p.h> +#include <private/qqmltypewrapper_p.h> #endif #include <private/qqmlirbuilder_p.h> #include <QCoreApplication> @@ -285,7 +286,8 @@ void CompilationUnit::unlink() if (runtimeLookups) { for (uint i = 0; i < data->lookupTableSize; ++i) { QV4::Lookup &l = runtimeLookups[i]; - if (l.getter == QV4::QObjectWrapper::lookupGetter) { + if (l.getter == QV4::QObjectWrapper::lookupGetter + || l.getter == QQmlTypeWrapper::lookupSingletonProperty) { if (QQmlPropertyCache *pc = l.qobjectLookup.propertyCache) pc->release(); } else if (l.getter == QQmlValueTypeWrapper::lookupGetter) { @@ -293,7 +295,8 @@ void CompilationUnit::unlink() pc->release(); } - if (l.qmlContextPropertyGetter == QQmlContextWrapper::lookupScopeObjectProperty) { + if (l.qmlContextPropertyGetter == QQmlContextWrapper::lookupScopeObjectProperty + || l.qmlContextPropertyGetter == QQmlContextWrapper::lookupContextObjectProperty) { if (QQmlPropertyCache *pc = l.qobjectLookup.propertyCache) pc->release(); } diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index 4d85f25d4b..9c4f99d93a 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -480,25 +480,26 @@ void QV4::Compiler::JSUnitGenerator::writeClass(char *b, const QV4::Compiler::Cl static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_BYTECODE"); if (showCode) { - qDebug() << "=== Class " << stringForIndex(cls->nameIndex) << "static methods" << cls->nStaticMethods << "methods" << cls->nMethods; + qDebug() << "=== Class" << stringForIndex(cls->nameIndex) << "static methods" + << cls->nStaticMethods << "methods" << cls->nMethods; qDebug() << " constructor:" << cls->constructorFunction; - const char *staticString = ": static "; for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { - if (i == cls->nStaticMethods) - staticString = ": "; - const char *type; + QDebug output = qDebug().nospace(); + output << " " << i << ": "; + if (i < cls->nStaticMethods) + output << "static "; switch (cls->methodTable()[i].type) { case CompiledData::Method::Getter: - type = "get "; break; + output << "get "; break; case CompiledData::Method::Setter: - type = "set "; break; + output << "set "; break; default: - type = ""; - + break; } - qDebug() << " " << i << staticString << type << stringForIndex(cls->methodTable()[i].name) << cls->methodTable()[i].function; + output << stringForIndex(cls->methodTable()[i].name) << " " + << cls->methodTable()[i].function; } - qDebug(); + qDebug().space(); } } diff --git a/src/qml/doc/snippets/code/backend/main.qml b/src/qml/doc/snippets/code/backend/main.qml index fadc9cd768..316dea215e 100644 --- a/src/qml/doc/snippets/code/backend/main.qml +++ b/src/qml/doc/snippets/code/backend/main.qml @@ -72,7 +72,7 @@ ApplicationWindow { placeholderText: qsTr("User name") anchors.centerIn: parent - onTextChanged: backend.userName = text + onEditingFinished: backend.userName = text } //![username_input] } diff --git a/src/qml/jit/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp index 73e396890e..c64f1406b5 100644 --- a/src/qml/jit/qv4baselineassembler.cpp +++ b/src/qml/jit/qv4baselineassembler.cpp @@ -91,6 +91,8 @@ public: PlatformAssemblerCommon::callRuntime(functionName, funcPtr); if (dest == CallResultDestination::InAccumulator) saveReturnValueInAccumulator(); + else if (AccumulatorRegister == ReturnValueRegister) + loadUndefined(); } void saveReturnValueInAccumulator() @@ -391,6 +393,8 @@ public: PlatformAssemblerCommon::callRuntime(functionName, funcPtr); if (dest == CallResultDestination::InAccumulator) saveReturnValueInAccumulator(); + else if (AccumulatorRegisterValue == ReturnValueRegisterValue) + loadUndefined(); } void saveReturnValueInAccumulator() @@ -920,7 +924,7 @@ void BaselineAssembler::loadValue(ReturnedValue value) void BaselineAssembler::storeHeapObject(int reg) { - pasm()->storeHeapObject(PlatformAssembler::ReturnValueRegisterValue, regAddr(reg)); + pasm()->storeHeapObject(PlatformAssembler::AccumulatorRegisterValue, regAddr(reg)); } void BaselineAssembler::loadImport(int index) diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index 51cd15099d..b106d62e8b 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -66,6 +66,8 @@ void BaselineJIT::generate() labels = collectLabelsInBytecode(code, len); as->generatePrologue(); + // Make sure the ACC register is initialized and not clobbered by the caller. + as->loadAccumulatorFromFrame(); decode(code, len); as->generateEpilogue(); @@ -567,10 +569,12 @@ void BaselineJIT::generate_SetException() { as->setException(); } void BaselineJIT::generate_CreateCallContext() { + as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(1); as->passCppFrameAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(ExecutionContext::newCallContext, CallResultDestination::Ignore); // keeps result in return value register + BASELINEJIT_GENERATE_RUNTIME_CALL(ExecutionContext::newCallContext, CallResultDestination::InAccumulator); as->storeHeapObject(CallData::Context); + as->loadAccumulatorFromFrame(); } void BaselineJIT::generate_PushCatchContext(int index, int name) { as->pushCatchContext(index, name); } @@ -582,9 +586,10 @@ void BaselineJIT::generate_PushWithContext() as->prepareCallWithArgCount(2); as->passJSSlotAsArg(0, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createWithContext, CallResultDestination::Ignore); // keeps result in return value register + BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createWithContext, CallResultDestination::InAccumulator); as->checkException(); as->storeHeapObject(CallData::Context); + as->loadAccumulatorFromFrame(); } void BaselineJIT::generate_PushBlockContext(int index) diff --git a/src/qml/jsruntime/qv4arraydata.cpp b/src/qml/jsruntime/qv4arraydata.cpp index 654d33b8d1..ba62f078f0 100644 --- a/src/qml/jsruntime/qv4arraydata.cpp +++ b/src/qml/jsruntime/qv4arraydata.cpp @@ -654,6 +654,8 @@ bool ArrayElementLessThan::operator()(Value v1, Value v2) const jsCallData->args[0] = v1; jsCallData->args[1] = v2; result = o->call(jsCallData); + if (scope.hasException()) + return false; return result->toNumber() < 0; } diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index b5b421fa39..b67eceb7f7 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -361,7 +361,7 @@ ReturnedValue ArrayPrototype::method_toString(const FunctionObject *builtin, con ScopedString string(scope, scope.engine->newString(QStringLiteral("join"))); ScopedFunctionObject f(scope, that->get(string)); if (f) - return f->call(that, argv, argc); + return checkedResult(scope.engine, f->call(that, argv, argc)); return ObjectPrototype::method_toString(builtin, that, argv, argc); } @@ -1209,6 +1209,7 @@ ReturnedValue ArrayPrototype::method_every(const FunctionObject *b, const Value arguments[1] = Value::fromDouble(k); arguments[2] = instance; r = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); ok = r->toBoolean(); } return Encode(ok); @@ -1276,6 +1277,7 @@ ReturnedValue ArrayPrototype::method_some(const FunctionObject *b, const Value * arguments[1] = Value::fromDouble(k); arguments[2] = instance; result = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); if (result->toBoolean()) return Encode(true); } @@ -1345,6 +1347,7 @@ ReturnedValue ArrayPrototype::method_map(const FunctionObject *b, const Value *t arguments[1] = Value::fromDouble(k); arguments[2] = instance; mapped = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); a->arraySet(k, mapped); } return a.asReturnedValue(); @@ -1380,6 +1383,7 @@ ReturnedValue ArrayPrototype::method_filter(const FunctionObject *b, const Value arguments[1] = Value::fromDouble(k); arguments[2] = instance; selected = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); if (selected->toBoolean()) { a->arraySet(to, arguments[0]); ++to; @@ -1430,6 +1434,7 @@ ReturnedValue ArrayPrototype::method_reduce(const FunctionObject *b, const Value arguments[2] = Value::fromDouble(k); arguments[3] = instance; acc = callback->call(nullptr, arguments, 4); + CHECK_EXCEPTION(); } ++k; } @@ -1483,6 +1488,7 @@ ReturnedValue ArrayPrototype::method_reduceRight(const FunctionObject *b, const arguments[2] = Value::fromDouble(k - 1); arguments[3] = instance; acc = callback->call(nullptr, arguments, 4); + CHECK_EXCEPTION(); } --k; } diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index a13fb37a52..f643a1180f 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -1547,7 +1547,7 @@ ReturnedValue DatePrototype::method_toJSON(const FunctionObject *b, const Value if (!toIso) return v4->throwTypeError(); - return toIso->call(O, nullptr, 0); + return checkedResult(v4, toIso->call(O, nullptr, 0)); } ReturnedValue DatePrototype::method_symbolToPrimitive(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index f9747207db..a4831422d3 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1090,8 +1090,11 @@ QUrl ExecutionEngine::resolvedUrl(const QString &file) void ExecutionEngine::markObjects(MarkStack *markStack) { for (int i = 0; i < NClasses; ++i) - if (classes[i]) + if (classes[i]) { classes[i]->mark(markStack); + if (markStack->top >= markStack->limit) + markStack->drain(); + } markStack->drain(); identifierTable->markObjects(markStack); diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index 41a21ba379..fbc7897c67 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -357,7 +357,7 @@ ReturnedValue FunctionPrototype::method_apply(const QV4::FunctionObject *b, cons return v4->throwTypeError(); thisObject = argc ? argv : nullptr; if (argc < 2 || argv[1].isNullOrUndefined()) - return f->call(thisObject, argv, 0); + return checkedResult(v4, f->call(thisObject, argv, 0)); Object *arr = argv[1].objectValue(); if (!arr) @@ -391,13 +391,14 @@ ReturnedValue FunctionPrototype::method_apply(const QV4::FunctionObject *b, cons } } - return f->call(thisObject, arguments, len); + return checkedResult(v4, f->call(thisObject, arguments, len)); } ReturnedValue FunctionPrototype::method_call(const QV4::FunctionObject *b, const Value *thisObject, const Value *argv, int argc) { + QV4::ExecutionEngine *v4 = b->engine(); if (!thisObject->isFunctionObject()) - return b->engine()->throwTypeError(); + return v4->throwTypeError(); const FunctionObject *f = static_cast<const FunctionObject *>(thisObject); @@ -406,7 +407,7 @@ ReturnedValue FunctionPrototype::method_call(const QV4::FunctionObject *b, const ++argv; --argc; } - return f->call(thisObject, argv, argc); + return checkedResult(v4, f->call(thisObject, argv, argc)); } ReturnedValue FunctionPrototype::method_bind(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) @@ -706,12 +707,12 @@ void Heap::BoundFunction::init(QV4::ExecutionContext *scope, QV4::FunctionObject ReturnedValue BoundFunction::virtualCall(const FunctionObject *fo, const Value *, const Value *argv, int argc) { - const BoundFunction *f = static_cast<const BoundFunction *>(fo); - Scope scope(f->engine()); - - if (scope.hasException()) + QV4::ExecutionEngine *v4 = fo->engine(); + if (v4->hasException) return Encode::undefined(); + const BoundFunction *f = static_cast<const BoundFunction *>(fo); + Scope scope(v4); Scoped<MemberData> boundArgs(scope, f->boundArgs()); ScopedFunctionObject target(scope, f->target()); JSCallData jsCallData(scope, (boundArgs ? boundArgs->size() : 0) + argc); @@ -722,7 +723,7 @@ ReturnedValue BoundFunction::virtualCall(const FunctionObject *fo, const Value * argp += boundArgs->size(); } memcpy(argp, argv, argc*sizeof(Value)); - return target->call(jsCallData); + return checkedResult(v4, target->call(jsCallData)); } ReturnedValue BoundFunction::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *) diff --git a/src/qml/jsruntime/qv4functionobject_p.h b/src/qml/jsruntime/qv4functionobject_p.h index e03d49c74d..ec250659bc 100644 --- a/src/qml/jsruntime/qv4functionobject_p.h +++ b/src/qml/jsruntime/qv4functionobject_p.h @@ -328,6 +328,10 @@ inline bool FunctionObject::isBoundFunction() const return d()->vtable() == BoundFunction::staticVTable(); } +inline ReturnedValue checkedResult(QV4::ExecutionEngine *v4, ReturnedValue result) +{ + return v4->hasException ? QV4::Encode::undefined() : result; +} } diff --git a/src/qml/jsruntime/qv4globalobject.cpp b/src/qml/jsruntime/qv4globalobject.cpp index becdc3bc55..107aebc502 100644 --- a/src/qml/jsruntime/qv4globalobject.cpp +++ b/src/qml/jsruntime/qv4globalobject.cpp @@ -376,12 +376,12 @@ ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, if (function->isStrict() || isStrict) { ScopedFunctionObject e(scope, FunctionObject::createScriptFunction(ctx, function)); ScopedValue thisObject(scope, directCall ? scope.engine->currentStackFrame->thisObject() : scope.engine->globalObject->asReturnedValue()); - return e->call(thisObject, nullptr, 0); + return checkedResult(v4, e->call(thisObject, nullptr, 0)); } ScopedValue thisObject(scope, scope.engine->currentStackFrame->thisObject()); - return function->call(thisObject, nullptr, 0, ctx); + return checkedResult(v4, function->call(thisObject, nullptr, 0, ctx)); } diff --git a/src/qml/jsruntime/qv4jsonobject.cpp b/src/qml/jsruntime/qv4jsonobject.cpp index 936c032fad..ce759111f4 100644 --- a/src/qml/jsruntime/qv4jsonobject.cpp +++ b/src/qml/jsruntime/qv4jsonobject.cpp @@ -703,6 +703,8 @@ QString Stringify::Str(const QString &key, const Value &v) *jsCallData->thisObject = value; jsCallData->args[0] = v4->newString(key); value = toJSON->call(jsCallData); + if (v4->hasException) + return QString(); } } @@ -714,6 +716,8 @@ QString Stringify::Str(const QString &key, const Value &v) jsCallData->args[1] = value; *jsCallData->thisObject = holder; value = replacerFunction->call(jsCallData); + if (v4->hasException) + return QString(); } o = value->asReturnedValue(); diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index f8999342d1..86b96e0e3c 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -304,7 +304,8 @@ ReturnedValue Lookup::getterAccessor(Lookup *l, ExecutionEngine *engine, const V if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); + return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call( + &object, nullptr, 0)); } } l->getter = getterFallback; @@ -321,7 +322,8 @@ ReturnedValue Lookup::getterProtoAccessor(Lookup *l, ExecutionEngine *engine, co if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); + return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call( + &object, nullptr, 0)); } return getterTwoClasses(l, engine, object); } @@ -341,7 +343,8 @@ ReturnedValue Lookup::getterProtoAccessorTwoClasses(Lookup *l, ExecutionEngine * if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); + return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call( + &object, nullptr, 0)); } } l->getter = getterFallback; @@ -386,7 +389,8 @@ ReturnedValue Lookup::primitiveGetterAccessor(Lookup *l, ExecutionEngine *engine if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - return static_cast<const FunctionObject *>(getter)->call(&object, nullptr, 0); + return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call( + &object, nullptr, 0)); } } l->getter = getterGeneric; @@ -424,7 +428,8 @@ ReturnedValue Lookup::globalGetterProtoAccessor(Lookup *l, ExecutionEngine *engi if (!getter->isFunctionObject()) // ### catch at resolve time return Encode::undefined(); - return static_cast<const FunctionObject *>(getter)->call(engine->globalObject, nullptr, 0); + return checkedResult(engine, static_cast<const FunctionObject *>(getter)->call( + engine->globalObject, nullptr, 0)); } l->globalGetter = globalGetterGeneric; return globalGetterGeneric(l, engine); diff --git a/src/qml/jsruntime/qv4numberobject.cpp b/src/qml/jsruntime/qv4numberobject.cpp index 13f6912371..0044e0bc68 100644 --- a/src/qml/jsruntime/qv4numberobject.cpp +++ b/src/qml/jsruntime/qv4numberobject.cpp @@ -196,7 +196,7 @@ ReturnedValue NumberPrototype::method_isSafeInteger(const FunctionObject *, cons return Encode(false); double iv = v.toInteger(); - return Encode(dv == iv && std::fabs(iv) <= (2^53)-1); + return Encode(dv == iv && std::fabs(iv) <= (1LL << 53) - 1); } ReturnedValue NumberPrototype::method_isNaN(const FunctionObject *, const Value *, const Value *argv, int argc) diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 7dd0a247d6..fa24fde9c2 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -104,7 +104,7 @@ ReturnedValue Object::getValueAccessor(const Value &thisObject, const Value &v, Scope scope(f->engine()); JSCallData jsCallData(scope); *jsCallData->thisObject = thisObject; - return f->call(jsCallData); + return checkedResult(scope.engine, f->call(jsCallData)); } bool Object::putValue(uint memberIndex, PropertyAttributes attrs, const Value &value) diff --git a/src/qml/jsruntime/qv4objectproto.cpp b/src/qml/jsruntime/qv4objectproto.cpp index 6b4c3ba71a..c412f19a0a 100644 --- a/src/qml/jsruntime/qv4objectproto.cpp +++ b/src/qml/jsruntime/qv4objectproto.cpp @@ -666,7 +666,7 @@ ReturnedValue ObjectPrototype::method_toLocaleString(const FunctionObject *b, co if (!f) THROW_TYPE_ERROR(); - return f->call(thisObject, argv, argc); + return checkedResult(scope.engine, f->call(thisObject, argv, argc)); } ReturnedValue ObjectPrototype::method_valueOf(const FunctionObject *b, const Value *thisObject, const Value *, int) diff --git a/src/qml/jsruntime/qv4promiseobject.cpp b/src/qml/jsruntime/qv4promiseobject.cpp index 40a0dfaa57..adb440b6a0 100644 --- a/src/qml/jsruntime/qv4promiseobject.cpp +++ b/src/qml/jsruntime/qv4promiseobject.cpp @@ -617,7 +617,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this } ScopedObject nextPromise(scope, Value::fromReturnedValue(resolve->call(thisObject, nextValue, 1))); - if (!nextPromise || scope.hasException()) { + if (scope.hasException() || !nextPromise) { ScopedValue completion(scope, Runtime::method_iteratorClose(e, iteratorObject, doneValue)); if (scope.hasException()) { completion = e->exceptionValue->asReturnedValue(); @@ -763,7 +763,7 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi } ScopedObject nextPromise(scope, Value::fromReturnedValue(resolve->call(thisObject, nextValue, 1))); - if (!nextPromise || scope.hasException()) { + if (scope.hasException() || !nextPromise) { ScopedValue completion(scope, Runtime::method_iteratorClose(e, iteratorObject, doneValue)); if (scope.hasException()) { completion = e->exceptionValue->asReturnedValue(); diff --git a/src/qml/jsruntime/qv4proxy.cpp b/src/qml/jsruntime/qv4proxy.cpp index 9325e2e53b..8a76ed09fe 100644 --- a/src/qml/jsruntime/qv4proxy.cpp +++ b/src/qml/jsruntime/qv4proxy.cpp @@ -96,6 +96,8 @@ ReturnedValue ProxyObject::virtualGet(const Managed *m, PropertyKey id, const Va cdata.args[2] = *receiver; ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return Encode::undefined(); ScopedProperty targetDesc(scope); PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); if (attributes != Attr_Invalid && !attributes.isConfigurable()) { @@ -136,7 +138,7 @@ bool ProxyObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Val cdata.args[3] = *receiver; ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); - if (!trapResult->toBoolean()) + if (scope.engine->hasException || !trapResult->toBoolean()) return false; ScopedProperty targetDesc(scope); PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); @@ -176,7 +178,7 @@ bool ProxyObject::virtualDeleteProperty(Managed *m, PropertyKey id) cdata.args[2] = o->d(); // ### fix receiver handling ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); - if (!trapResult->toBoolean()) + if (scope.engine->hasException || !trapResult->toBoolean()) return false; ScopedProperty targetDesc(scope); PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); @@ -211,6 +213,8 @@ bool ProxyObject::virtualHasProperty(const Managed *m, PropertyKey id) cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return false; bool result = trapResult->toBoolean(); if (!result) { ScopedProperty targetDesc(scope); @@ -251,6 +255,8 @@ PropertyAttributes ProxyObject::virtualGetOwnProperty(const Managed *m, Property cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return Attr_Invalid; if (!trapResult->isObject() && !trapResult->isUndefined()) { scope.engine->throwTypeError(); return Attr_Invalid; @@ -323,7 +329,7 @@ bool ProxyObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Pro cdata.args[2] = ObjectPrototype::fromPropertyDescriptor(scope.engine, p, attrs); ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); - bool result = trapResult->toBoolean(); + bool result = !scope.engine->hasException && trapResult->toBoolean(); if (!result) return false; @@ -373,6 +379,8 @@ bool ProxyObject::virtualIsExtensible(const Managed *m) cdata.args[0] = target; ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return false; bool result = trapResult->toBoolean(); if (result != target->isExtensible()) { scope.engine->throwTypeError(); @@ -404,6 +412,8 @@ bool ProxyObject::virtualPreventExtensions(Managed *m) cdata.args[0] = target; ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return false; bool result = trapResult->toBoolean(); if (result && target->isExtensible()) { scope.engine->throwTypeError(); @@ -439,6 +449,8 @@ Heap::Object *ProxyObject::virtualGetPrototypeOf(const Managed *m) cdata.args[0] = target; ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return nullptr; if (!trapResult->isNull() && !trapResult->isObject()) { scope.engine->throwTypeError(); return nullptr; @@ -482,7 +494,7 @@ bool ProxyObject::virtualSetPrototypeOf(Managed *m, const Object *p) cdata.args[1] = p ? p->asReturnedValue() : Encode::null(); ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); - bool result = trapResult->toBoolean(); + bool result = !scope.engine->hasException && trapResult->toBoolean(); if (!result) return false; if (!target->isExtensible()) { @@ -573,6 +585,8 @@ OwnPropertyKeyIterator *ProxyObject::virtualOwnPropertyKeys(const Object *m, Val JSCallData cdata(scope, 1, nullptr, handler); cdata.args[0] = target; ScopedObject trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); + if (scope.engine->hasException) + return nullptr; if (!trapResult) { scope.engine->throwTypeError(); return nullptr; @@ -699,7 +713,7 @@ ReturnedValue ProxyFunctionObject::virtualCall(const FunctionObject *f, const Va if (scope.hasException()) return Encode::undefined(); if (trap->isNullOrUndefined()) - return target->call(thisObject, argv, argc); + return checkedResult(scope.engine, target->call(thisObject, argv, argc)); if (!trap->isFunctionObject()) return scope.engine->throwTypeError(); diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 63b6435112..917c4d3cb5 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -384,7 +384,7 @@ ReturnedValue QObjectWrapper::getQmlProperty(QV4::ExecutionEngine *engine, QQmlC if (hasProperty) *hasProperty = true; - if (property) + if (property && result != &local) *property = result; return getProperty(engine, object, result); diff --git a/src/qml/jsruntime/qv4reflect.cpp b/src/qml/jsruntime/qv4reflect.cpp index 15dcb602eb..3950fb139e 100644 --- a/src/qml/jsruntime/qv4reflect.cpp +++ b/src/qml/jsruntime/qv4reflect.cpp @@ -98,7 +98,8 @@ ReturnedValue Reflect::method_apply(const FunctionObject *f, const Value *, cons if (scope.hasException()) return Encode::undefined(); - return static_cast<const FunctionObject &>(argv[0]).call(&argv[1], arguments.argv, arguments.argc); + return checkedResult(scope.engine, static_cast<const FunctionObject &>(argv[0]).call( + &argv[1], arguments.argv, arguments.argc)); } ReturnedValue Reflect::method_construct(const FunctionObject *f, const Value *, const Value *argv, int argc) diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp index 39a2e96b45..2693384cdb 100644 --- a/src/qml/jsruntime/qv4regexpobject.cpp +++ b/src/qml/jsruntime/qv4regexpobject.cpp @@ -452,6 +452,8 @@ ReturnedValue RegExpPrototype::exec(ExecutionEngine *engine, const Object *o, co ScopedFunctionObject exec(scope, o->get(key)); if (exec) { ScopedValue result(scope, exec->call(o, s, 1)); + if (scope.hasException()) + RETURN_UNDEFINED(); if (!result->isNull() && !result->isObject()) return scope.engine->throwTypeError(); return result->asReturnedValue(); @@ -708,6 +710,8 @@ ReturnedValue RegExpPrototype::method_replace(const FunctionObject *f, const Val cData->args[nCaptures + 1] = Encode(position); cData->args[nCaptures + 2] = s; ScopedValue replValue(scope, replaceFunction->call(cData)); + if (scope.hasException()) + return Encode::undefined(); replacement = replValue->toQString(); } else { replacement = RegExp::getSubstitution(matchString->toQString(), s->toQString(), position, cData.args, nCaptures, replaceValue->toQString()); diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 6a242ba5f6..57beaa8ccd 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -373,7 +373,7 @@ QV4::ReturnedValue Runtime::method_instanceof(ExecutionEngine *engine, const Val return engine->throwTypeError(); ScopedValue result(scope, fHasInstance->call(&rval, &lval, 1)); - return Encode(result->toBoolean()); + return scope.hasException() ? Encode::undefined() : Encode(result->toBoolean()); } QV4::ReturnedValue Runtime::method_in(ExecutionEngine *engine, const Value &left, const Value &right) @@ -489,6 +489,8 @@ ReturnedValue RuntimeHelpers::ordinaryToPrimitive(ExecutionEngine *engine, const ScopedValue conv(scope, object->get(meth1)); if (FunctionObject *o = conv->as<FunctionObject>()) { result = o->call(object, nullptr, 0); + if (engine->hasException) + return Encode::undefined(); if (result->isPrimitive()) return result->asReturnedValue(); } @@ -499,6 +501,8 @@ ReturnedValue RuntimeHelpers::ordinaryToPrimitive(ExecutionEngine *engine, const conv = object->get(meth2); if (FunctionObject *o = conv->as<FunctionObject>()) { result = o->call(object, nullptr, 0); + if (engine->hasException) + return Encode::undefined(); if (result->isPrimitive()) return result->asReturnedValue(); } @@ -775,6 +779,8 @@ ReturnedValue Runtime::method_getIterator(ExecutionEngine *engine, const Value & return engine->throwTypeError(); JSCallData cData(scope, 0, nullptr, o); ScopedObject it(scope, f->call(cData)); + if (engine->hasException) + return Encode::undefined(); if (!it) return engine->throwTypeError(); return it->asReturnedValue(); @@ -1321,7 +1327,8 @@ ReturnedValue Runtime::method_callGlobalLookup(ExecutionEngine *engine, uint ind return throwPropertyIsNotAFunctionTypeError(engine, &thisObject, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()); - return static_cast<FunctionObject &>(function).call(&thisObject, argv, argc); + return checkedResult(engine, static_cast<FunctionObject &>(function).call( + &thisObject, argv, argc)); } ReturnedValue Runtime::method_callQmlContextPropertyLookup(ExecutionEngine *engine, uint index, Value *argv, int argc) @@ -1334,7 +1341,8 @@ ReturnedValue Runtime::method_callQmlContextPropertyLookup(ExecutionEngine *engi return throwPropertyIsNotAFunctionTypeError(engine, thisObject, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]->toQString()); - return static_cast<FunctionObject &>(function).call(thisObject, argv, argc); + return checkedResult(engine, static_cast<FunctionObject &>(function).call( + thisObject, argv, argc)); } ReturnedValue Runtime::method_callPossiblyDirectEval(ExecutionEngine *engine, Value *argv, int argc) @@ -1353,7 +1361,7 @@ ReturnedValue Runtime::method_callPossiblyDirectEval(ExecutionEngine *engine, Va if (function->d() == engine->evalFunction()->d()) return static_cast<EvalFunction *>(function.getPointer())->evalCall(thisObject, argv, argc, true); - return function->call(thisObject, argv, argc); + return checkedResult(engine, function->call(thisObject, argv, argc)); } ReturnedValue Runtime::method_callName(ExecutionEngine *engine, int nameIndex, Value *argv, int argc) @@ -1371,7 +1379,7 @@ ReturnedValue Runtime::method_callName(ExecutionEngine *engine, int nameIndex, V return throwPropertyIsNotAFunctionTypeError(engine, thisObject, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]->toQString()); - return f->call(thisObject, argv, argc); + return checkedResult(engine, f->call(thisObject, argv, argc)); } ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, Value *base, int nameIndex, Value *argv, int argc) @@ -1410,7 +1418,7 @@ ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, Value *base, return engine->throwTypeError(error); } - return f->call(base, argv, argc); + return checkedResult(engine, f->call(base, argv, argc)); } ReturnedValue Runtime::method_callPropertyLookup(ExecutionEngine *engine, Value *base, uint index, Value *argv, int argc) @@ -1422,7 +1430,7 @@ ReturnedValue Runtime::method_callPropertyLookup(ExecutionEngine *engine, Value if (!f.isFunctionObject()) return engine->throwTypeError(); - return static_cast<FunctionObject &>(f).call(base, argv, argc); + return checkedResult(engine, static_cast<FunctionObject &>(f).call(base, argv, argc)); } ReturnedValue Runtime::method_callElement(ExecutionEngine *engine, Value *base, const Value &index, Value *argv, int argc) @@ -1439,7 +1447,7 @@ ReturnedValue Runtime::method_callElement(ExecutionEngine *engine, Value *base, if (!f) return engine->throwTypeError(); - return f->call(base, argv, argc); + return checkedResult(engine, f->call(base, argv, argc)); } ReturnedValue Runtime::method_callValue(ExecutionEngine *engine, const Value &func, Value *argv, int argc) @@ -1447,14 +1455,16 @@ ReturnedValue Runtime::method_callValue(ExecutionEngine *engine, const Value &fu if (!func.isFunctionObject()) return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); Value undef = Value::undefinedValue(); - return static_cast<const FunctionObject &>(func).call(&undef, argv, argc); + return checkedResult(engine, static_cast<const FunctionObject &>(func).call( + &undef, argv, argc)); } ReturnedValue Runtime::method_callWithReceiver(ExecutionEngine *engine, const Value &func, const Value *thisObject, Value *argv, int argc) { if (!func.isFunctionObject()) return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); - return static_cast<const FunctionObject &>(func).call(thisObject, argv, argc); + return checkedResult(engine, static_cast<const FunctionObject &>(func).call( + thisObject, argv, argc)); } struct CallArgs { @@ -1508,7 +1518,8 @@ ReturnedValue Runtime::method_callWithSpread(ExecutionEngine *engine, const Valu if (engine->hasException) return Encode::undefined(); - return static_cast<const FunctionObject &>(function).call(&thisObject, arguments.argv, arguments.argc); + return checkedResult(engine, static_cast<const FunctionObject &>(function).call( + &thisObject, arguments.argv, arguments.argc)); } ReturnedValue Runtime::method_construct(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) @@ -1551,7 +1562,7 @@ ReturnedValue Runtime::method_tailCall(CppStackFrame *frame, ExecutionEngine *en if (!frame->callerCanHandleTailCall || !fo.canBeTailCalled() || engine->debugger() || unsigned(argc) > fo.formalParameterCount()) { // Cannot tailcall, do a normal call: - return fo.call(&thisObject, argv, argc); + return checkedResult(engine, fo.call(&thisObject, argv, argc)); } memcpy(frame->jsFrame->args, argv, argc * sizeof(Value)); diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index 1eef12a491..e01aadb5cf 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -440,6 +440,8 @@ public: argv[0] = convertElementToValue(m_v4, lhs); argv[1] = convertElementToValue(m_v4, rhs); QV4::ScopedValue result(scope, compare->call(m_v4->globalObject, argv, 2)); + if (scope.engine->hasException) + return false; return result->toNumber() < 0; } diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index 6afa6d36d6..43185db676 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -570,7 +570,7 @@ ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value ScopedFunctionObject fo(scope, f); if (!fo) return scope.engine->throwTypeError(); - return fo->call(r, thisObject, 1); + return checkedResult(scope.engine, fo->call(r, thisObject, 1)); } } @@ -590,7 +590,7 @@ ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value ScopedFunctionObject match(scope, that->get(scope.engine->symbol_match())); if (!match) return scope.engine->throwTypeError(); - return match->call(that, s, 1); + return checkedResult(scope.engine, match->call(that, s, 1)); } ReturnedValue StringPrototype::method_normalize(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) @@ -861,6 +861,7 @@ ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Val Value that = Value::undefinedValue(); replacement = searchCallback->call(&that, arguments, numCaptures + 2); + CHECK_EXCEPTION(); result += string.midRef(lastEnd, matchStart - lastEnd); result += replacement->toQString(); lastEnd = matchEnd; diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index faf7934c06..e1ef19e42b 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -765,6 +765,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b arguments[1] = Value::fromDouble(k); arguments[2] = v; r = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); ok = r->toBoolean(); } return Encode(ok); @@ -864,6 +865,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject * arguments[1] = Value::fromDouble(k); arguments[2] = instance; selected = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); if (selected->toBoolean()) { ++arguments; scope.alloc(1); @@ -1203,6 +1205,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, arguments[1] = Value::fromDouble(k); arguments[2] = instance; mapped = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); a->put(k, mapped); } return a->asReturnedValue(); @@ -1252,6 +1255,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject * arguments[2] = Value::fromDouble(k); arguments[3] = instance; acc = callback->call(nullptr, arguments, 4); + CHECK_EXCEPTION(); } ++k; } @@ -1307,6 +1311,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObj arguments[2] = Value::fromDouble(k - 1); arguments[3] = instance; acc = callback->call(nullptr, arguments, 4); + CHECK_EXCEPTION(); } --k; } @@ -1368,6 +1373,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, arguments[1] = Value::fromDouble(k); arguments[2] = instance; result = callback->call(that, arguments, 3); + CHECK_EXCEPTION(); if (result->toBoolean()) return Encode(true); } diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index ce85e48b72..128ae3528d 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -869,6 +869,8 @@ struct ValueArray { } else { while (v < end) { v->mark(markStack); + if (markStack->top >= markStack->limit) + markStack->drain(); ++v; } } diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp index cac68bdcaf..7286b0ad27 100644 --- a/src/qml/memory/qv4mm.cpp +++ b/src/qml/memory/qv4mm.cpp @@ -473,12 +473,15 @@ void Chunk::sortIntoBins(HeapItem **bins, uint nBins) uint freeStart = i*Bits + index; usedSlots &= ~((static_cast<quintptr>(1) << index) - 1); while (!usedSlots) { - ++i; - if (i == EntriesInBitmap) { - usedSlots = (quintptr)-1; + if (++i < EntriesInBitmap) { + usedSlots = (objectBitmap[i]|extendsBitmap[i]); + } else { + Q_ASSERT(i == EntriesInBitmap); + // Overflows to 0 when counting trailing zeroes above in next iteration. + // Then, all the bits are zeroes and we break. + usedSlots = std::numeric_limits<quintptr>::max(); break; } - usedSlots = (objectBitmap[i]|extendsBitmap[i]); #ifndef QT_NO_DEBUG allocatedSlots += qPopulationCount(usedSlots); // qDebug() << hex << " i=" << i << "used=" << usedSlots; @@ -1209,6 +1212,8 @@ void MemoryManager::collectFromJSStack(MarkStack *markStack) const Q_ASSERT(m->inUse()); // Skip pointers to already freed objects, they are bogus as well m->mark(markStack); + if (markStack->top >= markStack->limit) + markStack->drain(); } ++v; } diff --git a/src/qml/qml/qqmlcontext.cpp b/src/qml/qml/qqmlcontext.cpp index 3710cee162..66ba6fdaf3 100644 --- a/src/qml/qml/qqmlcontext.cpp +++ b/src/qml/qml/qqmlcontext.cpp @@ -51,6 +51,7 @@ #include <qjsengine.h> #include <QtCore/qvarlengtharray.h> #include <private/qmetaobject_p.h> +#include <QtQml/private/qqmlcontext_p.h> #include <QtCore/qdebug.h> QT_BEGIN_NAMESPACE @@ -562,8 +563,8 @@ void QQmlContextData::emitDestruction() emit a->destruction(); } - QQmlContextData * child = childContexts; - while (child) { + QQmlContextDataRef child = childContexts; + while (!child.isNull()) { child->emitDestruction(); child = child->nextChild; } @@ -625,12 +626,12 @@ void QQmlContextData::clearContext() void QQmlContextData::destroy() { Q_ASSERT(refCount == 0); - linkedContext = nullptr; // avoid recursion ++refCount; if (engine) invalidate(); + linkedContext = nullptr; Q_ASSERT(refCount == 1); clearContext(); diff --git a/src/qml/qml/qqmldirparser_p.h b/src/qml/qml/qqmldirparser_p.h index bfb75d0509..637852091f 100644 --- a/src/qml/qml/qqmldirparser_p.h +++ b/src/qml/qml/qqmldirparser_p.h @@ -78,7 +78,7 @@ public: static void checkNonRelative(const char *item, const QString &typeName, const QString &fileName) { - if (fileName.startsWith(QLatin1Char('/')) || fileName.contains(QLatin1Char(':'))) { + if (fileName.startsWith(QLatin1Char('/'))) { qWarning() << item << typeName << "is specified with non-relative URL" << fileName << "in a qmldir file." << "URLs in qmldir files should be relative to the qmldir file's directory."; diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index c8166695ba..6f1994bfce 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -907,6 +907,8 @@ QQmlPropertyPrivate::signalExpression(const QQmlProperty &that) if (!(that.type() & QQmlProperty::SignalProperty)) return nullptr; + if (!that.d->object) + return nullptr; QQmlData *data = QQmlData::get(that.d->object); if (!data) return nullptr; @@ -946,6 +948,8 @@ void QQmlPropertyPrivate::takeSignalExpression(const QQmlProperty &that, return; } + if (!that.d->object) + return; QQmlData *data = QQmlData::get(that.d->object, nullptr != expr); if (!data) return; diff --git a/src/qml/qml/qqmlvaluetypewrapper.cpp b/src/qml/qml/qqmlvaluetypewrapper.cpp index b22d1530e2..5d7a2130b2 100644 --- a/src/qml/qml/qqmlvaluetypewrapper.cpp +++ b/src/qml/qml/qqmlvaluetypewrapper.cpp @@ -192,7 +192,11 @@ ReturnedValue QQmlValueTypeWrapper::create(ExecutionEngine *engine, QObject *obj r->d()->object = object; r->d()->property = property; r->d()->setPropertyCache(QJSEnginePrivate::get(engine)->cache(metaObject)); - r->d()->valueType = QQmlValueTypeFactory::valueType(typeId); + auto valueType = QQmlValueTypeFactory::valueType(typeId); + if (!valueType) { + return engine->throwTypeError(QString::fromLatin1("Type %1 is not a value type").arg(QString::fromLocal8Bit(QMetaType::typeName(typeId)))); + } + r->d()->valueType = valueType; r->d()->gadgetPtr = nullptr; return r->asReturnedValue(); } @@ -204,7 +208,11 @@ ReturnedValue QQmlValueTypeWrapper::create(ExecutionEngine *engine, const QVaria Scoped<QQmlValueTypeWrapper> r(scope, engine->memoryManager->allocate<QQmlValueTypeWrapper>()); r->d()->setPropertyCache(QJSEnginePrivate::get(engine)->cache(metaObject)); - r->d()->valueType = QQmlValueTypeFactory::valueType(typeId); + auto valueType = QQmlValueTypeFactory::valueType(typeId); + if (!valueType) { + return engine->throwTypeError(QString::fromLatin1("Type %1 is not a value type").arg(QString::fromLocal8Bit(QMetaType::typeName(typeId)))); + } + r->d()->valueType = valueType; r->d()->gadgetPtr = nullptr; r->d()->setValue(value); return r->asReturnedValue(); diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 69b7876cf6..ff2a783dcd 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -2780,10 +2780,12 @@ bool QQmlListModelParser::applyProperty(const QQmlRefPointer<QV4::CompiledData:: QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr)); QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id])); - QV4::ReturnedValue result = function->call(v4->globalObject, nullptr, 0); - QJSValue v; - QJSValuePrivate::setValue(&v, v4, result); + QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0)); + if (v4->hasException) + v4->catchException(); + else + QJSValuePrivate::setValue(&v, v4, result->asReturnedValue()); value.setValue<QJSValue>(v); } else { QByteArray script = scriptStr.toUtf8(); diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index dc1a9a92f9..186ee06b28 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -84,7 +84,14 @@ Q_LOGGING_CATEGORY(lcPinchHandler, "qt.quick.handler.pinch") \image touchpoints-pinchhandler.png - \sa PinchArea + \note The pinch begins when the number of fingers pressed is between + \l {MultiPointHandler::minimumPointCount}{minimumPointCount} and + \l {MultiPointHandler::maximumPointCount}{maximumPointCount}, inclusive. + Until then, PinchHandler tracks the positions of any pressed fingers, + but if it's a disallowed number, it does not scale or rotate + its \l target, and the \l active property remains \c false. + + \sa PinchArea, QPointerEvent::pointCount() */ QQuickPinchHandler::QQuickPinchHandler(QQuickItem *parent) @@ -248,19 +255,12 @@ bool QQuickPinchHandler::wantsPointerEvent(QQuickPointerEvent *event) */ /*! - \qmlproperty int QtQuick::PinchHandler::minimumTouchPoints - - The pinch begins when this number of fingers are pressed. - Until then, PinchHandler tracks the positions of any pressed fingers, - but if it's an insufficient number, it does not scale or rotate - its \l target, and the \l active property will remain false. -*/ - -/*! \qmlproperty bool QtQuick::PinchHandler::active - This property is true when all the constraints (epecially \l minimumTouchPoints) - are satisfied and the \l target, if any, is being manipulated. + This property is \c true when all the constraints (epecially + \l {MultiPointHandler::minimumPointCount}{minimumPointCount} and + \l {MultiPointHandler::maximumPointCount}{maximumPointCount}) are satisfied + and the \l target, if any, is being manipulated. */ void QQuickPinchHandler::onActiveChanged() diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 9e32ccfee9..a403b393a3 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -464,8 +464,8 @@ void QQuickKeyNavigationAttached::setLeft(QQuickItem *i) Q_D(QQuickKeyNavigationAttached); if (d->leftSet && d->left == i) return; + d->leftSet = d->left != i; d->left = i; - d->leftSet = true; QQuickKeyNavigationAttached* other = qobject_cast<QQuickKeyNavigationAttached*>(qmlAttachedPropertiesObject<QQuickKeyNavigationAttached>(i)); if (other && !other->d_func()->rightSet){ @@ -486,8 +486,8 @@ void QQuickKeyNavigationAttached::setRight(QQuickItem *i) Q_D(QQuickKeyNavigationAttached); if (d->rightSet && d->right == i) return; + d->rightSet = d->right != i; d->right = i; - d->rightSet = true; QQuickKeyNavigationAttached* other = qobject_cast<QQuickKeyNavigationAttached*>(qmlAttachedPropertiesObject<QQuickKeyNavigationAttached>(i)); if (other && !other->d_func()->leftSet){ @@ -508,8 +508,8 @@ void QQuickKeyNavigationAttached::setUp(QQuickItem *i) Q_D(QQuickKeyNavigationAttached); if (d->upSet && d->up == i) return; + d->upSet = d->up != i; d->up = i; - d->upSet = true; QQuickKeyNavigationAttached* other = qobject_cast<QQuickKeyNavigationAttached*>(qmlAttachedPropertiesObject<QQuickKeyNavigationAttached>(i)); if (other && !other->d_func()->downSet){ @@ -530,8 +530,8 @@ void QQuickKeyNavigationAttached::setDown(QQuickItem *i) Q_D(QQuickKeyNavigationAttached); if (d->downSet && d->down == i) return; + d->downSet = d->down != i; d->down = i; - d->downSet = true; QQuickKeyNavigationAttached* other = qobject_cast<QQuickKeyNavigationAttached*>(qmlAttachedPropertiesObject<QQuickKeyNavigationAttached>(i)); if (other && !other->d_func()->upSet) { @@ -552,8 +552,8 @@ void QQuickKeyNavigationAttached::setTab(QQuickItem *i) Q_D(QQuickKeyNavigationAttached); if (d->tabSet && d->tab == i) return; + d->tabSet = d->tab != i; d->tab = i; - d->tabSet = true; QQuickKeyNavigationAttached* other = qobject_cast<QQuickKeyNavigationAttached*>(qmlAttachedPropertiesObject<QQuickKeyNavigationAttached>(i)); if (other && !other->d_func()->backtabSet) { @@ -574,8 +574,8 @@ void QQuickKeyNavigationAttached::setBacktab(QQuickItem *i) Q_D(QQuickKeyNavigationAttached); if (d->backtabSet && d->backtab == i) return; + d->backtabSet = d->backtab != i; d->backtab = i; - d->backtabSet = true; QQuickKeyNavigationAttached* other = qobject_cast<QQuickKeyNavigationAttached*>(qmlAttachedPropertiesObject<QQuickKeyNavigationAttached>(i)); if (other && !other->d_func()->tabSet) { @@ -3753,6 +3753,9 @@ QList<QQuickItem *> QQuickItem::childItems() const as the painting of its children, to its bounding rectangle. If you set clipping during an item's paint operation, remember to re-set it to prevent clipping the rest of your scene. + + \note Clipping can affect rendering performance. See \l {Clipping} for more + information. */ bool QQuickItem::clip() const { diff --git a/src/quick/items/qquickitemgrabresult.cpp b/src/quick/items/qquickitemgrabresult.cpp index f298803c7f..9631eb7bf3 100644 --- a/src/quick/items/qquickitemgrabresult.cpp +++ b/src/quick/items/qquickitemgrabresult.cpp @@ -40,6 +40,7 @@ #include <private/qtquickglobal_p.h> #include "qquickitemgrabresult.h" +#include "qquickrendercontrol.h" #include "qquickwindow.h" #include "qquickitem.h" #if QT_CONFIG(quick_shadereffect) @@ -291,7 +292,11 @@ QQuickItemGrabResult *QQuickItemGrabResultPrivate::create(QQuickItem *item, cons return nullptr; } - if (!item->window()->isVisible()) { + QWindow *effectiveWindow = item->window(); + if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(item->window())) + effectiveWindow = renderWindow; + + if (!effectiveWindow->isVisible()) { qmlWarning(item) << "grabToImage: item's window is not visible"; return nullptr; } diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index c4c22526f3..7ff39b1d88 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -138,6 +138,8 @@ public: bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) override; + void fixupHeader(); + void fixupHeaderCompleted(); QQuickListView::Orientation orient; qreal visiblePos; qreal averageSize; @@ -166,6 +168,12 @@ public: QString nextSection; qreal overshootDist; + + qreal desiredViewportPosition; + qreal fixupHeaderPosition; + bool headerNeedsSeparateFixup : 1; + bool desiredHeaderVisible : 1; + bool correctFlick : 1; bool inFlickCorrection : 1; @@ -179,7 +187,9 @@ public: , highlightPosAnimator(nullptr), highlightWidthAnimator(nullptr), highlightHeightAnimator(nullptr) , highlightMoveVelocity(400), highlightResizeVelocity(400), highlightResizeDuration(-1) , sectionCriteria(nullptr), currentSectionItem(nullptr), nextSectionItem(nullptr) - , overshootDist(0.0), correctFlick(false), inFlickCorrection(false) + , overshootDist(0.0), desiredViewportPosition(0.0), fixupHeaderPosition(0.0) + , headerNeedsSeparateFixup(false), desiredHeaderVisible(false) + , correctFlick(false), inFlickCorrection(false) { highlightMoveDuration = -1; //override default value set in base class } @@ -1387,6 +1397,31 @@ void QQuickListViewPrivate::updateFooter() emit q->footerItemChanged(); } +void QQuickListViewPrivate::fixupHeaderCompleted() +{ + headerNeedsSeparateFixup = false; + QObjectPrivate::disconnect(&timeline, &QQuickTimeLine::updated, this, &QQuickListViewPrivate::fixupHeader); +} + +void QQuickListViewPrivate::fixupHeader() +{ + FxListItemSG *listItem = static_cast<FxListItemSG*>(header); + const bool fixingUp = (orient == QQuickListView::Vertical ? vData : hData).fixingUp; + if (fixingUp && headerPositioning == QQuickListView::PullBackHeader && visibleItems.count()) { + int fixupDura = timeline.duration(); + if (fixupDura < 0) + fixupDura = fixupDuration/2; + const int t = timeline.time(); + + const qreal progress = qreal(t)/fixupDura; + const qreal ultimateHeaderPosition = desiredHeaderVisible ? desiredViewportPosition : desiredViewportPosition - headerSize(); + const qreal headerPosition = fixupHeaderPosition * (1 - progress) + ultimateHeaderPosition * progress; + const qreal viewPos = isContentFlowReversed() ? -position() - size() : position(); + const qreal clampedPos = qBound(originPosition() - headerSize(), headerPosition, lastPosition() - size()); + listItem->setPosition(qBound(viewPos - headerSize(), clampedPos, viewPos)); + } +} + void QQuickListViewPrivate::updateHeader() { Q_Q(QQuickListView); @@ -1404,9 +1439,14 @@ void QQuickListViewPrivate::updateHeader() if (headerPositioning == QQuickListView::OverlayHeader) { listItem->setPosition(isContentFlowReversed() ? -position() - size() : position()); } else if (visibleItems.count()) { + const bool fixingUp = (orient == QQuickListView::Vertical ? vData : hData).fixingUp; if (headerPositioning == QQuickListView::PullBackHeader) { - qreal viewPos = isContentFlowReversed() ? -position() - size() : position(); - qreal clampedPos = qBound(originPosition() - headerSize(), listItem->position(), lastPosition() - headerSize() - size()); + qreal headerPosition = listItem->position(); + const qreal viewPos = isContentFlowReversed() ? -position() - size() : position(); + // Make sure the header is not shown if we absolutely do not have any plans to show it + if (fixingUp && !headerNeedsSeparateFixup) + headerPosition = viewPos - headerSize(); + qreal clampedPos = qBound(originPosition() - headerSize(), headerPosition, lastPosition() - size()); listItem->setPosition(qBound(viewPos - headerSize(), clampedPos, viewPos)); } else { qreal startPos = originPosition(); @@ -1524,13 +1564,46 @@ void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte bias = -bias; tempPosition -= bias; } - FxViewItem *topItem = snapItemAt(tempPosition+highlightRangeStart); + + qreal snapOffset = 0; + qreal overlayHeaderOffset = 0; + bool isHeaderWithinBounds = false; + if (header) { + qreal visiblePartOfHeader = header->position() + header->size() - tempPosition; + isHeaderWithinBounds = visiblePartOfHeader > 0; + switch (headerPositioning) { + case QQuickListView::OverlayHeader: + snapOffset = header->size(); + overlayHeaderOffset = header->size(); + break; + case QQuickListView::InlineHeader: + if (isHeaderWithinBounds && tempPosition < originPosition()) + // For the inline header, we want to snap to the first item + // if we're more than halfway down the inline header. + // So if we look for an item halfway down of the header + snapOffset = header->size() / 2; + break; + case QQuickListView::PullBackHeader: + desiredHeaderVisible = visiblePartOfHeader > header->size()/2; + if (qFuzzyCompare(header->position(), tempPosition)) { + // header was pulled down; make sure it remains visible and snap items to bottom of header + snapOffset = header->size(); + } else if (desiredHeaderVisible) { + // More than 50% of the header is shown. Show it fully. + // Scroll the view so the next item snaps to the header. + snapOffset = header->size(); + overlayHeaderOffset = header->size(); + } + break; + } + } + FxViewItem *topItem = snapItemAt(tempPosition + snapOffset + highlightRangeStart); if (strictHighlightRange && currentItem && (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))) { // StrictlyEnforceRange always keeps an item in range updateHighlight(); topItem = currentItem; } - FxViewItem *bottomItem = snapItemAt(tempPosition+highlightRangeEnd); + FxViewItem *bottomItem = snapItemAt(tempPosition + snapOffset + highlightRangeEnd); if (strictHighlightRange && currentItem && (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))) { // StrictlyEnforceRange always keeps an item in range updateHighlight(); @@ -1538,27 +1611,92 @@ void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExte } qreal pos; bool isInBounds = -position() > maxExtent && -position() <= minExtent; - if (topItem && (isInBounds || strictHighlightRange)) { - if (topItem->index == 0 && header && tempPosition+highlightRangeStart < header->position()+header->size()/2 && !strictHighlightRange) { - pos = isContentFlowReversed() ? - header->position() + highlightRangeStart - size() : header->position() - highlightRangeStart; + + if (header && !topItem && isInBounds) { + // We are trying to pull back further than needed + switch (headerPositioning) { + case QQuickListView::OverlayHeader: + pos = startPosition() - overlayHeaderOffset; + break; + case QQuickListView::InlineHeader: + pos = isContentFlowReversed() ? header->size() - size() : header->position(); + break; + case QQuickListView::PullBackHeader: + pos = isContentFlowReversed() ? -size() : startPosition(); + break; + } + } else if (topItem && (isInBounds || strictHighlightRange)) { + if (topItem->index == 0 && header && !hasStickyHeader() && tempPosition+highlightRangeStart < header->position()+header->size()/2 && !strictHighlightRange) { + pos = isContentFlowReversed() ? -header->position() + highlightRangeStart - size() : (header->position() - highlightRangeStart + header->size()); } else { - if (isContentFlowReversed()) - pos = qMax(qMin(-static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size(), -maxExtent), -minExtent); - else - pos = qMax(qMin(static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart, -maxExtent), -minExtent); + if (header && headerPositioning == QQuickListView::PullBackHeader) { + // We pulled down the header. If it isn't pulled all way down, we need to snap + // the header. + if (qFuzzyCompare(tempPosition, header->position())) { + // It is pulled all way down. Scroll-snap the content, but not the header. + if (isContentFlowReversed()) + pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + snapOffset; + else + pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - snapOffset; + } else { + // Header is not pulled all way down, make it completely visible or hide it. + // Depends on how much of the header is visible. + if (desiredHeaderVisible) { + // More than half of the header is visible - show it. + // Scroll so that the topItem is aligned to a fully visible header + if (isContentFlowReversed()) + pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + headerSize(); + else + pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - headerSize(); + } else { + // Less than half is visible - hide the header. Scroll so + // that the topItem is aligned to the top of the view + if (isContentFlowReversed()) + pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size(); + else + pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart; + } + } + + headerNeedsSeparateFixup = isHeaderWithinBounds || desiredHeaderVisible; + if (headerNeedsSeparateFixup) { + // We need to animate the header independently if it starts visible or should end as visible, + // since the header should not necessarily follow the content. + // Store the desired viewport position. + // Also store the header position so we know where to animate the header from (fixupHeaderPosition). + // We deduce the desired header position from the desiredViewportPosition variable. + pos = qBound(-minExtent, pos, -maxExtent); + desiredViewportPosition = isContentFlowReversed() ? -pos - size() : pos; + + FxListItemSG *headerItem = static_cast<FxListItemSG*>(header); + fixupHeaderPosition = headerItem->position(); + + // follow the same fixup timeline + QObjectPrivate::connect(&timeline, &QQuickTimeLine::updated, this, &QQuickListViewPrivate::fixupHeader); + QObjectPrivate::connect(&timeline, &QQuickTimeLine::completed, this, &QQuickListViewPrivate::fixupHeaderCompleted); + } + } else if (isContentFlowReversed()) { + pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + overlayHeaderOffset; + } else { + pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - overlayHeaderOffset; + } } } else if (bottomItem && isInBounds) { if (isContentFlowReversed()) - pos = qMax(qMin(-static_cast<FxListItemSG*>(bottomItem)->itemPosition() + highlightRangeEnd - size(), -maxExtent), -minExtent); + pos = -static_cast<FxListItemSG*>(bottomItem)->itemPosition() + highlightRangeEnd - size() + overlayHeaderOffset; else - pos = qMax(qMin(static_cast<FxListItemSG*>(bottomItem)->itemPosition() - highlightRangeEnd, -maxExtent), -minExtent); + pos = static_cast<FxListItemSG*>(bottomItem)->itemPosition() - highlightRangeEnd - overlayHeaderOffset; } else { QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); return; } + pos = qBound(-minExtent, pos, -maxExtent); qreal dist = qAbs(data.move + pos); - if (dist > 0) { + if (dist >= 0) { + // Even if dist == 0 we still start the timeline, because we use the same timeline for + // moving the header. And we might need to move the header while the content does not + // need moving timeline.reset(data.move); if (fixupMode != Immediate) { timeline.move(data.move, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index 5124802dbf..0cdd90d529 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -1079,6 +1079,12 @@ void QQuickMouseArea::itemChange(ItemChange change, const ItemChangeData &value) } setHovered(!d->hovered); } + if (d->pressed && (!isVisible())) { + // This happens when the mouse area sets itself disabled or hidden + // inside the press handler. In that case we should not keep the internal + // state as pressed, since we never became the mouse grabber. + ungrabMouse(); + } break; default: break; diff --git a/src/quick/items/qquickshadereffect.cpp b/src/quick/items/qquickshadereffect.cpp index 78458ce6b0..1d931a656f 100644 --- a/src/quick/items/qquickshadereffect.cpp +++ b/src/quick/items/qquickshadereffect.cpp @@ -843,7 +843,11 @@ void QQuickShaderEffect::itemChange(ItemChange change, const ItemChangeData &val return; } #endif - m_impl->handleItemChange(change, value); + // It's possible for itemChange to be called during destruction when deleting + // the QQuickShaderEffectImpl. We nullify m_impl before deleting it via another pointer + // to it, so we must check that it's not null before trying to use it here. + if (m_impl) + m_impl->handleItemChange(change, value); QQuickItem::itemChange(change, value); } diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index aac7137ff3..56ce752987 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -2954,6 +2954,7 @@ void QQuickTextInputPrivate::setTopPadding(qreal value, bool reset) } if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { updateLayout(); + q->updateCursorRectangle(); emit q->topPaddingChanged(); } } @@ -2968,6 +2969,7 @@ void QQuickTextInputPrivate::setLeftPadding(qreal value, bool reset) } if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { updateLayout(); + q->updateCursorRectangle(); emit q->leftPaddingChanged(); } } @@ -2982,6 +2984,7 @@ void QQuickTextInputPrivate::setRightPadding(qreal value, bool reset) } if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { updateLayout(); + q->updateCursorRectangle(); emit q->rightPaddingChanged(); } } @@ -2996,6 +2999,7 @@ void QQuickTextInputPrivate::setBottomPadding(qreal value, bool reset) } if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { updateLayout(); + q->updateCursorRectangle(); emit q->bottomPaddingChanged(); } } @@ -4705,6 +4709,7 @@ void QQuickTextInput::setPadding(qreal padding) d->extra.value().padding = padding; d->updateLayout(); + updateCursorRectangle(); emit paddingChanged(); if (!d->extra.isAllocated() || !d->extra->explicitTopPadding) emit topPaddingChanged(); diff --git a/src/quick/items/qquickview.cpp b/src/quick/items/qquickview.cpp index c411da9519..70d7f57b81 100644 --- a/src/quick/items/qquickview.cpp +++ b/src/quick/items/qquickview.cpp @@ -76,7 +76,7 @@ void QQuickViewPrivate::init(QQmlEngine* e) } QQuickViewPrivate::QQuickViewPrivate() - : root(nullptr), component(nullptr), resizeMode(QQuickView::SizeViewToRootObject), initialSize(0,0) + : component(nullptr), resizeMode(QQuickView::SizeViewToRootObject), initialSize(0,0) { } @@ -92,10 +92,8 @@ void QQuickViewPrivate::execute() return; } - if (root) { + if (root) delete root; - root = nullptr; - } if (component) { delete component; component = nullptr; @@ -212,7 +210,6 @@ QQuickView::~QQuickView() // be a child of the QQuickViewPrivate, and will be destroyed by its dtor Q_D(QQuickView); delete d->root; - d->root = nullptr; } /*! @@ -263,7 +260,8 @@ void QQuickView::setContent(const QUrl& url, QQmlComponent *component, QObject* return; } - d->setRootObject(item); + if (!d->setRootObject(item)) + delete item; emit statusChanged(status()); } @@ -486,43 +484,55 @@ void QQuickView::continueExecute() return; } - d->setRootObject(obj); + if (!d->setRootObject(obj)) + delete obj; emit statusChanged(status()); } /*! \internal + + Sets \a obj as root object and returns true if that operation succeeds. + Otherwise returns \c false. If \c false is returned, the root object is + \c nullptr afterwards. You can explicitly set the root object to nullptr, + and the return value will be \c true. */ -void QQuickViewPrivate::setRootObject(QObject *obj) +bool QQuickViewPrivate::setRootObject(QObject *obj) { Q_Q(QQuickView); if (root == obj) - return; + return true; + + delete root; + if (obj == nullptr) + return true; + if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(obj)) { root = sgItem; sgItem->setParentItem(q->QQuickWindow::contentItem()); QQml_setParent_noEvent(sgItem, q->QQuickWindow::contentItem()); - } else if (qobject_cast<QWindow *>(obj)) { - qWarning() << "QQuickView does not support using windows as a root item." << endl - << endl - << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << endl; - } else { - qWarning() << "QQuickView only supports loading of root objects that derive from QQuickItem." << endl - << endl - << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << endl - << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << endl; - delete obj; - root = nullptr; - } - if (root) { initialSize = rootObjectSize(); if ((resizeMode == QQuickView::SizeViewToRootObject || q->width() <= 1 || q->height() <= 1) && initialSize != q->size()) { q->resize(initialSize); } initResize(); + return true; + } + + if (qobject_cast<QWindow *>(obj)) { + qWarning() << "QQuickView does not support using a window as a root item." << endl + << endl + << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << endl; + return false; } + + qWarning() << "QQuickView only supports loading of root objects that derive from QQuickItem." << endl + << endl + << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << endl + << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << endl; + return false; } /*! diff --git a/src/quick/items/qquickview_p.h b/src/quick/items/qquickview_p.h index 3f284c0519..75e369411f 100644 --- a/src/quick/items/qquickview_p.h +++ b/src/quick/items/qquickview_p.h @@ -91,7 +91,7 @@ public: void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &) override; void initResize(); void updateSize(); - void setRootObject(QObject *); + bool setRootObject(QObject *); void init(QQmlEngine* e = nullptr); diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index c55db04566..3c97475e86 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -70,6 +70,8 @@ #include <QtCore/QLibraryInfo> #include <QtCore/QRunnable> #include <QtQml/qqmlincubator.h> +#include <QtQml/qqmlinfo.h> +#include <QtQml/private/qqmlmetatype_p.h> #include <QtQuick/private/qquickpixmapcache_p.h> @@ -293,6 +295,79 @@ static bool transformDirtyOnItemOrAncestor(const QQuickItem *item) } #endif +/*! + * \internal + + A "polish loop" can occur inside QQuickWindowPrivate::polishItems(). It is when an item calls + polish() on an(other?) item from updatePolish(). If this anomaly happens repeatedly and without + interruption (of a well-behaved updatePolish() that doesn't call polish()), it is a strong + indication that we are heading towards an infinite polish loop. A polish loop is not a bug in + Qt Quick - it is a bug caused by ill-behaved items put in the scene. + + We can detect this sequence of polish loops easily, since the + QQuickWindowPrivate::itemsToPolish is basically a stack: polish() will push to it, and + polishItems() will pop from it. + Therefore if updatePolish() calls polish(), the immediate next item polishItems() processes is + the item that was polished by the previous call to updatePolish(). + We therefore just need to count the number of polish loops we detected in _sequence_. +*/ +struct PolishLoopDetector +{ + PolishLoopDetector(const QVector<QQuickItem*> &itemsToPolish) + : itemsToPolish(itemsToPolish) + { + } + + /* + * returns true when it detected a likely infinite loop + * (suggests it should abort the polish loop) + **/ + bool check(QQuickItem *item, int itemsRemainingBeforeUpdatePolish) + { + if (itemsToPolish.count() > itemsRemainingBeforeUpdatePolish) { + // Detected potential polish loop. + ++numPolishLoopsInSequence; + if (numPolishLoopsInSequence >= 1000) { + // Start to warn about polish loop after 1000 consecutive polish loops + if (numPolishLoopsInSequence == 100000) { + // We have looped 100,000 times without actually reducing the list of items to + // polish, give up for now. + // This is not a fix, just a remedy so that the application can be somewhat + // responsive. + numPolishLoopsInSequence = 0; + return true; + } else if (numPolishLoopsInSequence < 1005) { + // Show the 5 next items involved in the polish loop. + // (most likely they will be the same 5 items...) + QQuickItem *guiltyItem = itemsToPolish.last(); + qmlWarning(item) << "possible QQuickItem::polish() loop"; + + auto typeAndObjectName = [](QQuickItem *item) { + QString typeName = QQmlMetaType::prettyTypeName(item); + QString objName = item->objectName(); + if (!objName.isNull()) + return QString::fromLatin1("%1(%2)").arg(typeName, objName); + return typeName; + }; + + qmlWarning(guiltyItem) << typeAndObjectName(guiltyItem) + << " called polish() inside updatePolish() of " << typeAndObjectName(item); + + if (numPolishLoopsInSequence == 1004) + // Enough warnings. Reset counter in order to speed things up and re-detect + // more loops + numPolishLoopsInSequence = 0; + } + } + } else { + numPolishLoopsInSequence = 0; + } + return false; + } + const QVector<QQuickItem*> &itemsToPolish; // Just a ref to the one in polishItems() + int numPolishLoopsInSequence = 0; +}; + void QQuickWindowPrivate::polishItems() { // An item can trigger polish on another item, or itself for that matter, @@ -300,20 +375,21 @@ void QQuickWindowPrivate::polishItems() // iterate through the set, we must continue pulling items out until it // is empty. // In the case where polish is called from updatePolish() either directly - // or indirectly, we use a recursionSafeguard to print a warning to - // the user. - int recursionSafeguard = INT_MAX; - while (!itemsToPolish.isEmpty() && --recursionSafeguard > 0) { + // or indirectly, we use a PolishLoopDetector to determine if a warning should + // be printed to the user. + + PolishLoopDetector polishLoopDetector(itemsToPolish); + while (!itemsToPolish.isEmpty()) { QQuickItem *item = itemsToPolish.takeLast(); QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); itemPrivate->polishScheduled = false; + const int itemsRemaining = itemsToPolish.count(); itemPrivate->updatePolish(); item->updatePolish(); + if (polishLoopDetector.check(item, itemsRemaining) == true) + break; } - if (recursionSafeguard == 0) - qWarning("QQuickWindow: possible QQuickItem::polish() loop"); - #if QT_CONFIG(im) if (QQuickItem *focusItem = q_func()->activeFocusItem()) { // If the current focus item, or any of its anchestors, has changed location @@ -1655,6 +1731,14 @@ bool QQuickWindow::event(QEvent *e) emit closing(&qev); e->setAccepted(qev.isAccepted()); } break; + case QEvent::PlatformSurface: + if ((static_cast<QPlatformSurfaceEvent *>(e))->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { + // Ensure that the rendering thread is notified before + // the QPlatformWindow is destroyed. + if (d->windowManager) + d->windowManager->hide(this); + } + break; case QEvent::FocusAboutToChange: #if QT_CONFIG(im) if (d->activeFocusItem) diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index 2e91bafa7c..28f75b71d6 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -502,10 +502,18 @@ QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window) void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window) { QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); - if (!cd->isRenderable() || !m_windows.contains(window)) + if (!m_windows.contains(window)) return; + // Even if the window is not renderable, + // renderWindow() called on different window + // should not delete QSGTexture's + // from this unrenderable window. m_windows[window].updatePending = true; + + if (!cd->isRenderable()) + return; + window->requestUpdate(); } diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index 2043b50545..34942bdd9a 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -176,11 +176,8 @@ void QQuickAbstractAnimationPrivate::commence() animationInstance = new QQuickAnimatorProxyJob(animationInstance, q); animationInstance->addAnimationChangeListener(this, QAbstractAnimationJob::Completion); } + emit q->started(); animationInstance->start(); - if (animationInstance->isStopped()) { - running = false; - emit q->stopped(); - } } } @@ -293,10 +290,8 @@ void QQuickAbstractAnimation::setRunning(bool r) d->animationInstance->setLoopCount(d->animationInstance->currentLoop() + d->loopCount); supressStart = true; //we want the animation to continue, rather than restart } - if (!supressStart) { + if (!supressStart) d->commence(); - emit started(); - } } else { if (d->paused) { d->paused = false; //reset paused state to false when stopped @@ -314,7 +309,16 @@ void QQuickAbstractAnimation::setRunning(bool r) } } - emit runningChanged(d->running); + + if (r == d->running) { + // This might happen if we start an animation with 0 duration: This will result in that + // commence() will emit started(), and then when it starts it will call setCurrentTime(0), + // (which is both start and end time of the animation), so it will also end up calling + // setRunning(false) (recursively) and stop the animation. + // Therefore, the state of d->running will in that case be different than r if we are back in + // the root stack frame of the recursive calls to setRunning() + emit runningChanged(d->running); + } } /*! diff --git a/src/quick/util/qquickbehavior.cpp b/src/quick/util/qquickbehavior.cpp index d9b13067ad..31c0ecddf6 100644 --- a/src/quick/util/qquickbehavior.cpp +++ b/src/quick/util/qquickbehavior.cpp @@ -143,7 +143,7 @@ void QQuickBehavior::setAnimation(QQuickAbstractAnimation *animation) void QQuickBehaviorPrivate::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState,QAbstractAnimationJob::State) { - if (!blockRunningChanged) + if (!blockRunningChanged && animation) animation->notifyRunningChanged(newState == QAbstractAnimationJob::Running); } diff --git a/tests/auto/qml/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp b/tests/auto/qml/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp index 57b0905a8a..150734496e 100644 --- a/tests/auto/qml/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp +++ b/tests/auto/qml/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp @@ -68,6 +68,7 @@ private slots: void insertAnimation(); void clear(); void pauseResume(); + void deleteFromListener(); }; void tst_QSequentialAnimationGroupJob::initTestCase() @@ -126,15 +127,23 @@ protected: class StateChangeListener: public QAnimationJobChangeListener { public: - virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State) + virtual void animationStateChanged( + QAbstractAnimationJob *job, QAbstractAnimationJob::State newState, + QAbstractAnimationJob::State) { states << newState; + if (beEvil) { + delete job->group(); + groupDeleted = true; + } } void clear() { states.clear(); } int count() const { return states.count(); } QList<QAbstractAnimationJob::State> states; + bool beEvil = false; + bool groupDeleted = false; }; class FinishedListener: public QAnimationJobChangeListener @@ -1644,6 +1653,37 @@ void tst_QSequentialAnimationGroupJob::uncontrolledWithLoops() QTRY_COMPARE(group.state(), QAbstractAnimationJob::Stopped); } +void tst_QSequentialAnimationGroupJob::deleteFromListener() +{ + QSequentialAnimationGroupJob *group = new QSequentialAnimationGroupJob; + + UncontrolledAnimation *uncontrolled = new UncontrolledAnimation(); + TestAnimation *shortLoop = new TestAnimation(100); + UncontrolledAnimation *more = new UncontrolledAnimation(); + + shortLoop->setLoopCount(-1); + + group->appendAnimation(uncontrolled); + group->appendAnimation(shortLoop); + group->appendAnimation(more); + + StateChangeListener listener; + listener.beEvil = true; + shortLoop->addAnimationChangeListener(&listener, QAbstractAnimationJob::StateChange); + group->setLoopCount(2); + + group->start(); + + QCOMPARE(group->currentLoop(), 0); + QCOMPARE(group->state(), QAbstractAnimationJob::Running); + QTRY_COMPARE(uncontrolled->state(), QAbstractAnimationJob::Running); + + QVERIFY(!listener.groupDeleted); + uncontrolled->stop(); + + QTRY_VERIFY(listener.groupDeleted); + // It's dead, Jim. +} QTEST_MAIN(tst_QSequentialAnimationGroupJob) #include "tst_qsequentialanimationgroupjob.moc" diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index dc8ef232c1..da116aaed0 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -374,6 +374,8 @@ private slots: void getThisObject(); void semicolonAfterProperty(); + void gcCrashRegressionTest(); + private: // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); static void verifyContextLifetime(QQmlContextData *ctxt); @@ -9065,6 +9067,53 @@ void tst_qqmlecmascript::semicolonAfterProperty() QVERIFY(!test.isNull()); } +void tst_qqmlecmascript::gcCrashRegressionTest() +{ + const QString qmljs = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmljs"; + if (!QFile::exists(qmljs)) { + QSKIP("Tets requires qmljs"); + } + QProcess process; + + QTemporaryFile infile; + QVERIFY(infile.open()); + infile.write(R"js( + function i_want_to_break_free() { + var n = 400; + var m = 10; + var regex = new RegExp("(ab)".repeat(n), "g"); // g flag to trigger the vulnerable path + var part = "ab".repeat(n); // matches have to be at least size 2 to prevent interning + var s = (part + "|").repeat(m); + var cnt = 0; + var ary = []; + s.replace(regex, function() { + for (var i = 1; i < arguments.length-2; ++i) { + if (typeof arguments[i] !== 'string') { + i_am_free = arguments[i]; + throw "success"; + } + ary[cnt++] = arguments[i]; // root everything to force GC + } + return "x"; + }); + } + try { i_want_to_break_free(); } catch (e) {console.log("hi") } + console.log(typeof(i_am_free)); // will print "object" + )js"); + infile.close(); + + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + environment.insert("QV4_GC_MAX_STACK_SIZE", "32768"); + + process.setProcessEnvironment(environment); + process.start(qmljs, QStringList({infile.fileName()})); + QVERIFY(process.waitForStarted()); + const qint64 pid = process.processId(); + QVERIFY(pid != 0); + QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitCode(), 0); +} + QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc" diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 27e06c6f67..1bf044ec32 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -149,6 +149,8 @@ private slots: void floatToStringPrecision(); void copy(); + + void signalExpressionWithoutObject(); private: QQmlEngine engine; }; @@ -2106,6 +2108,14 @@ void tst_qqmlproperty::initTestCase() qmlRegisterType<MyContainer>("Test",1,0,"MyContainer"); } +void tst_qqmlproperty::signalExpressionWithoutObject() +{ + QQmlProperty invalid; + QQmlPropertyPrivate::setSignalExpression(invalid, nullptr); + QQmlBoundSignalExpression *expr = QQmlPropertyPrivate::signalExpression(invalid); + QVERIFY(!expr); +} + QTEST_MAIN(tst_qqmlproperty) #include "tst_qqmlproperty.moc" diff --git a/tests/auto/quick/qquickanimations/data/signalorder.qml b/tests/auto/quick/qquickanimations/data/signalorder.qml new file mode 100644 index 0000000000..35029b0c84 --- /dev/null +++ b/tests/auto/quick/qquickanimations/data/signalorder.qml @@ -0,0 +1,27 @@ +import QtQuick 2.12 + + +Item { + id: wrapper + width: 400; height: 400 + + Rectangle { + id: greenRect + width: 50; height: 50 + color: "red" + + ColorAnimation on color { + id: colorAnimation + objectName: "ColorAnimation" + to: "green" + duration: 10 + running: false + } + + ParallelAnimation { + id: parallelAnimation + objectName: "ParallelAnimation" + running: false + } + } +} diff --git a/tests/auto/quick/qquickanimations/qquickanimations.pro b/tests/auto/quick/qquickanimations/qquickanimations.pro index 1c5494a24a..fa46cb422d 100644 --- a/tests/auto/quick/qquickanimations/qquickanimations.pro +++ b/tests/auto/quick/qquickanimations/qquickanimations.pro @@ -64,6 +64,7 @@ OTHER_FILES += \ data/scriptActionCrash.qml \ data/sequentialAnimationNullChildBug.qml \ data/signals.qml \ + data/signalorder.qml \ data/transitionAssignmentBug.qml \ data/valuesource.qml \ data/valuesource2.qml diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index d9b42bdeb5..e0ce2d7134 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -89,6 +89,8 @@ private slots: void easingProperties(); void rotation(); void startStopSignals(); + void signalOrder_data(); + void signalOrder(); void runningTrueBug(); void nonTransitionBug(); void registrationBug(); @@ -1292,6 +1294,47 @@ void tst_qquickanimations::startStopSignals() QVERIFY(timer.elapsed() >= 200); } +void tst_qquickanimations::signalOrder_data() +{ + QTest::addColumn<QByteArray>("animationType"); + QTest::addColumn<int>("duration"); + + QTest::addRow("ColorAnimation, duration = 10") << QByteArray("ColorAnimation") << 10; + QTest::addRow("ColorAnimation, duration = 0") << QByteArray("ColorAnimation") << 0; + QTest::addRow("ParallelAnimation, duration = 0") << QByteArray("ParallelAnimation") << 0; +} + +void tst_qquickanimations::signalOrder() +{ + QFETCH(QByteArray, animationType); + QFETCH(int, duration); + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("signalorder.qml")); + QScopedPointer<QObject> obj(c.create()); + auto *root = qobject_cast<QQuickItem *>(obj.data()); + QVERIFY(root); + QQuickAbstractAnimation *animation = root->findChild<QQuickAbstractAnimation*>(animationType); + QVector<const char*> actualSignalOrder; + connect(animation, &QQuickAbstractAnimation::started , [&actualSignalOrder] () { + actualSignalOrder.append("started"); + }); + connect(animation, &QQuickAbstractAnimation::stopped , [&actualSignalOrder] () { + actualSignalOrder.append("stopped"); + }); + connect(animation, &QQuickAbstractAnimation::finished , [&actualSignalOrder] () { + actualSignalOrder.append("finished"); + }); + QSignalSpy finishedSpy(animation, SIGNAL(finished())); + if (QQuickColorAnimation *colorAnimation = qobject_cast<QQuickColorAnimation*>(animation)) + colorAnimation->setDuration(duration); + + animation->start(); + QTRY_VERIFY(finishedSpy.count()); + const QVector<const char*> expectedSignalOrder = { "started", "stopped", "finished" }; + QCOMPARE(actualSignalOrder, expectedSignalOrder); +} + void tst_qquickanimations::runningTrueBug() { //ensure we start correctly when "running: true" is explicitly set diff --git a/tests/auto/quick/qquickbehaviors/data/delete.qml b/tests/auto/quick/qquickbehaviors/data/delete.qml new file mode 100644 index 0000000000..1bf0267b84 --- /dev/null +++ b/tests/auto/quick/qquickbehaviors/data/delete.qml @@ -0,0 +1,37 @@ +import QtQuick 2.12 + +Item { + visible: true + width: 640 + height: 480 + + Component.onCompleted: { + myLoader.active = false + } + + Loader { + id: myLoader + + active: true + sourceComponent: Item { + width: 100 + height: 100 + id: myPopup + + NumberAnimation { + id: anim + } + + Rectangle { + color: "black" + Component.onCompleted: { + opacity = 20 + } + + Behavior on opacity { + animation: anim + } + } + } + } +} diff --git a/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp b/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp index fa9eba095d..8a962ce240 100644 --- a/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp +++ b/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp @@ -73,6 +73,7 @@ private slots: void disabledWriteWhileRunning(); void aliasedProperty(); void innerBehaviorOverwritten(); + void safeToDelete(); }; void tst_qquickbehaviors::simpleBehavior() @@ -598,6 +599,16 @@ void tst_qquickbehaviors::innerBehaviorOverwritten() QVERIFY(item->property("behaviorTriggered").toBool()); } +// QTBUG-76749 +void tst_qquickbehaviors::safeToDelete() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("delete.qml")); + QVERIFY(c.create()); +} + + + QTEST_MAIN(tst_qquickbehaviors) #include "tst_qquickbehaviors.moc" diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp index 7e132f97b6..61b6902b43 100644 --- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -40,6 +40,7 @@ #include "../../shared/util.h" #include "../shared/viewtestutil.h" #include <QSignalSpy> +#include <QtCore/qregularexpression.h> #ifdef TEST_QTBUG_60123 #include <QWidget> @@ -111,10 +112,15 @@ public: } bool wasPolished; + int repolishLoopCount = 0; protected: virtual void updatePolish() { wasPolished = true; + if (repolishLoopCount > 0) { + --repolishLoopCount; + polish(); + } } public slots: @@ -197,6 +203,9 @@ private slots: void qtBug60123(); #endif + void polishLoopDetection_data(); + void polishLoopDetection(); + private: enum PaintOrderOp { @@ -1427,6 +1436,79 @@ void tst_qquickitem::polishOnCompleted() QTRY_VERIFY(item->wasPolished); } +struct PolishItemSpan { + int itemCount; // Number of items... + int repolishCount; // ...repolishing 'repolishCount' times +}; + +/* + * For instance, two consecutive spans {99,0} and {1,2000} } instructs to + * construct 99 items with no repolish, and 1 item with 2000 repolishes (in that sibling order) + */ +typedef QVector<PolishItemSpan> PolishItemSpans; + +Q_DECLARE_METATYPE(PolishItemSpan) +Q_DECLARE_METATYPE(PolishItemSpans) + +void tst_qquickitem::polishLoopDetection_data() +{ + QTest::addColumn<PolishItemSpans>("listOfItemsToPolish"); + QTest::addColumn<int>("expectedNumberOfWarnings"); + + QTest::newRow("test1.100") << PolishItemSpans({ {1, 100} }) << 0; + QTest::newRow("test1.1002") << PolishItemSpans({ {1, 1002} }) << 3; + QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 10; + + QTest::newRow("test5.1") << PolishItemSpans({ {5, 1} }) << 0; + QTest::newRow("test5.10") << PolishItemSpans({ {5, 10} }) << 0; + QTest::newRow("test5.100") << PolishItemSpans({ {5, 100} }) << 0; + QTest::newRow("test5.1000") << PolishItemSpans({ {5, 1000} }) << 5; + + QTest::newRow("test1000.1") << PolishItemSpans({ {1000,1} }) << 0; + QTest::newRow("test2000.1") << PolishItemSpans({ {2000,1} }) << 0; + + QTest::newRow("test99.0-1.1100") << PolishItemSpans({ {99,0},{1,1100} }) << 5; + QTest::newRow("test98.0-2.1100") << PolishItemSpans({ {98,0},{2,1100} }) << 5+5; + + // reverse the two above + QTest::newRow("test1.1100-99.0") << PolishItemSpans({ {1,1100},{99,0} }) << 5; + QTest::newRow("test2.1100-98.0") << PolishItemSpans({ {2,1100},{98,0} }) << 5+5; +} + +void tst_qquickitem::polishLoopDetection() +{ + QFETCH(PolishItemSpans, listOfItemsToPolish); + QFETCH(int, expectedNumberOfWarnings); + + QQuickWindow window; + window.resize(200, 200); + window.show(); + + TestPolishItem *item = nullptr; + int count = 0; + for (PolishItemSpan s : listOfItemsToPolish) { + for (int i = 0; i < s.itemCount; ++i) { + item = new TestPolishItem(window.contentItem()); + item->setSize(QSizeF(200, 100)); + item->repolishLoopCount = s.repolishCount; + item->setObjectName(QString::fromLatin1("obj%1").arg(count++)); + } + } + + for (int i = 0; i < expectedNumberOfWarnings; ++i) { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*possible QQuickItem..polish.. loop.*")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*TestPolishItem.* called polish.. inside updatePolish.. of TestPolishItem.*")); + } + + QList<QQuickItem*> items = window.contentItem()->childItems(); + for (int i = 0; i < items.count(); ++i) { + static_cast<TestPolishItem*>(items.at(i))->doPolish(); + } + item = static_cast<TestPolishItem*>(items.first()); + // item is the last item, so we wait until the last item reached 0 + QVERIFY(QTest::qWaitFor([=](){return item->repolishLoopCount == 0 && item->wasPolished;})); +} + void tst_qquickitem::wheelEvent_data() { QTest::addColumn<bool>("visible"); diff --git a/tests/auto/quick/qquickitem2/data/keynavigationtest_repeater.qml b/tests/auto/quick/qquickitem2/data/keynavigationtest_repeater.qml new file mode 100644 index 0000000000..12ce10e139 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/keynavigationtest_repeater.qml @@ -0,0 +1,37 @@ +import QtQuick 2.0 + +Grid { + property var textModel: ["1", "2", "3", "4", "5"] + columns: 5 + width: 50*textModel.length + + Repeater { + id: repeater + model: textModel.length + Rectangle { + width: 50 + height: 50 + color: focus ? "red" : "lightgrey" + focus: index == 2 + Text { + id: t + text: textModel[index] + } + KeyNavigation.left: repeater.itemAt(index - 1) + KeyNavigation.right: repeater.itemAt(index + 1) + } + } + + function verify() { + for (var i = 0; i < repeater.count; i++) { + var item = repeater.itemAt(i); + var prev = repeater.itemAt(i - 1); + var next = repeater.itemAt(i + 1); + if (item.KeyNavigation.left != prev || item.KeyNavigation.right != next) + return false; + } + + return true; + } +} + diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index 399535cfa6..db473d1ea1 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -89,6 +89,7 @@ private slots: void keyNavigation_implicitDestroy(); void keyNavigation_focusReason(); void keyNavigation_loop(); + void keyNavigation_repeater(); void layoutMirroring(); void layoutMirroringWindow(); void layoutMirroringIllegalParent(); @@ -2273,6 +2274,24 @@ void tst_QQuickItem::keyNavigation_loop() delete window; } +void tst_QQuickItem::keyNavigation_repeater() +{ + // QTBUG-83356 + QQuickView *window = new QQuickView(nullptr); + window->setBaseSize(QSize(240,320)); + + window->setSource(testFileUrl("keynavigationtest_repeater.qml")); + window->show(); + window->requestActivate(); + + QVariant result; + QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "verify", + Q_RETURN_ARG(QVariant, result))); + QVERIFY(result.toBool()); + + delete window; +} + void tst_QQuickItem::smooth() { QQmlComponent component(&engine); diff --git a/tests/auto/quick/qquicklistview/data/headerSnapToItem.qml b/tests/auto/quick/qquicklistview/data/headerSnapToItem.qml new file mode 100644 index 0000000000..1e5a811630 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/headerSnapToItem.qml @@ -0,0 +1,67 @@ +import QtQuick 2.12 + +Rectangle { + + width: 240 + height: 320 + color: "#ffffff" + + Component { + id: myDelegate + Rectangle { + id: wrapper + objectName: "wrapper" + width: list.orientation == ListView.Vertical ? 240 : 20 + height: list.orientation == ListView.Vertical ? 20 : 240 + border.width: 1 + border.color: "black" + Text { + text: index + ":" + (list.orientation == ListView.Vertical ? parent.y : parent.x).toFixed(0) + } + color: ListView.isCurrentItem ? "lightsteelblue" : "white" + } + } + + ListView { + id: list + objectName: "list" + focus: true + width: 240 + height: 200 + clip: true + snapMode: ListView.SnapToItem + headerPositioning: ListView.OverlayHeader + model: 30 + delegate: myDelegate + orientation: ListView.Vertical + verticalLayoutDirection: ListView.BottomToTop + + header: Rectangle { + width: list.orientation == Qt.Vertical ? 240 : 30 + height: list.orientation == Qt.Vertical ? 30 : 240 + objectName: "header"; + color: "green" + z: 11 + Text { + anchors.centerIn: parent + text: "header " + (list.orientation == ListView.Vertical ? parent.y : parent.x).toFixed(1) + } + } + } + + Rectangle { + color: "red" + opacity: 0.5 + width: txt.implicitWidth + 50 + height: txt.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + + Text { + id: txt + anchors.centerIn: parent + text: "header position: " + (list.orientation == ListView.Vertical ? list.headerItem.y : list.headerItem.x).toFixed(1) + + "\ncontent position: " + (list.orientation == ListView.Vertical ? list.contentY : list.contentX).toFixed(1) + } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 25212b06f6..be3f2601d6 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -30,6 +30,7 @@ #include <QtCore/QStringListModel> #include <QtCore/QSortFilterProxyModel> #include <QtGui/QStandardItemModel> +#include <QtGui/QStyleHints> #include <QtQuick/qquickview.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> @@ -181,6 +182,8 @@ private slots: void creationContext(); void snapToItem_data(); void snapToItem(); + void headerSnapToItem_data(); + void headerSnapToItem(); void snapToItemWithSpacing_QTBUG_59852(); void snapOneItemResize_QTBUG_43555(); void snapOneItem_data(); @@ -5364,6 +5367,600 @@ void tst_QQuickListView::snapToItemWithSpacing_QTBUG_59852() releaseView(window); } +static void drag_helper(QWindow *window, QPoint *startPos, const QPoint &delta) +{ + QPoint pos = *startPos; + int dragDistance = delta.manhattanLength(); + Q_ASSERT(qAbs(delta.x()) >= 1 || qAbs(delta.y()) >= 1); + + const int stepSize = 8; + QPoint unitVector(0, 0); + if (delta.x()) + unitVector.setX(qBound(-1, delta.x(), 1)); + if (delta.y()) + unitVector.setY(qBound(-1, delta.y(), 1)); + QPoint dragStepSize = unitVector * stepSize; + int nDragSteps = qAbs(dragDistance/stepSize); + + for (int i = 0 ; i < nDragSteps; ++i) { + QTest::mouseMove(window, pos); + pos += dragStepSize; + } + // Move to the final position + pos = *startPos + delta; + QTest::mouseMove(window, pos); + *startPos = pos; +} + +static void dragtwice(QWindow *window, QPoint *startPos, const QPoint &delta1, const QPoint &delta2) +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QPoint &pos = *startPos; + QPoint unitVector(0, 0); + if (delta1.x()) + unitVector.setX(qBound(-1, delta1.x(), 1)); + if (delta1.y()) + unitVector.setY(qBound(-1, delta1.y(), 1)); + + // go just beyond the drag theshold + drag_helper(window, &pos, unitVector * (dragThreshold + 1)); + drag_helper(window, &pos, unitVector); + + // next drag will actually scroll the listview + if (delta1.manhattanLength() >= 1) + drag_helper(window, &pos, delta1); + if (delta2.manhattanLength() >= 1) + drag_helper(window, &pos, delta2); +} + +struct MyListView : public QQuickListView{ + qreal contentPosition() const + { + return (orientation() == QQuickListView::Horizontal ? contentX(): contentY()); + } + + qreal headerPosition() const + { + return (orientation() == QQuickListView::Horizontal ? headerItem()->x() : headerItem()->y()); + } +}; + +void tst_QQuickListView::headerSnapToItem() +{ + QFETCH(QQuickItemView::LayoutDirection, layoutDirection); + QFETCH(QQuickListView::HeaderPositioning, headerPositioning); + QFETCH(int, firstDragDistance); + QFETCH(int, secondDragDistance); + QFETCH(int, expectedContentPosition); + QFETCH(int, expectedHeaderPosition); + + QQuickView *window = getView(); + window->setSource(testFileUrl("headerSnapToItem.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + MyListView *listview = static_cast<MyListView *>(findItem<QQuickListView>(window->rootObject(), "list")); + QVERIFY(listview != nullptr); + + const bool horizontal = layoutDirection < QQuickItemView::VerticalTopToBottom; + listview->setOrientation(horizontal ? QQuickListView::Horizontal : QQuickListView::Vertical); + + if (horizontal) + listview->setLayoutDirection(static_cast<Qt::LayoutDirection>(layoutDirection)); + else + listview->setVerticalLayoutDirection(static_cast<QQuickItemView::VerticalLayoutDirection>(layoutDirection)); + + listview->setHeaderPositioning(headerPositioning); + QTest::qWaitFor([&]() { return !QQuickItemPrivate::get(listview)->polishScheduled; }); + + QQuickItem *contentItem = listview->contentItem(); + QVERIFY(contentItem != nullptr); + QQuickItem *header = findItem<QQuickItem>(contentItem, "header"); + QVERIFY(header != nullptr); + QCOMPARE(header, listview->headerItem()); + + QPoint startPos = (QPointF(listview->width(), listview->height())/2).toPoint(); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, startPos, 200); + + QPoint firstDragDelta = horizontal ? QPoint(firstDragDistance, 0) : QPoint(0, firstDragDistance); + QPoint secondDragDelta = horizontal ? QPoint(secondDragDistance, 0) : QPoint(0, secondDragDistance); + + dragtwice(window, &startPos, firstDragDelta, secondDragDelta); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, startPos, 200); // Wait 200 ms before we release to avoid trigger a flick + + // wait for the "fixup" animation to finish + QTest::qWaitFor([&]() + { return !listview->isMoving();} + ); + + QCOMPARE(listview->contentPosition(), expectedContentPosition); + QCOMPARE(listview->headerPosition(), expectedHeaderPosition); +} + +void tst_QQuickListView::headerSnapToItem_data() +{ + QTest::addColumn<QQuickItemView::LayoutDirection>("layoutDirection"); + QTest::addColumn<QQuickListView::HeaderPositioning>("headerPositioning"); + QTest::addColumn<int>("firstDragDistance"); + QTest::addColumn<int>("secondDragDistance"); + QTest::addColumn<int>("expectedContentPosition"); + QTest::addColumn<int>("expectedHeaderPosition"); + + // -------------------- + // InlineHeader TopToBottom + QTest::newRow("InlineHeader TopToBottom -10") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -10 << 0 + << -30 << -30; + + QTest::newRow("InlineHeader TopToBottom -14") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -14 << 0 + << -30 << -30; + + QTest::newRow("InlineHeader TopToBottom -16") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -16 << 0 + << 0 << -30; + + QTest::newRow("InlineHeader TopToBottom -30") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -30 << 0 + << 0 << -30; + + QTest::newRow("InlineHeader TopToBottom -39") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -39 << 0 + << 0 << -30; + + QTest::newRow("InlineHeader TopToBottom -41") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -41 << 0 + << 20 << -30; + + QTest::newRow("InlineHeader TopToBottom -65+10") << QQuickItemView::VerticalTopToBottom + << QQuickListView::InlineHeader + << -65 << 10 + << 20 << -30; + + // -------------------- + // InlineHeader BottomToTop + QTest::newRow("InlineHeader BottomToTop +10") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 10 << 0 + << -170 << 0; + + QTest::newRow("InlineHeader BottomToTop +14") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 14 << 0 + << -170 << 0; + + QTest::newRow("InlineHeader BottomToTop +16") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 16 << 0 + << -200 << 0; + + QTest::newRow("InlineHeader BottomToTop +30") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 30 << 0 + << -200 << 0; + + QTest::newRow("InlineHeader BottomToTop +39") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 39 << 0 + << -200 << 0; + + QTest::newRow("InlineHeader BottomToTop +41") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 41 << 0 + << -220 << 0; + + QTest::newRow("InlineHeader BottomToTop +65-10") << QQuickItemView::VerticalBottomToTop + << QQuickListView::InlineHeader + << 65 << -10 + << -220 << 0; + + // -------------------- + // InlineHeader LeftToRight + QTest::newRow("InlineHeader LeftToRight -10") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -10 << 0 + << -30 << -30; + + QTest::newRow("InlineHeader LeftToRight -14") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -14 << 0 + << -30 << -30; + + QTest::newRow("InlineHeader LeftToRight -16") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -16 << 0 + << 0 << -30; + + QTest::newRow("InlineHeader LeftToRight -30") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -30 << 0 + << 0 << -30; + + QTest::newRow("InlineHeader LeftToRight -39") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -39 << 0 + << 0 << -30; + + QTest::newRow("InlineHeader LeftToRight -41") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -41 << 0 + << 20 << -30; + + QTest::newRow("InlineHeader LeftToRight -65+10") << QQuickItemView::LeftToRight + << QQuickListView::InlineHeader + << -65 << 10 + << 20 << -30; + + // -------------------- + // InlineHeader RightToLeft + QTest::newRow("InlineHeader RightToLeft +10") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 10 << 0 + << -210 << 0; + + QTest::newRow("InlineHeader RightToLeft +14") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 14 << 0 + << -210 << 0; + + QTest::newRow("InlineHeader RightToLeft +16") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 16 << 0 + << -240 << 0; + + QTest::newRow("InlineHeader RightToLeft +30") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 30 << 0 + << -240 << 0; + + QTest::newRow("InlineHeader RightToLeft +39") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 39 << 0 + << -240 << 0; + + QTest::newRow("InlineHeader RightToLeft +41") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 41 << 0 + << -260 << 0; + + QTest::newRow("InlineHeader RightToLeft +65-10") << QQuickItemView::RightToLeft + << QQuickListView::InlineHeader + << 65 << -10 + << -260 << 0; + + // -------------------- + // OverlayHeader TopToBottom + QTest::newRow("OverlayHeader TopToBottom +9") << QQuickItemView::VerticalTopToBottom + << QQuickListView::OverlayHeader + << 9 << 0 + << -30 << -30; + + QTest::newRow("OverlayHeader TopToBottom -9") << QQuickItemView::VerticalTopToBottom + << QQuickListView::OverlayHeader + << -9 << 0 + << -30 << -30; + + QTest::newRow("OverlayHeader TopToBottom -11") << QQuickItemView::VerticalTopToBottom + << QQuickListView::OverlayHeader + << -11 << 0 + << -10 << -10; + + QTest::newRow("OverlayHeader TopToBottom -29") << QQuickItemView::VerticalTopToBottom + << QQuickListView::OverlayHeader + << -29 << 0 + << -10 << -10; + + QTest::newRow("OverlayHeader TopToBottom -31") << QQuickItemView::VerticalTopToBottom + << QQuickListView::OverlayHeader + << -31 << 0 + << 10 << 10; + + // -------------------- + // OverlayHeader BottomToTop + QTest::newRow("OverlayHeader BottomToTop -9") << QQuickItemView::VerticalBottomToTop + << QQuickListView::OverlayHeader + << -9 << 0 + << -170 << 0; + + QTest::newRow("OverlayHeader BottomToTop +9") << QQuickItemView::VerticalBottomToTop + << QQuickListView::OverlayHeader + << 9 << 0 + << -170 << 0; + + QTest::newRow("OverlayHeader BottomToTop +11") << QQuickItemView::VerticalBottomToTop + << QQuickListView::OverlayHeader + << 11 << 0 + << -190 << -20; + + QTest::newRow("OverlayHeader BottomToTop +29") << QQuickItemView::VerticalBottomToTop + << QQuickListView::OverlayHeader + << 29 << 0 + << -190 << -20; + + QTest::newRow("OverlayHeader BottomToTop +31") << QQuickItemView::VerticalBottomToTop + << QQuickListView::OverlayHeader + << 31 << 0 + << -210 << -40; + + // -------------------- + // OverlayHeader LeftToRight + QTest::newRow("OverlayHeader LeftToRight +9") << QQuickItemView::LeftToRight + << QQuickListView::OverlayHeader + << 9 << 0 + << -30 << -30; + + QTest::newRow("OverlayHeader LeftToRight -9") << QQuickItemView::LeftToRight + << QQuickListView::OverlayHeader + << -9 << 0 + << -30 << -30; + + QTest::newRow("OverlayHeader LeftToRight -11") << QQuickItemView::LeftToRight + << QQuickListView::OverlayHeader + << -11 << 0 + << -10 << -10; + + QTest::newRow("OverlayHeader LeftToRight -29") << QQuickItemView::LeftToRight + << QQuickListView::OverlayHeader + << -29 << 0 + << -10 << -10; + + QTest::newRow("OverlayHeader LeftToRight -31") << QQuickItemView::LeftToRight + << QQuickListView::OverlayHeader + << -31 << 0 + << 10 << 10; + + // -------------------- + // OverlayHeader RightToLeft + QTest::newRow("OverlayHeader RightToLeft -9") << QQuickItemView::RightToLeft + << QQuickListView::OverlayHeader + << -9 << 0 + << -210 << 0; + + QTest::newRow("OverlayHeader RightToLeft +9") << QQuickItemView::RightToLeft + << QQuickListView::OverlayHeader + << 9 << 0 + << -210 << 0; + + QTest::newRow("OverlayHeader RightToLeft +11") << QQuickItemView::RightToLeft + << QQuickListView::OverlayHeader + << 11 << 0 + << -230 << -20; + + QTest::newRow("OverlayHeader RightToLeft +29") << QQuickItemView::RightToLeft + << QQuickListView::OverlayHeader + << 29 << 0 + << -230 << -20; + + QTest::newRow("OverlayHeader RightToLeft +31") << QQuickItemView::RightToLeft + << QQuickListView::OverlayHeader + << 31 << 0 + << -250 << -40; + + // -------------------- + // PullbackHeader TopToBottom + QTest::newRow("PullbackHeader TopToBottom -2") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -2 << 0 + << -30 << -30; + + QTest::newRow("PullbackHeader TopToBottom -10") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -10 << 0 + << -30 << -30; + + QTest::newRow("PullbackHeader TopToBottom -11") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -11 << 0 + << -10 << -10; + + QTest::newRow("PullbackHeader TopToBottom -14") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -14 << 0 + << -10 << -10; + + QTest::newRow("PullbackHeader TopToBottom -16") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -16 << 0 + << 0 << -30; + + QTest::newRow("PullbackHeader TopToBottom -20") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -20 << 0 + << 0 << -30; + + QTest::newRow("PullbackHeader TopToBottom -65+10") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -65 << 10 + << 20 << -10; + + QTest::newRow("PullbackHeader TopToBottom -65+20") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -65 << 20 + << 10 << 10; + + // Should move header even if contentY doesn't move (its aligned with top) + QTest::newRow("PullbackHeader TopToBottom -55+5") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -55 << 5 + << 20 << -10; + + // Should move header even if contentY doesn't move (it's aligned with header) + QTest::newRow("PullbackHeader TopToBottom -76+16") << QQuickItemView::VerticalTopToBottom + << QQuickListView::PullBackHeader + << -76 << 16 + << 30 << 30; + + // -------------------- + // PullbackHeader BottomToTop + QTest::newRow("PullbackHeader BottomToTop +2") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +2 << 0 + << -170 << 0; + + QTest::newRow("PullbackHeader BottomToTop +9") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +9 << 0 + << -170 << 0; + + QTest::newRow("PullbackHeader BottomToTop +11") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +11 << 0 + << -190 << -20; + + QTest::newRow("PullbackHeader BottomToTop +14") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +14 << 0 + << -190 << -20; + + QTest::newRow("PullbackHeader BottomToTop +16") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +16 << 0 + << -200 << 0; + + QTest::newRow("PullbackHeader BottomToTop +20") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +20 << 0 + << -200 << 0; + + QTest::newRow("PullbackHeader BottomToTop +65-10") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +65 << -10 + << -220 << -20; + + QTest::newRow("PullbackHeader BottomToTop +65-20") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << +65 << -20 + << -210 << -40; + + // Should move header even if contentY doesn't move (it's aligned with top) + QTest::newRow("PullbackHeader BottomToTop +55-5") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << 55 << -5 + << -220 << -20; + + // Should move header even if contentY doesn't move (it's aligned with header) + QTest::newRow("PullbackHeader BottomToTop +76-16") << QQuickItemView::VerticalBottomToTop + << QQuickListView::PullBackHeader + << 76 << -16 + << -230 << -60; + + // -------------------- + // PullbackHeader LeftToRight + QTest::newRow("PullbackHeader LeftToRight -2") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -2 << 0 + << -30 << -30; + + QTest::newRow("PullbackHeader LeftToRight -10") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -10 << 0 + << -30 << -30; + + QTest::newRow("PullbackHeader LeftToRight -11") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -11 << 0 + << -10 << -10; + + QTest::newRow("PullbackHeader LeftToRight -14") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -14 << 0 + << -10 << -10; + + QTest::newRow("PullbackHeader LeftToRight -16") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -16 << 0 + << 0 << -30; + + QTest::newRow("PullbackHeader LeftToRight -20") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -20 << 0 + << 0 << -30; + + QTest::newRow("PullbackHeader LeftToRight -65+10") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -65 << 10 + << 20 << -10; + + QTest::newRow("PullbackHeader LeftToRight -65+20") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -65 << 20 + << 10 << 10; + + // Should move header even if contentX doesn't move (its aligned with top) + QTest::newRow("PullbackHeader LeftToRight -55+5") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -55 << 5 + << 20 << -10; + + // Should move header even if contentX doesn't move (it's aligned with header) + QTest::newRow("PullbackHeader LeftToRight -76+16") << QQuickItemView::LeftToRight + << QQuickListView::PullBackHeader + << -76 << 16 + << 30 << 30; + + // -------------------- + // PullbackHeader RightToLeft + QTest::newRow("PullbackHeader RightToLeft +2") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +2 << 0 + << -210 << 0; + + QTest::newRow("PullbackHeader RightToLeft +9") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +9 << 0 + << -210 << 0; + + QTest::newRow("PullbackHeader RightToLeft +11") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +11 << 0 + << -230 << -20; + + QTest::newRow("PullbackHeader RightToLeft +14") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +14 << 0 + << -230 << -20; + + QTest::newRow("PullbackHeader RightToLeft +16") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +16 << 0 + << -240 << 0; + + QTest::newRow("PullbackHeader RightToLeft +20") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +20 << 0 + << -240 << 0; + + QTest::newRow("PullbackHeader RightToLeft +65-10") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +65 << -10 + << -260 << -20; + + QTest::newRow("PullbackHeader RightToLeft +65-20") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << +65 << -20 + << -250 << -40; + + // Should move header even if contentX doesn't move (it's aligned with top) + QTest::newRow("PullbackHeader RightToLeft +55-5") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << 55 << -5 + << -260 << -20; + + // Should move header even if contentX doesn't move (it's aligned with header) + QTest::newRow("PullbackHeader RightToLeft +76-16") << QQuickItemView::RightToLeft + << QQuickListView::PullBackHeader + << 76 << -16 + << -270 << -60; + +} + void tst_QQuickListView::snapOneItemResize_QTBUG_43555() { QScopedPointer<QQuickView> window(createView()); diff --git a/tests/auto/quick/qquickmousearea/data/settingHiddenInPressUngrabs.qml b/tests/auto/quick/qquickmousearea/data/settingHiddenInPressUngrabs.qml new file mode 100644 index 0000000000..4532dabfd8 --- /dev/null +++ b/tests/auto/quick/qquickmousearea/data/settingHiddenInPressUngrabs.qml @@ -0,0 +1,36 @@ +import QtQuick 2.11 +import QtQuick.Window 2.11 + +Window { + id: window + visible: true + width: 200 + height: 200 + + MouseArea { + objectName: "cat" + anchors.fill: parent + onPressed: visible = false + width: parent.width / 2 + height: parent.height + Rectangle { + width: 10 + height: 10 + color: "blue" + } + } + MouseArea { + objectName: "mouse" + x: parent.width / 2 + width: parent.width / 2 + height: parent.height + onPressed: { + enabled = false + } + Rectangle { + width: 10 + height: 10 + color: "blue" + } + } +} diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 52d1458a53..0c44121830 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -158,6 +158,7 @@ private slots: void pressOneAndTapAnother(); void mask(); void nestedEventDelivery(); + void settingHiddenInPressUngrabs(); private: int startDragDistance() const { @@ -2306,6 +2307,43 @@ void tst_QQuickMouseArea::nestedEventDelivery() // QTBUG-70898 QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, QPoint(50,150)); } +void tst_QQuickMouseArea::settingHiddenInPressUngrabs() +{ + // When an item sets itself hidden, while handling pressed, it doesn't receive the grab. + // But that in turn means it doesn't see any release events, so we need to make sure it + // receives an ungrab event. + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("settingHiddenInPressUngrabs.qml")); + QScopedPointer<QQuickWindow> window(qmlobject_cast<QQuickWindow *>(c.create())); + QVERIFY(window.data()); + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickMouseArea *catArea = window->findChild<QQuickMouseArea*>("cat"); + QVERIFY(catArea != nullptr); + auto pointOnCatArea = catArea->mapToScene(QPointF(5.0, 5.0)).toPoint(); + QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, pointOnCatArea); + + QCoreApplication::processEvents(); + // The click hides the cat area + QTRY_VERIFY(!catArea->isVisible()); + // The cat area is not stuck in pressed state. + QVERIFY(!catArea->pressed()); + + QQuickMouseArea *mouseArea = window->findChild<QQuickMouseArea*>("mouse"); + QVERIFY(mouseArea != nullptr); + auto pointOnMouseArea = mouseArea->mapToScene(QPointF(5.0, 5.0)).toPoint(); + QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, pointOnMouseArea); + + QCoreApplication::processEvents(); + // The click disables the mouse area + QTRY_VERIFY(!mouseArea->isEnabled()); + // The mouse area is not stuck in pressed state. + QVERIFY(!mouseArea->pressed()); +} + QTEST_MAIN(tst_QQuickMouseArea) #include "tst_qquickmousearea.moc" diff --git a/tests/auto/quick/qquickshadereffect/data/hideParent.qml b/tests/auto/quick/qquickshadereffect/data/hideParent.qml new file mode 100644 index 0000000000..aeeb5b5f78 --- /dev/null +++ b/tests/auto/quick/qquickshadereffect/data/hideParent.qml @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 + +Item { + id: root + width: 640 + height: 480 + objectName: "qtbug86402Container" + + property bool finished + + Item { + id: popup + objectName: "popup" + width: 200 + height: 200 + + Rectangle { + id: rect + objectName: "rect" + implicitWidth: 100 + implicitHeight: 100 + color: "blue" + + Item { + id: ripple + objectName: "ripple" + anchors.fill: parent + visible: false + + Rectangle { + id: rippleBox + objectName: "rippleBox" + property real cx + property real cy + x: cx - width / 2 + y: cy - height / 2 + width: 0 + height: width + radius: width / 2 + color: Qt.darker("red", 1.8) + } + layer.effect: ShaderEffect { + id: mask + objectName: "shaderEffect" + property variant source + property variant mask: rect + + fragmentShader: " + uniform lowp sampler2D source; + uniform lowp sampler2D mask; + varying highp vec2 qt_TexCoord0; + void main() { + gl_FragColor = texture2D(source, qt_TexCoord0) * texture2D(mask, qt_TexCoord0).a; + }" + } + } + + SequentialAnimation { + id: rippleStartAnimation + running: popup.visible + onFinished: { + popup.parent = null + rippleEndAnimation.start() + } + + ScriptAction { + script: { + rippleBox.width = 0 + rippleBox.opacity = 0.3 + ripple.visible = true + ripple.layer.enabled = true + } + } + NumberAnimation { + target: rippleBox + property: "width" + from: 0 + to: Math.max(rect.width, + rect.height) * 2.2 + duration: 100 + } + } + SequentialAnimation { + id: rippleEndAnimation + + onFinished: root.finished = true + + //Causes Crash on QT Versions > 5.12.5 + NumberAnimation { + target: rippleBox + property: "opacity" + to: 0 + duration: 100 + } + ScriptAction { + script: { + rippleBox.opacity = 0 + ripple.layer.enabled = false + ripple.visible = false + } + } + } + } + } +} diff --git a/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp b/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp index f601f520bb..1db4c8e0d5 100644 --- a/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp +++ b/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp @@ -79,6 +79,8 @@ private slots: void deleteShaderEffectSource(); void twoImagesOneShaderEffect(); + void hideParent(); + private: enum PresenceFlags { VertexPresent = 0x01, @@ -320,6 +322,18 @@ void tst_qquickshadereffect::twoImagesOneShaderEffect() delete view; } +// QTBUG-86402: hiding the parent of an item that uses an effect should not cause a crash. +void tst_qquickshadereffect::hideParent() +{ + QScopedPointer<QQuickView> view(new QQuickView); + view->setSource(testFileUrl("hideParent.qml")); + QCOMPARE(view->status(), QQuickView::Ready); + view->show(); + QVERIFY(QTest::qWaitForWindowExposed(view.data())); + // Should finish without crashing. + QTRY_VERIFY(view->rootObject()->property("finished").toBool()); +} + QTEST_MAIN(tst_qquickshadereffect) #include "tst_qquickshadereffect.moc" diff --git a/tests/auto/quick/qquicktext/BLACKLIST b/tests/auto/quick/qquicktext/BLACKLIST index 531d981159..9317ee37bc 100644 --- a/tests/auto/quick/qquicktext/BLACKLIST +++ b/tests/auto/quick/qquicktext/BLACKLIST @@ -4,3 +4,5 @@ msvc-2015 [fontSizeMode] opensuse-42.1 +[hAlignVisual] +sles diff --git a/tests/auto/quick/qquicktextinput/data/checkCursorDelegateWhenPaddingChanged.qml b/tests/auto/quick/qquicktextinput/data/checkCursorDelegateWhenPaddingChanged.qml new file mode 100644 index 0000000000..e6f07b4687 --- /dev/null +++ b/tests/auto/quick/qquicktextinput/data/checkCursorDelegateWhenPaddingChanged.qml @@ -0,0 +1,16 @@ +import QtQuick 2.12 + +Rectangle { + width: 200 + height: 200 + TextInput { + objectName: "textInput" + leftPadding: 10 + focus: true + cursorDelegate: Rectangle { + objectName: "cursorDelegate" + width: 5 + color: "red" + } + } +} diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index ed2d535fda..f736d0a873 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -231,6 +231,7 @@ private slots: void QTBUG_51115_readOnlyResetsSelection(); + void checkCursorDelegateWhenPaddingChanged(); private: void simulateKey(QWindow *, int key); @@ -7003,6 +7004,35 @@ void tst_qquicktextinput::QTBUG_51115_readOnlyResetsSelection() QCOMPARE(obj->selectedText(), QString()); } +void tst_qquicktextinput::checkCursorDelegateWhenPaddingChanged() +{ + QQuickView view; + view.setSource(testFileUrl("checkCursorDelegateWhenPaddingChanged.qml")); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + QQuickTextInput *textInput = view.rootObject()->findChild<QQuickTextInput *>("textInput"); + QVERIFY(textInput); + + QQuickItem *cursorDelegate = textInput->findChild<QQuickItem *>("cursorDelegate"); + QVERIFY(cursorDelegate); + + QCOMPARE(cursorDelegate->x(), textInput->leftPadding()); + QCOMPARE(cursorDelegate->y(), textInput->topPadding()); + + textInput->setPadding(5); + QCOMPARE(cursorDelegate->x(), textInput->leftPadding()); + QCOMPARE(cursorDelegate->y(), textInput->topPadding()); + + textInput->setTopPadding(10); + QCOMPARE(cursorDelegate->x(), textInput->leftPadding()); + QCOMPARE(cursorDelegate->y(), textInput->topPadding()); + + textInput->setLeftPadding(10); + QCOMPARE(cursorDelegate->x(), textInput->leftPadding()); + QCOMPARE(cursorDelegate->y(), textInput->topPadding()); +} + QTEST_MAIN(tst_qquicktextinput) #include "tst_qquicktextinput.moc" diff --git a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in index 75fbb0fcf3..5eb9c4442c 100644 --- a/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in +++ b/tools/qmlcachegen/Qt5QuickCompilerConfig.cmake.in @@ -85,6 +85,6 @@ but not all the files it references. list(APPEND compiler_output ${loader_source}) endif() - qt5_add_resources(output_resources ${filtered_rcc_files} OPTIONS ${options}) + qt5_add_resources(output_resources ${filtered_rcc_files} OPTIONS ${rcc_options}) set(${outfiles} ${output_resources} ${compiler_output} PARENT_SCOPE) endfunction() diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp index 6732766b46..13a0021051 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.cpp +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -303,7 +303,7 @@ void QmlProfilerApplication::flush() { if (m_recording) { m_pendingRequest = REQUEST_FLUSH; - m_qmlProfilerClient->sendRecordingStatus(false); + m_qmlProfilerClient->setRecording(false); } else { if (m_profilerData->save(m_interactiveOutputFile)) { m_profilerData->clear(); @@ -393,7 +393,7 @@ void QmlProfilerApplication::userCommand(const QString &command) if (cmd == Constants::CMD_RECORD || cmd == Constants::CMD_RECORD2) { m_pendingRequest = REQUEST_TOGGLE_RECORDING; - m_qmlProfilerClient->sendRecordingStatus(!m_recording); + m_qmlProfilerClient->setRecording(!m_recording); } else if (cmd == Constants::CMD_QUIT || cmd == Constants::CMD_QUIT2) { m_pendingRequest = REQUEST_QUIT; if (m_recording) { diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp index d5662a0182..9ec143975e 100644 --- a/tools/qmlprofiler/qmlprofilerdata.cpp +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -101,7 +101,6 @@ QmlProfilerData::~QmlProfilerData() void QmlProfilerData::clear() { - d->eventTypes.clear(); d->events.clear(); d->traceEndTime = std::numeric_limits<qint64>::min(); |