diff options
Diffstat (limited to 'src/qml/qml/qqmlengine.cpp')
-rw-r--r-- | src/qml/qml/qqmlengine.cpp | 1876 |
1 files changed, 722 insertions, 1154 deletions
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 7cd4bf8579..62b7675e1d 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -1,66 +1,24 @@ -/**************************************************************************** -** -** 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 "qqmldirparser_p.h" -#include "qqmlextensioninterface.h" -#include "qqmllist_p.h" -#include "qqmltypenamecache_p.h" #include "qqmlnotifier_p.h" #include "qqmlincubator.h" #include "qqmlabstracturlinterceptor.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/qsettings.h> #include <QtCore/qmetaobject.h> #include <QDebug> #include <QtCore/qcoreapplication.h> @@ -69,11 +27,14 @@ #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" #include <QNetworkAccessManager> -#include <QtNetwork/qnetworkconfigmanager.h> #endif #include <private/qobject_p.h> @@ -86,75 +47,22 @@ #if QT_CONFIG(qml_animation) #include <private/qqmltimer_p.h> #endif -#if QT_CONFIG(qml_list_model) -#include <private/qqmllistmodel_p.h> -#endif #include <private/qqmlplatform_p.h> -#include <private/qquickpackage_p.h> -#if QT_CONFIG(qml_delegate_model) -#include <private/qqmldelegatemodel_p.h> -#endif -#include <private/qqmlobjectmodel_p.h> -#if QT_CONFIG(qml_worker_script) -#include <private/qquickworkerscript_p.h> -#endif -#include <private/qqmlinstantiator_p.h> #include <private/qqmlloggingcategory_p.h> +#include <private/qv4sequenceobject_p.h> #ifdef Q_OS_WIN // for %APPDATA% # include <qt_windows.h> -# ifndef Q_OS_WINRT -# include <shlobj.h> -# endif +# include <shlobj.h> # include <qlibrary.h> # ifndef CSIDL_APPDATA # define CSIDL_APPDATA 0x001a // <username>\Application Data # endif #endif // Q_OS_WIN -Q_DECLARE_METATYPE(QQmlProperty) - QT_BEGIN_NAMESPACE -void qmlRegisterBaseTypes(const char *uri, int versionMajor, int versionMinor) -{ - QQmlEnginePrivate::registerBaseTypes(uri, versionMajor, versionMinor); - QQmlEnginePrivate::registerQtQuick2Types(uri, versionMajor, versionMinor); - QQmlValueTypeFactory::registerValueTypes(uri, versionMajor, versionMinor); -} - -// Declared in qqml.h -int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, - const char *uri, int versionMajor, - int versionMinor, const char *qmlName, - const QString& reason) -{ - QQmlPrivate::RegisterType type = { - 0, - - 0, - 0, - 0, - nullptr, - reason, - - uri, versionMajor, versionMinor, qmlName, &staticMetaObject, - - QQmlAttachedPropertiesFunc(), - nullptr, - - 0, - 0, - 0, - - nullptr, nullptr, - - nullptr, - 0 - }; - - return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); -} +void qml_register_types_QML(); /*! \qmltype QtObject @@ -212,67 +120,9 @@ int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, \endcode */ -bool QQmlEnginePrivate::qml_debugging_enabled = false; +Q_CONSTINIT std::atomic<bool> QQmlEnginePrivate::qml_debugging_enabled{false}; bool QQmlEnginePrivate::s_designerMode = false; -// these types are part of the QML language -void QQmlEnginePrivate::registerBaseTypes(const char *uri, int versionMajor, int versionMinor) -{ - qmlRegisterType<QQmlComponent>(uri,versionMajor,versionMinor,"Component"); - qmlRegisterType<QObject>(uri,versionMajor,versionMinor,"QtObject"); - qmlRegisterType<QQmlBind>(uri, versionMajor, versionMinor,"Binding"); - qmlRegisterType<QQmlBind,8>(uri, versionMajor, (versionMinor < 8 ? 8 : versionMinor), "Binding"); //Only available in >=2.8 - qmlRegisterCustomType<QQmlConnections>(uri, versionMajor, 0, "Connections", new QQmlConnectionsParser); - if (!strcmp(uri, "QtQuick")) - qmlRegisterCustomType<QQmlConnections,1>(uri, versionMajor, 7, "Connections", new QQmlConnectionsParser); //Only available in QtQuick >=2.7 - else - qmlRegisterCustomType<QQmlConnections,1>(uri, versionMajor, 3, "Connections", new QQmlConnectionsParser); //Only available in QtQml >=2.3 -#if QT_CONFIG(qml_animation) - qmlRegisterType<QQmlTimer>(uri, versionMajor, versionMinor,"Timer"); -#endif - qmlRegisterType<QQmlInstantiator>(uri, versionMajor, (versionMinor < 1 ? 1 : versionMinor), "Instantiator"); //Only available in >=2.1 - qmlRegisterType<QQmlInstanceModel>(); - - qmlRegisterType<QQmlLoggingCategory>(uri, versionMajor, 8, "LoggingCategory"); //Only available in >=2.8 - qmlRegisterType<QQmlLoggingCategory,1>(uri, versionMajor, 12, "LoggingCategory"); //Only available in >=2.12 -} - - -// These QtQuick types' implementation resides in the QtQml module -void QQmlEnginePrivate::registerQtQuick2Types(const char *uri, int versionMajor, int versionMinor) -{ -#if QT_CONFIG(qml_list_model) - qmlRegisterType<QQmlListElement>(uri, versionMajor, versionMinor, "ListElement"); // Now in QtQml.Models, here for compatibility - qmlRegisterCustomType<QQmlListModel>(uri, versionMajor, versionMinor, "ListModel", new QQmlListModelParser); // Now in QtQml.Models, here for compatibility -#endif -#if QT_CONFIG(qml_worker_script) - qmlRegisterType<QQuickWorkerScript>(uri, versionMajor, versionMinor, "WorkerScript"); -#endif - qmlRegisterType<QQuickPackage>(uri, versionMajor, versionMinor, "Package"); -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) -#if QT_CONFIG(qml_delegate_model) - qmlRegisterType<QQmlDelegateModel>(uri, versionMajor, versionMinor, "VisualDataModel"); - qmlRegisterType<QQmlDelegateModelGroup>(uri, versionMajor, versionMinor, "VisualDataGroup"); -#endif - qmlRegisterType<QQmlObjectModel>(uri, versionMajor, versionMinor, "VisualItemModel"); -#endif // < Qt 6 -} - -void QQmlEnginePrivate::defineQtQuick2Module() -{ - // register the base types into the QtQuick namespace - registerBaseTypes("QtQuick",2,0); - - // register the QtQuick2 types which are implemented in the QtQml module. - registerQtQuick2Types("QtQuick",2,0); -#if QT_CONFIG(qml_locale) - qmlRegisterUncreatableType<QQmlLocale>("QtQuick", 2, 0, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); -#endif - - // Auto-increment the import to stay in sync with ALL future QtQuick minor versions from 5.11 onward - qmlRegisterModule("QtQuick", 2, QT_VERSION_MINOR); -} - bool QQmlEnginePrivate::designerMode() { return s_designerMode; @@ -347,436 +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 "winrt" - WinRT / UWP - \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, {QtQuick.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) - -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), - cleanup(nullptr), 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 - urlInterceptor(nullptr), 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); - while (cleanup) { - QQmlCleanup *c = cleanup; - cleanup = c->next; - if (cleanup) cleanup->prev = &cleanup; - c->next = nullptr; - c->prev = nullptr; - c->clear(); - } - - 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; - - // since unregisterInternalCompositeType() will not be called in this - // case, we have to clean up the type registration manually - QMetaType::unregisterType(iter.value()->metaTypeId); - QMetaType::unregisterType(iter.value()->listMetaTypeId); - } #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 (QQmlContextData *lc = d->ownContext->linkedContext; lc; lc = lc->linkedContext) { - lc->invalidate(); - if (lc->contextObject == o) - lc->contextObject = nullptr; - } - d->ownContext->invalidate(); - if (d->ownContext->contextObject == o) - d->ownContext->contextObject = 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->contextObject = 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() - : ownedByQml1(false), 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), parentFrozen(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(); @@ -789,18 +267,9 @@ QQmlData::~QQmlData() void QQmlData::destroyed(QAbstractDeclarativeData *d, QObject *o) { QQmlData *ddata = static_cast<QQmlData *>(d); - if (ddata->ownedByQml1) - return; ddata->destroyed(o); } -void QQmlData::parentChanged(QAbstractDeclarativeData *d, QObject *o, QObject *p) -{ - QQmlData *ddata = static_cast<QQmlData *>(d); - if (ddata->ownedByQml1) - return; - ddata->parentChanged(o, p); -} class QQmlThreadNotifierProxyObject : public QObject { @@ -828,7 +297,6 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in { QQmlData *ddata = QQmlData::get(object, false); if (!ddata) return; // Probably being deleted - if (ddata->ownedByQml1) return; // In general, QML only supports QObject's that live on the same thread as the QQmlEngine // that they're exposed to. However, to make writing "worker objects" that calculate data @@ -836,47 +304,48 @@ 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 && - QThread::currentThreadId() != QObjectPrivate::get(object)->threadData->threadId.load()) { - if (!QObjectPrivate::get(object)->threadData->thread) + // 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(); + if (QThread::currentThreadId() != objectThreadData->threadId.loadRelaxed()) { + if (!objectThreadData->thread.loadAcquire()) return; QMetaMethod m = QMetaObjectPrivate::signal(object->metaObject(), index); QList<QByteArray> parameterTypes = m.parameterTypes(); - int *types = (int *)malloc((parameterTypes.count() + 1) * sizeof(int)); - void **args = (void **) malloc((parameterTypes.count() + 1) *sizeof(void *)); + auto ev = std::make_unique<QMetaCallEvent>(m.methodIndex(), 0, nullptr, + object, index, + parameterTypes.size() + 1); - types[0] = 0; // return type - args[0] = nullptr; // return value + 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::VoidStar; + types[ii + 1] = QMetaType(QMetaType::VoidStar); else - types[ii + 1] = QMetaType::type(typeName); + types[ii + 1] = QMetaType::fromName(typeName); - if (!types[ii + 1]) { + if (!types[ii + 1].isValid()) { qWarning("QObject::connect: Cannot queue arguments of type '%s'\n" "(Make sure '%s' is registered using qRegisterMetaType().)", typeName.constData(), typeName.constData()); - free(types); - free(args); return; } - args[ii + 1] = QMetaType::create(types[ii + 1], a[ii + 1]); + args[ii + 1] = types[ii + 1].create(a[ii + 1]); } - QMetaCallEvent *ev = new QMetaCallEvent(m.methodIndex(), 0, nullptr, object, index, - parameterTypes.count() + 1, types, args); - QQmlThreadNotifierProxyObject *mpo = new QQmlThreadNotifierProxyObject; mpo->target = object; - mpo->moveToThread(QObjectPrivate::get(object)->threadData->thread); - QCoreApplication::postEvent(mpo, ev); + mpo->moveToThread(objectThreadData->thread.loadAcquire()); + QCoreApplication::postEvent(mpo, ev.release()); } else { QQmlNotifierEndpoint *ep = ddata->notify(index); @@ -887,16 +356,12 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in int QQmlData::receivers(QAbstractDeclarativeData *d, const QObject *, int index) { QQmlData *ddata = static_cast<QQmlData *>(d); - if (ddata->ownedByQml1) - return 0; return ddata->endpointCount(index); } bool QQmlData::isSignalConnected(QAbstractDeclarativeData *d, const QObject *, int index) { QQmlData *ddata = static_cast<QQmlData *>(d); - if (ddata->ownedByQml1) - return false; return ddata->signalHasEndpoint(index); } @@ -916,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); } } @@ -929,40 +398,70 @@ void QQmlData::setQueuedForDeletion(QObject *object) if (object) { if (QQmlData *ddata = QQmlData::get(object)) { if (ddata->ownContext) { - Q_ASSERT(ddata->ownContext == ddata->context); - ddata->context->emitDestruction(); - if (ddata->ownContext->contextObject == object) - ddata->ownContext->contextObject = nullptr; - ddata->ownContext = nullptr; + Q_ASSERT(ddata->ownContext.data() == ddata->context); + 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; @@ -971,72 +470,46 @@ void QQmlEnginePrivate::init() Q_Q(QQmlEngine); if (baseModulesUninitialized) { - qmlRegisterType<QQmlComponent>("QML", 1, 0, "Component"); // required for the Compiler. - registerBaseTypes("QtQml", 2, 0); // import which provides language building blocks. -#if QT_CONFIG(qml_locale) - qmlRegisterUncreatableType<QQmlLocale>("QtQml", 2, 2, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); -#endif + // 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>>); - // Auto-increment the import to stay in sync with ALL future QtQml minor versions from 5.11 onward - qmlRegisterModule("QtQml", 2, QT_VERSION_MINOR); + 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<QQmlV4Handle>(); - qRegisterMetaType<QQmlBinding*>(); - - v8engine()->setEngine(q); + q->handle()->setQmlEngine(q); rootContext = new QQmlContext(q,true); } -#if QT_CONFIG(qml_worker_script) -QQuickWorkerScriptEngine *QQmlEnginePrivate::getWorkerScriptEngine() -{ - Q_Q(QQmlEngine); - if (!workerScriptEngine) - workerScriptEngine = new QQuickWorkerScriptEngine(q); - return workerScriptEngine; -} -#endif - /*! \class QQmlEngine \since 5.0 \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. + 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. - 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. + 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}. - \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 - - In this case, the Text item will be created in the engine's - \l {QQmlEngine::rootContext()}{root context}. - - \sa QQmlComponent, QQmlContext, {QML Global Object} + \sa QQmlComponent, QQmlContext, {QML Global Object}, QQmlApplicationEngine */ /*! @@ -1067,30 +540,29 @@ 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); - d->typeLoader.invalidate(); - // Emit onDestruction signals for the root context before // we destroy the contexts, engine, Singleton Types etc. that // may be required to handle the destruction signal. - QQmlContextData::get(rootContext())->emitDestruction(); + QQmlContextPrivate::get(rootContext())->emitDestruction(); // clean up all singleton type instances which we own. // 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? - QList<QQmlType> singletonTypes = QQmlMetaType::qmlSingletonTypes(); - for (const QQmlType &currType : singletonTypes) - currType.singletonInstanceInfo()->destroy(this); + d->singletonInstances.clear(); delete d->rootContext; d->rootContext = nullptr; + + d->typeLoader.invalidate(); } /*! \fn void QQmlEngine::quit() @@ -1127,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(); } /*! @@ -1152,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. @@ -1172,42 +687,78 @@ QQmlContext *QQmlEngine::rootContext() const return d->rootContext; } +#if QT_DEPRECATED_SINCE(6, 0) /*! \internal + \deprecated This API is private for 5.1 - Sets the \a urlInterceptor to be used when resolving URLs in QML. + Returns the last QQmlAbstractUrlInterceptor. It must not be modified outside + the GUI thread. +*/ +QQmlAbstractUrlInterceptor *QQmlEngine::urlInterceptor() const +{ + Q_D(const QQmlEngine); + return d->urlInterceptors.last(); +} +#endif + +/*! + Adds a \a urlInterceptor to be used when resolving URLs in QML. This also applies to URLs used for loading script files and QML types. - This should not be modifed while the engine is loading files, or URL - selection may be inconsistent. + The URL interceptors should not be modifed while the engine is loading files, + or URL selection may be inconsistent. Multiple URL interceptors, when given, + will be called in the order they were added for each URL. + + QQmlEngine does not take ownership of the interceptor and won't delete it. */ -void QQmlEngine::setUrlInterceptor(QQmlAbstractUrlInterceptor *urlInterceptor) +void QQmlEngine::addUrlInterceptor(QQmlAbstractUrlInterceptor *urlInterceptor) { Q_D(QQmlEngine); - d->urlInterceptor = urlInterceptor; + d->urlInterceptors.append(urlInterceptor); } /*! - \internal - This API is private for 5.1 + Remove a \a urlInterceptor that was previously added using + \l addUrlInterceptor. The URL interceptors should not be modifed while the + engine is loading files, or URL selection may be inconsistent. - Returns the current QQmlAbstractUrlInterceptor. It must not be modified outside - the GUI thread. + This does not delete the interceptor, but merely removes it from the engine. + You can re-use it on the same or a different engine afterwards. */ -QQmlAbstractUrlInterceptor *QQmlEngine::urlInterceptor() const +void QQmlEngine::removeUrlInterceptor(QQmlAbstractUrlInterceptor *urlInterceptor) +{ + Q_D(QQmlEngine); + d->urlInterceptors.removeOne(urlInterceptor); +} + +/*! + Run the current URL interceptors on the given \a url of the given \a type and + return the result. + */ +QUrl QQmlEngine::interceptUrl(const QUrl &url, QQmlAbstractUrlInterceptor::DataType type) const { Q_D(const QQmlEngine); - return d->urlInterceptor; + QUrl result = url; + for (QQmlAbstractUrlInterceptor *interceptor : d->urlInterceptors) + result = interceptor->intercept(result, type); + 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(&imageProviderMutex); + return imageProviders.value(providerIdLower); } #if QT_CONFIG(qml_network) @@ -1299,8 +850,10 @@ QNetworkAccessManager *QQmlEngine::networkAccessManager() const void QQmlEngine::addImageProvider(const QString &providerId, QQmlImageProviderBase *provider) { Q_D(QQmlEngine); - QMutexLocker locker(&d->mutex); - d->imageProviders.insert(providerId.toLower(), QSharedPointer<QQmlImageProviderBase>(provider)); + QString providerIdLower = providerId.toLower(); + QSharedPointer<QQmlImageProviderBase> sp(provider); + QMutexLocker locker(&d->imageProviderMutex); + d->imageProviders.insert(std::move(providerIdLower), std::move(sp)); } /*! @@ -1311,8 +864,9 @@ void QQmlEngine::addImageProvider(const QString &providerId, QQmlImageProviderBa QQmlImageProviderBase *QQmlEngine::imageProvider(const QString &providerId) const { Q_D(const QQmlEngine); - QMutexLocker locker(&d->mutex); - return d->imageProviders.value(providerId.toLower()).data(); + const QString providerIdLower = providerId.toLower(); + QMutexLocker locker(&d->imageProviderMutex); + return d->imageProviders.value(providerIdLower).data(); } /*! @@ -1323,8 +877,9 @@ QQmlImageProviderBase *QQmlEngine::imageProvider(const QString &providerId) cons void QQmlEngine::removeImageProvider(const QString &providerId) { Q_D(QQmlEngine); - QMutexLocker locker(&d->mutex); - d->imageProviders.take(providerId.toLower()); + const QString providerIdLower = providerId.toLower(); + QMutexLocker locker(&d->imageProviderMutex); + d->imageProviders.take(providerIdLower); } /*! @@ -1341,7 +896,9 @@ QUrl QQmlEngine::baseUrl() const { Q_D(const QQmlEngine); if (d->baseUrl.isEmpty()) { - return QUrl::fromLocalFile(QDir::currentPath() + QDir::separator()); + const QString currentPath = QDir::currentPath(); + const QString rootPath = QDir::rootPath(); + return QUrl::fromLocalFile((currentPath == rootPath) ? rootPath : (currentPath + QDir::separator())); } else { return d->baseUrl; } @@ -1386,6 +943,75 @@ 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 + + Capture the given property as part of a binding. + */ +void QQmlEngine::captureProperty(QObject *object, const QMetaProperty &property) const +{ + Q_D(const QQmlEngine); + if (d->propertyCapture && !property.isConstant()) { + d->propertyCapture->captureProperty( + object, property.propertyIndex(), + QMetaObjectPrivate::signalIndex(property.notifySignal())); + } +} + +/*! + \qmlproperty string Qt::uiLanguage + \since 5.15 + + The uiLanguage holds the name of the language to be used for user interface + string translations. It is exposed in C++ as QQmlEngine::uiLanguage property. + + You can set the value freely and use it in bindings. It is recommended to set it + after installing translators in your application. By convention, an empty string + means no translation from the language used in the source code is intended to occur. + + If you're using QQmlApplicationEngine and the value changes, QQmlEngine::retranslate() + will be called. +*/ + /*! \fn template<typename T> T QQmlEngine::singletonInstance(int qmlTypeId) @@ -1397,58 +1023,77 @@ void QQmlEngine::setOutputWarningsToStandardError(bool enabled) type, either a default constructed QJSValue or a \c nullptr is returned. QObject* example: - \code - class MySingleton : public QObject { - Q_OBJECT - static int typeId; - // ... - }; - // Register with QObject* callback - MySingleton::typeId = qmlRegisterSingletonType<MySingleton>(...); - - // Retrieve as QObject* - QQmlEngine engine; - MySingleton* instance = engine.singletonInstance<MySingleton*>(MySingleton::typeId); - \endcode + \snippet code/src_qml_qqmlengine.cpp 0 + \codeline + \snippet code/src_qml_qqmlengine.cpp 1 + \codeline + \snippet code/src_qml_qqmlengine.cpp 2 QJSValue example: - \code - // Register with QJSValue callback - int typeId = qmlRegisterSingletonType(...); - // Retrieve as QJSValue - QQmlEngine engine; - QJSValue instance = engine.singletonInstance<QJSValue>(typeId); - \endcode + \snippet code/src_qml_qqmlengine.cpp 3 + \codeline + \snippet code/src_qml_qqmlengine.cpp 4 - It is recommended to store the QML type id during registration, e.g. as a static member - in the singleton class. Otherwise, a costly lookup via qmlTypeId() has to be performed - at run-time. + It is recommended to store the QML type id, e.g. as a static member in the + singleton class. The lookup via qmlTypeId() is costly. - \sa qmlRegisterSingletonType(), qmlTypeId() + \sa QML_SINGLETON, qmlRegisterSingletonType(), qmlTypeId() \since 5.12 */ template<> QJSValue QQmlEngine::singletonInstance<QJSValue>(int qmlTypeId) { - QQmlType type = QQmlMetaType::qmlType(qmlTypeId, QQmlMetaType::TypeIdCategory::QmlType); + Q_D(QQmlEngine); + QQmlType type = QQmlMetaType::qmlTypeById(qmlTypeId); if (!type.isValid() || !type.isSingleton()) return QJSValue(); - QQmlType::SingletonInstanceInfo* info = type.singletonInstanceInfo(); - info->init(this); + return d->singletonInstance<QJSValue>(type); +} - if (QObject* o = info->qobjectApi(this)) - return this->newQObject(o); - else { - QJSValue value = info->scriptApi(this); - if (!value.isUndefined()) - return value; - } - return QJSValue(); +/*! + \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); } /*! @@ -1458,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); - QQmlContextData *context = QQmlContextData::get(d->rootContext)->childContexts; - while (context) { - context->refreshExpressions(); - context = context->nextChild; - } + 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) { @@ -1515,78 +1152,10 @@ void QQmlEngine::setContextForObject(QObject *object, QQmlContext *context) return; } - QQmlContextData *contextData = QQmlContextData::get(context); + QQmlRefPointer<QQmlContextData> contextData = QQmlContextData::get(context); Q_ASSERT(data->context == nullptr); - data->context = contextData; - contextData->addObject(data); -} - -/*! - \enum QQmlEngine::ObjectOwnership - - ObjectOwnership controls whether or not QML automatically destroys the - QObject when the corresponding JavaScript object is garbage collected by the - engine. The two ownership options are: - - \value CppOwnership The object is owned by C++ code and QML will never delete - it. The JavaScript destroy() method cannot be used on these objects. This - option is similar to QScriptEngine::QtOwnership. - - \value JavaScriptOwnership The object is owned by JavaScript. When the object - is returned to QML as the return value of a method call, QML will track it - and delete it if there are no remaining JavaScript references to it and - it has no QObject::parent(). An object tracked by one QQmlEngine will be - deleted during that QQmlEngine's destructor. Thus, JavaScript references - between objects with JavaScriptOwnership from two different engines will - not be valid if one of these engines is deleted. This option is similar to - QScriptEngine::ScriptOwnership. - - Generally an application doesn't need to set an object's ownership - explicitly. QML uses a heuristic to set the default ownership. By default, an - object that is created by QML has JavaScriptOwnership. The exception to this - are the root objects created by calling QQmlComponent::create() or - QQmlComponent::beginCreate(), which have CppOwnership by default. The - ownership of these root-level objects is considered to have been transferred - to the C++ caller. - - Objects not-created by QML have CppOwnership by default. The exception to this - are objects returned from C++ method calls; their ownership will be set to - JavaScriptOwnership. This applies only to explicit invocations of Q_INVOKABLE - methods or slots, but not to property getter invocations. - - Calling setObjectOwnership() overrides the default ownership heuristic used by - QML. -*/ - -/*! - Sets the \a ownership of \a object. -*/ -void QQmlEngine::setObjectOwnership(QObject *object, ObjectOwnership ownership) -{ - if (!object) - return; - - QQmlData *ddata = QQmlData::get(object, true); - if (!ddata) - return; - - ddata->indestructible = (ownership == CppOwnership)?true:false; - ddata->explicitIndestructibleSet = true; -} - -/*! - Returns the ownership of \a object. -*/ -QQmlEngine::ObjectOwnership QQmlEngine::objectOwnership(QObject *object) -{ - if (!object) - return CppOwnership; - - QQmlData *ddata = QQmlData::get(object, false); - if (!ddata) - return CppOwnership; - else - return ddata->indestructible?CppOwnership:JavaScriptOwnership; + data->context = contextData.data(); + contextData->addOwnedObject(data); } /*! @@ -1594,163 +1163,13 @@ QQmlEngine::ObjectOwnership QQmlEngine::objectOwnership(QObject *object) */ 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; -} - -namespace QtQml { - -void qmlExecuteDeferred(QObject *object) -{ - QQmlData *data = QQmlData::get(object); - - if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object)) { - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine); - - QQmlComponentPrivate::DeferredState state; - QQmlComponentPrivate::beginDeferred(ep, object, &state); - - // Release the reference for the deferral action (we still have one from construction) - data->releaseDeferredData(); - - QQmlComponentPrivate::completeDeferred(ep, &state); - } -} - -QQmlContext *qmlContext(const QObject *obj) -{ - return QQmlEngine::contextForObject(obj); -} - -QQmlEngine *qmlEngine(const QObject *obj) -{ - QQmlData *data = QQmlData::get(obj, false); - if (!data || !data->context) - return nullptr; - return data->context->engine; -} - -static QObject *resolveAttachedProperties(QQmlAttachedPropertiesFunc pf, QQmlData *data, - QObject *object, bool create) -{ - if (!pf) - return nullptr; - - QObject *rv = data->hasExtendedData() ? data->attachedProperties()->value(pf) : 0; - if (rv || !create) - return rv; - - rv = pf(object); - - if (rv) - data->attachedProperties()->insert(pf, rv); - - return rv; -} - -QObject *qmlAttachedPropertiesObjectById(int id, const QObject *object, bool create) -{ - QQmlData *data = QQmlData::get(object, create); - - // Attached properties are only on objects created by QML, - // unless explicitly requested (create==true) - if (!data) - return nullptr; - - QQmlEnginePrivate *engine = QQmlEnginePrivate::get(data->context); - return resolveAttachedProperties(QQmlMetaType::attachedPropertiesFuncById(engine, id), data, - const_cast<QObject *>(object), create); -} - -QObject *qmlAttachedPropertiesObject(int *idCache, const QObject *object, - const QMetaObject *attachedMetaObject, bool create) -{ - if (*idCache == -1) { - QQmlEngine *engine = object ? qmlEngine(object) : nullptr; - *idCache = QQmlMetaType::attachedPropertiesFuncId(engine ? QQmlEnginePrivate::get(engine) : nullptr, attachedMetaObject); - } - - if (*idCache == -1 || !object) - return nullptr; - - return qmlAttachedPropertiesObjectById(*idCache, object, create); -} - -QQmlAttachedPropertiesFunc qmlAttachedPropertiesFunction(QObject *object, - const QMetaObject *attachedMetaObject) -{ - QQmlEngine *engine = object ? qmlEngine(object) : nullptr; - return QQmlMetaType::attachedPropertiesFunc(engine ? QQmlEnginePrivate::get(engine) : nullptr, - attachedMetaObject); -} - -QObject *qmlAttachedPropertiesObject(QObject *object, QQmlAttachedPropertiesFunc func, bool create) -{ - if (!object) - return nullptr; - - QQmlData *data = QQmlData::get(object, create); - - // Attached properties are only on objects created by QML, - // unless explicitly requested (create==true) - if (!data) - return nullptr; - - return resolveAttachedProperties(func, data, object, create); -} - -} // namespace QtQml - -#if QT_DEPRECATED_SINCE(5, 1) - -// Also define symbols outside namespace to keep binary compatibility with Qt 5.0 - -Q_QML_EXPORT void qmlExecuteDeferred(QObject *obj) -{ - QtQml::qmlExecuteDeferred(obj); -} - -Q_QML_EXPORT QQmlContext *qmlContext(const QObject *obj) -{ - return QtQml::qmlContext(obj); -} - -Q_QML_EXPORT QQmlEngine *qmlEngine(const QObject *obj) -{ - return QtQml::qmlEngine(obj); -} - -Q_QML_EXPORT QObject *qmlAttachedPropertiesObjectById(int id, const QObject *obj, bool create) -{ - return QtQml::qmlAttachedPropertiesObjectById(id, obj, create); -} - -Q_QML_EXPORT QObject *qmlAttachedPropertiesObject(int *idCache, const QObject *object, - const QMetaObject *attachedMetaObject, - bool create) -{ - return QtQml::qmlAttachedPropertiesObject(idCache, object, attachedMetaObject, create); -} - -#endif // QT_DEPRECATED_SINCE(5, 1) - class QQmlDataExtended { public: QQmlDataExtended(); @@ -1820,7 +1239,9 @@ void QQmlData::NotifyList::layout() todo = nullptr; } -void QQmlData::deferData(int objectIndex, const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, QQmlContextData *context) +void QQmlData::deferData( + int objectIndex, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QQmlRefPointer<QQmlContextData> &context) { QQmlData::DeferredData *deferData = new QQmlData::DeferredData; deferData->deferredIdx = objectIndex; @@ -1828,13 +1249,14 @@ void QQmlData::deferData(int objectIndex, const QQmlRefPointer<QV4::CompiledData deferData->context = context; const QV4::CompiledData::Object *compiledObject = compilationUnit->objectAt(objectIndex); - const QV4::CompiledData::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); @@ -1856,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; + + } } } @@ -1914,8 +1360,8 @@ void QQmlData::destroyed(QObject *object) nextContextObject->prevContextObject = prevContextObject; if (prevContextObject) *prevContextObject = nextContextObject; - else if (outerContext && outerContext->contextObjects == this) - outerContext->contextObjects = nextContextObject; + else if (outerContext && outerContext->ownedObjects() == this) + outerContext->setOwnedObjects(nextContextObject); QQmlAbstractBinding *binding = bindings; while (binding) { @@ -1925,7 +1371,7 @@ void QQmlData::destroyed(QObject *object) if (bindings && !bindings->ref.deref()) delete bindings; - compilationUnit = nullptr; + compilationUnit.reset(); qDeleteAll(deferredData); deferredData.clear(); @@ -1972,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; @@ -1996,25 +1443,6 @@ void QQmlData::destroyed(QObject *object) this->~QQmlData(); } -DEFINE_BOOL_CONFIG_OPTION(parentTest, QML_PARENT_TEST); - -void QQmlData::parentChanged(QObject *object, QObject *parent) -{ - if (parentTest()) { - if (parentFrozen && !QObjectPrivate::get(object)->wasDeleted) { - QString on; - QString pn; - - { QDebug dbg(&on); dbg << object; on = on.left(on.length() - 1); } - { QDebug dbg(&pn); dbg << parent; pn = pn.left(pn.length() - 1); } - - qFatal("Object %s has had its parent frozen by QML and cannot be changed.\n" - "User code is attempting to change it to %s.\n" - "This behavior is NOT supported!", qPrintable(on), qPrintable(pn)); - } - } -} - QQmlData::BindingBitsType *QQmlData::growBits(QObject *obj, int bit) { BindingBitsType *bits = (bindingBitsArraySize == InlineBindingArraySize) ? bindingBitsValue : bindingBits; @@ -2042,16 +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); - if (ddata->propertyCache) - ddata->propertyCache->addref(); + ddata->propertyCache = QQmlMetaType::propertyCache(object, QTypeRevision {}); return ddata->propertyCache; } @@ -2077,38 +1503,38 @@ static void dumpwarning(const QQmlError &error) switch (error.messageType()) { case QtDebugMsg: QMessageLogger(error.url().toString().toLatin1().constData(), - error.line(), nullptr).debug().nospace() - << qPrintable(error.toString()); + error.line(), nullptr).debug().noquote().nospace() + << error.toString(); break; case QtInfoMsg: QMessageLogger(error.url().toString().toLatin1().constData(), - error.line(), nullptr).info().nospace() - << qPrintable(error.toString()); + error.line(), nullptr).info().noquote().nospace() + << error.toString(); break; case QtWarningMsg: case QtFatalMsg: // fatal does not support streaming, and furthermore, is actually fatal. Probably not desirable for QML. QMessageLogger(error.url().toString().toLatin1().constData(), - error.line(), nullptr).warning().nospace() - << qPrintable(error.toString()); + error.line(), nullptr).warning().noquote().nospace() + << error.toString(); break; case QtCriticalMsg: QMessageLogger(error.url().toString().toLatin1().constData(), - error.line(), nullptr).critical().nospace() - << qPrintable(error.toString()); + error.line(), nullptr).critical().noquote().nospace() + << error.toString(); break; } } 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); } @@ -2116,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); } @@ -2153,10 +1579,11 @@ void QQmlEnginePrivate::warning(QQmlEnginePrivate *engine, const QList<QQmlError dumpwarning(error); } -QList<QQmlError> QQmlEnginePrivate::qmlErrorFromDiagnostics(const QString &fileName, const QList<DiagnosticMessage> &diagnosticMessages) +QList<QQmlError> QQmlEnginePrivate::qmlErrorFromDiagnostics( + const QString &fileName, const QList<QQmlJS::DiagnosticMessage> &diagnosticMessages) { QList<QQmlError> errors; - for (const DiagnosticMessage &m : diagnosticMessages) { + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { if (m.isWarning()) { qWarning("%s:%d : %s", qPrintable(fileName), m.loc.startLine, qPrintable(m.message)); continue; @@ -2165,8 +1592,8 @@ QList<QQmlError> QQmlEnginePrivate::qmlErrorFromDiagnostics(const QString &fileN QQmlError error; error.setUrl(QUrl(fileName)); error.setDescription(m.message); - error.setLine(m.loc.startLine); - error.setColumn(m.loc.startColumn); + error.setLine(qmlConvertSourceCoordinate<quint32, int>(m.loc.startLine)); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>(m.loc.startColumn)); errors << error; } return errors; @@ -2198,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) { @@ -2216,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 QML2_IMPORT_PATH environment variable, - and the builtin \c Qml2ImportsPath from QLibraryInfo. + By default, this list contains the paths mentioned in + \l {QML Import Path}. \sa addImportPath(), setImportPathList() */ @@ -2232,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 QML2_IMPORT_PATH environment variable, - and the builtin \c Qml2ImportsPath 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() */ @@ -2262,7 +1691,6 @@ void QQmlEngine::addPluginPath(const QString& path) d->importDatabase.addPluginPath(path); } - /*! Returns the list of directories where the engine searches for native plugins for imported modules (referenced in the \c qmldir file). @@ -2294,19 +1722,32 @@ void QQmlEngine::setPluginPathList(const QStringList &paths) d->importDatabase.setPluginPathList(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 QQmlExtensionPlugin interface. + 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(), -1, errors); + QQmlTypeLoaderQmldirContent qmldir; + QQmlPluginImporter importer( + uri, QTypeRevision(), &d->importDatabase, &qmldir, &d->typeLoader, errors); + return importer.importDynamicPlugin(filePath, uri, false).isValid(); } +#endif +#endif /*! \property QQmlEngine::offlineStoragePath @@ -2315,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. @@ -2323,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 @@ -2335,12 +1788,14 @@ QString QQmlEngine::offlineStoragePath() const Q_D(const QQmlEngine); if (d->offlineStoragePath.isEmpty()) { - QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + 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; @@ -2367,125 +1822,191 @@ QString QQmlEnginePrivate::offlineStorageDatabaseDirectory() const return q->offlineStoragePath() + QDir::separator() + QLatin1String("Databases") + QDir::separator(); } -bool QQmlEnginePrivate::isQObject(int t) +template<> +QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) { - Locker locker(this); - return m_compositeTypes.contains(t) || QQmlMetaType::isQObject(t); -} + Q_Q(QQmlEngine); -QObject *QQmlEnginePrivate::toQObject(const QVariant &v, bool *ok) const -{ - Locker locker(this); - int t = v.userType(); - if (t == QMetaType::QObjectStar || m_compositeTypes.contains(t)) { - if (ok) *ok = true; - return *(QObject *const *)(v.constData()); - } else { - return QQmlMetaType::toQObject(v, ok); + 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()) { + QObject *o = value.toQObject(); + // 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)); + } + singletonInstances.convertAndInsert(v4engine(), siinfo, &value); + + } else if (siinfo->qobjectCallback) { + QObject *o = siinfo->qobjectCallback(q, q); + if (!o) { + QQmlError error; + error.setMessageType(QtMsgType::QtCriticalMsg); + error.setDescription(QString::asprintf("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", + qPrintable(QString::fromUtf8(type.typeName())))); + warning(error); + } else { + type.createProxy(o); + + // if this object can use a property cache, create it now + 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); + } + } + + value = q->newQObject(o); + singletonInstances.convertAndInsert(v4engine(), siinfo, &value); + } else if (!siinfo->url.isEmpty()) { + QQmlComponent component(q, siinfo->url, QQmlComponent::PreferSynchronous); + if (component.isError()) { + warning(component.errors()); + v4engine()->throwError(QLatin1String("Due to the preceding error(s), Singleton \"%1\" could not be loaded.").arg(QString::fromUtf8(type.typeName()))); + + return QJSValue(QJSValue::UndefinedValue); + } + QObject *o = component.beginCreate(q->rootContext()); + value = q->newQObject(o); + singletonInstances.convertAndInsert(v4engine(), siinfo, &value); + component.completeCreate(); } -} -QQmlMetaType::TypeCategory QQmlEnginePrivate::typeCategory(int t) const -{ - Locker locker(this); - if (m_compositeTypes.contains(t)) - return QQmlMetaType::Object; - return QQmlMetaType::typeCategory(t); + return value; } -bool QQmlEnginePrivate::isList(int t) const +bool QQmlEnginePrivate::isTypeLoaded(const QUrl &url) const { - return QQmlMetaType::isList(t); + return typeLoader.isTypeLoaded(url); } -int QQmlEnginePrivate::listType(int t) const +bool QQmlEnginePrivate::isScriptLoaded(const QUrl &url) const { - return QQmlMetaType::listType(t); + return typeLoader.isScriptLoaded(url); } -QQmlMetaObject QQmlEnginePrivate::rawMetaObjectForType(int t) const +void QQmlEnginePrivate::executeRuntimeFunction(const QUrl &url, qsizetype functionIndex, + QObject *thisObject, int argc, void **args, + QMetaType *types) { - Locker locker(this); - auto iter = m_compositeTypes.constFind(t); - if (iter != m_compositeTypes.cend()) { - return QQmlMetaObject((*iter)->rootPropertyCache().data()); - } else { - QQmlType type = QQmlMetaType::qmlType(t); - return QQmlMetaObject(type.baseMetaObject()); + 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::Scoped<QV4::JavaScriptFunctionObject> 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()); } -} -QQmlMetaObject QQmlEnginePrivate::metaObjectForType(int t) const -{ - Locker locker(this); - auto iter = m_compositeTypes.constFind(t); - if (iter != m_compositeTypes.cend()) { - return QQmlMetaObject((*iter)->rootPropertyCache().data()); - } else { - QQmlType type = QQmlMetaType::qmlType(t); - return QQmlMetaObject(type.metaObject()); - } + v4->callInContext(function, thisObject, callContext, argc, args, types); } -QQmlPropertyCache *QQmlEnginePrivate::propertyCacheForType(int t) +QV4::ExecutableCompilationUnit *QQmlEnginePrivate::compilationUnitFromUrl(const QUrl &url) { - Locker locker(this); - auto iter = m_compositeTypes.constFind(t); - if (iter != m_compositeTypes.cend()) { - return (*iter)->rootPropertyCache().data(); - } else { - QQmlType type = QQmlMetaType::qmlType(t); - locker.unlock(); - return type.isValid() ? cache(type.metaObject()) : nullptr; + QV4::ExecutionEngine *v4 = v4engine(); + if (auto unit = v4->compilationUnitForUrl(url)) { + if (!unit->runtimeStrings) + unit->populate(); + return unit.data(); } -} -QQmlPropertyCache *QQmlEnginePrivate::rawPropertyCacheForType(int t, int minorVersion) -{ - Locker locker(this); - auto iter = m_compositeTypes.constFind(t); - if (iter != m_compositeTypes.cend()) { - return (*iter)->rootPropertyCache().data(); - } else { - QQmlType type = QQmlMetaType::qmlType(t); - locker.unlock(); + auto unit = typeLoader.getType(url)->compilationUnit(); + if (!unit) + return nullptr; - if (minorVersion >= 0) - return type.isValid() ? cache(type, minorVersion) : nullptr; - else - return type.isValid() ? cache(type.baseMetaObject()) : nullptr; - } + auto executable = v4->executableCompilationUnit(std::move(unit)); + executable->populate(); + return executable.data(); } -void QQmlEnginePrivate::registerInternalCompositeType(QV4::CompiledData::CompilationUnit *compilationUnit) +QQmlRefPointer<QQmlContextData> +QQmlEnginePrivate::createInternalContext(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, + const QQmlRefPointer<QQmlContextData> &parentContext, + int subComponentIndex, bool isComponentRoot) { - compilationUnit->isRegisteredWithEngine = true; + Q_ASSERT(unit); - Locker locker(this); - // The QQmlCompiledData is not referenced here, but it is removed from this - // hash in the QQmlCompiledData destructor - m_compositeTypes.insert(compilationUnit->metaTypeId, compilationUnit); -} + QQmlRefPointer<QQmlContextData> context; + context = QQmlContextData::createRefCounted(parentContext); + context->setInternal(true); + context->setImports(unit->typeNameCache()); + context->initFromTypeCompilationUnit(unit, subComponentIndex); -void QQmlEnginePrivate::unregisterInternalCompositeType(QV4::CompiledData::CompilationUnit *compilationUnit) -{ - compilationUnit->isRegisteredWithEngine = false; + const auto *dependentScripts = unit->dependentScriptsPtr(); + const qsizetype dependentScriptsSize = dependentScripts->size(); + if (isComponentRoot && dependentScriptsSize) { + QV4::ExecutionEngine *v4 = v4engine(); + Q_ASSERT(v4); + QV4::Scope scope(v4); - Locker locker(this); - m_compositeTypes.remove(compilationUnit->metaTypeId); -} - -bool QQmlEnginePrivate::isTypeLoaded(const QUrl &url) const -{ - return typeLoader.isTypeLoaded(url); -} + 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))); + } -bool QQmlEnginePrivate::isScriptLoaded(const QUrl &url) const -{ - return typeLoader.isScriptLoaded(url); + return context; } -#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) +#if defined(Q_OS_WIN) // Normalize a file name using Shell API. As opposed to converting it // to a short 8.3 name and back, this also works for drives where 8.3 notation // is disabled (see 8dot3name options of fsutil.exe). @@ -2517,7 +2038,7 @@ static inline QString shellNormalizeFileName(const QString &name) canonicalName[0] = canonicalName.at(0).toUpper(); return QDir::cleanPath(canonicalName); } -#endif // Q_OS_WIN && !Q_OS_WINRT +#endif // Q_OS_WIN bool QQml_isFileCaseCorrect(const QString &fileName, int lengthIn /* = -1 */) { @@ -2525,7 +2046,7 @@ bool QQml_isFileCaseCorrect(const QString &fileName, int lengthIn /* = -1 */) QFileInfo info(fileName); const QString absolute = info.absoluteFilePath(); -#if defined(Q_OS_DARWIN) || defined(Q_OS_WINRT) +#if defined(Q_OS_DARWIN) const QString canonical = info.canonicalFilePath(); #elif defined(Q_OS_WIN) // No difference if the path is qrc based @@ -2562,8 +2083,8 @@ bool QQml_isFileCaseCorrect(const QString &fileName, int lengthIn /* = -1 */) return false; } #else - Q_UNUSED(lengthIn) - Q_UNUSED(fileName) + Q_UNUSED(lengthIn); + Q_UNUSED(fileName); #endif return true; } @@ -2592,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" |