diff options
-rw-r--r-- | src/manager-lib/qmlinprocruntime.cpp | 219 | ||||
-rw-r--r-- | src/manager-lib/qmlinprocruntime.h | 9 | ||||
-rw-r--r-- | tests/auto/qml/windowitem/tst_windowitem.qml | 2 |
3 files changed, 168 insertions, 62 deletions
diff --git a/src/manager-lib/qmlinprocruntime.cpp b/src/manager-lib/qmlinprocruntime.cpp index ad9d4752..a6be38a1 100644 --- a/src/manager-lib/qmlinprocruntime.cpp +++ b/src/manager-lib/qmlinprocruntime.cpp @@ -6,13 +6,13 @@ #include <QQmlEngine> #include <QQmlContext> #include <QQmlComponent> +#include <QQmlIncubator> #include <QCoreApplication> #include <QTimer> #include <QMetaObject> #include <private/qv4engine_p.h> #include <private/qqmlcontext_p.h> #include <private/qqmlcontextdata_p.h> -#include <QQuickView> #include "applicationmanagerwindow.h" #include "inprocesssurfaceitem.h" @@ -48,7 +48,7 @@ QmlInProcRuntime::QmlInProcRuntime(Application *app, QmlInProcRuntimeManager *ma if (rt) impl = rt->m_applicationInterfaceImpl.get(); else - qCCritical(LogRuntime) << "Cannot determine Runtime to setup a ApplicationInterface object"; + qCCritical(LogQmlRuntime) << "Cannot determine Runtime to setup a ApplicationInterface object"; Q_ASSERT(rt); return impl; }); @@ -57,6 +57,8 @@ QmlInProcRuntime::QmlInProcRuntime(Application *app, QmlInProcRuntimeManager *ma QmlInProcRuntime::~QmlInProcRuntime() { + stop(true); + // if there is still a window present at this point, fire the 'closing' signal (probably) again, // because it's still the duty of WindowManager together with qml-ui to free and delete this item!! for (auto i = m_surfaces.size(); i; --i) @@ -66,12 +68,13 @@ QmlInProcRuntime::~QmlInProcRuntime() bool QmlInProcRuntime::start() { Q_ASSERT(!m_rootObject); + Q_ASSERT(state() == Am::NotRunning); if (!m_inProcessQmlEngine) return false; if (!m_app) { - qCCritical(LogSystem) << "tried to start without an app object"; + qCCritical(LogQmlRuntime) << "tried to start without an app object"; return false; } @@ -81,81 +84,175 @@ bool QmlInProcRuntime::start() const QString currentDir = QDir::currentPath() + QDir::separator(); const QString codeDir = m_app->codeDir() + QDir::separator(); + const QUrl qmlFileUrl = filePathToUrl(m_app->info()->absoluteCodeFilePath(), codeDir); - loadResources(variantToStringList(configuration().value(u"resources"_s)), currentDir); - loadResources(variantToStringList(m_app->runtimeParameters().value(u"resources"_s)), codeDir); - - const QStringList configPluginPaths = variantToStringList(configuration().value(u"pluginPaths"_s)); - const QStringList runtimePluginPaths = variantToStringList(m_app->runtimeParameters().value(u"pluginPaths"_s)); - if (!configPluginPaths.isEmpty() || !runtimePluginPaths.isEmpty()) { - addPluginPaths(configPluginPaths, currentDir); - addPluginPaths(runtimePluginPaths, codeDir); - } - qCDebug(LogQmlRuntime) << " * plugin paths:" << qApp->libraryPaths(); - - const QStringList configImportPaths = variantToStringList(configuration().value(u"importPaths"_s)); - const QStringList runtimeImportPaths = variantToStringList(m_app->runtimeParameters().value(u"importPaths"_s)); - if (!configImportPaths.isEmpty() || !runtimeImportPaths.isEmpty()) { - addImportPaths(configImportPaths, currentDir); - addImportPaths(runtimeImportPaths, codeDir); + if (!m_component) { + m_component = new QQmlComponent(m_inProcessQmlEngine, this); + connect(m_component, &QQmlComponent::statusChanged, + this, [this](QQmlComponent::Status status) { + if (status == QQmlComponent::Loading) { + // ignore + } else if (status == QQmlComponent::Error) { + m_componentErrorString = m_component->errorString(); + qCCritical(LogQmlRuntime).noquote().nospace() << "Failed to load component " + << m_app->info()->absoluteCodeFilePath() + << ":\n" << m_componentErrorString; + finish(3, Am::NormalExit); + } else if (status == QQmlComponent::Ready) { + incubate(); + } + }); } - qCDebug(LogQmlRuntime) << " * Qml import paths:" << m_inProcessQmlEngine->importPathList(); - const QUrl qmlFileUrl = filePathToUrl(m_app->info()->absoluteCodeFilePath(), codeDir); - QQmlComponent *component = new QQmlComponent(m_inProcessQmlEngine, qmlFileUrl); + switch (m_component->status()) { + case QQmlComponent::Loading: + Q_ASSERT(false); + return false; - if (!component->isReady()) { - qCCritical(LogSystem).noquote().nospace() << "Failed to load component " - << m_app->info()->absoluteCodeFilePath() - << ":\n" << component->errorString(); + case QQmlComponent::Error: // we tried before and failed + qCCritical(LogQmlRuntime).noquote().nospace() << "Failed to load component " + << m_app->info()->absoluteCodeFilePath() + << "earlier:\n" << m_componentErrorString; return false; - } - emit signaler()->aboutToStart(this); + case QQmlComponent::Null: { // first time - load the component + const QStringList configPluginPaths = variantToStringList(configuration().value(u"pluginPaths"_s)); + const QStringList runtimePluginPaths = variantToStringList(m_app->runtimeParameters().value(u"pluginPaths"_s)); + if (!configPluginPaths.isEmpty() || !runtimePluginPaths.isEmpty()) { + addPluginPaths(configPluginPaths, currentDir); + addPluginPaths(runtimePluginPaths, codeDir); + } + qCDebug(LogQmlRuntime) << " * plugin paths:" << qApp->libraryPaths(); + + const QStringList configImportPaths = variantToStringList(configuration().value(u"importPaths"_s)); + const QStringList runtimeImportPaths = variantToStringList(m_app->runtimeParameters().value(u"importPaths"_s)); + if (!configImportPaths.isEmpty() || !runtimeImportPaths.isEmpty()) { + addImportPaths(configImportPaths, currentDir); + addImportPaths(runtimeImportPaths, codeDir); + } + qCDebug(LogQmlRuntime) << " * Qml import paths:" << m_inProcessQmlEngine->importPathList(); + + loadResources(variantToStringList(configuration().value(u"resources"_s)), currentDir); + loadResources(variantToStringList(m_app->runtimeParameters().value(u"resources"_s)), codeDir); - setState(Am::StartingUp); + m_component->loadUrl(qmlFileUrl, QQmlComponent::Asynchronous); + if (m_component->isError()) { // undocumented, but this can happen without a statusChange signal + qCCritical(LogQmlRuntime).noquote().nospace() << "Failed to load component " + << m_app->info()->absoluteCodeFilePath() + << ":\n" << m_component->errorString(); + return false; + } + + emit signaler()->aboutToStart(this); + setState(Am::StartingUp); + return true; + } + case QQmlComponent::Ready: // already loaded and ready to go + emit signaler()->aboutToStart(this); + setState(Am::StartingUp); + + incubate(); + return true; + } + Q_ASSERT(false); + return false; +} + +void QmlInProcRuntime::incubate() +{ // We are running each application in its own, separate Qml context. // This way, we can export a unique runtime-tag to this context to then later // determine at runtime which application is currently active. auto appContext = new QQmlContext(m_inProcessQmlEngine->rootContext(), this); - if (!tagQmlContext(appContext, QVariant::fromValue(this))) - qCritical() << "Could not tag the application QML context"; - - QObject *obj = component->beginCreate(appContext); - - QMetaObject::invokeMethod(this, [component, obj, this]() { - component->completeCreate(); - delete component; - if (!obj) { - qCCritical(LogSystem) << "could not load" << m_app->info()->absoluteCodeFilePath() << ": no root object"; - finish(3, Am::NormalExit); - } else { - if (state() == Am::ShuttingDown) { - delete obj; - return; - } + if (!tagQmlContext(appContext, QVariant::fromValue(this))) { + qCCritical(LogQmlRuntime) << "Could not tag the application QML context"; + finish(4, Am::NormalExit); + return; + } - if (!qobject_cast<ApplicationManagerWindow*>(obj)) { - QQuickItem *item = qobject_cast<QQuickItem*>(obj); - if (item) { - auto surfaceItem = new InProcessSurfaceItem; - item->setParentItem(surfaceItem); - addSurfaceItem(QSharedPointer<InProcessSurfaceItem>(surfaceItem)); + m_component->create(m_incubator, appContext, nullptr); + + // QQmlIncubator has no signal for when it's done, so we have to poll via a timer + if (!m_incubationTimer) { + m_incubationTimer = new QTimer(this); + m_incubationTimer->setInterval(1000/60); + + connect(m_incubationTimer, &QTimer::timeout, this, [this]() { + if (m_incubator.isError()) { + const auto errors = m_incubator.errors(); + QStringList strErrors; + for (const auto &e : errors) + strErrors << e.toString(); + qCCritical(LogQmlRuntime) << "Failed to load component:" << m_app->info()->absoluteCodeFilePath() + << ":\n *" << strErrors.join(u"\n *"); + finish(3, Am::NormalExit); + } else if (m_incubator.isReady()) { + auto *obj = m_incubator.object(); + Q_ASSERT(obj); + + if (state() == Am::ShuttingDown) { + delete obj; + } else { + if (!qobject_cast<ApplicationManagerWindow *>(obj)) { + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (item) { + auto surfaceItem = new InProcessSurfaceItem; + item->setParentItem(surfaceItem); + addSurfaceItem(QSharedPointer<InProcessSurfaceItem>(surfaceItem)); + } + } + m_rootObject = obj; + setState(Am::Running); + + if (!m_document.isEmpty()) + openDocument(m_document, QString()); } + } else { + return; // wait for the next timer tick } - m_rootObject = obj; - setState(Am::Running); - - if (!m_document.isEmpty()) - openDocument(m_document, QString()); - } - }, Qt::QueuedConnection); - return true; + // cleanup regardless if we succeeded or failed + m_incubator.clear(); + m_incubationTimer->stop(); + m_incubationTimer->deleteLater(); + m_incubationTimer = nullptr; + }); + } + m_incubationTimer->start(); } void QmlInProcRuntime::stop(bool forceKill) { + switch (state()) { + case Am::NotRunning: + case Am::ShuttingDown: + return; + + case Am::StartingUp: + setState(Am::ShuttingDown); + if (m_component && m_component->isLoading()) { + // QML can deal with a loading component being deleted + delete m_component; + m_component = nullptr; + } else { + // the incubation finished, but the incubationTimer didn't fire yet + if (m_incubator.isReady() && m_incubator.object()) + delete m_incubator.object(); + // cancel any active incubations + m_incubator.clear(); + if (m_incubationTimer) + m_incubationTimer->stop(); + } + if (forceKill) + finish(9 /* POSIX SIGKILL */, Am::ForcedExit); + else + finish(0, Am::NormalExit); + return; + + case Am::Running: + break; + } + setState(Am::ShuttingDown); if (!forceKill) @@ -203,10 +300,10 @@ void QmlInProcRuntime::finish(int exitCode, Am::ExitStatus status) } if (printWarning) { - qCWarning(LogSystem, "In-process runtime for application '%s' %s", + qCWarning(LogQmlRuntime, "In-process runtime for application '%s' %s", (m_app ? qPrintable(m_app->id()) : "<null>"), cause.constData()); } else { - qCDebug(LogSystem, "In-process runtime for application '%s' %s", + qCDebug(LogQmlRuntime, "In-process runtime for application '%s' %s", (m_app ? qPrintable(m_app->id()) : "<null>"), cause.constData()); } diff --git a/src/manager-lib/qmlinprocruntime.h b/src/manager-lib/qmlinprocruntime.h index 048121cd..5eb029f4 100644 --- a/src/manager-lib/qmlinprocruntime.h +++ b/src/manager-lib/qmlinprocruntime.h @@ -9,8 +9,11 @@ #include <QtAppManManager/abstractruntime.h> #include <QtCore/QSharedPointer> +#include <QtQml/QQmlIncubator> QT_FORWARD_DECLARE_CLASS(QQmlContext) +QT_FORWARD_DECLARE_CLASS(QQmlComponent) +QT_FORWARD_DECLARE_CLASS(QTimer) QT_BEGIN_NAMESPACE_AM @@ -65,11 +68,17 @@ private: void addPluginPaths(const QStringList &pluginPaths, const QString &baseDir); void addImportPaths(const QStringList &importPaths, const QString &baseDir); + void incubate(); + // used by QmlInProcApplicationManagerWindowImpl to register surfaceItems void addSurfaceItem(const QSharedPointer<InProcessSurfaceItem> &surface); QObject *m_rootObject = nullptr; QList<QSharedPointer<InProcessSurfaceItem>> m_surfaces; + QQmlComponent *m_component = nullptr; + QString m_componentErrorString; + QQmlIncubator m_incubator; + QTimer *m_incubationTimer = nullptr; friend class QmlInProcApplicationManagerWindowImpl; // for emitting signals on behalf of this class in onComplete friend class QmlInProcApplicationInterfaceImpl; // for handling the quit() signal diff --git a/tests/auto/qml/windowitem/tst_windowitem.qml b/tests/auto/qml/windowitem/tst_windowitem.qml index 0e91d94c..33d62252 100644 --- a/tests/auto/qml/windowitem/tst_windowitem.qml +++ b/tests/auto/qml/windowitem/tst_windowitem.qml @@ -382,6 +382,7 @@ Item { app = ApplicationManager.application("test.windowitem.multiwin"); app.start(); + tryCompare(app, "runState", Am.Running); tryCompare(windowItemsModel, "count", 2, spyTimeout); tryCompare(WindowManager, "count", 2, spyTimeout); @@ -389,7 +390,6 @@ Item { var firstWindow = windowItemsModel.get(0).window; var secondWindow = windowItemsModel.get(1).window; - compare(app.runState, Am.Running); compare(firstWindow.contentState, WindowObject.SurfaceWithContent); firstWindow.close(); |