From aac1e246e46ab6ea70c14a61b1340d529763787d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 23 Mar 2017 13:03:06 +0100 Subject: StackView: provide better warnings and errors Check for valid URLs and test object types. Throw warnings when appropriate instead of crashing later. Task-number: QTBUG-59634 Change-Id: Ia269dc8afd31b618f1ff7aec94d684029cb78244 Reviewed-by: Mitch Curtis --- src/quicktemplates2/qquickstackelement.cpp | 19 +++++++++++++--- src/quicktemplates2/qquickstackelement_p_p.h | 4 ++-- src/quicktemplates2/qquickstackview.cpp | 34 ++++++++++++++++++++-------- src/quicktemplates2/qquickstackview_p.cpp | 18 ++++++++++----- src/quicktemplates2/qquickstackview_p_p.h | 4 ++-- tests/auto/controls/data/tst_stackview.qml | 14 ++++++++++++ 6 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/quicktemplates2/qquickstackelement.cpp b/src/quicktemplates2/qquickstackelement.cpp index aace170b..27331afe 100644 --- a/src/quicktemplates2/qquickstackelement.cpp +++ b/src/quicktemplates2/qquickstackelement.cpp @@ -122,17 +122,30 @@ QQuickStackElement::~QQuickStackElement() delete context; } -QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickStackView *view) +QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickStackView *view, QString *error) { + QUrl url(str); + if (!url.isValid()) { + *error = QStringLiteral("invalid url: ") + str; + return nullptr; + } + QQuickStackElement *element = new QQuickStackElement; - element->component = new QQmlComponent(qmlEngine(view), QUrl(str), view); + element->component = new QQmlComponent(qmlEngine(view), url, view); element->ownComponent = true; return element; } -QQuickStackElement *QQuickStackElement::fromObject(QObject *object, QQuickStackView *view) +QQuickStackElement *QQuickStackElement::fromObject(QObject *object, QQuickStackView *view, QString *error) { Q_UNUSED(view); + QQmlComponent *component = qobject_cast(object); + QQuickItem *item = qobject_cast(object); + if (!component && !item) { + *error = QQmlMetaType::prettyTypeName(object) + QStringLiteral(" is not supported. Must be Item or Component."); + return nullptr; + } + QQuickStackElement *element = new QQuickStackElement; element->component = qobject_cast(object); element->item = qobject_cast(object); diff --git a/src/quicktemplates2/qquickstackelement_p_p.h b/src/quicktemplates2/qquickstackelement_p_p.h index 9cb11855..2308f47d 100644 --- a/src/quicktemplates2/qquickstackelement_p_p.h +++ b/src/quicktemplates2/qquickstackelement_p_p.h @@ -67,8 +67,8 @@ class QQuickStackElement : public QQuickItemViewTransitionableItem, public QQuic public: ~QQuickStackElement(); - static QQuickStackElement *fromString(const QString &str, QQuickStackView *view); - static QQuickStackElement *fromObject(QObject *object, QQuickStackView *view); + static QQuickStackElement *fromString(const QString &str, QQuickStackView *view, QString *error); + static QQuickStackElement *fromObject(QObject *object, QQuickStackView *view, QString *error); bool load(QQuickStackView *parent); void incubate(QObject *object); diff --git a/src/quicktemplates2/qquickstackview.cpp b/src/quicktemplates2/qquickstackview.cpp index dbc8c7bb..155f5f4d 100644 --- a/src/quicktemplates2/qquickstackview.cpp +++ b/src/quicktemplates2/qquickstackview.cpp @@ -490,7 +490,8 @@ void QQuickStackView::push(QQmlV4Function *args) if (lastArg->isInt32()) operation = static_cast(lastArg->toInt32()); - QList elements = d->parseElements(args); + QStringList errors; + QList elements = d->parseElements(0, args, &errors); // Remove any items that are already in the stack, as they can't be in two places at once. for (int i = 0; i < elements.size(); ) { QQuickStackElement *element = elements.at(i); @@ -500,8 +501,13 @@ void QQuickStackView::push(QQmlV4Function *args) ++i; } - if (elements.isEmpty()) { - d->warn(QStringLiteral("nothing to push")); + if (!errors.isEmpty() || elements.isEmpty()) { + if (!errors.isEmpty()) { + for (const QString &error : qAsConst(errors)) + d->warn(error); + } else { + d->warn(QStringLiteral("nothing to push")); + } args->setReturnValue(QV4::Encode::null()); return; } @@ -733,9 +739,15 @@ void QQuickStackView::replace(QQmlV4Function *args) else if (!firstArg->isInt32()) target = d->findElement(firstArg); - QList elements = d->parseElements(args, target ? 1 : 0); - if (elements.isEmpty()) { - d->warn(QStringLiteral("nothing to push")); + QStringList errors; + QList elements = d->parseElements(target ? 1 : 0, args, &errors); + if (!errors.isEmpty() || elements.isEmpty()) { + if (!errors.isEmpty()) { + for (const QString &error : qAsConst(errors)) + d->warn(error); + } else { + d->warn(QStringLiteral("nothing to push")); + } args->setReturnValue(QV4::Encode::null()); return; } @@ -972,12 +984,16 @@ void QQuickStackView::componentComplete() QQuickControl::componentComplete(); Q_D(QQuickStackView); + QScopedValueRollback rollback(d->operation, QStringLiteral("initialItem")); QQuickStackElement *element = nullptr; + QString error; if (QObject *o = d->initialItem.value()) - element = QQuickStackElement::fromObject(o, this); + element = QQuickStackElement::fromObject(o, this, &error); else if (d->initialItem.canConvert()) - element = QQuickStackElement::fromString(d->initialItem.toString(), this); - if (d->pushElement(element)) { + element = QQuickStackElement::fromString(d->initialItem.toString(), this, &error); + if (!error.isEmpty()) { + d->warn(error); + } else if (d->pushElement(element)) { emit depthChanged(); d->setCurrentItem(element); element->setStatus(QQuickStackView::Active); diff --git a/src/quicktemplates2/qquickstackview_p.cpp b/src/quicktemplates2/qquickstackview_p.cpp index 077c13e3..c6f9291d 100644 --- a/src/quicktemplates2/qquickstackview_p.cpp +++ b/src/quicktemplates2/qquickstackview_p.cpp @@ -90,7 +90,7 @@ static bool initProperties(QQuickStackElement *element, const QV4::Value &props, return false; } -QList QQuickStackViewPrivate::parseElements(QQmlV4Function *args, int from) +QList QQuickStackViewPrivate::parseElements(int from, QQmlV4Function *args, QStringList *errors) { QV4::ExecutionEngine *v4 = args->v4engine(); QV4::Scope scope(v4); @@ -103,8 +103,9 @@ QList QQuickStackViewPrivate::parseElements(QQmlV4Function if (QV4::ArrayObject *array = arg->as()) { int len = array->getLength(); for (int j = 0; j < len; ++j) { + QString error; QV4::ScopedValue value(scope, array->getIndexed(j)); - QQuickStackElement *element = createElement(value); + QQuickStackElement *element = createElement(value, &error); if (element) { if (j < len - 1) { QV4::ScopedValue props(scope, array->getIndexed(j + 1)); @@ -112,10 +113,13 @@ QList QQuickStackViewPrivate::parseElements(QQmlV4Function ++j; } elements += element; + } else if (!error.isEmpty()) { + *errors += error; } } } else { - QQuickStackElement *element = createElement(arg); + QString error; + QQuickStackElement *element = createElement(arg, &error); if (element) { if (i < argc - 1) { QV4::ScopedValue props(scope, (*args)[i + 1]); @@ -123,6 +127,8 @@ QList QQuickStackViewPrivate::parseElements(QQmlV4Function ++i; } elements += element; + } else if (!error.isEmpty()) { + *errors += error; } } } @@ -147,13 +153,13 @@ QQuickStackElement *QQuickStackViewPrivate::findElement(const QV4::Value &value) return nullptr; } -QQuickStackElement *QQuickStackViewPrivate::createElement(const QV4::Value &value) +QQuickStackElement *QQuickStackViewPrivate::createElement(const QV4::Value &value, QString *error) { Q_Q(QQuickStackView); if (const QV4::String *s = value.as()) - return QQuickStackElement::fromString(s->toQString(), q); + return QQuickStackElement::fromString(s->toQString(), q, error); if (const QV4::QObjectWrapper *o = value.as()) - return QQuickStackElement::fromObject(o->object(), q); + return QQuickStackElement::fromObject(o->object(), q, error); return nullptr; } diff --git a/src/quicktemplates2/qquickstackview_p_p.h b/src/quicktemplates2/qquickstackview_p_p.h index 532a2b57..26d741f8 100644 --- a/src/quicktemplates2/qquickstackview_p_p.h +++ b/src/quicktemplates2/qquickstackview_p_p.h @@ -76,10 +76,10 @@ public: void setCurrentItem(QQuickStackElement *element); - QList parseElements(QQmlV4Function *args, int from = 0); + QList parseElements(int from, QQmlV4Function *args, QStringList *errors); QQuickStackElement *findElement(QQuickItem *item) const; QQuickStackElement *findElement(const QV4::Value &value) const; - QQuickStackElement *createElement(const QV4::Value &value); + QQuickStackElement *createElement(const QV4::Value &value, QString *error); bool pushElements(const QList &elements); bool pushElement(QQuickStackElement *element); bool popElements(QQuickStackElement *element); diff --git a/tests/auto/controls/data/tst_stackview.qml b/tests/auto/controls/data/tst_stackview.qml index dd56d4f0..c56a04de 100644 --- a/tests/auto/controls/data/tst_stackview.qml +++ b/tests/auto/controls/data/tst_stackview.qml @@ -339,6 +339,10 @@ TestCase { ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: nothing to push") compare(control.push(StackView.Immediate), null) + // unsupported type + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: QtObject is not supported. Must be Item or Component.") + control.push(Qt.createQmlObject('import QtQml 2.0; QtObject { }', control)) + // push(item) var item1 = component.createObject(control, {objectName:"1"}) compare(control.push(item1, StackView.Immediate), item1) @@ -435,6 +439,10 @@ TestCase { ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: nothing to push") compare(control.replace(StackView.Immediate), null) + // unsupported type + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: QtObject is not supported. Must be Item or Component.") + compare(control.replace(Qt.createQmlObject('import QtQml 2.0; QtObject { }', control)), null) + // replace(item) var item1 = component.createObject(control, {objectName:"1"}) compare(control.replace(item1, StackView.Immediate), item1) @@ -917,6 +925,12 @@ TestCase { ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: " + error) control.replace(Qt.resolvedUrl("non-existent.qml")) + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: invalid url: x://[v]") + control.push("x://[v]") + + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: invalid url: x://[v]") + control.replace("x://[v]") + control.pop() } -- cgit v1.2.3