diff options
Diffstat (limited to 'src/qml/qml/qqmlengine.cpp')
-rw-r--r-- | src/qml/qml/qqmlengine.cpp | 1319 |
1 files changed, 545 insertions, 774 deletions
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 30f24a8091..c7812059a1 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1,67 +1,23 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmlengine_p.h" #include "qqmlengine.h" -#include "qqmlcomponentattached_p.h" #include "qqmlcontext_p.h" #include "qqml.h" #include "qqmlcontext.h" -#include "qqmlexpression.h" -#include "qqmlcomponent.h" -#include "qqmlvme_p.h" -#include "qqmlstringconverters_p.h" #include "qqmlscriptstring.h" #include "qqmlglobal_p.h" -#include "qqmlcomponent_p.h" -#include "qqmlextensioninterface.h" -#include "qqmllist_p.h" -#include "qqmltypenamecache_p.h" #include "qqmlnotifier_p.h" #include "qqmlincubator.h" #include "qqmlabstracturlinterceptor.h" -#include "qqmlsourcecoordinate_p.h" + #include <private/qqmldirparser_p.h> #include <private/qqmlboundsignal_p.h> #include <private/qqmljsdiagnosticmessage_p.h> #include <private/qqmltype_p_p.h> +#include <private/qqmlpluginimporter_p.h> #include <QtCore/qstandardpaths.h> #include <QtCore/qmetaobject.h> #include <QDebug> @@ -71,6 +27,10 @@ #include <QtCore/qmutex.h> #include <QtCore/qthread.h> #include <private/qthread_p.h> +#include <private/qqmlscriptdata_p.h> +#include <QtQml/private/qqmlcomponentattached_p.h> +#include <QtQml/private/qqmlsourcecoordinate_p.h> +#include <QtQml/private/qqmlcomponent_p.h> #if QT_CONFIG(qml_network) #include "qqmlnetworkaccessmanagerfactory.h" @@ -89,10 +49,7 @@ #endif #include <private/qqmlplatform_p.h> #include <private/qqmlloggingcategory_p.h> - -#if QT_CONFIG(qml_sequence_object) #include <private/qv4sequenceobject_p.h> -#endif #ifdef Q_OS_WIN // for %APPDATA% # include <qt_windows.h> @@ -105,6 +62,8 @@ QT_BEGIN_NAMESPACE +void qml_register_types_QML(); + /*! \qmltype QtObject \instantiates QObject @@ -161,7 +120,7 @@ QT_BEGIN_NAMESPACE \endcode */ -bool QQmlEnginePrivate::qml_debugging_enabled = false; +Q_CONSTINIT std::atomic<bool> QQmlEnginePrivate::qml_debugging_enabled{false}; bool QQmlEnginePrivate::s_designerMode = false; bool QQmlEnginePrivate::designerMode() @@ -238,426 +197,64 @@ QQmlImageProviderBase::~QQmlImageProviderBase() { } - -/*! -\qmltype Qt -\inqmlmodule QtQml -\instantiates QQmlEnginePrivate -\ingroup qml-utility-elements -\keyword QmlGlobalQtObject -\brief Provides a global object with useful enums and functions from Qt. - -The \c Qt object is a global object with utility functions, properties and enums. - -It is not instantiable; to use it, call the members of the global \c Qt object directly. -For example: - -\qml -import QtQuick 2.0 - -Text { - color: Qt.rgba(1, 0, 0, 1) - text: Qt.md5("hello, world") -} -\endqml - - -\section1 Enums - -The Qt object contains the enums available in the \l [QtCore]{Qt}{Qt Namespace}. For example, you can access -the \l Qt::LeftButton and \l Qt::RightButton enumeration values as \c Qt.LeftButton and \c Qt.RightButton. - - -\section1 Types - -The Qt object also contains helper functions for creating objects of specific -data types. This is primarily useful when setting the properties of an item -when the property has one of the following types: -\list -\li \c rect - use \l{Qt::rect()}{Qt.rect()} -\li \c point - use \l{Qt::point()}{Qt.point()} -\li \c size - use \l{Qt::size()}{Qt.size()} -\endlist - -If the \c QtQuick module has been imported, the following helper functions for -creating objects of specific data types are also available for clients to use: -\list -\li \c color - use \l{Qt::rgba()}{Qt.rgba()}, \l{Qt::hsla()}{Qt.hsla()}, \l{Qt::darker()}{Qt.darker()}, \l{Qt::lighter()}{Qt.lighter()} or \l{Qt::tint()}{Qt.tint()} -\li \c font - use \l{Qt::font()}{Qt.font()} -\li \c vector2d - use \l{Qt::vector2d()}{Qt.vector2d()} -\li \c vector3d - use \l{Qt::vector3d()}{Qt.vector3d()} -\li \c vector4d - use \l{Qt::vector4d()}{Qt.vector4d()} -\li \c quaternion - use \l{Qt::quaternion()}{Qt.quaternion()} -\li \c matrix4x4 - use \l{Qt::matrix4x4()}{Qt.matrix4x4()} -\endlist - -There are also string based constructors for these types. See \l{qtqml-typesystem-basictypes.html}{QML Basic Types} for more information. - -\section1 Date/Time Formatters - -The Qt object contains several functions for formatting QDateTime, QDate and QTime values. - -\list - \li \l{Qt::formatDateTime}{string Qt.formatDateTime(datetime date, variant format)} - \li \l{Qt::formatDate}{string Qt.formatDate(datetime date, variant format)} - \li \l{Qt::formatTime}{string Qt.formatTime(datetime date, variant format)} -\endlist - -The format specification is described at \l{Qt::formatDateTime}{Qt.formatDateTime}. - - -\section1 Dynamic Object Creation -The following functions on the global object allow you to dynamically create QML -items from files or strings. See \l{Dynamic QML Object Creation from JavaScript} for an overview -of their use. - -\list - \li \l{Qt::createComponent()}{object Qt.createComponent(url)} - \li \l{Qt::createQmlObject()}{object Qt.createQmlObject(string qml, object parent, string filepath)} -\endlist - - -\section1 Other Functions - -The following functions are also on the Qt object. - -\list - \li \l{Qt::quit()}{Qt.quit()} - \li \l{Qt::md5()}{Qt.md5(string)} - \li \l{Qt::btoa()}{string Qt.btoa(string)} - \li \l{Qt::atob()}{string Qt.atob(string)} - \li \l{Qt::binding()}{object Qt.binding(function)} - \li \l{Qt::locale()}{object Qt.locale()} - \li \l{Qt::resolvedUrl()}{string Qt.resolvedUrl(string)} - \li \l{Qt::openUrlExternally()}{Qt.openUrlExternally(string)} - \li \l{Qt::fontFamilies()}{list<string> Qt.fontFamilies()} -\endlist -*/ - -/*! - \qmlproperty object Qt::platform - \since 5.1 - - The \c platform object provides info about the underlying platform. - - Its properties are: - - \table - \row - \li \c platform.os - \li - - This read-only property contains the name of the operating system. - - Possible values are: - - \list - \li \c "android" - Android - \li \c "ios" - iOS - \li \c "tvos" - tvOS - \li \c "linux" - Linux - \li \c "osx" - \macos - \li \c "qnx" - QNX (since Qt 5.9.3) - \li \c "unix" - Other Unix-based OS - \li \c "windows" - Windows - \li \c "wasm" - WebAssembly - \endlist - - \row - \li \c platform.pluginName - \li This is the name of the platform set on the QGuiApplication instance - as returned by \l QGuiApplication::platformName() - - \endtable -*/ - -/*! - \qmlproperty object Qt::application - \since 5.1 - - The \c application object provides access to global application state - properties shared by many QML components. - - Its properties are: - - \table - \row - \li \c application.active - \li - Deprecated, use Qt.application.state == Qt.ApplicationActive instead. - - \row - \li \c application.state - \li - This read-only property indicates the current state of the application. - - Possible values are: - - \list - \li Qt.ApplicationActive - The application is the top-most and focused application, and the - user is able to interact with the application. - \li Qt.ApplicationInactive - The application is visible or partially visible, but not selected - to be in front, the user cannot interact with the application. - On desktop platforms, this typically means that the user activated - another application. On mobile platforms, it is more common to - enter this state when the OS is interrupting the user with for - example incoming calls, SMS-messages or dialogs. This is usually a - transient state during which the application is paused. The user - may return focus to your application, but most of the time it will - be the first indication that the application is going to be suspended. - While in this state, consider pausing or stopping any activity that - should not continue when the user cannot interact with your - application, such as a video, a game, animations, or sensors. - You should also avoid performing CPU-intensive tasks which might - slow down the application in front. - \li Qt.ApplicationSuspended - The application is suspended and not visible to the user. On - mobile platforms, the application typically enters this state when - the user returns to the home screen or switches to another - application. While in this state, the application should ensure - that the user perceives it as always alive and does not lose his - progress, saving any persistent data. The application should cease - all activities and be prepared for code execution to stop. While - suspended, the application can be killed at any time without - further warnings (for example when low memory forces the OS to purge - suspended applications). - \li Qt.ApplicationHidden - The application is hidden and runs in the background. This is the - normal state for applications that need to do background processing, - like playing music, while the user interacts with other applications. - The application should free up all graphical resources when entering - this state. A Qt Quick application should not usually handle this state - at the QML level. Instead, you should unload the entire UI and reload - the QML files whenever the application becomes active again. - \endlist - - \row - \li \c application.layoutDirection - \li - This read-only property can be used to query the default layout direction of the - application. On system start-up, the default layout direction depends on the - application's language. The property has a value of \c Qt.RightToLeft in locales - where text and graphic elements are read from right to left, and \c Qt.LeftToRight - where the reading direction flows from left to right. You can bind to this - property to customize your application layouts to support both layout directions. - - Possible values are: - - \list - \li Qt.LeftToRight - Text and graphics elements should be positioned - from left to right. - \li Qt.RightToLeft - Text and graphics elements should be positioned - from right to left. - \endlist - \row - \li \c application.font - \li This read-only property holds the default application font as - returned by \l QGuiApplication::font(). - \row - \li \c application.arguments - \li This is a string list of the arguments the executable was invoked with. - \row - \li \c application.name - \li This is the application name set on the QCoreApplication instance. This property can be written - to in order to set the application name. - \row - \li \c application.displayName (since Qt 5.9) - \li This is the application display name set on the QGuiApplication instance. This property can be written - to in order to set the application display name. - \row - \li \c application.version - \li This is the application version set on the QCoreApplication instance. This property can be written - to in order to set the application version. - \row - \li \c application.organization - \li This is the organization name set on the QCoreApplication instance. This property can be written - to in order to set the organization name. - \row - \li \c application.domain - \li This is the organization domain set on the QCoreApplication instance. This property can be written - to in order to set the organization domain. - - \row - \li \c application.supportsMultipleWindows - \li This read-only property can be used to determine whether or not the - platform supports multiple windows. Some embedded platforms do not support - multiple windows, for example. - - \row - \li \c application.screens - \li An array containing the descriptions of all connected screens. The - elements of the array are objects with the same properties as the - \l{Screen} attached object. In practice the array corresponds to the screen - list returned by QGuiApplication::screens(). In addition to examining - properties like name, width, height, etc., the array elements can also be - assigned to the screen property of Window items, thus serving as an - alternative to the C++ side's QWindow::setScreen(). This property has been - added in Qt 5.9. - - \endtable - - The object also has one signal, aboutToQuit(), which is the same as \l QCoreApplication::aboutToQuit(). - - The following example uses the \c application object to indicate - whether the application is currently active: - - \snippet qml/application.qml document - - Note that when using QML without a QGuiApplication, the following properties will be undefined: - \list - \li application.active - \li application.state - \li application.layoutDirection - \li application.font - \endlist - - \sa Screen, Window, {Window::screen}{Window.screen} -*/ - -/*! - \qmlproperty object Qt::inputMethod - \since 5.0 - - The \c inputMethod object allows access to application's QInputMethod object - and all its properties and slots. See the QInputMethod documentation for - further details. -*/ - -/*! - \qmlproperty object Qt::styleHints - \since 5.5 - - The \c styleHints object provides platform-specific style hints and settings. - See the QStyleHints documentation for further details. - - \note The \c styleHints object is only available when using the Qt Quick module. - - The following example uses the \c styleHints object to determine whether an - item should gain focus on mouse press or touch release: - \code - import QtQuick 2.4 - - MouseArea { - id: button - - onPressed: { - if (!Qt.styleHints.setFocusOnTouchRelease) - button.forceActiveFocus() - } - onReleased: { - if (Qt.styleHints.setFocusOnTouchRelease) - button.forceActiveFocus() - } - } - \endcode -*/ - -/*! -\qmlmethod object Qt::include(string url, jsobject callback) -\deprecated - -This method should not be used. Use ECMAScript modules instead and the native -JavaScript \c import and \c export statements instead. - -Includes another JavaScript file. This method can only be used from within JavaScript files, -and not regular QML files. - -This imports all functions from \a url into the current script's namespace. - -Qt.include() returns an object that describes the status of the operation. The object has -a single property, \c {status}, that is set to one of the following values: - -\table -\header \li Symbol \li Value \li Description -\row \li result.OK \li 0 \li The include completed successfully. -\row \li result.LOADING \li 1 \li Data is being loaded from the network. -\row \li result.NETWORK_ERROR \li 2 \li A network error occurred while fetching the url. -\row \li result.EXCEPTION \li 3 \li A JavaScript exception occurred while executing the included code. -An additional \c exception property will be set in this case. -\endtable - -The \c status property will be updated as the operation progresses. - -If provided, \a callback is invoked when the operation completes. The callback is passed -the same object as is returned from the Qt.include() call. -*/ -// Qt.include() is implemented in qv4include.cpp - -QQmlEnginePrivate::QQmlEnginePrivate(QQmlEngine *e) -: propertyCapture(nullptr), rootContext(nullptr), -#if QT_CONFIG(qml_debug) - profiler(nullptr), -#endif - outputWarningsToMsgLog(true), - erroredBindings(nullptr), inProgressCreations(0), -#if QT_CONFIG(qml_worker_script) - workerScriptEngine(nullptr), -#endif - activeObjectCreator(nullptr), -#if QT_CONFIG(qml_network) - networkAccessManager(nullptr), networkAccessManagerFactory(nullptr), -#endif - scarceResourcesRefCount(0), importDatabase(e), typeLoader(e), - uniqueId(1), incubatorCount(0), incubationController(nullptr) -{ -} - QQmlEnginePrivate::~QQmlEnginePrivate() { if (inProgressCreations) qWarning() << QQmlEngine::tr("There are still \"%1\" items in the process of being created at engine destruction.").arg(inProgressCreations); - doDeleteInEngineThread(); - if (incubationController) incubationController->d = nullptr; incubationController = nullptr; QQmlMetaType::freeUnusedTypesAndCaches(); - for (auto iter = m_compositeTypes.cbegin(), end = m_compositeTypes.cend(); iter != end; ++iter) - iter.value()->isRegisteredWithEngine = false; #if QT_CONFIG(qml_debug) delete profiler; #endif + qDeleteAll(cachedValueTypeInstances); } void QQmlPrivate::qdeclarativeelement_destructor(QObject *o) { - if (QQmlData *d = QQmlData::get(o)) { + QObjectPrivate *p = QObjectPrivate::get(o); + if (QQmlData *d = QQmlData::get(p)) { + const auto invalidate = [](QQmlContextData *c) {c->invalidate();}; if (d->ownContext) { - for (QQmlRefPointer<QQmlContextData> lc = d->ownContext->linkedContext().data(); lc; - lc = lc->linkedContext()) { - lc->invalidate(); - if (lc->contextObject() == o) - lc->setContextObject(nullptr); - } - d->ownContext->invalidate(); - if (d->ownContext->contextObject() == o) - d->ownContext->setContextObject(nullptr); - d->ownContext = nullptr; + d->ownContext->deepClearContextObject(o, invalidate, invalidate); + d->ownContext.reset(); d->context = nullptr; + Q_ASSERT(!d->outerContext || d->outerContext->contextObject() != o); + } else if (d->outerContext && d->outerContext->contextObject() == o) { + d->outerContext->deepClearContextObject(o, invalidate, invalidate); } - if (d->outerContext && d->outerContext->contextObject() == o) - d->outerContext->setContextObject(nullptr); + if (d->hasVMEMetaObject || d->hasInterceptorMetaObject) { + // This is somewhat dangerous because another thread might concurrently + // try to resolve the dynamic metaobject. In practice this will then + // lead to either the code path that still returns the interceptor + // metaobject or the code path that returns the string casted one. Both + // is fine if you cannot actually touch the object itself. Since the + // other thread is obviously not synchronized to this one, it can't. + // + // In particular we do this when delivering the frameSwapped() signal + // in QQuickWindow. The handler for frameSwapped() is written in a way + // that is thread safe as long as QQuickWindow's dtor hasn't finished. + // QQuickWindow's dtor does synchronize with the render thread, but it + // runs _after_ qdeclarativeelement_destructor. + static_cast<QQmlInterceptorMetaObject *>(p->metaObject)->invalidate(); + d->hasVMEMetaObject = d->hasInterceptorMetaObject = false; + } // Mark this object as in the process of deletion to // prevent it resolving in bindings QQmlData::markAsDeleted(o); - - // Disconnect the notifiers now - during object destruction this would be too late, since - // the disconnect call wouldn't be able to call disconnectNotify(), as it isn't possible to - // get the metaobject anymore. - d->disconnectNotifiers(); } } -QQmlData::QQmlData() - : ownMemory(true), indestructible(true), explicitIndestructibleSet(false), +QQmlData::QQmlData(Ownership ownership) + : ownMemory(ownership == OwnsMemory), indestructible(true), explicitIndestructibleSet(false), hasTaintedV4Object(false), isQueuedForDeletion(false), rootObjectInCreation(false), - hasInterceptorMetaObject(false), hasVMEMetaObject(false), - bindingBitsArraySize(InlineBindingArraySize), notifyList(nullptr), - bindings(nullptr), signalHandlers(nullptr), nextContextObject(nullptr), prevContextObject(nullptr), - lineNumber(0), columnNumber(0), jsEngineId(0), - propertyCache(nullptr), guards(nullptr), extendedData(nullptr) + hasInterceptorMetaObject(false), hasVMEMetaObject(false), hasConstWrapper(false), dummy(0), + bindingBitsArraySize(InlineBindingArraySize) { memset(bindingBitsValue, 0, sizeof(bindingBitsValue)); init(); @@ -707,7 +304,10 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in // QQmlEngine to emit signals from a different thread. These signals are then automatically // marshalled back onto the QObject's thread and handled by QML from there. This is tested // by the qqmlecmascript::threadSignal() autotest. - if (!ddata->notifyList) + + // Relaxed semantics here. If we're on a different thread we might schedule a useless event, + // but that should be rare. + if (!ddata->notifyList.loadRelaxed()) return; auto objectThreadData = QObjectPrivate::get(object)->threadData.loadRelaxed(); @@ -718,14 +318,14 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in QMetaMethod m = QMetaObjectPrivate::signal(object->metaObject(), index); QList<QByteArray> parameterTypes = m.parameterTypes(); - QScopedPointer<QMetaCallEvent> ev(new QMetaCallEvent(m.methodIndex(), 0, nullptr, - object, index, - parameterTypes.count() + 1)); + auto ev = std::make_unique<QMetaCallEvent>(m.methodIndex(), 0, nullptr, + object, index, + parameterTypes.size() + 1); void **args = ev->args(); QMetaType *types = ev->types(); - for (int ii = 0; ii < parameterTypes.count(); ++ii) { + for (int ii = 0; ii < parameterTypes.size(); ++ii) { const QByteArray &typeName = parameterTypes.at(ii); if (typeName.endsWith('*')) types[ii + 1] = QMetaType(QMetaType::VoidStar); @@ -745,7 +345,7 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in QQmlThreadNotifierProxyObject *mpo = new QQmlThreadNotifierProxyObject; mpo->target = object; mpo->moveToThread(objectThreadData->thread.loadAcquire()); - QCoreApplication::postEvent(mpo, ev.take()); + QCoreApplication::postEvent(mpo, ev.release()); } else { QQmlNotifierEndpoint *ep = ddata->notify(index); @@ -781,11 +381,15 @@ int QQmlData::endpointCount(int index) void QQmlData::markAsDeleted(QObject *o) { - QQmlData::setQueuedForDeletion(o); - - QObjectPrivate *p = QObjectPrivate::get(o); - for (QList<QObject *>::const_iterator it = p->children.constBegin(), end = p->children.constEnd(); it != end; ++it) { - QQmlData::markAsDeleted(*it); + QVarLengthArray<QObject *> workStack; + workStack.push_back(o); + while (!workStack.isEmpty()) { + auto currentObject = workStack.last(); + workStack.pop_back(); + QQmlData::setQueuedForDeletion(currentObject); + auto currentObjectPriv = QObjectPrivate::get(currentObject); + for (QObject *child: std::as_const(currentObjectPriv->children)) + workStack.push_back(child); } } @@ -795,39 +399,69 @@ void QQmlData::setQueuedForDeletion(QObject *object) if (QQmlData *ddata = QQmlData::get(object)) { if (ddata->ownContext) { Q_ASSERT(ddata->ownContext.data() == ddata->context); - ddata->context->emitDestruction(); - if (ddata->ownContext->contextObject() == object) - ddata->ownContext->setContextObject(nullptr); - ddata->ownContext = nullptr; + ddata->ownContext->deepClearContextObject(object); + ddata->ownContext.reset(); ddata->context = nullptr; } ddata->isQueuedForDeletion = true; + + // Disconnect the notifiers now - during object destruction this would be too late, + // since the disconnect call wouldn't be able to call disconnectNotify(), as it isn't + // possible to get the metaobject anymore. + // Also, there is no point in evaluating bindings in order to set properties on + // half-deleted objects. + ddata->disconnectNotifiers(DeleteNotifyList::No); } } } -void QQmlData::flushPendingBindingImpl(QQmlPropertyIndex index) +void QQmlData::flushPendingBinding(int coreIndex) { - clearPendingBindingBit(index.coreIndex()); + clearPendingBindingBit(coreIndex); // Find the binding QQmlAbstractBinding *b = bindings; - while (b && (b->targetPropertyIndex().coreIndex() != index.coreIndex() || + while (b && (b->targetPropertyIndex().coreIndex() != coreIndex || b->targetPropertyIndex().hasValueTypeIndex())) b = b->nextBinding(); - if (b && b->targetPropertyIndex().coreIndex() == index.coreIndex() && + if (b && b->targetPropertyIndex().coreIndex() == coreIndex && !b->targetPropertyIndex().hasValueTypeIndex()) b->setEnabled(true, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); } -QQmlData::DeferredData::DeferredData() -{ -} +QQmlData::DeferredData::DeferredData() = default; +QQmlData::DeferredData::~DeferredData() = default; -QQmlData::DeferredData::~DeferredData() -{ +template<> +int qmlRegisterType<void>(const char *uri, int versionMajor, int versionMinor, const char *qmlName) +{ + QQmlPrivate::RegisterType type = { + QQmlPrivate::RegisterType::CurrentVersion, + QMetaType(), + QMetaType(), + 0, nullptr, nullptr, + QString(), + nullptr, + uri, + QTypeRevision::fromVersion(versionMajor, versionMinor), + qmlName, + nullptr, + nullptr, + nullptr, + -1, + -1, + -1, + nullptr, + nullptr, + nullptr, + QTypeRevision::zero(), + -1, + QQmlPrivate::ValueTypeCreationMethod::None, + }; + + return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); } bool QQmlEnginePrivate::baseModulesUninitialized = true; @@ -836,23 +470,25 @@ void QQmlEnginePrivate::init() Q_Q(QQmlEngine); if (baseModulesUninitialized) { + // Register builtins + qml_register_types_QML(); + + // No need to specifically register those. + static_assert(std::is_same_v<QStringList, QList<QString>>); + static_assert(std::is_same_v<QVariantList, QList<QVariant>>); - // required for the Compiler. - qmlRegisterType<QObject>("QML", 1, 0, "QtObject"); - qmlRegisterType<QQmlComponent>("QML", 1, 0, "Component"); + qRegisterMetaType<QQmlScriptString>(); + qRegisterMetaType<QQmlComponent::Status>(); + qRegisterMetaType<QList<QObject*> >(); + qRegisterMetaType<QQmlBinding*>(); + + // Protect the module: We don't want any URL interceptor to mess with the builtins. + qmlProtectModule("QML", 1); QQmlData::init(); baseModulesUninitialized = false; } - qRegisterMetaType<QVariant>(); - qRegisterMetaType<QQmlScriptString>(); - qRegisterMetaType<QJSValue>(); - qRegisterMetaType<QQmlComponent::Status>(); - qRegisterMetaType<QList<QObject*> >(); - qRegisterMetaType<QList<int> >(); - qRegisterMetaType<QQmlBinding*>(); - q->handle()->setQmlEngine(q); rootContext = new QQmlContext(q,true); @@ -864,29 +500,16 @@ void QQmlEnginePrivate::init() \inmodule QtQml \brief The QQmlEngine class provides an environment for instantiating QML components. - Each QML component is instantiated in a QQmlContext. - QQmlContext's are essential for passing data to QML - components. In QML, contexts are arranged hierarchically and this - hierarchy is managed by the QQmlEngine. - - Prior to creating any QML components, an application must have - created a QQmlEngine to gain access to a QML context. The - following example shows how to create a simple Text item. - - \code - QQmlEngine engine; - QQmlComponent component(&engine); - component.setData("import QtQuick 2.0\nText { text: \"Hello world!\" }", QUrl()); - QQuickItem *item = qobject_cast<QQuickItem *>(component.create()); - - //add item to view, etc - ... - \endcode + A QQmlEngine is used to manage \l{QQmlComponent}{components} and objects created from + them and execute their bindings and functions. QQmlEngine also inherits from + \l{QJSEngine} which allows seamless integration between your QML components and + JavaScript code. - In this case, the Text item will be created in the engine's - \l {QQmlEngine::rootContext()}{root context}. + Each QML component is instantiated in a QQmlContext. In QML, contexts are arranged + hierarchically and this hierarchy is managed by the QQmlEngine. By default, + components are instantiated in the \l {QQmlEngine::rootContext()}{root context}. - \sa QQmlComponent, QQmlContext, {QML Global Object} + \sa QQmlComponent, QQmlContext, {QML Global Object}, QQmlApplicationEngine */ /*! @@ -917,11 +540,12 @@ QQmlEngine::QQmlEngine(QQmlEnginePrivate &dd, QObject *parent) invalidated, but not destroyed (unless they are parented to the QQmlEngine object). - See QJSEngine docs for details on cleaning up the JS engine. + See ~QJSEngine() for details on cleaning up the JS engine. */ QQmlEngine::~QQmlEngine() { Q_D(QQmlEngine); + handle()->inShutdown = true; QJSEnginePrivate::removeFromDebugServer(this); // Emit onDestruction signals for the root context before @@ -933,9 +557,7 @@ QQmlEngine::~QQmlEngine() // we do this here and not in the private dtor since otherwise a crash can // occur (if we are the QObject parent of the QObject singleton instance) // XXX TODO: performance -- store list of singleton types separately? - const QList<QQmlType> singletonTypes = QQmlMetaType::qmlSingletonTypes(); - for (const QQmlType &currType : singletonTypes) - d->destroySingletonInstance(currType); + d->singletonInstances.clear(); delete d->rootContext; d->rootContext = nullptr; @@ -977,14 +599,35 @@ QQmlEngine::~QQmlEngine() Once the component cache has been cleared, components must be loaded before any new objects can be created. - \sa trimComponentCache() + \note Any existing objects created from QML components retain their types, + even if you clear the component cache. This includes singleton objects. If you + create more objects from the same QML code after clearing the cache, the new + objects will be of different types than the old ones. Assigning such a new + object to a property of its declared type belonging to an object created + before clearing the cache won't work. + + As a general rule of thumb, make sure that no objects created from QML + components are alive when you clear the component cache. + + \sa trimComponentCache(), clearSingletons() */ void QQmlEngine::clearComponentCache() { Q_D(QQmlEngine); + + // Contexts can hold on to CUs but live on the JS heap. + // Use a non-incremental GC run to get rid of those. + QV4::MemoryManager *mm = handle()->memoryManager; + auto oldLimit = mm->gcStateMachine->timeLimit; + mm->setGCTimeLimit(-1); + mm->runGC(); + mm->gcStateMachine->timeLimit = std::move(oldLimit); + + handle()->clearCompilationUnits(); d->typeLoader.lock(); d->typeLoader.clearCache(); d->typeLoader.unlock(); + QQmlMetaType::freeUnusedTypesAndCaches(); } /*! @@ -1002,10 +645,32 @@ void QQmlEngine::clearComponentCache() void QQmlEngine::trimComponentCache() { Q_D(QQmlEngine); + handle()->trimCompilationUnits(); d->typeLoader.trimCache(); } /*! + Clears all singletons the engine owns. + + This function drops all singleton instances, deleting any QObjects owned by + the engine among them. This is useful to make sure that no QML-created objects + are left before calling clearComponentCache(). + + QML properties holding QObject-based singleton instances become null if the + engine owns the singleton or retain their value if the engine doesn't own it. + The singletons are not automatically re-created by accessing existing + QML-created objects. Only when new components are instantiated, the singletons + are re-created. + + \sa clearComponentCache() + */ +void QQmlEngine::clearSingletons() +{ + Q_D(QQmlEngine); + d->singletonInstances.clear(); +} + +/*! Returns the engine's root context. The root context is automatically created by the QQmlEngine. @@ -1080,20 +745,19 @@ QUrl QQmlEngine::interceptUrl(const QUrl &url, QQmlAbstractUrlInterceptor::DataT return result; } -void QQmlEnginePrivate::registerFinalizeCallback(QObject *obj, int index) +/*! + Returns the list of currently active URL interceptors. + */ +QList<QQmlAbstractUrlInterceptor *> QQmlEngine::urlInterceptors() const { - if (activeObjectCreator) { - activeObjectCreator->finalizeCallbacks()->append(qMakePair(QPointer<QObject>(obj), index)); - } else { - void *args[] = { nullptr }; - QMetaObject::metacall(obj, QMetaObject::InvokeMetaMethod, index, args); - } + Q_D(const QQmlEngine); + return d->urlInterceptors; } QSharedPointer<QQmlImageProviderBase> QQmlEnginePrivate::imageProvider(const QString &providerId) const { const QString providerIdLower = providerId.toLower(); - QMutexLocker locker(&mutex); + QMutexLocker locker(&imageProviderMutex); return imageProviders.value(providerIdLower); } @@ -1188,7 +852,7 @@ void QQmlEngine::addImageProvider(const QString &providerId, QQmlImageProviderBa Q_D(QQmlEngine); QString providerIdLower = providerId.toLower(); QSharedPointer<QQmlImageProviderBase> sp(provider); - QMutexLocker locker(&d->mutex); + QMutexLocker locker(&d->imageProviderMutex); d->imageProviders.insert(std::move(providerIdLower), std::move(sp)); } @@ -1201,7 +865,7 @@ QQmlImageProviderBase *QQmlEngine::imageProvider(const QString &providerId) cons { Q_D(const QQmlEngine); const QString providerIdLower = providerId.toLower(); - QMutexLocker locker(&d->mutex); + QMutexLocker locker(&d->imageProviderMutex); return d->imageProviders.value(providerIdLower).data(); } @@ -1214,7 +878,7 @@ void QQmlEngine::removeImageProvider(const QString &providerId) { Q_D(QQmlEngine); const QString providerIdLower = providerId.toLower(); - QMutexLocker locker(&d->mutex); + QMutexLocker locker(&d->imageProviderMutex); d->imageProviders.take(providerIdLower); } @@ -1279,6 +943,45 @@ void QQmlEngine::setOutputWarningsToStandardError(bool enabled) d->outputWarningsToMsgLog = enabled; } + +/*! + \since 6.6 + If this method is called inside of a function that is part of + a binding in QML, the binding will be treated as a translation binding. + + \code + class I18nAwareClass : public QObject { + + //... + + QString text() const + { + if (auto engine = qmlEngine(this)) + engine->markCurrentFunctionAsTranslationBinding(); + return tr("Hello, world!"); + } + }; + \endcode + + \note This function is mostly useful if you wish to provide your + own alternative to the qsTr function. To ensure that properties + exposed from C++ classes are updated on language changes, it is + instead recommended to react to \c LanguageChange events. That + is a more general mechanism which also works when the class is + used in a non-QML context, and has slightly less overhead. However, + using \c markCurrentFunctionAsTranslationBinding can be acceptable + when the class is already closely tied to the QML engine. + For more details, see \l {Prepare for Dynamic Language Changes} + + \sa QQmlEngine::retranslate +*/ +void QQmlEngine::markCurrentFunctionAsTranslationBinding() +{ + Q_D(QQmlEngine); + if (auto propertyCapture = d->propertyCapture) + propertyCapture->captureTranslation(); +} + /*! \internal @@ -1343,7 +1046,7 @@ template<> QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId) { Q_D(QQmlEngine); - QQmlType type = QQmlMetaType::qmlType(qmlTypeId, QQmlMetaType::TypeIdCategory::QmlType); + QQmlType type = QQmlMetaType::qmlTypeById(qmlTypeId); if (!type.isValid() || !type.isSingleton()) return QJSValue(); @@ -1351,6 +1054,48 @@ QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId) return d->singletonInstance<QJSValue>(type); } + +/*! + \fn template<typename T> T QQmlEngine::singletonInstance(QAnyStringView uri, QAnyStringView typeName) + + \overload + Returns the instance of a singleton type named \a typeName from the module specified by \a uri. + + This method can be used as an alternative to calling qmlTypeId followed by the id based overload of + singletonInstance. This is convenient when one only needs to do a one time setup of a + singleton; if repeated access to the singleton is required, caching its typeId will allow + faster subsequent access via the + \l {QQmlEngine::singletonInstance(int qmlTypeId)}{type-id based overload}. + + The template argument \e T may be either QJSValue or a pointer to a QObject-derived + type and depends on how the singleton was registered. If no instance of \e T has been + created yet, it is created now. If \a typeName does not represent a valid singleton + type, either a default constructed QJSValue or a \c nullptr is returned. + + \snippet code/src_qml_qqmlengine.cpp 5 + + \sa QML_SINGLETON, qmlRegisterSingletonType(), qmlTypeId() + \since 6.5 +*/ +template<> +QJSValue QQmlEngine::singletonInstance<QJSValue>(QAnyStringView uri, QAnyStringView typeName) +{ + Q_D(QQmlEngine); + + auto loadHelper = QQml::makeRefPointer<LoadHelper>(&d->typeLoader, uri); + + auto [moduleStatus, type] = loadHelper->resolveType(typeName); + + if (moduleStatus == LoadHelper::ResolveTypeResult::NoSuchModule) + return {}; + if (!type.isValid()) + return {}; + if (!type.isSingleton()) + return {}; + + return d->singletonInstance<QJSValue>(type); +} + /*! Refreshes all binding expressions that use strings marked for translation. @@ -1358,31 +1103,23 @@ QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId) QCoreApplication::installTranslator, to ensure that your user-interface shows up-to-date translations. - \note Due to a limitation in the implementation, this function - refreshes all the engine's bindings, not only those that use strings - marked for translation. - This may be optimized in a future release. - \since 5.10 */ void QQmlEngine::retranslate() { Q_D(QQmlEngine); - for (QQmlRefPointer<QQmlContextData> context - = QQmlContextData::get(d->rootContext)->childContexts(); - context; context = context->nextChild()) { - context->refreshExpressions(); - } + d->translationLanguage.notify(); } /*! - Returns the QQmlContext for the \a object, or 0 if no + Returns the QQmlContext for the \a object, or nullptr if no context has been set. - When the QQmlEngine instantiates a QObject, the context is - set automatically. + When the QQmlEngine instantiates a QObject, an internal context is assigned + to it automatically. Such internal contexts are read-only. You cannot set + context properties on them. - \sa qmlContext(), qmlEngine() + \sa qmlContext(), qmlEngine(), QQmlContext::setContextProperty() */ QQmlContext *QQmlEngine::contextForObject(const QObject *object) { @@ -1426,27 +1163,13 @@ void QQmlEngine::setContextForObject(QObject *object, QQmlContext *context) */ bool QQmlEngine::event(QEvent *e) { - Q_D(QQmlEngine); - if (e->type() == QEvent::User) - d->doDeleteInEngineThread(); - else if (e->type() == QEvent::LanguageChange) { + if (e->type() == QEvent::LanguageChange) { retranslate(); } return QJSEngine::event(e); } -void QQmlEnginePrivate::doDeleteInEngineThread() -{ - QFieldList<Deletable, &Deletable::next> list; - mutex.lock(); - list.copyAndClear(toDeleteInEngineThread); - mutex.unlock(); - - while (Deletable *d = list.takeFirst()) - delete d; -} - class QQmlDataExtended { public: QQmlDataExtended(); @@ -1526,13 +1249,14 @@ void QQmlData::deferData( deferData->context = context; const QV4::CompiledData::Object *compiledObject = compilationUnit->objectAt(objectIndex); - const QV4::BindingPropertyData &propertyData = compilationUnit->bindingPropertyDataPerObject.at(objectIndex); + const QV4::CompiledData::BindingPropertyData *propertyData + = compilationUnit->bindingPropertyDataPerObjectAt(objectIndex); const QV4::CompiledData::Binding *binding = compiledObject->bindingTable(); for (quint32 i = 0; i < compiledObject->nBindings; ++i, ++binding) { - const QQmlPropertyData *property = propertyData.at(i); - if (property && binding->flags & QV4::CompiledData::Binding::IsDeferredBinding) - deferData->bindings.insert(property->coreIndex(), binding); + const QQmlPropertyData *property = propertyData->at(i); + if (binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding)) + deferData->bindings.insert(property ? property->coreIndex() : -1, binding); } deferredData.append(deferData); @@ -1554,49 +1278,73 @@ void QQmlData::releaseDeferredData() void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint) { - if (!notifyList) { - notifyList = (NotifyList *)malloc(sizeof(NotifyList)); - notifyList->connectionMask = 0; - notifyList->maximumTodoIndex = 0; - notifyList->notifiesSize = 0; - notifyList->todo = nullptr; - notifyList->notifies = nullptr; + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + + NotifyList *list = notifyList.loadRelaxed(); + + if (!list) { + list = new NotifyList; + // We don't really care when this change takes effect on other threads. The notifyList can + // only become non-null once in the life time of a QQmlData. It becomes null again when the + // underlying QObject is deleted. At that point any interaction with the QQmlData is UB + // anyway. So, for all intents and purposese, the list becomes non-null once and then stays + // non-null "forever". We can apply relaxed semantics. + notifyList.storeRelaxed(list); } Q_ASSERT(!endpoint->isConnected()); index = qMin(index, 0xFFFF - 1); - notifyList->connectionMask |= (1ULL << quint64(index % 64)); - if (index < notifyList->notifiesSize) { + // Likewise, we don't really care _when_ the change in the connectionMask is propagated to other + // threads. Cross-thread event ordering is inherently nondeterministic. Therefore, when querying + // the conenctionMask in the presence of concurrent modification, any result is correct. + list->connectionMask.storeRelaxed( + list->connectionMask.loadRelaxed() | (1ULL << quint64(index % 64))); - endpoint->next = notifyList->notifies[index]; + if (index < list->notifiesSize) { + endpoint->next = list->notifies[index]; if (endpoint->next) endpoint->next->prev = &endpoint->next; - endpoint->prev = ¬ifyList->notifies[index]; - notifyList->notifies[index] = endpoint; - + endpoint->prev = &list->notifies[index]; + list->notifies[index] = endpoint; } else { - notifyList->maximumTodoIndex = qMax(int(notifyList->maximumTodoIndex), index); + list->maximumTodoIndex = qMax(int(list->maximumTodoIndex), index); - endpoint->next = notifyList->todo; + endpoint->next = list->todo; if (endpoint->next) endpoint->next->prev = &endpoint->next; - endpoint->prev = ¬ifyList->todo; - notifyList->todo = endpoint; + endpoint->prev = &list->todo; + list->todo = endpoint; } } -void QQmlData::disconnectNotifiers() +void QQmlData::disconnectNotifiers(QQmlData::DeleteNotifyList doDelete) { - if (notifyList) { - while (notifyList->todo) - notifyList->todo->disconnect(); - for (int ii = 0; ii < notifyList->notifiesSize; ++ii) { - while (QQmlNotifierEndpoint *ep = notifyList->notifies[ii]) + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + if (NotifyList *list = notifyList.loadRelaxed()) { + while (QQmlNotifierEndpoint *todo = list->todo) + todo->disconnect(); + for (int ii = 0; ii < list->notifiesSize; ++ii) { + while (QQmlNotifierEndpoint *ep = list->notifies[ii]) ep->disconnect(); } - free(notifyList->notifies); - free(notifyList); - notifyList = nullptr; + free(list->notifies); + + if (doDelete == DeleteNotifyList::Yes) { + // We can only get here from QQmlData::destroyed(), and that can only come from the + // the QObject dtor. If you're still sending signals at that point you have UB already + // without any threads. Therefore, it's enough to apply relaxed semantics. + notifyList.storeRelaxed(nullptr); + delete list; + } else { + // We can use relaxed semantics here. The worst thing that can happen is that some + // signal is falsely reported as connected. Signal connectedness across threads + // is not quite deterministic anyway. + list->connectionMask.storeRelaxed(0); + list->maximumTodoIndex = 0; + list->notifiesSize = 0; + list->notifies = nullptr; + + } } } @@ -1623,7 +1371,7 @@ void QQmlData::destroyed(QObject *object) if (bindings && !bindings->ref.deref()) delete bindings; - compilationUnit = nullptr; + compilationUnit.reset(); qDeleteAll(deferredData); deferredData.clear(); @@ -1670,17 +1418,18 @@ void QQmlData::destroyed(QObject *object) free(bindingBits); if (propertyCache) - propertyCache->release(); + propertyCache.reset(); - ownContext = nullptr; + ownContext.reset(); while (guards) { - QQmlGuard<QObject> *guard = static_cast<QQmlGuard<QObject> *>(guards); - *guard = (QObject *)nullptr; - guard->objectDestroyed(object); + auto *guard = guards; + guard->setObject(nullptr); + if (guard->objectDestroyed) + guard->objectDestroyed(guard); } - disconnectNotifiers(); + disconnectNotifiers(DeleteNotifyList::Yes); if (extendedData) delete extendedData; @@ -1721,14 +1470,14 @@ QQmlData *QQmlData::createQQmlData(QObjectPrivate *priv) { Q_ASSERT(priv); Q_ASSERT(!priv->isDeletingChildren); - priv->declarativeData = new QQmlData; + priv->declarativeData = new QQmlData(OwnsMemory); return static_cast<QQmlData *>(priv->declarativeData); } -QQmlPropertyCache *QQmlData::createPropertyCache(QJSEngine *engine, QObject *object) +QQmlPropertyCache::ConstPtr QQmlData::createPropertyCache(QObject *object) { QQmlData *ddata = QQmlData::get(object, /*create*/true); - ddata->propertyCache = QJSEnginePrivate::get(engine)->cache(object, QTypeRevision {}, true); + ddata->propertyCache = QQmlMetaType::propertyCache(object, QTypeRevision {}); return ddata->propertyCache; } @@ -1778,14 +1527,14 @@ static void dumpwarning(const QQmlError &error) static void dumpwarning(const QList<QQmlError> &errors) { - for (int ii = 0; ii < errors.count(); ++ii) + for (int ii = 0; ii < errors.size(); ++ii) dumpwarning(errors.at(ii)); } void QQmlEnginePrivate::warning(const QQmlError &error) { Q_Q(QQmlEngine); - q->warnings(QList<QQmlError>() << error); + emit q->warnings(QList<QQmlError>({error})); if (outputWarningsToMsgLog) dumpwarning(error); } @@ -1793,7 +1542,7 @@ void QQmlEnginePrivate::warning(const QQmlError &error) void QQmlEnginePrivate::warning(const QList<QQmlError> &errors) { Q_Q(QQmlEngine); - q->warnings(errors); + emit q->warnings(errors); if (outputWarningsToMsgLog) dumpwarning(errors); } @@ -1876,7 +1625,8 @@ void QQmlEnginePrivate::cleanupScarceResources() The newly added \a path will be first in the importPathList(). - \sa setImportPathList(), {QML Modules} + \b {See also} \l setImportPathList(), \l {QML Modules}, + and \l [QtQml] {QML Import Path} */ void QQmlEngine::addImportPath(const QString& path) { @@ -1894,9 +1644,8 @@ void QQmlEngine::addImportPath(const QString& path) provided by that module. A \c qmldir file is required for defining the type version mapping and possibly QML extensions plugins. - By default, the list contains the directory of the application executable, - paths specified in the \c QML_IMPORT_PATH environment variable, - and the builtin \c QmlImportsPath from QLibraryInfo. + By default, this list contains the paths mentioned in + \l {QML Import Path}. \sa addImportPath(), setImportPathList() */ @@ -1910,9 +1659,11 @@ QStringList QQmlEngine::importPathList() const Sets \a paths as the list of directories where the engine searches for installed modules in a URL-based directory structure. - By default, the list contains the directory of the application executable, - paths specified in the \c QML_IMPORT_PATH environment variable, - and the builtin \c QmlImportsPath from QLibraryInfo. + By default, this list contains the paths mentioned in + \l {QML Import Path}. + + \warning Calling setImportPathList does not preserve the default + import paths. \sa importPathList(), addImportPath() */ @@ -1972,21 +1723,31 @@ void QQmlEngine::setPluginPathList(const QStringList &paths) } #if QT_CONFIG(library) +#if QT_DEPRECATED_SINCE(6, 4) /*! + \deprecated [6.4] Import the module from QML with an "import" statement instead. + Imports the plugin named \a filePath with the \a uri provided. Returns true if the plugin was successfully imported; otherwise returns false. On failure and if non-null, the \a errors list will have any errors which occurred prepended to it. The plugin has to be a Qt plugin which implements the QQmlEngineExtensionPlugin interface. + + \note Directly loading plugins like this can confuse the module import logic. In order to make + the import logic load plugins from a specific place, you can use \l addPluginPath(). Each + plugin should be part of a QML module that you can import using the "import" statement. */ bool QQmlEngine::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors) { Q_D(QQmlEngine); - return d->importDatabase.importDynamicPlugin(filePath, uri, QString(), QTypeRevision(), false, - errors).isValid(); + QQmlTypeLoaderQmldirContent qmldir; + QQmlPluginImporter importer( + uri, QTypeRevision(), &d->importDatabase, &qmldir, &d->typeLoader, errors); + return importer.importDynamicPlugin(filePath, uri, false).isValid(); } #endif +#endif /*! \property QQmlEngine::offlineStoragePath @@ -1995,7 +1756,7 @@ bool QQmlEngine::importPlugin(const QString &filePath, const QString &uri, QList Returns the directory where SQL and other offline storage is placed. - The SQL databases created with openDatabase() are stored here. + The SQL databases created with \c openDatabaseSync() are stored here. The default is QML/OfflineStorage in the platform-standard user application data directory. @@ -2003,11 +1764,23 @@ bool QQmlEngine::importPlugin(const QString &filePath, const QString &uri, QList Note that the path may not currently exist on the filesystem, so callers wanting to \e create new files at this location should create it first - see QDir::mkpath(). + + \sa {Qt Quick Local Storage QML Types} +*/ + +/*! + \fn void QQmlEngine::offlineStoragePathChanged() + This signal is emitted when \l offlineStoragePath changes. + \since 6.5 */ + void QQmlEngine::setOfflineStoragePath(const QString& dir) { Q_D(QQmlEngine); + if (dir == d->offlineStoragePath) + return; d->offlineStoragePath = dir; + Q_EMIT offlineStoragePathChanged(); } QString QQmlEngine::offlineStoragePath() const @@ -2017,10 +1790,12 @@ QString QQmlEngine::offlineStoragePath() const if (d->offlineStoragePath.isEmpty()) { QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QQmlEnginePrivate *e = const_cast<QQmlEnginePrivate *>(d); - if (!dataLocation.isEmpty()) + if (!dataLocation.isEmpty()) { e->offlineStoragePath = dataLocation.replace(QLatin1Char('/'), QDir::separator()) + QDir::separator() + QLatin1String("QML") + QDir::separator() + QLatin1String("OfflineStorage"); + Q_EMIT e->q_func()->offlineStoragePathChanged(); + } } return d->offlineStoragePath; @@ -2047,148 +1822,18 @@ QString QQmlEnginePrivate::offlineStorageDatabaseDirectory() const return q->offlineStoragePath() + QDir::separator() + QLatin1String("Databases") + QDir::separator(); } -static QQmlPropertyCache *propertyCacheForPotentialInlineComponentType(int t, const QHash<int, QV4::ExecutableCompilationUnit *>::const_iterator &iter) { - if (t != (*iter)->typeIds.id.id()) { - // this is an inline component, and what we have in the iterator is currently the parent compilation unit - for (auto &&icDatum: (*iter)->inlineComponentData) - if (icDatum.typeIds.id.id() == t) - return (*iter)->propertyCaches.at(icDatum.objectIndex); - } - return (*iter)->rootPropertyCache().data(); -} - -/*! - * \internal - * - * Look up by type's baseMetaObject. - */ -QQmlMetaObject QQmlEnginePrivate::rawMetaObjectForType(int t) const -{ - if (QQmlPropertyCache *composite = findPropertyCacheInCompositeTypes(t)) - return QQmlMetaObject(composite); - - QQmlType type = QQmlMetaType::qmlType(t); - return QQmlMetaObject(type.baseMetaObject()); -} - -/*! - * \internal - * - * Look up by type's metaObject. - */ -QQmlMetaObject QQmlEnginePrivate::metaObjectForType(int t) const -{ - if (QQmlPropertyCache *composite = findPropertyCacheInCompositeTypes(t)) - return QQmlMetaObject(composite); - - QQmlType type = QQmlMetaType::qmlType(t); - return QQmlMetaObject(type.metaObject()); -} - -/*! - * \internal - * - * Look up by type's metaObject and version. - */ -QQmlPropertyCache *QQmlEnginePrivate::propertyCacheForType(int t) -{ - if (QQmlPropertyCache *composite = findPropertyCacheInCompositeTypes(t)) - return composite; - - QQmlType type = QQmlMetaType::qmlType(t); - return type.isValid() ? cache(type.metaObject(), type.version()) : nullptr; -} - -/*! - * \internal - * - * Look up by type's baseMetaObject and unspecified/any version. - * TODO: Is this correct? Passing a plain QTypeRevision() rather than QTypeRevision::zero() or - * the actual type's version seems strange. The behavior has been in place for a while. - */ -QQmlPropertyCache *QQmlEnginePrivate::rawPropertyCacheForType(int t) -{ - if (QQmlPropertyCache *composite = findPropertyCacheInCompositeTypes(t)) - return composite; - - QQmlType type = QQmlMetaType::qmlType(t); - return type.isValid() ? cache(type.baseMetaObject(), QTypeRevision()) : nullptr; -} - -/*! - * \internal - * - * Look up by QQmlType and version. We only fall back to lookup by metaobject if the type - * has no revisiononed attributes here. Unspecified versions are interpreted as "any". - */ -QQmlPropertyCache *QQmlEnginePrivate::rawPropertyCacheForType(int t, QTypeRevision version) -{ - if (QQmlPropertyCache *composite = findPropertyCacheInCompositeTypes(t)) - return composite; - - QQmlType type = QQmlMetaType::qmlType(t); - if (!type.isValid()) - return nullptr; - - if (type.containsRevisionedAttributes()) - return QQmlMetaType::propertyCache(type, version); - - if (const QMetaObject *metaObject = type.metaObject()) - return cache(metaObject, version); - - return nullptr; -} - -QQmlPropertyCache *QQmlEnginePrivate::findPropertyCacheInCompositeTypes(int t) const -{ - Locker locker(this); - auto iter = m_compositeTypes.constFind(t); - return (iter == m_compositeTypes.constEnd()) - ? nullptr - : propertyCacheForPotentialInlineComponentType(t, iter); -} - -void QQmlEnginePrivate::registerInternalCompositeType(QV4::ExecutableCompilationUnit *compilationUnit) -{ - compilationUnit->isRegisteredWithEngine = true; - - Locker locker(this); - // The QQmlCompiledData is not referenced here, but it is removed from this - // hash in the QQmlCompiledData destructor - m_compositeTypes.insert(compilationUnit->typeIds.id.id(), compilationUnit); - for (auto &&data: compilationUnit->inlineComponentData) - m_compositeTypes.insert(data.typeIds.id.id(), compilationUnit); -} - -void QQmlEnginePrivate::unregisterInternalCompositeType(QV4::ExecutableCompilationUnit *compilationUnit) -{ - compilationUnit->isRegisteredWithEngine = false; - - Locker locker(this); - m_compositeTypes.remove(compilationUnit->typeIds.id.id()); - for (auto&& icDatum: compilationUnit->inlineComponentData) - m_compositeTypes.remove(icDatum.typeIds.id.id()); -} - -QV4::ExecutableCompilationUnit *QQmlEnginePrivate::obtainExecutableCompilationUnit(int typeId) -{ - Locker locker(this); - return m_compositeTypes.value(typeId, nullptr); -} - template<> QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) { Q_Q(QQmlEngine); - QJSValue value = singletonInstances.value(type); - if (!value.isUndefined()) { - return value; - } - - QQmlType::SingletonInstanceInfo *siinfo = type.singletonInstanceInfo(); + QQmlType::SingletonInstanceInfo::ConstPtr siinfo = type.singletonInstanceInfo(); Q_ASSERT(siinfo != nullptr); + QJSValue value = singletonInstances.value(siinfo); + if (!value.isUndefined()) + return value; + if (siinfo->scriptCallback) { value = siinfo->scriptCallback(q, q); if (value.isQObject()) { @@ -2197,7 +1842,7 @@ QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) // should behave identically to QML singleton types. q->setContextForObject(o, new QQmlContext(q->rootContext(), q)); } - singletonInstances.convertAndInsert(v4engine(), type, &value); + singletonInstances.convertAndInsert(v4engine(), siinfo, &value); } else if (siinfo->qobjectCallback) { QObject *o = siinfo->qobjectCallback(q, q); @@ -2211,13 +1856,22 @@ QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) type.createProxy(o); // if this object can use a property cache, create it now - QQmlData::ensurePropertyCache(q, o); + QQmlData::ensurePropertyCache(o); + + // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) + // should behave identically to QML singleton types. You can, however, manually + // assign a context; and clearSingletons() retains the contexts, in which case + // we don't want to see warnings about the object already having a context. + QQmlData *data = QQmlData::get(o, true); + if (!data->context) { + auto contextData = QQmlContextData::get(new QQmlContext(q->rootContext(), q)); + data->context = contextData.data(); + contextData->addOwnedObject(data); + } } - // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) - // should behave identically to QML singleton types. - q->setContextForObject(o, new QQmlContext(q->rootContext(), q)); + value = q->newQObject(o); - singletonInstances.convertAndInsert(v4engine(), type, &value); + singletonInstances.convertAndInsert(v4engine(), siinfo, &value); } else if (!siinfo->url.isEmpty()) { QQmlComponent component(q, siinfo->url, QQmlComponent::PreferSynchronous); if (component.isError()) { @@ -2228,26 +1882,13 @@ QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) } QObject *o = component.beginCreate(q->rootContext()); value = q->newQObject(o); - singletonInstances.convertAndInsert(v4engine(), type, &value); + singletonInstances.convertAndInsert(v4engine(), siinfo, &value); component.completeCreate(); } return value; } -void QQmlEnginePrivate::destroySingletonInstance(const QQmlType &type) -{ - Q_ASSERT(type.isSingleton() || type.isCompositeSingleton()); - - QObject* o = singletonInstances.take(type).toQObject(); - if (o) { - QQmlData *ddata = QQmlData::get(o, false); - if (type.singletonInstanceInfo()->url.isEmpty() && ddata && ddata->indestructible && ddata->explicitIndestructibleSet) - return; - delete o; - } -} - bool QQmlEnginePrivate::isTypeLoaded(const QUrl &url) const { return typeLoader.isTypeLoaded(url); @@ -2258,28 +1899,111 @@ bool QQmlEnginePrivate::isScriptLoaded(const QUrl &url) const return typeLoader.isScriptLoaded(url); } -QJSValue QQmlEnginePrivate::executeRuntimeFunction(const QUrl &url, qsizetype functionIndex, - QObject *thisObject, int argc, void **args, QMetaType *types) +void QQmlEnginePrivate::executeRuntimeFunction(const QUrl &url, qsizetype functionIndex, + QObject *thisObject, int argc, void **args, + QMetaType *types) { - Q_Q(QQmlEngine); - if (const auto unit = typeLoader.getType(url)->compilationUnit()) { - Q_ASSERT(functionIndex >= 0); - Q_ASSERT(thisObject); - - if (!unit->engine) - unit->linkToEngine(q->handle()); - - if (unit->runtimeFunctions.length() <= functionIndex) - return QJSValue(); - - QQmlContext *ctx = q->contextForObject(thisObject); - if (!ctx) - ctx = q->rootContext(); - return QJSValuePrivate::fromReturnedValue( - q->handle()->callInContext(unit->runtimeFunctions[functionIndex], thisObject, - QQmlContextData::get(ctx), argc, args, types)); + const auto unit = compilationUnitFromUrl(url); + if (!unit) + return; + executeRuntimeFunction(unit, functionIndex, thisObject, argc, args, types); +} + +void QQmlEnginePrivate::executeRuntimeFunction(const QV4::ExecutableCompilationUnit *unit, + qsizetype functionIndex, QObject *thisObject, + int argc, void **args, QMetaType *types) +{ + Q_ASSERT(unit); + Q_ASSERT((functionIndex >= 0) && (functionIndex < unit->runtimeFunctions.size())); + Q_ASSERT(thisObject); + + QQmlData *ddata = QQmlData::get(thisObject); + Q_ASSERT(ddata && ddata->outerContext); + + QV4::Function *function = unit->runtimeFunctions[functionIndex]; + Q_ASSERT(function); + Q_ASSERT(function->compiledFunction); + + QV4::ExecutionEngine *v4 = v4engine(); + + // NB: always use scriptContext() by default as this method ignores whether + // there's already a stack frame (except when dealing with closures). the + // method is called from C++ (through QQmlEngine::executeRuntimeFunction()) + // and thus the caller must ensure correct setup + QV4::Scope scope(v4); + QV4::ExecutionContext *ctx = v4->scriptContext(); + QV4::Scoped<QV4::ExecutionContext> callContext(scope, + QV4::QmlContext::create(ctx, ddata->outerContext, thisObject)); + + if (auto nested = function->nestedFunction()) { + // if a nested function is already known, call the closure directly + function = nested; + } else if (function->isClosureWrapper()) { + // if there is a nested function, but we don't know it, we need to call + // an outer function first and then the inner function. we fetch the + // return value of a function call (that is a closure) by calling a + // different version of ExecutionEngine::callInContext() that returns a + // QV4::ReturnedValue with no arguments since they are not needed by the + // outer function anyhow + QV4::ScopedFunctionObject result(scope, + v4->callInContext(function, thisObject, callContext, 0, nullptr)); + Q_ASSERT(result->function()); + Q_ASSERT(result->function()->compilationUnit == function->compilationUnit); + + // overwrite the function and its context + function = result->function(); + callContext = QV4::Scoped<QV4::ExecutionContext>(scope, result->scope()); } - return QJSValue(); + + v4->callInContext(function, thisObject, callContext, argc, args, types); +} + +QV4::ExecutableCompilationUnit *QQmlEnginePrivate::compilationUnitFromUrl(const QUrl &url) +{ + QV4::ExecutionEngine *v4 = v4engine(); + if (auto unit = v4->compilationUnitForUrl(url)) { + if (!unit->runtimeStrings) + unit->populate(); + return unit.data(); + } + + auto unit = typeLoader.getType(url)->compilationUnit(); + if (!unit) + return nullptr; + + auto executable = v4->executableCompilationUnit(std::move(unit)); + executable->populate(); + return executable.data(); +} + +QQmlRefPointer<QQmlContextData> +QQmlEnginePrivate::createInternalContext(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, + const QQmlRefPointer<QQmlContextData> &parentContext, + int subComponentIndex, bool isComponentRoot) +{ + Q_ASSERT(unit); + + QQmlRefPointer<QQmlContextData> context; + context = QQmlContextData::createRefCounted(parentContext); + context->setInternal(true); + context->setImports(unit->typeNameCache()); + context->initFromTypeCompilationUnit(unit, subComponentIndex); + + const auto *dependentScripts = unit->dependentScriptsPtr(); + const qsizetype dependentScriptsSize = dependentScripts->size(); + if (isComponentRoot && dependentScriptsSize) { + QV4::ExecutionEngine *v4 = v4engine(); + Q_ASSERT(v4); + QV4::Scope scope(v4); + + QV4::ScopedObject scripts(scope, v4->newArrayObject(dependentScriptsSize)); + context->setImportedScripts(QV4::PersistentValue(v4, scripts.asReturnedValue())); + QV4::ScopedValue v(scope); + for (qsizetype i = 0; i < dependentScriptsSize; ++i) + scripts->put(i, (v = dependentScripts->at(i)->scriptValueForContext(context))); + } + + return context; } #if defined(Q_OS_WIN) @@ -2389,6 +2113,53 @@ bool QQml_isFileCaseCorrect(const QString &fileName, int lengthIn /* = -1 */) \sa {QQmlEngine::contextForObject()}{contextForObject()}, qmlEngine() */ +void hasJsOwnershipIndicator(QQmlGuardImpl *) {}; + +LoadHelper::LoadHelper(QQmlTypeLoader *loader, QAnyStringView uri) + : QQmlTypeLoader::Blob({}, QQmlDataBlob::QmlFile, loader) + , m_uri(uri.toString()) + +{ + auto import = std::make_shared<PendingImport>(); + import->uri = m_uri; + QList<QQmlError> errorList; + if (!Blob::addImport(import, &errorList)) { + qCDebug(lcQmlImport) << "LoadHelper: Errors loading " << m_uri << errorList; + m_uri.clear(); // reset m_uri to remember the failure + } +} + +LoadHelper::ResolveTypeResult LoadHelper::resolveType(QAnyStringView typeName) +{ + QQmlType type; + if (!couldFindModule()) + return {ResolveTypeResult::NoSuchModule, type}; + QQmlTypeModule *module = QQmlMetaType::typeModule(m_uri, QTypeRevision{}); + if (module) { + type = module->type(typeName.toString(), {}); + if (type.isValid()) + return {ResolveTypeResult::ModuleFound, type}; + } + // The module exists (see check above), but there is no QQmlTypeModule + // ==> pure QML module, attempt resolveType + QTypeRevision versionReturn; + QList<QQmlError> errors; + QQmlImportNamespace *ns_return = nullptr; + m_importCache->resolveType( + typeLoader(), typeName.toString(), &type, &versionReturn, &ns_return, &errors); + return {ResolveTypeResult::ModuleFound, type}; +} + +bool LoadHelper::couldFindModule() const +{ + if (m_uri.isEmpty()) + return false; + for (const auto &import: std::as_const(m_unresolvedImports)) + if (import->priority == 0) // compare QQmlTypeData::allDependenciesDone + return false; + return true; +} + QT_END_NAMESPACE #include "moc_qqmlengine.cpp" |