2022-05-13 13:12:05 +00:00
// Copyright (C) 2016 The Qt Company Ltd.
2024-02-22 14:51:16 +00:00
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
2011-04-27 10:05:43 +00:00
2012-02-16 04:43:03 +00:00
# include <QQmlEngine>
# include <QQmlContext>
2011-04-27 10:05:43 +00:00
# include <QNetworkAccessManager>
# include <QPointer>
# include <QDir>
2011-10-23 23:03:51 +00:00
# include <QStandardPaths>
2012-04-19 12:38:38 +00:00
# include <QSignalSpy>
2011-04-27 10:05:43 +00:00
# include <QDebug>
2013-02-28 17:13:23 +00:00
# include <QBuffer>
2017-05-08 08:24:39 +00:00
# include <QCryptographicHash>
2012-02-16 04:43:03 +00:00
# include <QQmlComponent>
# include <QQmlNetworkAccessManagerFactory>
# include <QQmlExpression>
2012-05-03 22:32:45 +00:00
# include <QQmlIncubationController>
2017-05-31 13:54:15 +00:00
# include <QTemporaryDir>
2022-12-06 12:28:48 +00:00
# include <QQmlEngineExtensionPlugin>
2012-05-03 22:32:45 +00:00
# include <private/qqmlengine_p.h>
2019-07-09 07:25:40 +00:00
# include <private/qqmltypedata_p.h>
2021-03-17 14:08:22 +00:00
# include <private/qqmlcomponentattached_p.h>
2013-12-03 16:41:04 +00:00
# include <QQmlAbstractUrlInterceptor>
2021-08-06 10:27:35 +00:00
# include <QtQuickTestUtils/private/qmlutils_p.h>
2011-04-27 10:05:43 +00:00
2022-12-06 12:28:48 +00:00
# include "declarativelyregistered.h"
Q_IMPORT_QML_PLUGIN ( OnlyDeclarativePlugin )
2012-05-03 22:32:45 +00:00
class tst_qqmlengine : public QQmlDataTest
2011-04-27 10:05:43 +00:00
{
Q_OBJECT
public :
2021-08-06 10:27:35 +00:00
tst_qqmlengine ( ) : QQmlDataTest ( QT_QMLTEST_DATADIR ) { }
2011-04-27 10:05:43 +00:00
private slots :
2017-05-31 13:54:15 +00:00
void initTestCase ( ) override ;
2011-04-27 10:05:43 +00:00
void rootContext ( ) ;
2024-04-24 11:40:49 +00:00
# if QT_CONFIG(qml_network)
2011-04-27 10:05:43 +00:00
void networkAccessManager ( ) ;
2013-02-28 17:13:23 +00:00
void synchronousNetworkAccessManager ( ) ;
2024-04-24 11:40:49 +00:00
# endif
2011-04-27 10:05:43 +00:00
void baseUrl ( ) ;
void contextForObject ( ) ;
void offlineStoragePath ( ) ;
2017-01-09 16:06:25 +00:00
void offlineDatabaseStoragePath ( ) ;
2011-04-27 10:05:43 +00:00
void clearComponentCache ( ) ;
2012-05-03 22:32:45 +00:00
void trimComponentCache ( ) ;
void trimComponentCache_data ( ) ;
2021-08-19 13:08:41 +00:00
void clearSingletons ( ) ;
2012-05-21 03:40:22 +00:00
void repeatedCompilation ( ) ;
void failedCompilation ( ) ;
void failedCompilation_data ( ) ;
2011-04-27 10:05:43 +00:00
void outputWarningsToStandardError ( ) ;
void objectOwnership ( ) ;
2011-07-25 05:47:40 +00:00
void multipleEngines ( ) ;
2012-05-23 08:05:10 +00:00
void qtqmlModule_data ( ) ;
void qtqmlModule ( ) ;
2013-03-01 01:03:43 +00:00
void urlInterceptor_data ( ) ;
void urlInterceptor ( ) ;
2015-10-27 11:21:00 +00:00
void qmlContextProperties ( ) ;
2017-04-21 10:29:22 +00:00
void testGCCorruption ( ) ;
2017-09-14 12:38:36 +00:00
void testGroupedPropertyRevisions ( ) ;
2018-03-02 08:09:17 +00:00
void componentFromEval ( ) ;
2018-04-24 09:35:43 +00:00
void qrcUrls ( ) ;
2018-05-29 12:27:46 +00:00
void cppSignalAndEval ( ) ;
2018-05-31 19:41:47 +00:00
void singletonInstance ( ) ;
2019-03-26 12:57:33 +00:00
void aggressiveGc ( ) ;
2019-05-09 10:38:22 +00:00
void cachedGetterLookup_qtbug_75335 ( ) ;
2020-01-10 09:01:36 +00:00
void createComponentOnSingletonDestruction ( ) ;
2020-01-22 12:37:48 +00:00
void uiLanguage ( ) ;
2023-04-19 12:57:13 +00:00
void markCurrentFunctionAsTranslationBinding ( ) ;
2021-02-08 14:42:59 +00:00
void executeRuntimeFunction ( ) ;
2021-02-26 08:01:25 +00:00
void captureQProperty ( ) ;
2021-03-17 14:08:22 +00:00
void listWrapperAsListReference ( ) ;
void attachedObjectAsObject ( ) ;
2021-03-23 13:16:15 +00:00
void listPropertyAsQJSValue ( ) ;
2021-09-03 12:35:26 +00:00
void stringToColor ( ) ;
2022-03-25 09:56:41 +00:00
void qobjectToString ( ) ;
2022-06-24 07:56:01 +00:00
void qtNamespaceInQtObject ( ) ;
2022-08-25 09:42:23 +00:00
void nativeModuleImport ( ) ;
Allow limited extensions to globals
We can allow
a, overriding data members of globals, such as Error.name
b, adding members that don't clash with any internals
c, any manipulation of toString(), toLocaleString(), valueOf(),
and constructor
To that effect, add a "Locked" flag to our internal classes. If that is
set, disallow changing prototypes and when defining a property, check if
it shadows any non-configurable property. Furthermore, make all
non-primitive properties that are not meant to be overridden
non-configurable and non-writable.
constructor, toString(), toLocaleString() and valueOf() are exempt
because they are explicitly meant to be overridden by users. Therefore,
we let that happen and refrain from optimizing them or triggering their
implicit invocation in optimized code.
[ChangeLog][QtQml][Important Behavior Changes] The JavaScript global
objects are not frozen anymore in a QML engine. Instead, they are
selectively locked. You can extend the objects with new members as long
as you don't shadow any existing methods, and you can change or override
data members. This also means that most methods of Object.prototype,
which was previously exempt from the freezing, cannot be changed
anymore. You can, however, change or override constructor, toString(),
toLocaleString() and valueOf() on any prototype. Those are clearly meant
to be overridden by user code.
Fixes: QTBUG-101298
Task-number: QTBUG-84341
Change-Id: Id77db971f76c8f48b18e7a93607da5f947ecfc3e
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2022-08-29 10:00:52 +00:00
void lockedRootObject ( ) ;
2023-03-22 12:01:24 +00:00
void crossReferencingSingletonsDeletion ( ) ;
2023-09-28 08:36:46 +00:00
void bindingInstallUseAfterFree ( ) ;
2024-08-05 08:49:11 +00:00
void objectListArgumentMethod ( ) ;
2024-04-08 13:28:12 +00:00
void variantListQJsonConversion ( ) ;
2024-09-19 13:18:12 +00:00
void attachedObjectOfUnregistered ( ) ;
2024-10-11 20:13:21 +00:00
void dropCUOnEngineShutdown ( ) ;
2025-01-23 10:45:10 +00:00
void multiLoadedJavaScriptModule ( ) ;
2025-05-21 14:59:04 +00:00
void metaObjectOfScriptCU ( ) ;
2025-06-11 08:23:07 +00:00
void registerModule ( ) ;
2015-10-27 11:21:00 +00:00
2012-04-19 12:38:38 +00:00
public slots :
QObject * createAQObjectForOwnershipTest ( )
{
static QObject * ptr = new QObject ( ) ;
return ptr ;
}
2017-05-31 13:54:15 +00:00
private :
QTemporaryDir m_tempDir ;
2011-04-27 10:05:43 +00:00
} ;
2021-08-19 13:08:41 +00:00
class ObjectCaller : public QObject
{
Q_OBJECT
signals :
void doubleReply ( const double a ) ;
} ;
class CppSingleton : public QObject {
Q_OBJECT
public :
2021-08-27 11:42:44 +00:00
static uint instantiations ;
uint id = 0 ;
CppSingleton ( ) : id ( + + instantiations ) { }
2021-08-19 13:08:41 +00:00
static QObject * create ( QQmlEngine * qmlEngine , QJSEngine * jsEngine )
{
Q_UNUSED ( qmlEngine ) ;
Q_UNUSED ( jsEngine ) ;
return new CppSingleton ( ) ;
}
} ;
2021-08-27 11:42:44 +00:00
uint CppSingleton : : instantiations = 0 ;
2021-08-19 13:08:41 +00:00
class JsSingleton : public QObject {
Q_OBJECT
public :
2021-09-22 08:00:44 +00:00
static uint instantiations ;
uint id = 0 ;
JsSingleton ( ) : id ( + + instantiations ) { }
2021-08-19 13:08:41 +00:00
static QJSValue create ( QQmlEngine * qmlEngine , QJSEngine * jsEngine )
{
Q_UNUSED ( qmlEngine ) ;
QJSValue value = jsEngine - > newQObject ( new JsSingleton ( ) ) ;
return value ;
}
} ;
2021-09-22 08:00:44 +00:00
uint JsSingleton : : instantiations = 0 ;
2017-05-31 13:54:15 +00:00
void tst_qqmlengine : : initTestCase ( )
{
QVERIFY2 ( m_tempDir . isValid ( ) , qPrintable ( m_tempDir . errorString ( ) ) ) ;
QQmlDataTest : : initTestCase ( ) ;
}
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : rootContext ( )
2011-04-27 10:05:43 +00:00
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine ;
2011-04-27 10:05:43 +00:00
QVERIFY ( engine . rootContext ( ) ) ;
QCOMPARE ( engine . rootContext ( ) - > engine ( ) , & engine ) ;
2015-07-24 13:27:58 +00:00
QVERIFY ( ! engine . rootContext ( ) - > parentContext ( ) ) ;
2011-04-27 10:05:43 +00:00
}
2024-04-24 11:40:49 +00:00
# if QT_CONFIG(qml_network)
2012-02-16 04:43:03 +00:00
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
2011-04-27 10:05:43 +00:00
{
public :
2018-02-21 09:41:54 +00:00
NetworkAccessManagerFactory ( ) : manager ( nullptr ) { }
2011-04-27 10:05:43 +00:00
2021-04-11 09:12:51 +00:00
QNetworkAccessManager * create ( QObject * parent ) override {
2011-04-27 10:05:43 +00:00
manager = new QNetworkAccessManager ( parent ) ;
return manager ;
}
QNetworkAccessManager * manager ;
} ;
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : networkAccessManager ( )
2011-04-27 10:05:43 +00:00
{
2023-07-28 12:57:16 +00:00
std : : unique_ptr < QQmlEngine > engine = std : : make_unique < QQmlEngine > ( ) ;
2011-04-27 10:05:43 +00:00
2012-02-16 04:43:03 +00:00
// Test QQmlEngine created manager
2011-04-27 10:05:43 +00:00
QPointer < QNetworkAccessManager > manager = engine - > networkAccessManager ( ) ;
2018-02-21 09:41:54 +00:00
QVERIFY ( manager ! = nullptr ) ;
2011-04-27 10:05:43 +00:00
// Test factory created manager
2023-07-28 12:57:16 +00:00
engine . reset ( new QQmlEngine ) ;
2011-04-27 10:05:43 +00:00
NetworkAccessManagerFactory factory ;
engine - > setNetworkAccessManagerFactory ( & factory ) ;
2015-07-24 13:27:58 +00:00
QCOMPARE ( engine - > networkAccessManagerFactory ( ) , & factory ) ;
QNetworkAccessManager * engineNam = engine - > networkAccessManager ( ) ; // calls NetworkAccessManagerFactory::create()
QCOMPARE ( engineNam , factory . manager ) ;
2011-04-27 10:05:43 +00:00
}
2013-02-28 17:13:23 +00:00
class ImmediateReply : public QNetworkReply {
Q_OBJECT
public :
ImmediateReply ( ) {
setFinished ( true ) ;
}
2021-03-25 13:44:04 +00:00
qint64 readData ( char * , qint64 ) override {
2013-02-28 17:13:23 +00:00
return 0 ;
}
2021-03-25 13:44:04 +00:00
void abort ( ) override { }
2013-02-28 17:13:23 +00:00
} ;
class ImmediateManager : public QNetworkAccessManager {
Q_OBJECT
public :
2018-02-21 09:41:54 +00:00
ImmediateManager ( QObject * parent = nullptr ) : QNetworkAccessManager ( parent ) {
2013-02-28 17:13:23 +00:00
}
2021-04-11 09:12:51 +00:00
QNetworkReply * createRequest ( Operation , const QNetworkRequest & , QIODevice * outgoingData = nullptr ) override
{
2013-02-28 17:13:23 +00:00
Q_UNUSED ( outgoingData ) ;
return new ImmediateReply ;
}
} ;
class ImmediateFactory : public QQmlNetworkAccessManagerFactory {
public :
2021-04-11 09:12:51 +00:00
QNetworkAccessManager * create ( QObject * ) override
{ return new ImmediateManager ; }
2013-02-28 17:13:23 +00:00
} ;
void tst_qqmlengine : : synchronousNetworkAccessManager ( )
{
ImmediateFactory factory ;
QQmlEngine engine ;
engine . setNetworkAccessManagerFactory ( & factory ) ;
QQmlComponent c ( & engine , QUrl ( " myScheme://test.qml " ) ) ;
// reply is finished, so should not be in loading state.
QVERIFY ( ! c . isLoading ( ) ) ;
}
2024-04-24 11:40:49 +00:00
# endif
2013-02-28 17:13:23 +00:00
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : baseUrl ( )
2011-04-27 10:05:43 +00:00
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine ;
2011-04-27 10:05:43 +00:00
QUrl cwd = QUrl : : fromLocalFile ( QDir : : currentPath ( ) + QDir : : separator ( ) ) ;
QCOMPARE ( engine . baseUrl ( ) , cwd ) ;
QCOMPARE ( engine . rootContext ( ) - > resolvedUrl ( QUrl ( " main.qml " ) ) , cwd . resolved ( QUrl ( " main.qml " ) ) ) ;
QDir dir = QDir : : current ( ) ;
dir . cdUp ( ) ;
QVERIFY ( dir ! = QDir : : current ( ) ) ;
QDir : : setCurrent ( dir . path ( ) ) ;
2015-07-24 13:27:58 +00:00
QCOMPARE ( QDir : : current ( ) , dir ) ;
2011-04-27 10:05:43 +00:00
QUrl cwd2 = QUrl : : fromLocalFile ( QDir : : currentPath ( ) + QDir : : separator ( ) ) ;
QCOMPARE ( engine . baseUrl ( ) , cwd2 ) ;
QCOMPARE ( engine . rootContext ( ) - > resolvedUrl ( QUrl ( " main.qml " ) ) , cwd2 . resolved ( QUrl ( " main.qml " ) ) ) ;
engine . setBaseUrl ( cwd ) ;
QCOMPARE ( engine . baseUrl ( ) , cwd ) ;
QCOMPARE ( engine . rootContext ( ) - > resolvedUrl ( QUrl ( " main.qml " ) ) , cwd . resolved ( QUrl ( " main.qml " ) ) ) ;
2018-02-22 23:56:31 +00:00
const QString testPath = QDir : : currentPath ( ) + QLatin1String ( " / " ) ;
const QString rootPath = QDir : : rootPath ( ) ;
engine . setBaseUrl ( QUrl ( ) ) ;
// Check that baseUrl returns a url to a localFile
QCOMPARE ( engine . baseUrl ( ) . toLocalFile ( ) , testPath ) ;
QDir : : setCurrent ( QDir : : rootPath ( ) ) ;
// Make sure this also works when in the rootPath
QCOMPARE ( engine . baseUrl ( ) . toLocalFile ( ) , rootPath ) ;
2011-04-27 10:05:43 +00:00
}
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : contextForObject ( )
2011-04-27 10:05:43 +00:00
{
2023-07-28 12:57:16 +00:00
std : : unique_ptr < QQmlEngine > engine = std : : make_unique < QQmlEngine > ( ) ;
2011-04-27 10:05:43 +00:00
// Test null-object
2018-02-21 09:41:54 +00:00
QVERIFY ( ! QQmlEngine : : contextForObject ( nullptr ) ) ;
2011-04-27 10:05:43 +00:00
// Test an object with no context
QObject object ;
2015-07-24 13:27:58 +00:00
QVERIFY ( ! QQmlEngine : : contextForObject ( & object ) ) ;
2011-04-27 10:05:43 +00:00
// Test setting null-object
2018-02-21 09:41:54 +00:00
QQmlEngine : : setContextForObject ( nullptr , engine - > rootContext ( ) ) ;
2011-04-27 10:05:43 +00:00
// Test setting null-context
2018-02-21 09:41:54 +00:00
QQmlEngine : : setContextForObject ( & object , nullptr ) ;
2011-04-27 10:05:43 +00:00
// Test setting context
2012-02-16 04:43:03 +00:00
QQmlEngine : : setContextForObject ( & object , engine - > rootContext ( ) ) ;
2015-07-24 13:27:58 +00:00
QCOMPARE ( QQmlEngine : : contextForObject ( & object ) , engine - > rootContext ( ) ) ;
2011-04-27 10:05:43 +00:00
2012-02-16 04:43:03 +00:00
QQmlContext context ( engine - > rootContext ( ) ) ;
2011-04-27 10:05:43 +00:00
// Try changing context
2012-02-16 04:43:03 +00:00
QTest : : ignoreMessage ( QtWarningMsg , " QQmlEngine::setContextForObject(): Object already has a QQmlContext " ) ;
QQmlEngine : : setContextForObject ( & object , & context ) ;
2015-07-24 13:27:58 +00:00
QCOMPARE ( QQmlEngine : : contextForObject ( & object ) , engine - > rootContext ( ) ) ;
2011-04-27 10:05:43 +00:00
// Delete context
2023-07-28 12:57:16 +00:00
engine . reset ( ) ;
2015-07-24 13:27:58 +00:00
QVERIFY ( ! QQmlEngine : : contextForObject ( & object ) ) ;
2011-04-27 10:05:43 +00:00
}
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : offlineStoragePath ( )
2011-04-27 10:05:43 +00:00
{
// Without these set, QDesktopServices::storageLocation returns
// strings with extra "//" at the end. We set them to ignore this problem.
2012-02-16 04:43:03 +00:00
qApp - > setApplicationName ( " tst_qqmlengine " ) ;
2013-07-15 12:59:07 +00:00
qApp - > setOrganizationName ( " QtProject " ) ;
qApp - > setOrganizationDomain ( " www.qt-project.org " ) ;
2011-04-27 10:05:43 +00:00
2012-02-16 04:43:03 +00:00
QQmlEngine engine ;
2011-04-27 10:05:43 +00:00
2020-10-16 13:04:23 +00:00
QString dataLocation = QStandardPaths : : writableLocation ( QStandardPaths : : AppDataLocation ) ;
2012-04-19 03:50:39 +00:00
QCOMPARE ( dataLocation . isEmpty ( ) , engine . offlineStoragePath ( ) . isEmpty ( ) ) ;
QDir dir ( dataLocation ) ;
2011-04-27 10:05:43 +00:00
dir . mkpath ( " QML " ) ;
dir . cd ( " QML " ) ;
dir . mkpath ( " OfflineStorage " ) ;
dir . cd ( " OfflineStorage " ) ;
QCOMPARE ( QDir : : fromNativeSeparators ( engine . offlineStoragePath ( ) ) , dir . path ( ) ) ;
2022-09-27 08:27:30 +00:00
QSignalSpy offlineStoragePathSpy ( & engine , & QQmlEngine : : offlineStoragePathChanged ) ;
2011-04-27 10:05:43 +00:00
engine . setOfflineStoragePath ( QDir : : homePath ( ) ) ;
Port from container::count() and length() to size() - V5
This is a semantic patch using ClangTidyTransformator as in
qtbase/df9d882d41b741fef7c5beeddb0abe9d904443d8, but extended to
handle typedefs and accesses through pointers, too:
const std::string o = "object";
auto hasTypeIgnoringPointer = [](auto type) { return anyOf(hasType(type), hasType(pointsTo(type))); };
auto derivedFromAnyOfClasses = [&](ArrayRef<StringRef> classes) {
auto exprOfDeclaredType = [&](auto decl) {
return expr(hasTypeIgnoringPointer(hasUnqualifiedDesugaredType(recordType(hasDeclaration(decl))))).bind(o);
};
return exprOfDeclaredType(cxxRecordDecl(isSameOrDerivedFrom(hasAnyName(classes))));
};
auto renameMethod = [&] (ArrayRef<StringRef> classes,
StringRef from, StringRef to) {
return makeRule(cxxMemberCallExpr(on(derivedFromAnyOfClasses(classes)),
callee(cxxMethodDecl(hasName(from), parameterCountIs(0)))),
changeTo(cat(access(o, cat(to)), "()")),
cat("use '", to, "' instead of '", from, "'"));
};
renameMethod(<classes>, "count", "size");
renameMethod(<classes>, "length", "size");
except that on() was replaced with a matcher that doesn't ignoreParens().
a.k.a qt-port-to-std-compatible-api V5 with config Scope: 'Container'.
Change-Id: I58e1b41b91c34d2e860dbb5847b3752edbfc6fc9
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-10-08 17:56:03 +00:00
QCOMPARE ( offlineStoragePathSpy . size ( ) , 1 ) ;
2011-04-27 10:05:43 +00:00
QCOMPARE ( engine . offlineStoragePath ( ) , QDir : : homePath ( ) ) ;
}
2017-01-09 16:06:25 +00:00
void tst_qqmlengine : : offlineDatabaseStoragePath ( )
{
// Without these set, QDesktopServices::storageLocation returns
// strings with extra "//" at the end. We set them to ignore this problem.
qApp - > setApplicationName ( " tst_qqmlengine " ) ;
qApp - > setOrganizationName ( " QtProject " ) ;
qApp - > setOrganizationDomain ( " www.qt-project.org " ) ;
QQmlEngine engine ;
2020-10-16 13:04:23 +00:00
QString dataLocation = QStandardPaths : : writableLocation ( QStandardPaths : : AppDataLocation ) ;
2017-01-09 16:06:25 +00:00
const QString databaseName = QLatin1String ( " foo " ) ;
QString databaseLocation = engine . offlineStorageDatabaseFilePath ( databaseName ) ;
QCOMPARE ( dataLocation . isEmpty ( ) , databaseLocation . isEmpty ( ) ) ;
QDir dir ( dataLocation ) ;
dir . mkpath ( " QML " ) ;
dir . cd ( " QML " ) ;
dir . mkpath ( " OfflineStorage " ) ;
dir . cd ( " OfflineStorage " ) ;
dir . mkpath ( " Databases " ) ;
dir . cd ( " Databases " ) ;
QCOMPARE ( QFileInfo ( databaseLocation ) . dir ( ) . path ( ) , dir . path ( ) ) ;
QCryptographicHash md5 ( QCryptographicHash : : Md5 ) ;
md5 . addData ( databaseName . toUtf8 ( ) ) ;
QCOMPARE ( databaseLocation , QDir : : toNativeSeparators ( dir . filePath ( QLatin1String ( md5 . result ( ) . toHex ( ) ) ) ) ) ;
}
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : clearComponentCache ( )
2011-04-27 10:05:43 +00:00
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine ;
2011-04-27 10:05:43 +00:00
2017-05-31 13:54:15 +00:00
const QString fileName = m_tempDir . filePath ( QStringLiteral ( " temp.qml " ) ) ;
const QUrl fileUrl = QUrl : : fromLocalFile ( fileName ) ;
2011-04-27 10:05:43 +00:00
// Create original qml file
{
2017-05-31 13:54:15 +00:00
QFile file ( fileName ) ;
2011-04-27 10:05:43 +00:00
QVERIFY ( file . open ( QIODevice : : WriteOnly ) ) ;
2012-01-31 06:52:36 +00:00
file . write ( " import QtQuick 2.0 \n QtObject { \n property int test: 10 \n } \n " ) ;
2011-04-27 10:05:43 +00:00
file . close ( ) ;
}
// Test "test" property
{
2017-05-31 13:54:15 +00:00
QQmlComponent component ( & engine , fileUrl ) ;
2023-07-28 12:57:16 +00:00
std : : unique_ptr < QObject > obj { component . create ( ) } ;
QVERIFY ( obj . get ( ) ! = nullptr ) ;
2011-04-27 10:05:43 +00:00
QCOMPARE ( obj - > property ( " test " ) . toInt ( ) , 10 ) ;
}
// Modify qml file
{
2016-08-03 11:50:44 +00:00
// On macOS with HFS+ the precision of file times is measured in seconds, so to ensure that
// the newly written file has a modification date newer than an existing cache file, we must
// wait.
// Similar effects of lacking precision have been observed on some Linux systems.
QThread : : sleep ( 1 ) ;
2017-05-31 13:54:15 +00:00
QFile file ( fileName ) ;
2011-04-27 10:05:43 +00:00
QVERIFY ( file . open ( QIODevice : : WriteOnly ) ) ;
2012-01-31 06:52:36 +00:00
file . write ( " import QtQuick 2.0 \n QtObject { \n property int test: 11 \n } \n " ) ;
2011-04-27 10:05:43 +00:00
file . close ( ) ;
}
// Test cache hit
{
2017-05-31 13:54:15 +00:00
QQmlComponent component ( & engine , fileUrl ) ;
2023-07-28 12:57:16 +00:00
std : : unique_ptr < QObject > obj { component . create ( ) } ;
QVERIFY ( obj . get ( ) ! = nullptr ) ;
2011-04-27 10:05:43 +00:00
QCOMPARE ( obj - > property ( " test " ) . toInt ( ) , 10 ) ;
}
// Clear cache
engine . clearComponentCache ( ) ;
2024-09-10 13:42:59 +00:00
// Nothing holds on to any CU anymore. They should all be gone.
QVERIFY ( QQmlEnginePrivate : : get ( & engine ) - > v4engine ( ) - > compilationUnits ( ) . isEmpty ( ) ) ;
2011-04-27 10:05:43 +00:00
// Test cache refresh
{
2017-05-31 13:54:15 +00:00
QQmlComponent component ( & engine , fileUrl ) ;
2023-07-28 12:57:16 +00:00
std : : unique_ptr < QObject > obj { component . create ( ) } ;
QVERIFY ( obj . get ( ) ! = nullptr ) ;
2011-04-27 10:05:43 +00:00
QCOMPARE ( obj - > property ( " test " ) . toInt ( ) , 11 ) ;
2024-09-10 13:42:59 +00:00
engine . clearComponentCache ( ) ;
// The CU we are holding on to is still alive.
// Otherwise we cannot mark its objects for GC anymore.
QVERIFY ( ! QQmlEnginePrivate : : get ( & engine ) - > v4engine ( ) - > compilationUnits ( ) . isEmpty ( ) ) ;
2011-04-27 10:05:43 +00:00
}
2017-05-31 13:54:15 +00:00
// Regular Synchronous loading will leave us with an event posted
// to the gui thread and an extra refcount that will only be dropped after the
// event delivery. Call sendPostedEvents() to get rid of it so that
// the temporary directory can be removed.
QCoreApplication : : sendPostedEvents ( ) ;
2011-04-27 10:05:43 +00:00
}
2012-05-03 22:32:45 +00:00
struct ComponentCacheFunctions : public QObject , public QQmlIncubationController
{
Q_OBJECT
public :
QQmlEngine * engine ;
ComponentCacheFunctions ( QQmlEngine & e ) : engine ( & e ) { }
Q_INVOKABLE void trim ( )
{
// Wait for any pending deletions to occur
2018-02-21 09:41:54 +00:00
QCoreApplication : : sendPostedEvents ( nullptr , QEvent : : DeferredDelete ) ;
2012-05-03 22:32:45 +00:00
QCoreApplication : : processEvents ( ) ;
2016-05-29 17:21:54 +00:00
// There might be JS function objects around that hold a last ref to the compilation unit that's
// keeping the type compilation data (CompilationUnit) around. Let's collect them as well so that
// trim works well.
2023-10-19 08:05:28 +00:00
gc ( * engine ) ;
2016-05-29 17:21:54 +00:00
2012-05-03 22:32:45 +00:00
engine - > trimComponentCache ( ) ;
}
Q_INVOKABLE bool isTypeLoaded ( QString file )
{
2025-06-03 12:35:29 +00:00
return QQmlTypeLoader : : get ( engine )
- > isTypeLoaded ( tst_qqmlengine : : instance ( ) - > testFileUrl ( file ) ) ;
2012-05-03 22:32:45 +00:00
}
Q_INVOKABLE bool isScriptLoaded ( QString file )
{
2025-06-03 12:35:29 +00:00
return QQmlTypeLoader : : get ( engine )
- > isScriptLoaded ( tst_qqmlengine : : instance ( ) - > testFileUrl ( file ) ) ;
2012-05-03 22:32:45 +00:00
}
Q_INVOKABLE void beginIncubation ( )
{
startTimer ( 0 ) ;
}
Q_INVOKABLE void waitForIncubation ( )
{
while ( incubatingObjectCount ( ) > 0 ) {
QCoreApplication : : processEvents ( ) ;
}
}
private :
2021-03-25 13:44:04 +00:00
void timerEvent ( QTimerEvent * ) override
2012-05-03 22:32:45 +00:00
{
incubateFor ( 1000 ) ;
}
} ;
void tst_qqmlengine : : trimComponentCache ( )
{
QFETCH ( QString , file ) ;
QQmlEngine engine ;
ComponentCacheFunctions componentCache ( engine ) ;
engine . setIncubationController ( & componentCache ) ;
QQmlComponent component ( & engine , testFileUrl ( file ) ) ;
2019-07-03 15:25:55 +00:00
QVERIFY2 ( component . isReady ( ) , qPrintable ( component . errorString ( ) ) ) ;
2019-10-04 11:49:18 +00:00
QScopedPointer < QObject > object ( component . createWithInitialProperties ( {
{ " componentCache " , QVariant : : fromValue ( & componentCache ) }
} ) ) ;
2018-02-21 09:41:54 +00:00
QVERIFY ( object ! = nullptr ) ;
2012-05-03 22:32:45 +00:00
QCOMPARE ( object - > property ( " success " ) . toBool ( ) , true ) ;
}
void tst_qqmlengine : : trimComponentCache_data ( )
{
QTest : : addColumn < QString > ( " file " ) ;
// The various tests here are for two types of components: those that are
// empty apart from their inherited elements, and those that define new properties.
// For each there are five types of composition: extension, aggregation,
// aggregation via component, property and object-created-via-transient-component.
2023-08-07 08:20:42 +00:00
const QStringList components = {
" EmptyComponent " ,
" VMEComponent " ,
" EmptyExtendEmptyComponent " ,
" VMEExtendEmptyComponent " ,
" EmptyExtendVMEComponent " ,
" VMEExtendVMEComponent " ,
" EmptyAggregateEmptyComponent " ,
" VMEAggregateEmptyComponent " ,
" EmptyAggregateVMEComponent " ,
" VMEAggregateVMEComponent " ,
" EmptyPropertyEmptyComponent " ,
" VMEPropertyEmptyComponent " ,
" EmptyPropertyVMEComponent " ,
" VMEPropertyVMEComponent " ,
" VMETransientEmptyComponent " ,
" VMETransientVMEComponent " ,
} ;
for ( const QString & test : components ) {
2012-05-03 22:32:45 +00:00
// For these cases, we first test that the component instance keeps the components
// referenced, and then that the instantiated object keeps the components referenced
for ( int i = 1 ; i < = 2 ; + + i ) {
QString name ( QString ( " %1-%2 " ) . arg ( test ) . arg ( i ) ) ;
QString file ( QString ( " test%1.%2.qml " ) . arg ( test ) . arg ( i ) ) ;
QTest : : newRow ( name . toLatin1 ( ) . constData ( ) ) < < file ;
}
}
// Test that a transient component is correctly referenced
QTest : : newRow ( " TransientComponent-1 " ) < < " testTransientComponent.1.qml " ;
QTest : : newRow ( " TransientComponent-2 " ) < < " testTransientComponent.2.qml " ;
// Test that components can be reloaded after unloading
QTest : : newRow ( " ReloadComponent " ) < < " testReloadComponent.qml " ;
// Test that components are correctly referenced when dynamically loaded
QTest : : newRow ( " LoaderComponent " ) < < " testLoaderComponent.qml " ;
// Test that components are correctly referenced when incubated
QTest : : newRow ( " IncubatedComponent " ) < < " testIncubatedComponent.qml " ;
// Test that a top-level omponents is correctly referenced
QTest : : newRow ( " TopLevelComponent " ) < < " testTopLevelComponent.qml " ;
// TODO:
// Test that scripts are unloaded when no longer referenced
QTest : : newRow ( " ScriptComponent " ) < < " testScriptComponent.qml " ;
}
2021-08-19 13:08:41 +00:00
static QJSValue createValueSingleton ( QQmlEngine * , QJSEngine * ) { return 13u ; }
void tst_qqmlengine : : clearSingletons ( )
{
ObjectCaller objectCaller1 ;
ObjectCaller objectCaller2 ;
const int cppInstance = qmlRegisterSingletonInstance (
" ClearSingletons " , 1 , 0 , " CppInstance " , & objectCaller1 ) ;
2022-08-19 14:14:36 +00:00
# if QT_DEPRECATED_SINCE(6, 3)
QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
2021-08-19 13:08:41 +00:00
QQmlPrivate : : SingletonFunctor deprecatedSingletonFunctor ;
deprecatedSingletonFunctor . m_object = & objectCaller2 ;
const int deprecatedCppInstance = qmlRegisterSingletonType < ObjectCaller > (
" ClearSingletons " , 1 , 0 , " DeprecatedCppInstance " , deprecatedSingletonFunctor ) ;
2022-08-19 14:14:36 +00:00
QT_WARNING_POP
# endif // QT_DEPRECATED_SINCE(6, 3)
2021-08-19 13:08:41 +00:00
const int cppFactory = qmlRegisterSingletonType < CppSingleton > (
" ClearSingletons " , 1 , 0 , " CppFactory " , & CppSingleton : : create ) ;
const int jsValue = qmlRegisterSingletonType (
" ClearSingletons " , 1 , 0 , " JsValue " , & createValueSingleton ) ;
const int jsObject = qmlRegisterSingletonType (
" ClearSingletons " , 1 , 0 , " JsObject " , & JsSingleton : : create ) ;
const int qmlObject = qmlRegisterSingletonType (
testFileUrl ( " QmlSingleton.qml " ) , " ClearSingletons " , 1 , 0 , " QmlSingleton " ) ;
QQmlEngine engine ;
QCOMPARE ( engine . singletonInstance < ObjectCaller * > ( cppInstance ) , & objectCaller1 ) ;
2022-08-19 14:14:36 +00:00
# if QT_DEPRECATED_SINCE(6, 3)
2021-08-19 13:08:41 +00:00
QCOMPARE ( engine . singletonInstance < ObjectCaller * > ( deprecatedCppInstance ) , & objectCaller2 ) ;
2022-08-19 14:14:36 +00:00
# endif
2021-08-19 13:08:41 +00:00
const CppSingleton * oldCppSingleton = engine . singletonInstance < CppSingleton * > ( cppFactory ) ;
QVERIFY ( oldCppSingleton ! = nullptr ) ;
2021-08-27 11:42:44 +00:00
const uint oldCppSingletonId = oldCppSingleton - > id ;
QVERIFY ( oldCppSingletonId > 0 ) ;
QCOMPARE ( CppSingleton : : instantiations , oldCppSingletonId ) ;
2021-08-19 13:08:41 +00:00
QCOMPARE ( engine . singletonInstance < QJSValue > ( jsValue ) . toUInt ( ) , 13u ) ;
const JsSingleton * oldJsSingleton = engine . singletonInstance < JsSingleton * > ( jsObject ) ;
QVERIFY ( oldJsSingleton ! = nullptr ) ;
2021-09-22 08:00:44 +00:00
const uint oldJsSingletonId = oldJsSingleton - > id ;
2021-08-19 13:08:41 +00:00
const QObject * oldQmlSingleton = engine . singletonInstance < QObject * > ( qmlObject ) ;
QVERIFY ( oldQmlSingleton ! = nullptr ) ;
QQmlComponent c ( & engine ) ;
c . setData ( " import ClearSingletons \n "
" import QtQml \n "
" QtObject { \n "
" property QtObject a: CppInstance \n "
2022-08-19 14:14:36 +00:00
# if QT_DEPRECATED_SINCE(6, 3)
2021-08-19 13:08:41 +00:00
" property QtObject f: DeprecatedCppInstance \n "
2022-08-19 14:14:36 +00:00
# endif
2021-08-19 13:08:41 +00:00
" property QtObject b: CppFactory \n "
" property int c: JsValue \n "
" property QtObject d: JsObject \n "
" property QtObject e: QmlSingleton \n "
" } " , QUrl ( ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > singletonUser ( c . create ( ) ) ;
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " a " ) ) , & objectCaller1 ) ;
2022-08-19 14:14:36 +00:00
# if QT_DEPRECATED_SINCE(6, 3)
2021-08-19 13:08:41 +00:00
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " f " ) ) , & objectCaller2 ) ;
2022-08-19 14:14:36 +00:00
# endif
2021-08-19 13:08:41 +00:00
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " b " ) ) , oldCppSingleton ) ;
QCOMPARE ( singletonUser - > property ( " c " ) . toUInt ( ) , 13u ) ;
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " d " ) ) , oldJsSingleton ) ;
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " e " ) ) , oldQmlSingleton ) ;
engine . clearSingletons ( ) ;
2021-08-27 11:42:44 +00:00
QCOMPARE ( CppSingleton : : instantiations , oldCppSingletonId ) ;
2021-08-19 13:08:41 +00:00
QCOMPARE ( engine . singletonInstance < ObjectCaller * > ( cppInstance ) , & objectCaller1 ) ;
2022-08-19 14:14:36 +00:00
# if QT_DEPRECATED_SINCE(6, 3)
2021-08-19 13:08:41 +00:00
// Singleton instances created with previous versions of qmlRegisterSingletonInstance()
// are lost and cannot be accessed anymore. We can only call their synthesized factory
// functions once. This is not a big problem as you have to recompile in order to use
// clearSingletons() anyway.
QTest : : ignoreMessage (
QtWarningMsg ,
" <Unknown File>: Singleton registered by registerSingletonInstance "
" must only be accessed from one engine " ) ;
QTest : : ignoreMessage (
QtCriticalMsg ,
" <Unknown File>: qmlRegisterSingletonType(): \" DeprecatedCppInstance \" is not "
" available because the callback function returns a null pointer. " ) ;
QCOMPARE ( engine . singletonInstance < ObjectCaller * > ( deprecatedCppInstance ) , nullptr ) ;
2022-08-19 14:14:36 +00:00
# endif
2021-08-19 13:08:41 +00:00
2021-08-27 11:42:44 +00:00
QCOMPARE ( CppSingleton : : instantiations , oldCppSingletonId ) ;
2021-08-19 13:08:41 +00:00
const CppSingleton * newCppSingleton = engine . singletonInstance < CppSingleton * > ( cppFactory ) ;
2021-08-27 11:42:44 +00:00
QVERIFY ( newCppSingleton ! = nullptr ) ; // The pointer may be the same as the old one
QCOMPARE ( CppSingleton : : instantiations , oldCppSingletonId + 1 ) ;
QCOMPARE ( newCppSingleton - > id , CppSingleton : : instantiations ) ;
2021-08-19 13:08:41 +00:00
QCOMPARE ( engine . singletonInstance < QJSValue > ( jsValue ) . toUInt ( ) , 13u ) ;
const JsSingleton * newJsSingleton = engine . singletonInstance < JsSingleton * > ( jsObject ) ;
QVERIFY ( newJsSingleton ! = nullptr ) ;
2021-09-22 08:00:44 +00:00
QVERIFY ( newJsSingleton - > id ! = oldJsSingletonId ) ;
2021-08-19 13:08:41 +00:00
const QObject * newQmlSingleton = engine . singletonInstance < QObject * > ( qmlObject ) ;
QVERIFY ( newQmlSingleton ! = nullptr ) ;
QVERIFY ( newQmlSingleton ! = oldQmlSingleton ) ;
// Holding on to an old singleton instance is OK. We don't delete those.
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " a " ) ) , & objectCaller1 ) ;
2022-08-19 14:14:36 +00:00
# if QT_DEPRECATED_SINCE(6, 3)
2021-08-19 13:08:41 +00:00
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " f " ) ) , & objectCaller2 ) ;
2022-08-19 14:14:36 +00:00
# endif
2021-08-19 13:08:41 +00:00
// Deleting the singletons created by factories has cleared their references in QML.
// We don't renew them as the point of clearing the singletons is not having any
// singletons left afterwards.
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " b " ) ) , nullptr ) ;
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " d " ) ) , nullptr ) ;
QCOMPARE ( qvariant_cast < QObject * > ( singletonUser - > property ( " e " ) ) , nullptr ) ;
// Value types are unaffected as they are copied.
QCOMPARE ( singletonUser - > property ( " c " ) . toUInt ( ) , 13u ) ;
}
2012-05-21 03:40:22 +00:00
void tst_qqmlengine : : repeatedCompilation ( )
{
QQmlEngine engine ;
for ( int i = 0 ; i < 100 ; + + i ) {
engine . collectGarbage ( ) ;
engine . trimComponentCache ( ) ;
QQmlComponent component ( & engine , testFileUrl ( " repeatedCompilation.qml " ) ) ;
QVERIFY ( component . isReady ( ) ) ;
QScopedPointer < QObject > object ( component . create ( ) ) ;
2018-02-21 09:41:54 +00:00
QVERIFY ( object ! = nullptr ) ;
2012-05-21 03:40:22 +00:00
QCOMPARE ( object - > property ( " success " ) . toBool ( ) , true ) ;
}
}
void tst_qqmlengine : : failedCompilation ( )
{
QFETCH ( QString , file ) ;
QQmlEngine engine ;
QQmlComponent component ( & engine , testFileUrl ( file ) ) ;
2019-10-04 11:49:18 +00:00
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , " QQmlComponent: Component is not ready " ) ;
2012-05-21 03:40:22 +00:00
QVERIFY ( ! component . isReady ( ) ) ;
QScopedPointer < QObject > object ( component . create ( ) ) ;
2015-07-24 13:27:58 +00:00
QVERIFY ( object . isNull ( ) ) ;
2012-05-21 03:40:22 +00:00
engine . collectGarbage ( ) ;
engine . trimComponentCache ( ) ;
engine . clearComponentCache ( ) ;
}
void tst_qqmlengine : : failedCompilation_data ( )
{
QTest : : addColumn < QString > ( " file " ) ;
QTest : : newRow ( " Invalid URL " ) < < " failedCompilation.does.not.exist.qml " ;
QTest : : newRow ( " Invalid content " ) < < " failedCompilation.1.qml " ;
}
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : outputWarningsToStandardError ( )
2011-04-27 10:05:43 +00:00
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine ;
2011-04-27 10:05:43 +00:00
QCOMPARE ( engine . outputWarningsToStandardError ( ) , true ) ;
2012-02-16 04:43:03 +00:00
QQmlComponent c ( & engine ) ;
2012-01-31 06:52:36 +00:00
c . setData ( " import QtQuick 2.0; QtObject { property int a: undefined } " , QUrl ( ) ) ;
2011-04-27 10:05:43 +00:00
2015-07-24 11:39:05 +00:00
QVERIFY ( c . isReady ( ) ) ;
2011-04-27 10:05:43 +00:00
2012-12-18 10:41:06 +00:00
QQmlTestMessageHandler messageHandler ;
2011-04-27 10:05:43 +00:00
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o ( c . create ( ) ) ;
2011-04-27 10:05:43 +00:00
2018-02-21 09:41:54 +00:00
QVERIFY ( o ! = nullptr ) ;
2020-06-08 14:30:18 +00:00
o . reset ( ) ;
2011-04-27 10:05:43 +00:00
2022-10-05 05:29:16 +00:00
QCOMPARE ( messageHandler . messages ( ) . size ( ) , 1 ) ;
2018-04-09 11:39:23 +00:00
QCOMPARE ( messageHandler . messages ( ) . at ( 0 ) , QLatin1String ( " <Unknown File>:1:32: Unable to assign [undefined] to int " ) ) ;
2012-12-18 10:41:06 +00:00
messageHandler . clear ( ) ;
2011-04-27 10:05:43 +00:00
engine . setOutputWarningsToStandardError ( false ) ;
QCOMPARE ( engine . outputWarningsToStandardError ( ) , false ) ;
2020-06-08 14:30:18 +00:00
o . reset ( c . create ( ) ) ;
2011-04-27 10:05:43 +00:00
2018-02-21 09:41:54 +00:00
QVERIFY ( o ! = nullptr ) ;
2020-06-08 14:30:18 +00:00
o . reset ( ) ;
2011-04-27 10:05:43 +00:00
2012-12-18 10:41:06 +00:00
QVERIFY2 ( messageHandler . messages ( ) . isEmpty ( ) , qPrintable ( messageHandler . messageString ( ) ) ) ;
2011-04-27 10:05:43 +00:00
}
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : objectOwnership ( )
2011-04-27 10:05:43 +00:00
{
{
2018-02-21 09:41:54 +00:00
QCOMPARE ( QQmlEngine : : objectOwnership ( nullptr ) , QQmlEngine : : CppOwnership ) ;
QQmlEngine : : setObjectOwnership ( nullptr , QQmlEngine : : JavaScriptOwnership ) ;
QCOMPARE ( QQmlEngine : : objectOwnership ( nullptr ) , QQmlEngine : : CppOwnership ) ;
2011-04-27 10:05:43 +00:00
}
{
QObject o ;
2012-02-16 04:43:03 +00:00
QCOMPARE ( QQmlEngine : : objectOwnership ( & o ) , QQmlEngine : : CppOwnership ) ;
QQmlEngine : : setObjectOwnership ( & o , QQmlEngine : : CppOwnership ) ;
QCOMPARE ( QQmlEngine : : objectOwnership ( & o ) , QQmlEngine : : CppOwnership ) ;
QQmlEngine : : setObjectOwnership ( & o , QQmlEngine : : JavaScriptOwnership ) ;
QCOMPARE ( QQmlEngine : : objectOwnership ( & o ) , QQmlEngine : : JavaScriptOwnership ) ;
QQmlEngine : : setObjectOwnership ( & o , QQmlEngine : : CppOwnership ) ;
QCOMPARE ( QQmlEngine : : objectOwnership ( & o ) , QQmlEngine : : CppOwnership ) ;
2011-04-27 10:05:43 +00:00
}
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
2012-01-31 06:52:36 +00:00
c . setData ( " import QtQuick 2.0; QtObject { property QtObject object: QtObject {} } " , QUrl ( ) ) ;
2011-04-27 10:05:43 +00:00
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o ( c . create ( ) ) ;
QVERIFY ( ! o . isNull ( ) ) ;
2011-04-27 10:05:43 +00:00
2020-06-08 14:30:18 +00:00
QCOMPARE ( QQmlEngine : : objectOwnership ( o . data ( ) ) , QQmlEngine : : CppOwnership ) ;
2011-04-27 10:05:43 +00:00
QObject * o2 = qvariant_cast < QObject * > ( o - > property ( " object " ) ) ;
2012-02-16 04:43:03 +00:00
QCOMPARE ( QQmlEngine : : objectOwnership ( o2 ) , QQmlEngine : : JavaScriptOwnership ) ;
2011-04-27 10:05:43 +00:00
2020-06-08 14:30:18 +00:00
o . reset ( ) ;
2011-04-27 10:05:43 +00:00
}
2012-04-19 12:38:38 +00:00
{
QObject * ptr = createAQObjectForOwnershipTest ( ) ;
QSignalSpy spy ( ptr , SIGNAL ( destroyed ( ) ) ) ;
{
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
QQmlEngine : : setObjectOwnership ( ptr , QQmlEngine : : JavaScriptOwnership ) ;
2019-10-04 11:49:18 +00:00
c . setData ( " import QtQuick 2.0; Item { required property QtObject test; property int data: test.createAQObjectForOwnershipTest() ? 0 : 1 } " , QUrl ( ) ) ;
2012-04-19 12:38:38 +00:00
QVERIFY ( c . isReady ( ) ) ;
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o (
c . createWithInitialProperties ( { { " test " , QVariant : : fromValue ( this ) } } ) ) ;
2018-02-21 09:41:54 +00:00
QVERIFY ( o ! = nullptr ) ;
2012-04-19 12:38:38 +00:00
}
2022-10-05 05:29:16 +00:00
QTRY_VERIFY ( spy . size ( ) ) ;
2012-04-19 12:38:38 +00:00
}
{
QObject * ptr = new QObject ( ) ;
QSignalSpy spy ( ptr , SIGNAL ( destroyed ( ) ) ) ;
{
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
QQmlEngine : : setObjectOwnership ( ptr , QQmlEngine : : JavaScriptOwnership ) ;
2019-10-04 11:49:18 +00:00
c . setData ( " import QtQuick 2.0; QtObject { required property QtObject test; property var object: { var i = test; test ? 0 : 1 } } " , QUrl ( ) ) ;
2012-04-19 12:38:38 +00:00
QVERIFY ( c . isReady ( ) ) ;
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o (
c . createWithInitialProperties ( { { " test " , QVariant : : fromValue ( ptr ) } } ) ) ;
2018-02-21 09:41:54 +00:00
QVERIFY ( o ! = nullptr ) ;
2020-06-08 14:30:18 +00:00
QQmlProperty testProp ( o . data ( ) , " test " ) ;
2019-10-04 11:49:18 +00:00
testProp . write ( QVariant : : fromValue < QObject * > ( nullptr ) ) ;
2012-04-19 12:38:38 +00:00
}
2022-10-05 05:29:16 +00:00
QTRY_VERIFY ( spy . size ( ) ) ;
2012-04-19 12:38:38 +00:00
}
2011-04-27 10:05:43 +00:00
}
2011-07-25 05:47:40 +00:00
// Test an object can be accessed by multiple engines
2012-02-16 04:43:03 +00:00
void tst_qqmlengine : : multipleEngines ( )
2011-07-25 05:47:40 +00:00
{
QObject o ;
o . setObjectName ( " TestName " ) ;
// Simultaneous engines
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine1 ;
QQmlEngine engine2 ;
2011-07-25 05:47:40 +00:00
engine1 . rootContext ( ) - > setContextProperty ( " object " , & o ) ;
engine2 . rootContext ( ) - > setContextProperty ( " object " , & o ) ;
2018-02-21 09:41:54 +00:00
QQmlExpression expr1 ( engine1 . rootContext ( ) , nullptr , QString ( " object.objectName " ) ) ;
QQmlExpression expr2 ( engine2 . rootContext ( ) , nullptr , QString ( " object.objectName " ) ) ;
2011-07-25 05:47:40 +00:00
QCOMPARE ( expr1 . evaluate ( ) . toString ( ) , QString ( " TestName " ) ) ;
QCOMPARE ( expr2 . evaluate ( ) . toString ( ) , QString ( " TestName " ) ) ;
}
// Serial engines
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine1 ;
2011-07-25 05:47:40 +00:00
engine1 . rootContext ( ) - > setContextProperty ( " object " , & o ) ;
2018-02-21 09:41:54 +00:00
QQmlExpression expr1 ( engine1 . rootContext ( ) , nullptr , QString ( " object.objectName " ) ) ;
2011-07-25 05:47:40 +00:00
QCOMPARE ( expr1 . evaluate ( ) . toString ( ) , QString ( " TestName " ) ) ;
}
{
2012-02-16 04:43:03 +00:00
QQmlEngine engine1 ;
2011-07-25 05:47:40 +00:00
engine1 . rootContext ( ) - > setContextProperty ( " object " , & o ) ;
2018-02-21 09:41:54 +00:00
QQmlExpression expr1 ( engine1 . rootContext ( ) , nullptr , QString ( " object.objectName " ) ) ;
2011-07-25 05:47:40 +00:00
QCOMPARE ( expr1 . evaluate ( ) . toString ( ) , QString ( " TestName " ) ) ;
}
}
2012-05-23 08:05:10 +00:00
void tst_qqmlengine : : qtqmlModule_data ( )
{
QTest : : addColumn < QUrl > ( " testFile " ) ;
QTest : : addColumn < QString > ( " expectedError " ) ;
QTest : : addColumn < QStringList > ( " expectedWarnings " ) ;
QTest : : newRow ( " import QtQml of correct version (2.0) " )
< < testFileUrl ( " qtqmlModule.1.qml " )
< < QString ( )
< < QStringList ( ) ;
QTest : : newRow ( " import QtQml of incorrect version (3.0) " )
< < testFileUrl ( " qtqmlModule.2.qml " )
< < QString ( testFileUrl ( " qtqmlModule.2.qml " ) . toString ( ) + QLatin1String ( " :1 module \" QtQml \" version 3.0 is not installed \n " ) )
< < QStringList ( ) ;
QTest : : newRow ( " import QtQml of incorrect version (1.0) " )
< < testFileUrl ( " qtqmlModule.3.qml " )
< < QString ( testFileUrl ( " qtqmlModule.3.qml " ) . toString ( ) + QLatin1String ( " :1 module \" QtQml \" version 1.0 is not installed \n " ) )
< < QStringList ( ) ;
2020-01-17 09:46:54 +00:00
QTest : : newRow ( " import QtQml of old version (2.50) " )
2012-05-23 08:05:10 +00:00
< < testFileUrl ( " qtqmlModule.4.qml " )
2020-01-17 09:46:54 +00:00
< < QString ( )
2012-05-23 08:05:10 +00:00
< < QStringList ( ) ;
2012-11-24 19:36:33 +00:00
QTest : : newRow ( " QtQml 2.0 module provides Component, QtObject, Connections, Binding and Timer " )
2012-05-23 08:05:10 +00:00
< < testFileUrl ( " qtqmlModule.5.qml " )
< < QString ( )
< < QStringList ( ) ;
QTest : : newRow ( " can import QtQml then QtQuick " )
< < testFileUrl ( " qtqmlModule.6.qml " )
< < QString ( )
< < QStringList ( ) ;
QTest : : newRow ( " can import QtQuick then QtQml " )
< < testFileUrl ( " qtqmlModule.7.qml " )
< < QString ( )
< < QStringList ( ) ;
QTest : : newRow ( " no import results in no QtObject availability " )
< < testFileUrl ( " qtqmlModule.8.qml " )
< < QString ( testFileUrl ( " qtqmlModule.8.qml " ) . toString ( ) + QLatin1String ( " :4 QtObject is not a type \n " ) )
< < QStringList ( ) ;
QTest : : newRow ( " importing QtQml only results in no Item availability " )
< < testFileUrl ( " qtqmlModule.9.qml " )
< < QString ( testFileUrl ( " qtqmlModule.9.qml " ) . toString ( ) + QLatin1String ( " :4 Item is not a type \n " ) )
< < QStringList ( ) ;
2020-01-17 09:46:54 +00:00
QTest : : newRow ( " import QtQml of incorrect version (6.50) " )
< < testFileUrl ( " qtqmlModule.10.qml " )
< < QString ( testFileUrl ( " qtqmlModule.10.qml " ) . toString ( ) + QLatin1String ( " :1 module \" QtQml \" version 6.50 is not installed \n " ) )
< < QStringList ( ) ;
2012-05-23 08:05:10 +00:00
}
// Test that the engine registers the QtQml module
void tst_qqmlengine : : qtqmlModule ( )
{
QFETCH ( QUrl , testFile ) ;
QFETCH ( QString , expectedError ) ;
QFETCH ( QStringList , expectedWarnings ) ;
2023-08-07 08:20:42 +00:00
for ( const QString & w : std : : as_const ( expectedWarnings ) )
2012-05-23 08:05:10 +00:00
QTest : : ignoreMessage ( QtWarningMsg , qPrintable ( w ) ) ;
QQmlEngine e ;
QQmlComponent c ( & e , testFile ) ;
if ( expectedError . isEmpty ( ) ) {
2022-08-03 10:20:26 +00:00
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o ( c . create ( ) ) ;
2012-05-23 08:05:10 +00:00
QVERIFY ( o ) ;
} else {
QCOMPARE ( c . errorString ( ) , expectedError ) ;
}
}
2013-03-01 01:03:43 +00:00
class CustomSelector : public QQmlAbstractUrlInterceptor
{
public :
2013-07-18 13:02:37 +00:00
CustomSelector ( const QUrl & base ) : m_base ( base ) { }
2021-03-25 13:44:04 +00:00
QUrl intercept ( const QUrl & url , QQmlAbstractUrlInterceptor : : DataType d ) override
2013-03-01 01:03:43 +00:00
{
2025-05-12 14:31:06 +00:00
QString path = url . path ( ) ;
2019-07-03 15:25:55 +00:00
if ( ( url . scheme ( ) ! = QStringLiteral ( " file " ) & & url . scheme ( ) ! = QStringLiteral ( " qrc " ) )
2025-05-12 14:31:06 +00:00
| | path . contains ( " QtQml " ) ) {
2013-03-01 01:03:43 +00:00
return url ;
2025-05-12 14:31:06 +00:00
}
2013-03-01 01:03:43 +00:00
if ( ! m_interceptionPoints . contains ( d ) )
return url ;
2025-05-12 14:31:06 +00:00
if ( path . endsWith ( " Test.2/qmldir " ) ) {
// Special case
2019-07-03 15:25:55 +00:00
QUrl url = m_base ;
url . setPath ( m_base . path ( ) + " interception/module/intercepted/qmldir " ) ;
return url ;
}
2025-05-12 14:31:06 +00:00
qsizetype lastSlash = path . lastIndexOf ( ' / ' ) ;
if ( lastSlash < 0 )
lastSlash = 0 ;
if ( QStringView ( path ) . mid ( lastSlash ) = = u " /included.js " ) {
// Special case: We want this one to be double-intercepted
} else if ( QStringView ( path ) . left ( lastSlash ) . endsWith ( u " /intercepted " ) ) {
// Already intercepted. Don't do it again.
// In real applications this routinely happens when new URLs are constructed relative to
// components loaded from intercepted URLs. Any sensible URL interceptor has to filter
// for this.
2017-04-20 13:59:31 +00:00
return url ;
2025-05-12 14:31:06 +00:00
}
2013-07-18 13:02:37 +00:00
2025-05-12 14:31:06 +00:00
path . insert ( lastSlash , QStringLiteral ( " /intercepted " ) ) ;
2013-03-01 01:03:43 +00:00
QUrl ret = url ;
2025-05-12 14:31:06 +00:00
ret . setPath ( path ) ;
2013-03-01 01:03:43 +00:00
return ret ;
}
QList < QQmlAbstractUrlInterceptor : : DataType > m_interceptionPoints ;
2013-07-18 13:02:37 +00:00
QUrl m_base ;
2013-03-01 01:03:43 +00:00
} ;
Q_DECLARE_METATYPE ( QList < QQmlAbstractUrlInterceptor : : DataType > ) ;
2019-10-04 11:49:18 +00:00
2013-03-01 01:03:43 +00:00
void tst_qqmlengine : : urlInterceptor_data ( )
{
QTest : : addColumn < QUrl > ( " testFile " ) ;
QTest : : addColumn < QList < QQmlAbstractUrlInterceptor : : DataType > > ( " interceptionPoint " ) ;
QTest : : addColumn < QString > ( " expectedChildString " ) ;
QTest : : addColumn < QString > ( " expectedScriptString " ) ;
QTest : : addColumn < QString > ( " expectedResolvedUrl " ) ;
QTest : : addColumn < QString > ( " expectedAbsoluteUrl " ) ;
QTest : : newRow ( " InterceptTypes " )
< < testFileUrl ( " interception/types/urlInterceptor.qml " )
< < ( QList < QQmlAbstractUrlInterceptor : : DataType > ( ) < < QQmlAbstractUrlInterceptor : : QmlFile < < QQmlAbstractUrlInterceptor : : JavaScriptFile < < QQmlAbstractUrlInterceptor : : UrlString )
< < QStringLiteral ( " intercepted " )
< < QStringLiteral ( " intercepted " )
< < testFileUrl ( " interception/types/intercepted/doesNotExist.file " ) . toString ( )
< < QStringLiteral ( " file:///intercepted/doesNotExist.file " ) ;
QTest : : newRow ( " InterceptQmlDir " )
< < testFileUrl ( " interception/qmldir/urlInterceptor.qml " )
< < ( QList < QQmlAbstractUrlInterceptor : : DataType > ( ) < < QQmlAbstractUrlInterceptor : : QmldirFile < < QQmlAbstractUrlInterceptor : : UrlString )
< < QStringLiteral ( " intercepted " )
< < QStringLiteral ( " base file " )
< < testFileUrl ( " interception/qmldir/intercepted/doesNotExist.file " ) . toString ( )
< < QStringLiteral ( " file:///intercepted/doesNotExist.file " ) ;
2013-07-18 13:02:37 +00:00
QTest : : newRow ( " InterceptModule " ) //just a Test{}, needs to intercept the module import for it to work
< < testFileUrl ( " interception/module/urlInterceptor.qml " )
< < ( QList < QQmlAbstractUrlInterceptor : : DataType > ( ) < < QQmlAbstractUrlInterceptor : : QmldirFile )
< < QStringLiteral ( " intercepted " )
< < QStringLiteral ( " intercepted " )
< < testFileUrl ( " interception/module/intercepted/doesNotExist.file " ) . toString ( )
< < QStringLiteral ( " file:///doesNotExist.file " ) ;
2013-03-01 01:03:43 +00:00
QTest : : newRow ( " InterceptStrings " )
< < testFileUrl ( " interception/strings/urlInterceptor.qml " )
< < ( QList < QQmlAbstractUrlInterceptor : : DataType > ( ) < < QQmlAbstractUrlInterceptor : : UrlString )
< < QStringLiteral ( " base file " )
< < QStringLiteral ( " base file " )
< < testFileUrl ( " interception/strings/intercepted/doesNotExist.file " ) . toString ( )
< < QStringLiteral ( " file:///intercepted/doesNotExist.file " ) ;
2015-10-12 14:24:59 +00:00
QTest : : newRow ( " InterceptIncludes " )
< < testFileUrl ( " interception/includes/urlInterceptor.qml " )
< < ( QList < QQmlAbstractUrlInterceptor : : DataType > ( ) < < QQmlAbstractUrlInterceptor : : JavaScriptFile )
< < QStringLiteral ( " base file " )
< < QStringLiteral ( " intercepted include file " )
< < testFileUrl ( " interception/includes/doesNotExist.file " ) . toString ( )
< < QStringLiteral ( " file:///doesNotExist.file " ) ;
2013-03-01 01:03:43 +00:00
}
void tst_qqmlengine : : urlInterceptor ( )
{
QFETCH ( QUrl , testFile ) ;
QFETCH ( QList < QQmlAbstractUrlInterceptor : : DataType > , interceptionPoint ) ;
QFETCH ( QString , expectedChildString ) ;
QFETCH ( QString , expectedScriptString ) ;
QFETCH ( QString , expectedResolvedUrl ) ;
QFETCH ( QString , expectedAbsoluteUrl ) ;
QQmlEngine e ;
2019-07-03 15:25:55 +00:00
e . addImportPath ( testFileUrl ( " interception/imports " ) . url ( ) ) ;
2013-07-18 13:02:37 +00:00
CustomSelector cs ( testFileUrl ( " " ) ) ;
2013-03-01 01:03:43 +00:00
cs . m_interceptionPoints = interceptionPoint ;
2020-02-07 15:22:53 +00:00
e . addUrlInterceptor ( & cs ) ;
2013-03-01 01:03:43 +00:00
QQmlComponent c ( & e , testFile ) ; //Note that this can get intercepted too
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o ( c . create ( ) ) ;
2013-03-01 01:03:43 +00:00
if ( ! o )
qDebug ( ) < < c . errorString ( ) ;
QVERIFY ( o ) ;
//Test a URL as a property initialization
2020-06-15 15:53:16 +00:00
QCOMPARE ( o - > property ( " filePath " ) . toString ( ) , QUrl ( " doesNotExist.file " ) . toString ( ) ) ;
2013-03-01 01:03:43 +00:00
//Test a URL as a Type location
QCOMPARE ( o - > property ( " childString " ) . toString ( ) , expectedChildString ) ;
//Test a URL as a Script location
QCOMPARE ( o - > property ( " scriptString " ) . toString ( ) , expectedScriptString ) ;
//Test a URL as a resolveUrl() call
QCOMPARE ( o - > property ( " resolvedUrl " ) . toString ( ) , expectedResolvedUrl ) ;
QCOMPARE ( o - > property ( " absoluteUrl " ) . toString ( ) , expectedAbsoluteUrl ) ;
}
2015-10-27 11:21:00 +00:00
void tst_qqmlengine : : qmlContextProperties ( )
{
QQmlEngine e ;
QQmlComponent c ( & e , testFileUrl ( " TypeofQmlProperty.qml " ) ) ;
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o ( c . create ( ) ) ;
2015-10-27 11:21:00 +00:00
if ( ! o ) {
qDebug ( ) < < c . errorString ( ) ;
}
QVERIFY ( o ) ;
}
2017-04-21 10:29:22 +00:00
void tst_qqmlengine : : testGCCorruption ( )
{
QQmlEngine e ;
QQmlComponent c ( & e , testFileUrl ( " testGCCorruption.qml " ) ) ;
2020-06-08 14:30:18 +00:00
QScopedPointer < QObject > o ( c . create ( ) ) ;
2017-04-21 10:29:22 +00:00
QVERIFY2 ( o , qPrintable ( c . errorString ( ) ) ) ;
}
2017-09-14 12:38:36 +00:00
void tst_qqmlengine : : testGroupedPropertyRevisions ( )
{
QQmlEngine e ;
QQmlComponent c ( & e , testFileUrl ( " testGroupedPropertiesRevision.1.qml " ) ) ;
QScopedPointer < QObject > object ( c . create ( ) ) ;
QVERIFY2 ( object . data ( ) , qPrintable ( c . errorString ( ) ) ) ;
QQmlComponent c2 ( & e , testFileUrl ( " testGroupedPropertiesRevision.2.qml " ) ) ;
QVERIFY ( ! c2 . errorString ( ) . isEmpty ( ) ) ;
}
2018-03-02 08:09:17 +00:00
void tst_qqmlengine : : componentFromEval ( )
{
QQmlEngine engine ;
const QUrl testUrl = testFileUrl ( " EmptyComponent.qml " ) ;
QJSValue result = engine . evaluate ( " Qt.createComponent( \" " + testUrl . toString ( ) + " \" ); " ) ;
QPointer < QQmlComponent > component ( qobject_cast < QQmlComponent * > ( result . toQObject ( ) ) ) ;
QVERIFY ( ! component . isNull ( ) ) ;
QScopedPointer < QObject > item ( component - > create ( ) ) ;
QVERIFY ( ! item . isNull ( ) ) ;
}
2018-04-24 09:35:43 +00:00
void tst_qqmlengine : : qrcUrls ( )
{
QQmlEngine engine ;
2025-06-03 12:35:29 +00:00
QQmlTypeLoader * typeLoader = QQmlTypeLoader : : get ( & engine ) ;
2018-04-24 09:35:43 +00:00
{
2025-06-03 12:35:29 +00:00
QQmlRefPointer < QQmlTypeData > oneQml ( typeLoader - > getType ( QUrl ( " qrc:/qrcurls.qml " ) ) ) ;
2018-05-04 07:28:23 +00:00
QVERIFY ( oneQml . data ( ) ! = nullptr ) ;
2022-06-13 13:28:39 +00:00
QVERIFY ( ! oneQml - > backupSourceCode ( ) . isValid ( ) ) ;
2025-06-03 12:35:29 +00:00
QQmlRefPointer < QQmlTypeData > twoQml ( typeLoader - > getType ( QUrl ( " qrc:///qrcurls.qml " ) ) ) ;
2018-05-04 07:28:23 +00:00
QVERIFY ( twoQml . data ( ) ! = nullptr ) ;
QCOMPARE ( oneQml . data ( ) , twoQml . data ( ) ) ;
2018-04-24 09:35:43 +00:00
}
{
2025-06-03 12:35:29 +00:00
QQmlRefPointer < QQmlTypeData > oneJS ( typeLoader - > getType ( QUrl ( " qrc:/qrcurls.js " ) ) ) ;
2018-05-04 07:28:23 +00:00
QVERIFY ( oneJS . data ( ) ! = nullptr ) ;
2022-06-13 13:28:39 +00:00
QVERIFY ( ! oneJS - > backupSourceCode ( ) . isValid ( ) ) ;
2025-06-03 12:35:29 +00:00
QQmlRefPointer < QQmlTypeData > twoJS ( typeLoader - > getType ( QUrl ( " qrc:///qrcurls.js " ) ) ) ;
2018-05-04 07:28:23 +00:00
QVERIFY ( twoJS . data ( ) ! = nullptr ) ;
QCOMPARE ( oneJS . data ( ) , twoJS . data ( ) ) ;
2018-04-24 09:35:43 +00:00
}
}
2018-05-29 12:27:46 +00:00
void tst_qqmlengine : : cppSignalAndEval ( )
{
ObjectCaller objectCaller ;
QQmlEngine engine ;
2019-10-04 11:49:18 +00:00
qmlRegisterSingletonInstance ( " Test " , 1 , 0 , " CallerCpp " , & objectCaller ) ;
2018-05-29 12:27:46 +00:00
QQmlComponent c ( & engine ) ;
c . setData ( " import QtQuick 2.9 \n "
2019-10-04 11:49:18 +00:00
" import Test 1.0 \n "
2018-05-29 12:27:46 +00:00
" Item { \n "
" property var r: 0 \n "
" Connections { \n "
" target: CallerCpp; \n "
2019-10-04 11:49:18 +00:00
" function onDoubleReply() { \n "
2018-05-29 12:27:46 +00:00
" eval('var z = 1'); \n "
" r = a; \n "
" } \n "
" } \n "
" } " ,
QUrl ( QStringLiteral ( " qrc:/main.qml " ) ) ) ;
QScopedPointer < QObject > object ( c . create ( ) ) ;
QVERIFY ( ! object . isNull ( ) ) ;
emit objectCaller . doubleReply ( 1.1234 ) ;
QCOMPARE ( object - > property ( " r " ) , 1.1234 ) ;
}
2018-05-31 19:41:47 +00:00
class SomeQObjectClass : public QObject {
Q_OBJECT
public :
SomeQObjectClass ( ) : QObject ( nullptr ) { }
} ;
2020-01-27 12:55:14 +00:00
class Dayfly : public QObject
{
Q_OBJECT
} ;
2018-05-31 19:41:47 +00:00
void tst_qqmlengine : : singletonInstance ( )
{
QQmlEngine engine ;
int cppSingletonTypeId = qmlRegisterSingletonType < CppSingleton > ( " Test " , 1 , 0 , " CppSingleton " , & CppSingleton : : create ) ;
int jsValueSingletonTypeId = qmlRegisterSingletonType ( " Test " , 1 , 0 , " JsSingleton " , & JsSingleton : : create ) ;
{
// Cpp QObject singleton type
QJSValue value = engine . singletonInstance < QJSValue > ( cppSingletonTypeId ) ;
QVERIFY ( ! value . isUndefined ( ) ) ;
QVERIFY ( value . isQObject ( ) ) ;
QObject * instance = value . toQObject ( ) ;
QVERIFY ( instance ) ;
QCOMPARE ( instance - > metaObject ( ) - > className ( ) , " CppSingleton " ) ;
2022-12-06 12:28:48 +00:00
QCOMPARE ( engine . singletonInstance < CppSingleton * > ( " Test " , " CppSingleton " ) , instance ) ;
2018-05-31 19:41:47 +00:00
}
{
// QJSValue QObject singleton type
QJSValue value = engine . singletonInstance < QJSValue > ( jsValueSingletonTypeId ) ;
QVERIFY ( ! value . isUndefined ( ) ) ;
QVERIFY ( value . isQObject ( ) ) ;
QObject * instance = value . toQObject ( ) ;
QVERIFY ( instance ) ;
QCOMPARE ( instance - > metaObject ( ) - > className ( ) , " JsSingleton " ) ;
}
2019-06-06 07:55:20 +00:00
{
int data = 30 ;
auto id = qmlRegisterSingletonType < CppSingleton > ( " Qt.test " , 1 , 0 , " CapturingLambda " , [ data ] ( QQmlEngine * , QJSEngine * ) { // register qobject singleton with capturing lambda
auto o = new CppSingleton ;
o - > setProperty ( " data " , data ) ;
return o ;
} ) ;
QJSValue value = engine . singletonInstance < QJSValue > ( id ) ;
QVERIFY ( ! value . isUndefined ( ) ) ;
QVERIFY ( value . isQObject ( ) ) ;
QObject * instance = value . toQObject ( ) ;
QVERIFY ( instance ) ;
QCOMPARE ( instance - > metaObject ( ) - > className ( ) , " CppSingleton " ) ;
QCOMPARE ( instance - > property ( " data " ) , data ) ;
}
{
qmlRegisterSingletonType < CppSingleton > ( " Qt.test " , 1 , 0 , " NotAmbiguous " , [ ] ( QQmlEngine * qeng , QJSEngine * jeng ) - > QObject * { return CppSingleton : : create ( qeng , jeng ) ; } ) ; // test that overloads for qmlRegisterSingleton are not ambiguous
}
2019-06-14 14:54:14 +00:00
{
// Register QObject* directly
CppSingleton single ;
int id = qmlRegisterSingletonInstance ( " Qt.test " , 1 , 0 , " CppOwned " ,
& single ) ;
QQmlEngine engine2 ;
CppSingleton * singlePtr = engine2 . singletonInstance < CppSingleton * > ( id ) ;
QVERIFY ( singlePtr ) ;
QCOMPARE ( & single , singlePtr ) ;
QVERIFY ( engine2 . objectOwnership ( singlePtr ) = = QQmlEngine : : CppOwnership ) ;
}
{
CppSingleton single ;
QQmlEngine engineA ;
QQmlEngine engineB ;
int id = qmlRegisterSingletonInstance ( " Qt.test " , 1 , 0 , " CppOwned " , & single ) ;
auto singlePtr = engineA . singletonInstance < CppSingleton * > ( id ) ;
QVERIFY ( singlePtr ) ;
singlePtr = engineA . singletonInstance < CppSingleton * > ( id ) ; // accessing the singleton multiple times from the same engine is fine
QVERIFY ( singlePtr ) ;
QTest : : ignoreMessage ( QtMsgType : : QtCriticalMsg , " <Unknown File>: qmlRegisterSingletonType(): \" CppOwned \" is not available because the callback function returns a null pointer. " ) ;
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , " <Unknown File>: Singleton registered by registerSingletonInstance must only be accessed from one engine " ) ;
QCOMPARE ( & single , singlePtr ) ;
auto noSinglePtr = engineB . singletonInstance < CppSingleton * > ( id ) ;
QVERIFY ( ! noSinglePtr ) ;
}
{
CppSingleton single ;
QThread newThread { } ;
single . moveToThread ( & newThread ) ;
QCOMPARE ( single . thread ( ) , & newThread ) ;
QQmlEngine engineB ;
int id = qmlRegisterSingletonInstance ( " Qt.test " , 1 , 0 , " CppOwned " , & single ) ;
QTest : : ignoreMessage ( QtMsgType : : QtCriticalMsg , " <Unknown File>: qmlRegisterSingletonType(): \" CppOwned \" is not available because the callback function returns a null pointer. " ) ;
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , " <Unknown File>: Registered object must live in the same thread as the engine it was registered with " ) ;
auto noSinglePtr = engineB . singletonInstance < CppSingleton * > ( id ) ;
QVERIFY ( ! noSinglePtr ) ;
}
2019-06-06 07:55:20 +00:00
2022-12-06 12:28:48 +00:00
// test the case where we haven't loaded the module yet
{
auto singleton = engine . singletonInstance < PurelyDeclarativeSingleton * > ( " OnlyDeclarative " , " PurelyDeclarativeSingleton " ) ;
QVERIFY ( singleton ) ;
// requesting the singleton twice yields the same result
auto again = engine . singletonInstance < PurelyDeclarativeSingleton * > ( " OnlyDeclarative " , " PurelyDeclarativeSingleton " ) ;
QCOMPARE ( again , singleton ) ;
// different engines -> different singletons
QQmlEngine engine2 ;
auto differentEngine = engine2 . singletonInstance < PurelyDeclarativeSingleton * > ( " OnlyDeclarative " , " PurelyDeclarativeSingleton " ) ;
QCOMPARE_NE ( differentEngine , singleton ) ;
}
2018-05-31 19:41:47 +00:00
{
// Invalid types
QJSValue value ;
value = engine . singletonInstance < QJSValue > ( - 4711 ) ;
QVERIFY ( value . isUndefined ( ) ) ;
value = engine . singletonInstance < QJSValue > ( 1701 ) ;
QVERIFY ( value . isUndefined ( ) ) ;
}
{
// Valid, but non-singleton type
int typeId = qmlRegisterType < CppSingleton > ( " Test " , 1 , 0 , " NotASingleton " ) ;
QJSValue value = engine . singletonInstance < QJSValue > ( typeId ) ;
QVERIFY ( value . isUndefined ( ) ) ;
}
{
// Cpp QObject singleton type
CppSingleton * instance = engine . singletonInstance < CppSingleton * > ( cppSingletonTypeId ) ;
QVERIFY ( instance ) ;
QCOMPARE ( instance - > metaObject ( ) - > className ( ) , " CppSingleton " ) ;
QCOMPARE ( instance , engine . singletonInstance < QJSValue > ( cppSingletonTypeId ) . toQObject ( ) ) ;
}
{
// Wrong destination type
SomeQObjectClass * instance = engine . singletonInstance < SomeQObjectClass * > ( cppSingletonTypeId ) ;
QVERIFY ( ! instance ) ;
}
2019-10-04 14:20:29 +00:00
{
// deleted object
2023-07-28 12:57:16 +00:00
auto dayfly = std : : make_unique < Dayfly > ( ) ;
auto id = qmlRegisterSingletonInstance ( " Vanity " , 1 , 0 , " Dayfly " , dayfly . get ( ) ) ;
dayfly . reset ( ) ;
2019-10-04 14:20:29 +00:00
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , " <Unknown File>: The registered singleton has already been deleted. Ensure that it outlives the engine. " ) ;
QObject * instance = engine . singletonInstance < QObject * > ( id ) ;
QVERIFY ( ! instance ) ;
}
2018-05-31 19:41:47 +00:00
}
2019-03-26 12:57:33 +00:00
void tst_qqmlengine : : aggressiveGc ( )
{
const QByteArray origAggressiveGc = qgetenv ( " QV4_MM_AGGRESSIVE_GC " ) ;
qputenv ( " QV4_MM_AGGRESSIVE_GC " , " true " ) ;
{
QQmlEngine engine ; // freezing should not run into infinite recursion
QJSValue obj = engine . newObject ( ) ;
QVERIFY ( obj . isObject ( ) ) ;
}
qputenv ( " QV4_MM_AGGRESSIVE_GC " , origAggressiveGc ) ;
}
2019-05-09 10:38:22 +00:00
void tst_qqmlengine : : cachedGetterLookup_qtbug_75335 ( )
{
QQmlEngine engine ;
const QUrl testUrl = testFileUrl ( " CachedGetterLookup.qml " ) ;
QQmlComponent component ( & engine , testUrl ) ;
QVERIFY ( component . isReady ( ) ) ;
QScopedPointer < QObject > object ( component . create ( ) ) ;
QVERIFY ( object ! = nullptr ) ;
}
2020-01-10 09:01:36 +00:00
class EvilSingleton : public QObject
{
Q_OBJECT
public :
QPointer < QQmlEngine > m_engine ;
EvilSingleton ( QQmlEngine * engine ) : m_engine ( engine ) {
connect ( this , & QObject : : destroyed , this , [ this ] ( ) {
QQmlComponent component ( m_engine ) ;
component . setData ( " import QtQml 2.0 \n QtObject {} " , QUrl ( " file://Stuff.qml " ) ) ;
QVERIFY ( component . isReady ( ) ) ;
QScopedPointer < QObject > obj ( component . create ( ) ) ;
QVERIFY ( obj ) ;
} ) ;
}
} ;
void tst_qqmlengine : : createComponentOnSingletonDestruction ( )
{
qmlRegisterSingletonType < EvilSingleton > ( " foo.foo " , 1 , 0 , " Singleton " ,
[ ] ( QQmlEngine * engine , QJSEngine * ) {
return new EvilSingleton ( engine ) ;
} ) ;
QQmlEngine engine ;
QQmlComponent component ( & engine , testFileUrl ( " evilSingletonInstantiation.qml " ) ) ;
QVERIFY ( component . isReady ( ) ) ;
QScopedPointer < QObject > obj ( component . create ( ) ) ;
QVERIFY ( obj ) ;
}
2020-01-22 12:37:48 +00:00
void tst_qqmlengine : : uiLanguage ( )
{
2024-08-22 04:58:01 +00:00
const QRegularExpression bindingLoopWarningRegex ( " .*QML QtObject: Binding loop detected for property \" textToTranslate \" .* " ) ;
2020-10-23 07:50:53 +00:00
{
QQmlEngine engine ;
2020-01-22 12:37:48 +00:00
2020-10-23 07:50:53 +00:00
QObject : : connect ( & engine , & QJSEngine : : uiLanguageChanged , [ & engine ] ( ) {
engine . retranslate ( ) ;
} ) ;
2020-01-22 12:37:48 +00:00
2020-10-23 07:50:53 +00:00
QSignalSpy uiLanguageChangeSpy ( & engine , SIGNAL ( uiLanguageChanged ( ) ) ) ;
2020-01-22 12:37:48 +00:00
2020-10-23 07:50:53 +00:00
QQmlComponent component ( & engine , testFileUrl ( " uiLanguage.qml " ) ) ;
2020-01-22 12:37:48 +00:00
2024-08-22 04:58:01 +00:00
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , bindingLoopWarningRegex ) ;
2020-10-23 07:50:53 +00:00
QScopedPointer < QObject > object ( component . create ( ) ) ;
QVERIFY ( ! object . isNull ( ) ) ;
2020-01-22 12:37:48 +00:00
2020-10-23 07:50:53 +00:00
QVERIFY ( engine . uiLanguage ( ) . isEmpty ( ) ) ;
QCOMPARE ( object - > property ( " numberOfTranslationBindingEvaluations " ) . toInt ( ) , 1 ) ;
2020-01-22 12:37:48 +00:00
2024-08-22 04:58:01 +00:00
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , bindingLoopWarningRegex ) ;
2020-10-23 07:50:53 +00:00
engine . setUiLanguage ( " TestLanguage " ) ;
QCOMPARE ( object - > property ( " numberOfTranslationBindingEvaluations " ) . toInt ( ) , 2 ) ;
QCOMPARE ( object - > property ( " chosenLanguage " ) . toString ( ) , " TestLanguage " ) ;
2020-01-22 12:37:48 +00:00
2024-08-22 04:58:01 +00:00
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , bindingLoopWarningRegex ) ;
2020-10-23 07:50:53 +00:00
engine . evaluate ( " Qt.uiLanguage = \" anotherLanguage \" " ) ;
QCOMPARE ( engine . uiLanguage ( ) , QString ( " anotherLanguage " ) ) ;
QCOMPARE ( object - > property ( " numberOfTranslationBindingEvaluations " ) . toInt ( ) , 3 ) ;
QCOMPARE ( object - > property ( " chosenLanguage " ) . toString ( ) , " anotherLanguage " ) ;
}
{
QQmlEngine engine ;
QQmlComponent component ( & engine , testFileUrl ( " uiLanguage.qml " ) ) ;
2020-01-22 12:37:48 +00:00
2024-08-22 04:58:01 +00:00
QTest : : ignoreMessage ( QtMsgType : : QtWarningMsg , bindingLoopWarningRegex ) ;
2020-10-23 07:50:53 +00:00
QScopedPointer < QObject > object ( component . create ( ) ) ;
QVERIFY ( ! object . isNull ( ) ) ;
engine . setUiLanguage ( " TestLanguage " ) ;
QCOMPARE ( object - > property ( " chosenLanguage " ) . toString ( ) , " TestLanguage " ) ;
}
2020-01-22 12:37:48 +00:00
}
2023-04-19 12:57:13 +00:00
class I18nAwareClass : public QObject {
Q_OBJECT
QML_NAMED_ELEMENT ( I18nAware )
Q_PROPERTY ( QString text READ text NOTIFY textChanged )
signals :
void textChanged ( ) ;
public :
int counter = 0 ;
QString text ( )
{
if ( auto engine = qmlEngine ( this ) )
engine - > markCurrentFunctionAsTranslationBinding ( ) ;
return QLatin1String ( " Hello, %1 " ) . arg ( QString : : number ( counter + + ) ) ;
}
} ;
void tst_qqmlengine : : markCurrentFunctionAsTranslationBinding ( )
{
QQmlEngine engine ;
qmlRegisterTypesAndRevisions < I18nAwareClass > ( " i18ntest " , 1 ) ;
QQmlComponent comp ( & engine , testFileUrl ( " markCurrentFunctionAsTranslationBinding.qml " ) ) ;
std : : unique_ptr < QObject > root { comp . create ( ) } ;
QCOMPARE ( root - > property ( " result " ) , " Hello, 0 " ) ;
engine . retranslate ( ) ;
QCOMPARE ( root - > property ( " result " ) , " Hello, 1 " ) ;
}
2021-02-08 14:42:59 +00:00
void tst_qqmlengine : : executeRuntimeFunction ( )
{
QQmlEngine engine ;
QQmlEnginePrivate * priv = QQmlEnginePrivate : : get ( std : : addressof ( engine ) ) ;
const QUrl url = testFileUrl ( " runtimeFunctions.qml " ) ;
QQmlComponent component ( & engine , url ) ;
2022-04-08 13:49:24 +00:00
QVERIFY2 ( component . isReady ( ) , qPrintable ( component . errorString ( ) ) ) ;
2021-02-08 14:42:59 +00:00
QScopedPointer < QObject > dummy ( component . create ( ) ) ;
2022-04-08 13:49:24 +00:00
QVERIFY ( dummy ) ;
2021-02-08 14:42:59 +00:00
// getConstantValue():
2021-05-03 09:04:39 +00:00
int constant = 0 ;
void * a0 [ ] = { const_cast < void * > ( reinterpret_cast < const void * > ( std : : addressof ( constant ) ) ) } ;
QMetaType t0 [ ] = { QMetaType : : fromType < int > ( ) } ;
priv - > executeRuntimeFunction ( url , /* index = */ 0 , dummy . get ( ) , /* argc = */ 0 , a0 , t0 ) ;
2021-02-08 14:42:59 +00:00
QCOMPARE ( constant , 42 ) ;
// squareValue():
2021-05-03 09:04:39 +00:00
int squared = 0 ;
2021-02-08 14:42:59 +00:00
int x = 5 ;
2021-05-03 09:04:39 +00:00
void * a1 [ ] = { const_cast < void * > ( reinterpret_cast < const void * > ( std : : addressof ( squared ) ) ) ,
const_cast < void * > ( reinterpret_cast < const void * > ( std : : addressof ( x ) ) ) } ;
QMetaType t1 [ ] = { QMetaType : : fromType < int > ( ) , QMetaType : : fromType < int > ( ) } ;
priv - > executeRuntimeFunction ( url , /* index = */ 1 , dummy . get ( ) , /* argc = */ 1 , a1 , t1 ) ;
2021-02-08 14:42:59 +00:00
QCOMPARE ( squared , x * x ) ;
// concatenate():
2021-05-03 09:04:39 +00:00
QString concatenated ;
2021-02-08 14:42:59 +00:00
QString str1 = QStringLiteral ( " Hello " ) ; // uses "raw data" storage
QString str2 = QLatin1String ( " , Qml " ) ; // uses own QString storage
2021-05-03 09:04:39 +00:00
void * a2 [ ] = { const_cast < void * > ( reinterpret_cast < const void * > ( std : : addressof ( concatenated ) ) ) ,
2021-02-08 14:42:59 +00:00
const_cast < void * > ( reinterpret_cast < const void * > ( std : : addressof ( str1 ) ) ) ,
const_cast < void * > ( reinterpret_cast < const void * > ( std : : addressof ( str2 ) ) ) } ;
2021-05-03 09:04:39 +00:00
QMetaType t2 [ ] = { QMetaType : : fromType < QString > ( ) , QMetaType : : fromType < QString > ( ) ,
QMetaType : : fromType < QString > ( ) } ;
priv - > executeRuntimeFunction ( url , /* index = */ 2 , dummy . get ( ) , /* argc = */ 2 , a2 , t2 ) ;
2021-02-08 14:42:59 +00:00
QCOMPARE ( concatenated , str1 + str2 ) ;
2022-04-08 13:49:24 +00:00
// capture `this`:
QCOMPARE ( dummy - > property ( " foo " ) . toInt ( ) , 42 ) ;
QCOMPARE ( dummy - > property ( " bar " ) . toInt ( ) , 0 ) ;
priv - > executeRuntimeFunction ( url , /* index = */ 4 , dummy . get ( ) ) ;
QCOMPARE ( dummy - > property ( " bar " ) . toInt ( ) , 1 + 42 + 1 ) ;
QCOMPARE ( dummy - > property ( " baz " ) . toInt ( ) , - 100 ) ;
int y = 1 ;
void * a3 [ ] = { nullptr , const_cast < void * > ( reinterpret_cast < const void * > ( & y ) ) } ;
QMetaType t3 [ ] = { QMetaType : : fromType < void > ( ) , QMetaType : : fromType < int > ( ) } ;
priv - > executeRuntimeFunction ( url , /* index = */ 6 , dummy . get ( ) , 1 , a3 , t3 ) ;
QCOMPARE ( dummy - > property ( " bar " ) . toInt ( ) , - 98 ) ;
QCOMPARE ( dummy - > property ( " baz " ) . toInt ( ) , - 100 ) ;
2021-02-08 14:42:59 +00:00
}
2021-02-26 08:01:25 +00:00
class WithQProperty : public QObject
{
Q_OBJECT
Q_PROPERTY ( int foo READ foo WRITE setFoo BINDABLE fooBindable )
public :
WithQProperty ( QObject * parent = nullptr ) : QObject ( parent ) { m_foo . setValue ( 12 ) ; }
int foo ( ) const { return m_foo . value ( ) ; }
void setFoo ( int foo ) { m_foo . setValue ( foo ) ; }
QBindable < int > fooBindable ( ) { return QBindable < int > ( & m_foo ) ; }
int getFooWithCapture ( )
{
const QMetaObject * m = metaObject ( ) ;
currentEngine - > captureProperty ( this , m - > property ( m - > indexOfProperty ( " foo " ) ) ) ;
return m_foo . value ( ) ;
}
static QQmlEngine * currentEngine ;
private :
QProperty < int > m_foo ;
} ;
class WithoutQProperty : public QObject
{
Q_OBJECT
Q_PROPERTY ( int foo READ foo WRITE setFoo NOTIFY fooChanged )
public :
WithoutQProperty ( QObject * parent = nullptr ) : QObject ( parent ) , m_foo ( new WithQProperty ( this ) ) { }
int foo ( ) const { return m_foo - > getFooWithCapture ( ) ; }
void setFoo ( int foo ) {
if ( foo ! = m_foo - > foo ( ) ) {
m_foo - > setFoo ( foo ) ;
emit fooChanged ( ) ;
}
}
void triggerBinding ( int val )
{
m_foo - > setFoo ( val ) ;
}
signals :
void fooChanged ( ) ;
private :
WithQProperty * m_foo ;
} ;
QQmlEngine * WithQProperty : : currentEngine = nullptr ;
void tst_qqmlengine : : captureQProperty ( )
{
qmlRegisterType < WithoutQProperty > ( " Foo " , 1 , 0 , " WithoutQProperty " ) ;
QQmlEngine engine ;
WithQProperty : : currentEngine = & engine ;
QQmlComponent c ( & engine ) ;
c . setData ( " import Foo \n "
" WithoutQProperty { \n "
" property int x: foo \n "
" } " , QUrl ( ) ) ;
QVERIFY2 ( c . isReady ( ) , c . errorString ( ) . toUtf8 ( ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QCOMPARE ( o - > property ( " x " ) . toInt ( ) , 12 ) ;
static_cast < WithoutQProperty * > ( o . data ( ) ) - > triggerBinding ( 13 ) ;
QCOMPARE ( o - > property ( " x " ) . toInt ( ) , 13 ) ;
}
2021-03-17 14:08:22 +00:00
void tst_qqmlengine : : listWrapperAsListReference ( )
{
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
c . setData ( " import QtQml \n QtObject { \n property list<QtObject> c: [ QtObject {} ] \n } " , QUrl ( ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QJSManagedValue m = engine . toManagedValue ( o . data ( ) ) ;
QJSValue prop = m . property ( " c " ) ;
const QQmlListReference ref = qjsvalue_cast < QQmlListReference > ( prop ) ;
QCOMPARE ( ref . size ( ) , 1 ) ;
}
void tst_qqmlengine : : attachedObjectAsObject ( )
{
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
c . setData ( " import QtQml \n QtObject { property var a: Component } " , QUrl ( ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QJSManagedValue m = engine . toManagedValue ( o . data ( ) ) ;
QJSValue prop = m . property ( " a " ) ;
const QQmlComponentAttached * attached = qjsvalue_cast < QQmlComponentAttached * > ( prop ) ;
QCOMPARE ( attached , qmlAttachedPropertiesObject < QQmlComponent > ( o . data ( ) ) ) ;
}
2021-03-23 13:16:15 +00:00
class WithListProperty : public QObject
{
Q_OBJECT
Q_PROPERTY ( QQmlListProperty < QQmlComponent > components READ components CONSTANT )
QML_ELEMENT
public :
QQmlListProperty < QQmlComponent > components ( )
{
return QQmlListProperty < QQmlComponent > ( this , & m_components ) ;
}
private :
QList < QQmlComponent * > m_components ;
} ;
void tst_qqmlengine : : listPropertyAsQJSValue ( )
{
qmlRegisterTypesAndRevisions < WithListProperty > ( " Foo " , 1 ) ;
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
c . setData ( " import Foo \n WithListProperty {} " , QUrl ( ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
WithListProperty * parent = qobject_cast < WithListProperty * > ( o . data ( ) ) ;
QVERIFY ( parent ) ;
QQmlListProperty < QQmlComponent > prop = parent - > components ( ) ;
QJSValue val = engine . toScriptValue ( prop ) ;
QQmlListReference ref = engine . fromScriptValue < QQmlListReference > ( val ) ;
ref . append ( & c ) ;
QCOMPARE ( prop . count ( & prop ) , 1 ) ;
QCOMPARE ( prop . at ( & prop , 0 ) , & c ) ;
}
2021-09-03 12:35:26 +00:00
void tst_qqmlengine : : stringToColor ( )
{
QQmlEngine engine ;
// Make it import QtQuick, so that color becomes available.
QQmlComponent c ( & engine ) ;
c . setData ( " import QtQuick \n Item {} " , QUrl ( ) ) ;
QVERIFY ( c . isReady ( ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
const QMetaType metaType ( QMetaType : : QColor ) ;
QVariant color ( metaType ) ;
QtQml: Avoid potential gc issues
Implicitly constructing a value from a ReturnedValue muddies the
responsibility for ensuring that the gc can find the object.
With this change, we disable the implicit conversion. The expectation
for lifetime management is now:
- If a ReturnedValue is stored on the C++ stack, it must be put into a
QV4::Scoped class (or there should be a comment why not doing so is
safe). Passing a ReturnedValue to a function should no longer be
possible, unless the function takes a ReturnedValue, in which case the
expectation is that it stores the value in a place where it can be
seen by the gc, before doing anything that could trigger a gc run.
Using Value::fromReturnedValue can still be used to pass a Value on,
but in that case, the expectation is that there is a comment which
explains why this is safe.
- If a QV4::Value is obtained from a function call, it ought to be
stored in a ScopedValue, too. We currently can't enforce this easily,
so this should be checked during code review. A possible way forward
would be to disallow returning Values, but that would be a larger
change, and is deferred to the future.
- If a functions has a QV4::Value parameter, it's the callers'
responsibilty to ensure that the gc can find it.
Pick-to: 6.9 6.8 6.5
Fixes: QTBUG-131961
Change-Id: Iea055589d35a5f1ac36fe376d4389eb81de87961
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2024-12-12 13:39:37 +00:00
QV4 : : Scope scope ( engine . handle ( ) ) ;
QV4 : : ScopedValue colorString ( scope , engine . handle ( ) - > newString ( QStringLiteral ( " #abcdef " ) ) ) ;
2022-09-12 12:26:29 +00:00
QVERIFY ( QV4 : : ExecutionEngine : : metaTypeFromJS (
QtQml: Avoid potential gc issues
Implicitly constructing a value from a ReturnedValue muddies the
responsibility for ensuring that the gc can find the object.
With this change, we disable the implicit conversion. The expectation
for lifetime management is now:
- If a ReturnedValue is stored on the C++ stack, it must be put into a
QV4::Scoped class (or there should be a comment why not doing so is
safe). Passing a ReturnedValue to a function should no longer be
possible, unless the function takes a ReturnedValue, in which case the
expectation is that it stores the value in a place where it can be
seen by the gc, before doing anything that could trigger a gc run.
Using Value::fromReturnedValue can still be used to pass a Value on,
but in that case, the expectation is that there is a comment which
explains why this is safe.
- If a QV4::Value is obtained from a function call, it ought to be
stored in a ScopedValue, too. We currently can't enforce this easily,
so this should be checked during code review. A possible way forward
would be to disallow returning Values, but that would be a larger
change, and is deferred to the future.
- If a functions has a QV4::Value parameter, it's the callers'
responsibilty to ensure that the gc can find it.
Pick-to: 6.9 6.8 6.5
Fixes: QTBUG-131961
Change-Id: Iea055589d35a5f1ac36fe376d4389eb81de87961
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2024-12-12 13:39:37 +00:00
colorString ,
2021-09-03 12:35:26 +00:00
metaType , color . data ( ) ) ) ;
QVERIFY ( color . isValid ( ) ) ;
QCOMPARE ( color . metaType ( ) , metaType ) ;
QVariant variant ( QStringLiteral ( " #abcdef " ) ) ;
QVERIFY ( variant . convert ( metaType ) ) ;
QCOMPARE ( variant . metaType ( ) , metaType ) ;
QCOMPARE ( color , variant ) ;
}
2022-03-25 09:56:41 +00:00
class WithToString : public QObject
{
Q_OBJECT
QML_ELEMENT
public :
Q_INVOKABLE QString toString ( ) const { return QStringLiteral ( " things " ) ; }
} ;
class WithToNumber : public QObject
{
Q_OBJECT
QML_ELEMENT
public :
Q_INVOKABLE int toString ( ) const { return 4 ; }
} ;
void tst_qqmlengine : : qobjectToString ( )
{
qmlRegisterTypesAndRevisions < WithToString > ( " WithToString " , 1 ) ;
qmlRegisterTypesAndRevisions < WithToNumber > ( " WithToString " , 1 ) ;
QQmlEngine engine ;
QQmlComponent c ( & engine ) ;
c . setData ( R " (
import WithToString
import QtQml
WithToString {
id : self
property QtObject weird : WithToNumber { }
objectName : toString ( ) + ' ' + self . toString ( ) + ' ' + weird . toString ( )
}
) " , QUrl());
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QCOMPARE ( o - > objectName ( ) , QStringLiteral ( " things things 4 " ) ) ;
}
2022-06-24 07:56:01 +00:00
void tst_qqmlengine : : qtNamespaceInQtObject ( )
{
QQmlEngine engine ;
QJSValue qtObject = engine . globalObject ( ) . property ( QStringLiteral ( " Qt " ) ) ;
// Qt namespace enums are there.
QCOMPARE ( qtObject . property ( QStringLiteral ( " Checked " ) ) . toInt ( ) , 2 ) ;
// QtObject methods are also there.
QVERIFY ( qtObject . property ( QStringLiteral ( " rect " ) ) . isCallable ( ) ) ;
// QObject is also there.
QVERIFY ( qtObject . hasProperty ( QStringLiteral ( " objectName " ) ) ) ;
}
2022-08-25 09:42:23 +00:00
void tst_qqmlengine : : nativeModuleImport ( )
{
QQmlEngine engine ;
QJSValue name ( " TheName " ) ;
QJSValue obj = engine . newObject ( ) ;
obj . setProperty ( " name " , name ) ;
engine . registerModule ( " info.mjs " , obj ) ;
QQmlComponent c ( & engine , testFileUrl ( " nativeModuleImport.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QCOMPARE ( o - > property ( " a " ) . toString ( ) , QStringLiteral ( " Hello World " ) ) ;
QCOMPARE ( o - > property ( " b " ) . toString ( ) , QStringLiteral ( " TheName " ) ) ;
QCOMPARE ( o - > property ( " c " ) . toString ( ) , QStringLiteral ( " TheName " ) ) ;
QCOMPARE ( o - > property ( " d " ) . toString ( ) , QStringLiteral ( " TheName " ) ) ;
QCOMPARE ( o - > property ( " e " ) . toString ( ) , QStringLiteral ( " TheName " ) ) ;
}
Allow limited extensions to globals
We can allow
a, overriding data members of globals, such as Error.name
b, adding members that don't clash with any internals
c, any manipulation of toString(), toLocaleString(), valueOf(),
and constructor
To that effect, add a "Locked" flag to our internal classes. If that is
set, disallow changing prototypes and when defining a property, check if
it shadows any non-configurable property. Furthermore, make all
non-primitive properties that are not meant to be overridden
non-configurable and non-writable.
constructor, toString(), toLocaleString() and valueOf() are exempt
because they are explicitly meant to be overridden by users. Therefore,
we let that happen and refrain from optimizing them or triggering their
implicit invocation in optimized code.
[ChangeLog][QtQml][Important Behavior Changes] The JavaScript global
objects are not frozen anymore in a QML engine. Instead, they are
selectively locked. You can extend the objects with new members as long
as you don't shadow any existing methods, and you can change or override
data members. This also means that most methods of Object.prototype,
which was previously exempt from the freezing, cannot be changed
anymore. You can, however, change or override constructor, toString(),
toLocaleString() and valueOf() on any prototype. Those are clearly meant
to be overridden by user code.
Fixes: QTBUG-101298
Task-number: QTBUG-84341
Change-Id: Id77db971f76c8f48b18e7a93607da5f947ecfc3e
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2022-08-29 10:00:52 +00:00
void tst_qqmlengine : : lockedRootObject ( )
{
QQmlEngine engine ;
QQmlComponent c ( & engine , testFileUrl ( " lockedRootObject.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QTest : : ignoreMessage (
QtWarningMsg , " You cannot shadow the locked property 'hasOwnProperty' in QML. " ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QCOMPARE ( o - > property ( " myErrorName " ) . toString ( ) , QStringLiteral ( " MyError1 " ) ) ;
QCOMPARE ( o - > property ( " errorName " ) . toString ( ) , QStringLiteral ( " MyError2 " ) ) ;
QCOMPARE ( o - > property ( " mathMax " ) . toInt ( ) , 4 ) ;
QCOMPARE ( o - > property ( " extendGlobal " ) . toInt ( ) , 32 ) ;
QCOMPARE ( o - > property ( " prototypeTrick " ) . toString ( ) , QStringLiteral ( " SyntaxError " ) ) ;
QCOMPARE ( o - > property ( " shadowMethod1 " ) . toString ( ) , QStringLiteral ( " not a TypeError " ) ) ;
QCOMPARE ( o - > property ( " shadowMethod2 " ) . toBool ( ) , false ) ;
QCOMPARE ( o - > property ( " changeObjectProto1 " ) . toString ( ) , QStringLiteral ( " not an Object " ) ) ;
QCOMPARE ( o - > property ( " changeObjectProto2 " ) . toBool ( ) , false ) ;
QCOMPARE ( o - > property ( " defineProperty1 " ) . toString ( ) , QStringLiteral ( " not a URIError " ) ) ;
QCOMPARE ( o - > property ( " defineProperty2 " ) . toBool ( ) , false ) ;
}
2023-03-22 12:01:24 +00:00
void tst_qqmlengine : : crossReferencingSingletonsDeletion ( )
{
QQmlEngine engine ;
engine . addImportPath ( testFileUrl ( " crossReferencingSingletonsDeletion " ) . url ( ) ) ;
QQmlComponent c ( & engine , testFileUrl ( " crossReferencingSingletonsDeletion/Module/Main.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
std : : unique_ptr < QObject > o { c . create ( ) } ;
QVERIFY ( o ) ;
QCOMPARE ( o - > property ( " s " ) . toString ( ) , " SingletonA " ) ;
}
2023-09-28 08:36:46 +00:00
void tst_qqmlengine : : bindingInstallUseAfterFree ( )
{
QQmlEngine engine ;
QQmlComponent c ( & engine , testFileUrl ( " bindingInstallUseAfterFree.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
std : : unique_ptr < QObject > o { c . create ( ) } ;
QVERIFY ( o ) ;
}
2024-08-05 08:49:11 +00:00
void tst_qqmlengine : : objectListArgumentMethod ( )
{
QQmlEngine engine ;
QQmlComponent c ( & engine , testFileUrl ( " objectListArgumentMethod.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QTest : : ignoreMessage ( QtMsgType : : QtDebugMsg , " 5 " ) ;
std : : unique_ptr < QObject > o { c . create ( ) } ;
QVERIFY ( o ) ;
}
2024-04-08 13:28:12 +00:00
void tst_qqmlengine : : variantListQJsonConversion ( )
{
QQmlEngine engine ;
QQmlComponent c ( & engine , testFileUrl ( " variantListQJsonConversion.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QTest : : ignoreMessage ( QtMsgType : : QtDebugMsg , R " ([ " cpp " , " variant " , " list " ]) " ) ;
QTest : : ignoreMessage ( QtMsgType : : QtDebugMsg , R " ({ " test " :[ " cpp " , " variant " , " list " ]}) " ) ;
QTest : : ignoreMessage ( QtMsgType : : QtDebugMsg ,
R " ([{ " objectName " : " o0 " },{ " objectName " : " o1 " },{ " objectName " : " o2 " }]) " ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QVERIFY ( o ) ;
}
2024-09-19 13:18:12 +00:00
class UnregisteredAttached : public QObject
{
Q_OBJECT
public :
UnregisteredAttached ( QObject * parent = nullptr ) : QObject ( parent ) { }
} ;
class Unregistered : public QObject
{
Q_OBJECT
QML_ATTACHED ( UnregisteredAttached )
public :
static UnregisteredAttached * qmlAttachedProperties ( QObject * obj )
{
return new UnregisteredAttached ( obj ) ;
}
} ;
void tst_qqmlengine : : attachedObjectOfUnregistered ( )
{
QObject o ;
QObject * a = qmlAttachedPropertiesObject < Unregistered > ( & o ) ;
QVERIFY ( a ) ;
QVERIFY ( qobject_cast < UnregisteredAttached * > ( a ) ) ;
QObject * b = qmlAttachedPropertiesObject < Unregistered > ( & o ) ;
QCOMPARE ( a , b ) ;
QObject o2 ;
QObject * c = qmlAttachedPropertiesObject < Unregistered > ( & o2 ) ;
QVERIFY ( c ) ;
QVERIFY ( qobject_cast < UnregisteredAttached * > ( c ) ) ;
QVERIFY ( c ! = a ) ;
}
2024-10-11 20:13:21 +00:00
void tst_qqmlengine : : dropCUOnEngineShutdown ( )
{
QScopedPointer < QObject > o ;
const QUrl url ( " qrc:/some/Type.qml " ) ;
{
QQmlEngine e ;
QQmlComponent c ( & e ) ;
c . setData ( " import QtQml \n QtObject {} " , url ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
o . reset ( c . create ( ) ) ;
QVERIFY ( ! o . isNull ( ) ) ;
QVERIFY ( QByteArray ( o - > metaObject ( ) - > className ( ) ) . startsWith ( " Type_QMLTYPE_ " ) ) ;
QVERIFY ( QQmlMetaType : : qmlType ( url ) . isValid ( ) ) ;
QVERIFY ( ! QQmlMetaType : : obtainCompilationUnit ( url ) . isNull ( ) ) ;
}
// The object is still there (don't do this at home).
QVERIFY ( ! o . isNull ( ) ) ;
// QMetaObject is still ok.
QVERIFY ( QByteArray ( o - > metaObject ( ) - > className ( ) ) . startsWith ( " Type_QMLTYPE_ " ) ) ;
// But the QQmlType is gone.
QVERIFY ( ! QQmlMetaType : : qmlType ( url ) . isValid ( ) ) ;
// And also the CU.
QVERIFY ( QQmlMetaType : : obtainCompilationUnit ( url ) . isNull ( ) ) ;
}
2025-01-23 10:45:10 +00:00
void tst_qqmlengine : : multiLoadedJavaScriptModule ( )
{
QQmlEngine e ;
QQmlComponent c ( & e , testFileUrl ( " multiLoaded.qml " ) ) ;
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QTest : : ignoreMessage ( QtCriticalMsg , " In a1 - DATA_IN_A1 " ) ;
QTest : : ignoreMessage ( QtCriticalMsg , " In B1 DATA_IN_A1 " ) ;
QTest : : ignoreMessage ( QtCriticalMsg , " In B1 DATA_IN_A1 " ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QVERIFY ( ! o . isNull ( ) ) ;
}
2025-05-21 14:59:04 +00:00
void tst_qqmlengine : : metaObjectOfScriptCU ( )
{
QQmlEngine engine ;
engine . addImportPath ( dataDirectory ( ) ) ;
QQmlComponent c ( & engine ) ;
c . setData ( R " (
import JS
import QtQml
QtObject {
objectName : ColorUtils . withOpacity ( ' blue ' , 0.5 )
} ) " , QUrl());
QVERIFY2 ( c . isReady ( ) , qPrintable ( c . errorString ( ) ) ) ;
QScopedPointer < QObject > o ( c . create ( ) ) ;
QVERIFY ( ! o . isNull ( ) ) ;
QCOMPARE ( o - > objectName ( ) , " blue/0.5 " ) ;
// Don't crash when retrieving the (possibly non-existent) metaobject for the script.
for ( int i = QMetaType : : User + 1 ; QMetaType : : isRegistered ( i ) ; + + i )
QMetaType ( i ) . metaObject ( ) ;
}
2025-06-11 08:23:07 +00:00
void tst_qqmlengine : : registerModule ( )
{
// Make sure that registerModule() doesn't crash when invoked via QQmlEngine
QQmlEngine engine ;
QVERIFY ( engine . registerModule ( " magic " , QJSValue ( 63 ) ) ) ;
}
2012-02-16 04:43:03 +00:00
QTEST_MAIN ( tst_qqmlengine )
2011-04-27 10:05:43 +00:00
2012-02-16 04:43:03 +00:00
# include "tst_qqmlengine.moc"