Merge remote-tracking branch 'origin/5.9' into 5.10

Change-Id: I41ca9120a470a905c2f5c168c1de4cf970fa0fff
This commit is contained in:
Liang Qi 2018-02-02 09:50:10 +01:00
commit 2e65f6c2a5
24 changed files with 499 additions and 109 deletions

View File

@ -1466,6 +1466,9 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4:
QV4::CompiledData::Unit *qmlUnit = reinterpret_cast<QV4::CompiledData::Unit *>(data);
qmlUnit->unitSize = totalSize;
qmlUnit->flags |= QV4::CompiledData::Unit::IsQml;
// This unit's memory was allocated with malloc on the heap, so it's
// definitely not suitable for StaticData access.
qmlUnit->flags &= ~QV4::CompiledData::Unit::StaticData;
qmlUnit->offsetToImports = unitSize;
qmlUnit->nImports = output.imports.count();
qmlUnit->offsetToObjects = unitSize + importSize;

View File

@ -324,6 +324,19 @@
qmlRegisterSingletonType("Qt.example.qjsvalueApi", 1, 0, "MyApi", example_qjsvalue_singletontype_provider);
\endcode
Alternatively, you can use a C++11 lambda:
\code
qmlRegisterSingletonType("Qt.example.qjsvalueApi", 1, 0, "MyApi", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue {
Q_UNUSED(engine)
static int seedValue = 5;
QJSValue example = scriptEngine->newObject();
example.setProperty("someProperty", seedValue++);
return example;
});
\endcode
In order to use the registered singleton type in QML, you must import the singleton type.
\qml
import QtQuick 2.0
@ -423,6 +436,18 @@
qmlRegisterSingletonType<SingletonTypeExample>("Qt.example.qobjectSingleton", 1, 0, "MyApi", example_qobject_singletontype_provider);
\endcode
Alternatively, you can use a C++11 lambda:
\code
qmlRegisterSingletonType<SingletonTypeExample>("Qt.example.qjsvalueApi", 1, 0, "MyApi", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
SingletonTypeExample *example = new SingletonTypeExample();
return example;
});
\endcode
In order to use the registered singleton type in QML, you must import the singleton type.
\qml
import QtQuick 2.0

View File

@ -38,6 +38,8 @@ imperative code, in the case where complex custom application behavior is needed
QML source code is generally loaded by the engine through QML \e documents, which are
standalone documents of QML code. These can be used to define \l {QML Object Types}{QML object types} that can then be reused throughout an application.
Note that type names must begin with an uppercase letter in order
to be declared as QML object types in a QML file.
\section1 Import Statements

View File

@ -45,6 +45,8 @@ type, as discussed in \l {qtqml-documents-definetypes.html}
{Documents as QML object type definitions}, or by defining a QML type from C++
and registering the type with the QML engine, as discussed in
\l{qtqml-cppintegration-definetypes.html}{Defining QML Types from C++}.
Note that in both cases, the type name must begin with an uppercase letter in
order to be declared as a QML object type in a QML file.
\section1 Defining Object Types from QML

View File

@ -68,6 +68,22 @@ Page *getPage(Value *val) {
return reinterpret_cast<Page *>(reinterpret_cast<quintptr>(val) & ~((quintptr)(WTF::pageSize() - 1)));
}
QML_NEARLY_ALWAYS_INLINE void insertInFront(PersistentValueStorage *storage, Page *p)
{
p->header.next = reinterpret_cast<Page *>(storage->firstPage);
p->header.prev = reinterpret_cast<Page **>(&storage->firstPage);
if (p->header.next)
p->header.next->header.prev = &p->header.next;
storage->firstPage = p;
}
QML_NEARLY_ALWAYS_INLINE void unlink(Page *p)
{
if (p->header.prev)
*p->header.prev = p->header.next;
if (p->header.next)
p->header.next->header.prev = p->header.prev;
}
Page *allocatePage(PersistentValueStorage *storage)
{
@ -78,19 +94,14 @@ Page *allocatePage(PersistentValueStorage *storage)
p->header.engine = storage->engine;
p->header.alloc = page;
p->header.next = reinterpret_cast<Page *>(storage->firstPage);
p->header.prev = reinterpret_cast<Page **>(&storage->firstPage);
p->header.refCount = 0;
p->header.freeList = 0;
if (p->header.next)
p->header.next->header.prev = &p->header.next;
insertInFront(storage, p);
for (int i = 0; i < kEntriesPerPage - 1; ++i) {
p->values[i].setEmpty(i + 1);
}
p->values[kEntriesPerPage - 1].setEmpty(-1);
storage->firstPage = p;
return p;
}
@ -195,6 +206,12 @@ Value *PersistentValueStorage::allocate()
Value *v = p->values + p->header.freeList;
p->header.freeList = v->int_32();
if (p->header.freeList != -1 && p != firstPage) {
unlink(p);
insertInFront(this, p);
}
++p->header.refCount;
v->setRawValue(Encode::undefined());
@ -237,10 +254,7 @@ ExecutionEngine *PersistentValueStorage::getEngine(Value *v)
void PersistentValueStorage::freePage(void *page)
{
Page *p = static_cast<Page *>(page);
if (p->header.prev)
*p->header.prev = p->header.next;
if (p->header.next)
p->header.next->header.prev = p->header.prev;
unlink(p);
p->header.alloc.deallocate();
}

View File

@ -2080,29 +2080,38 @@ bool QQmlImportDatabase::importStaticPlugin(QObject *instance, const QString &ba
// Dynamic plugins are differentiated by their filepath. For static plugins we
// don't have that information so we use their address as key instead.
const QString uniquePluginID = QString::asprintf("%p", instance);
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
QMutexLocker lock(&plugins->mutex);
{
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
QMutexLocker lock(&plugins->mutex);
// Plugin types are global across all engines and should only be
// registered once. But each engine still needs to be initialized.
bool typesRegistered = plugins->contains(uniquePluginID);
bool engineInitialized = initializedPlugins.contains(uniquePluginID);
// Plugin types are global across all engines and should only be
// registered once. But each engine still needs to be initialized.
bool typesRegistered = plugins->contains(uniquePluginID);
if (typesRegistered) {
Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
"QQmlImportDatabase::importStaticPlugin",
"Internal error: Static plugin imported previously with different uri");
} else {
RegisteredPlugin plugin;
plugin.uri = uri;
plugin.loader = 0;
plugins->insert(uniquePluginID, plugin);
if (typesRegistered) {
Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
"QQmlImportDatabase::importStaticPlugin",
"Internal error: Static plugin imported previously with different uri");
} else {
RegisteredPlugin plugin;
plugin.uri = uri;
plugin.loader = 0;
plugins->insert(uniquePluginID, plugin);
if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
return false;
if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
return false;
}
// Release the lock on plugins early as we're done with the global part. Releasing the lock
// also allows other QML loader threads to acquire the lock while this thread is blocking
// in the initializeEngine call to the gui thread (which in turn may be busy waiting for
// other QML loader threads and thus not process the initializeEngine call).
}
if (!engineInitialized) {
// The plugin's per-engine initialization does not need lock protection, as this function is
// only called from the engine specific loader thread and importDynamicPlugin as well as
// importStaticPlugin are the only places of access.
if (!initializedPlugins.contains(uniquePluginID)) {
initializedPlugins.insert(uniquePluginID);
if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
@ -2124,68 +2133,77 @@ bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QStr
QFileInfo fileInfo(filePath);
const QString absoluteFilePath = fileInfo.absoluteFilePath();
QObject *instance = nullptr;
bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
QMutexLocker lock(&plugins->mutex);
bool typesRegistered = plugins->contains(absoluteFilePath);
{
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
QMutexLocker lock(&plugins->mutex);
bool typesRegistered = plugins->contains(absoluteFilePath);
if (typesRegistered) {
Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
"QQmlImportDatabase::importDynamicPlugin",
"Internal error: Plugin imported previously with different uri");
}
if (!engineInitialized || !typesRegistered) {
if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
if (errors) {
QQmlError error;
error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
errors->prepend(error);
}
return false;
if (typesRegistered) {
Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
"QQmlImportDatabase::importDynamicPlugin",
"Internal error: Plugin imported previously with different uri");
}
QPluginLoader* loader = 0;
if (!typesRegistered) {
loader = new QPluginLoader(absoluteFilePath);
if (!loader->load()) {
if (!engineInitialized || !typesRegistered) {
if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
if (errors) {
QQmlError error;
error.setDescription(loader->errorString());
error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
errors->prepend(error);
}
delete loader;
return false;
}
} else {
loader = plugins->value(absoluteFilePath).loader;
QPluginLoader* loader = 0;
if (!typesRegistered) {
loader = new QPluginLoader(absoluteFilePath);
if (!loader->load()) {
if (errors) {
QQmlError error;
error.setDescription(loader->errorString());
errors->prepend(error);
}
delete loader;
return false;
}
} else {
loader = plugins->value(absoluteFilePath).loader;
}
instance = loader->instance();
if (!typesRegistered) {
RegisteredPlugin plugin;
plugin.uri = uri;
plugin.loader = loader;
plugins->insert(absoluteFilePath, plugin);
// Continue with shared code path for dynamic and static plugins:
if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
return false;
}
}
QObject *instance = loader->instance();
// Release the lock on plugins early as we're done with the global part. Releasing the lock
// also allows other QML loader threads to acquire the lock while this thread is blocking
// in the initializeEngine call to the gui thread (which in turn may be busy waiting for
// other QML loader threads and thus not process the initializeEngine call).
}
if (!typesRegistered) {
RegisteredPlugin plugin;
plugin.uri = uri;
plugin.loader = loader;
plugins->insert(absoluteFilePath, plugin);
// Continue with shared code path for dynamic and static plugins:
if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
return false;
if (!engineInitialized) {
// The plugin's per-engine initialization does not need lock protection, as this function is
// only called from the engine specific loader thread and importDynamicPlugin as well as
// importStaticPlugin are the only places of access.
initializedPlugins.insert(absoluteFilePath);
if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData());
}
if (!engineInitialized) {
// things on the engine (eg. adding new global objects) have to be done for every
// engine.
// XXX protect against double initialization
initializedPlugins.insert(absoluteFilePath);
if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData());
}
}
}
return true;

View File

@ -1569,6 +1569,12 @@ QString registrationTypeString(QQmlType::RegistrationType typeType)
bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *data, const char *uri, const QString &typeName, int majorVersion = -1)
{
if (!typeName.isEmpty()) {
if (typeName.at(0).isLower()) {
QString failure(QCoreApplication::translate("qmlRegisterType", "Invalid QML %1 name \"%2\"; type names must begin with an uppercase letter"));
data->typeRegistrationFailures.append(failure.arg(registrationTypeString(typeType)).arg(typeName));
return false;
}
int typeNameLen = typeName.length();
for (int ii = 0; ii < typeNameLen; ++ii) {
if (!(typeName.at(ii).isLetterOrNumber() || typeName.at(ii) == '_')) {
@ -1814,6 +1820,9 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
else
return -1;
if (!dtype.isValid())
return -1;
QMutexLocker lock(metaTypeDataLock());
QQmlMetaTypeData *typeData = metaTypeData();
typeData->undeletableTypes.insert(dtype);

View File

@ -98,6 +98,8 @@ void Heap::QQmlValueTypeWrapper::destroy()
valueType->metaType.destruct(gadgetPtr);
::operator delete(gadgetPtr);
}
if (_propertyCache)
_propertyCache->release();
Object::destroy();
}

View File

@ -41,6 +41,10 @@ QObjectList or a \l QAbstractItemModel. The first three are useful for exposing
simpler datasets, while QAbstractItemModel provides a more flexible solution for
more complex models.
For a video tutorial that takes you through the whole process of exposing a C++
model to QML, see the
\l {https://youtu.be/9BcAYDlpuT8}{Using C++ Models in QML Tutorial}.
\section2 QStringList-based Model
A model may be a simple \l QStringList, which provides the contents of the list

View File

@ -1894,6 +1894,9 @@ void QQuickItemViewPrivate::layout()
inLayout = true;
// viewBounds contains bounds before any add/remove/move operation to the view
QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height());
if (!isValid() && !visibleItems.count()) {
clear();
setPosition(contentStartOffset());
@ -1960,7 +1963,6 @@ void QQuickItemViewPrivate::layout()
prepareVisibleItemTransitions();
QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height());
for (QList<FxViewItem*>::Iterator it = releasePendingTransition.begin();
it != releasePendingTransition.end(); ) {
FxViewItem *item = *it;

View File

@ -686,6 +686,13 @@ void QQuickLoaderPrivate::incubatorStateChanged(QQmlIncubator::Status status)
if (status == QQmlIncubator::Ready) {
object = incubator->object();
item = qmlobject_cast<QQuickItem*>(object);
if (!item) {
QQuickWindow *window = qmlobject_cast<QQuickWindow*>(object);
if (window) {
qCDebug(lcTransient) << window << "is transient for" << q->window();
window->setTransientParent(q->window());
}
}
emit q->itemChanged();
initResize();
incubator->clear();
@ -830,6 +837,18 @@ void QQuickLoader::componentComplete()
}
}
void QQuickLoader::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
if (change == ItemSceneChange) {
QQuickWindow *loadedWindow = qmlobject_cast<QQuickWindow *>(item());
if (loadedWindow) {
qCDebug(lcTransient) << loadedWindow << "is transient for" << value.window;
loadedWindow->setTransientParent(value.window);
}
}
QQuickItem::itemChange(change, value);
}
/*!
\qmlsignal QtQuick::Loader::loaded()

View File

@ -107,6 +107,7 @@ Q_SIGNALS:
protected:
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE;
void componentComplete() Q_DECL_OVERRIDE;
void itemChange(ItemChange change, const ItemChangeData &value) override;
private:
void setSource(const QUrl &sourceUrl, bool needsClear);

View File

@ -1108,7 +1108,8 @@ void QQuickTextInputPrivate::checkIsValid()
Q_Q(QQuickTextInput);
ValidatorState state = hasAcceptableInput(m_text);
m_validInput = state != InvalidInput;
if (!m_maskData)
m_validInput = state != InvalidInput;
if (state != AcceptableInput) {
if (m_acceptableInput) {
m_acceptableInput = false;
@ -3561,11 +3562,15 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
#if QT_CONFIG(validator)
if (m_validator) {
QString textCopy = m_text;
if (m_maskData)
textCopy = maskString(0, m_text, true);
int cursorCopy = m_cursor;
QValidator::State state = m_validator->validate(textCopy, cursorCopy);
if (m_maskData)
textCopy = m_text;
m_validInput = state != QValidator::Invalid;
m_acceptableInput = state == QValidator::Acceptable;
if (m_validInput) {
if (m_validInput && !m_maskData) {
if (m_text != textCopy) {
internalSetText(textCopy, cursorCopy);
return true;
@ -3574,31 +3579,8 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
}
}
#endif
if (m_maskData) {
m_validInput = true;
if (m_text.length() != m_maxLength) {
m_validInput = false;
m_acceptableInput = false;
} else {
for (int i = 0; i < m_maxLength; ++i) {
if (m_maskData[i].separator) {
if (m_text.at(i) != m_maskData[i].maskChar) {
m_validInput = false;
m_acceptableInput = false;
break;
}
} else {
if (!isValidInput(m_text.at(i), m_maskData[i].maskChar)) {
m_acceptableInput = false;
if (m_text.at(i) != m_blank)
m_validInput = false;
break;
}
}
}
}
}
if (m_maskData)
checkIsValid();
if (validateFromState >= 0 && wasValidInput && !m_validInput) {
if (m_transactions.count())

View File

@ -123,7 +123,13 @@ void QQuickWindowQmlImpl::componentComplete()
{
Q_D(QQuickWindowQmlImpl);
d->complete = true;
if (transientParent() && !transientParent()->isVisible()) {
QQuickItem *itemParent = qmlobject_cast<QQuickItem *>(QObject::parent());
if (itemParent && !itemParent->window()) {
qCDebug(lcTransient) << "window" << title() << "has invisible Item parent" << itemParent << "transientParent"
<< transientParent() << "declared visibility" << d->visibility << "; delaying show";
connect(itemParent, &QQuickItem::windowChanged, this,
&QQuickWindowQmlImpl::setWindowVisibility, Qt::QueuedConnection);
} else if (transientParent() && !transientParent()->isVisible()) {
connect(transientParent(), &QQuickWindow::visibleChanged, this,
&QQuickWindowQmlImpl::setWindowVisibility, Qt::QueuedConnection);
} else {
@ -137,9 +143,10 @@ void QQuickWindowQmlImpl::setWindowVisibility()
if (transientParent() && !transientParent()->isVisible())
return;
if (sender()) {
disconnect(transientParent(), &QWindow::visibleChanged, this,
&QQuickWindowQmlImpl::setWindowVisibility);
if (QQuickItem *senderItem = qmlobject_cast<QQuickItem *>(sender())) {
disconnect(senderItem, &QQuickItem::windowChanged, this, &QQuickWindowQmlImpl::setWindowVisibility);
} else if (sender()) {
disconnect(transientParent(), &QWindow::visibleChanged, this, &QQuickWindowQmlImpl::setWindowVisibility);
}
// We have deferred window creation until we have the full picture of what

View File

@ -33,6 +33,7 @@
#include <QProcess>
#include <QLibraryInfo>
#include <QSysInfo>
#include <private/qqmlcomponent_p.h>
class tst_qmlcachegen: public QObject
{
@ -115,6 +116,16 @@ void tst_qmlcachegen::loadGeneratedFile()
const QString cacheFilePath = testFilePath + QLatin1Char('c');
QVERIFY(QFile::exists(cacheFilePath));
{
QFile cache(cacheFilePath);
QVERIFY(cache.open(QIODevice::ReadOnly));
const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, sizeof(QV4::CompiledData::Unit)));
QVERIFY(cacheUnit);
QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::StaticData);
QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::PendingTypeCompilation);
}
QVERIFY(QFile::remove(testFilePath));
QQmlEngine engine;
@ -122,6 +133,13 @@ void tst_qmlcachegen::loadGeneratedFile()
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
QCOMPARE(obj->property("value").toInt(), 42);
auto componentPrivate = QQmlComponentPrivate::get(&component);
QVERIFY(componentPrivate);
auto compilationUnit = componentPrivate->compilationUnit;
QVERIFY(compilationUnit);
QVERIFY(compilationUnit->data);
QVERIFY(!(compilationUnit->data->flags & QV4::CompiledData::Unit::StaticData));
}
void tst_qmlcachegen::translationExpressionSupport()

View File

@ -279,6 +279,8 @@ private slots:
void accessDeletedObject();
void lowercaseTypeNames();
private:
QQmlEngine engine;
QStringList defaultImportPathList;
@ -4903,6 +4905,12 @@ void tst_qqmllanguage::accessDeletedObject()
QVERIFY(!o.isNull());
}
void tst_qqmllanguage::lowercaseTypeNames()
{
QCOMPARE(qmlRegisterType<QObject>("Test", 1, 0, "lowerCaseTypeName"), -1);
QCOMPARE(qmlRegisterSingletonType<QObject>("Test", 1, 0, "lowerCaseTypeName", nullptr), -1);
}
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"

View File

@ -0,0 +1,2 @@
module moduleWithStaticPlugin
plugin secondStaticPlugin

View File

@ -0,0 +1,2 @@
module moduleWithWaitingPlugin
plugin pluginThatWaits

View File

@ -29,6 +29,9 @@
#include <qdir.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlextensionplugin.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonarray.h>
#include <QDebug>
#if defined(Q_OS_MAC)
@ -73,12 +76,80 @@ private slots:
void importsChildPlugin();
void importsChildPlugin2();
void importsChildPlugin21();
void parallelPluginImport();
private:
QString m_importsDirectory;
QString m_dataImportsDirectory;
};
class PluginThatWaits : public QQmlExtensionPlugin
{
public:
static QByteArray metaData;
static QMutex initializeEngineEntered;
static QWaitCondition waitingForInitializeEngineEntry;
static QMutex leavingInitializeEngine;
static QWaitCondition waitingForInitializeEngineLeave;
void registerTypes(const char *uri) override
{
qmlRegisterModule(uri, 1, 0);
}
void initializeEngine(QQmlEngine *engine, const char *uri) override
{
initializeEngineEntered.lock();
leavingInitializeEngine.lock();
waitingForInitializeEngineEntry.wakeOne();
initializeEngineEntered.unlock();
waitingForInitializeEngineLeave.wait(&leavingInitializeEngine);
leavingInitializeEngine.unlock();
}
};
QByteArray PluginThatWaits::metaData;
QMutex PluginThatWaits::initializeEngineEntered;
QWaitCondition PluginThatWaits::waitingForInitializeEngineEntry;
QMutex PluginThatWaits::leavingInitializeEngine;
QWaitCondition PluginThatWaits::waitingForInitializeEngineLeave;
class SecondStaticPlugin : public QQmlExtensionPlugin
{
public:
static QByteArray metaData;
void registerTypes(const char *uri) override
{
qmlRegisterModule(uri, 1, 0);
}
};
QByteArray SecondStaticPlugin::metaData;
template <typename PluginType>
void registerStaticPlugin(const char *uri)
{
QStaticPlugin plugin;
plugin.instance = []() {
static PluginType plugin;
return static_cast<QObject*>(&plugin);
};
QJsonObject md;
md.insert(QStringLiteral("IID"), QQmlExtensionInterface_iid);
QJsonArray uris;
uris.append(uri);
md.insert(QStringLiteral("uri"), uris);
PluginType::metaData.append(QLatin1String("QTMETADATA "));
PluginType::metaData.append(QJsonDocument(md).toBinaryData());
plugin.rawMetaData = []() {
return PluginType::metaData.constData();
};
qRegisterStaticPluginFunction(plugin);
};
void tst_qqmlmoduleplugin::initTestCase()
{
QQmlDataTest::initTestCase();
@ -88,6 +159,9 @@ void tst_qqmlmoduleplugin::initTestCase()
m_dataImportsDirectory = directory() + QStringLiteral("/imports");
QVERIFY2(QFileInfo(m_dataImportsDirectory).isDir(),
qPrintable(QString::fromLatin1("Imports directory '%1' does not exist.").arg(m_dataImportsDirectory)));
registerStaticPlugin<PluginThatWaits>("moduleWithWaitingPlugin");
registerStaticPlugin<SecondStaticPlugin>("moduleWithStaticPlugin");
}
#define VERIFY_ERRORS(errorfile) \
@ -635,6 +709,51 @@ void tst_qqmlmoduleplugin::importsChildPlugin21()
delete object;
}
void tst_qqmlmoduleplugin::parallelPluginImport()
{
QMutexLocker locker(&PluginThatWaits::initializeEngineEntered);
QThread worker;
QObject::connect(&worker, &QThread::started, [&worker](){
// Engines in separate threads are tricky, but as long as we do not create a graphical
// object and move objects created by the engines across thread boundaries, this is safe.
// At the same time this allows us to place the engine's loader thread into the position
// where, without the fix for this bug, the global lock is acquired.
QQmlEngine engineInThread;
QQmlComponent component(&engineInThread);
component.setData("import moduleWithWaitingPlugin 1.0\nimport QtQml 2.0\nQtObject {}",
QUrl());
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
worker.quit();
});
worker.start();
PluginThatWaits::waitingForInitializeEngineEntry.wait(&PluginThatWaits::initializeEngineEntered);
{
// After acquiring this lock, the engine in the other thread as well as its type loader
// thread are blocked. However they should not hold the global plugin lock
// qmlEnginePluginsWithRegisteredTypes()->mutex in qqmllimports.cpp, allowing for the load
// of a component in a different engine with its own plugin to proceed.
QMutexLocker continuationLock(&PluginThatWaits::leavingInitializeEngine);
QQmlEngine secondEngine;
QQmlComponent secondComponent(&secondEngine);
secondComponent.setData("import moduleWithStaticPlugin 1.0\nimport QtQml 2.0\nQtObject {}",
QUrl());
QScopedPointer<QObject> o(secondComponent.create());
QVERIFY(!o.isNull());
PluginThatWaits::waitingForInitializeEngineLeave.wakeOne();
}
worker.wait();
}
QTEST_MAIN(tst_qqmlmoduleplugin)
#include "tst_qqmlmoduleplugin.moc"

View File

@ -10,4 +10,12 @@ include (../../shared/util.pri)
TESTDATA = data/* imports/* $$OUT_PWD/imports/*
waitingPlugin.files = moduleWithWaitingPlugin
waitingPlugin.prefix = /qt-project.org/imports/
RESOURCES += waitingPlugin
staticPlugin.files = moduleWithStaticPlugin
staticPlugin.prefix = /qt-project.org/imports/
RESOURCES += staticPlugin
QT += core-private gui-private qml-private network testlib

View File

@ -0,0 +1,27 @@
import QtQuick 2.0
import QtQuick.Window 2.1
Item {
width: 400
height: 400
objectName: "root Item"
Loader {
sourceComponent: Rectangle {
objectName: "yellow rectangle"
x: 50; y: 50; width: 300; height: 300
color: "yellow"
Window {
objectName: "red transient Window"
width: 100
height: 100
visible: true // makes it harder, because it wants to become visible before root has a window
color: "red"
title: "red"
flags: Qt.Dialog
onVisibilityChanged: console.log("visibility " + visibility)
onVisibleChanged: console.log("visible " + visible)
}
}
}
}

View File

@ -0,0 +1,22 @@
import QtQuick 2.0
import QtQuick.Window 2.1
Item {
width: 400
height: 400
objectName: "root Item"
Loader {
sourceComponent: Window {
objectName: "red transient Window"
width: 100
height: 100
visible: true // makes it harder, because it wants to become visible before root has a window
color: "red"
title: "red"
flags: Qt.Dialog
onVisibilityChanged: console.log("visibility " + visibility)
onVisibleChanged: console.log("visible " + visible)
}
}
}

View File

@ -32,11 +32,15 @@
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlincubator.h>
#include <QtQuick/qquickview.h>
#include <private/qquickloader_p.h>
#include <private/qquickwindowmodule_p.h>
#include "testhttpserver.h"
#include "../../shared/util.h"
#include "../shared/geometrytestutil.h"
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
class SlowComponent : public QQmlComponent
{
Q_OBJECT
@ -112,6 +116,8 @@ private slots:
void parented();
void sizeBound();
void QTBUG_30183();
void transientWindow();
void nestedTransientWindow();
void sourceComponentGarbageCollection();
@ -1159,6 +1165,86 @@ void tst_QQuickLoader::QTBUG_30183()
QCOMPARE(rect->height(), 120.0);
}
void tst_QQuickLoader::transientWindow() // QTBUG-52944
{
QQuickView view;
view.setSource(testFileUrl("itemLoaderWindow.qml"));
QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject());
QVERIFY(root);
QQuickLoader *loader = root->findChild<QQuickLoader *>();
QVERIFY(loader);
QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
QQuickWindowQmlImpl *loadedWindow = qobject_cast<QQuickWindowQmlImpl *>(loader->item());
QVERIFY(loadedWindow);
QCOMPARE(loadedWindow->visibility(), QWindow::Hidden);
QElapsedTimer timer;
qint64 viewVisibleTime = -1;
qint64 loadedWindowVisibleTime = -1;
connect(&view, &QWindow::visibleChanged,
[&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } );
connect(loadedWindow, &QQuickWindowQmlImpl::visibilityChanged,
[&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } );
timer.start();
view.show();
QTest::qWaitForWindowExposed(&view);
QTRY_VERIFY(loadedWindowVisibleTime >= 0);
QVERIFY(viewVisibleTime >= 0);
// now that we're sure they are both visible, which one became visible first?
qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item";
QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0);
QWindowList windows = QGuiApplication::topLevelWindows();
QTRY_COMPARE(windows.size(), 2);
// TODO Ideally we would now close the outer window and make sure the transient window closes too.
// It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed()
// but quitting an autotest doesn't make sense.
}
void tst_QQuickLoader::nestedTransientWindow() // QTBUG-52944
{
QQuickView view;
view.setSource(testFileUrl("itemLoaderItemWindow.qml"));
QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject());
QVERIFY(root);
QQuickLoader *loader = root->findChild<QQuickLoader *>();
QVERIFY(loader);
QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
QQuickItem *loadedItem = qobject_cast<QQuickItem *>(loader->item());
QVERIFY(loadedItem);
QQuickWindowQmlImpl *loadedWindow = loadedItem->findChild<QQuickWindowQmlImpl *>();
QVERIFY(loadedWindow);
QCOMPARE(loadedWindow->visibility(), QWindow::Hidden);
QElapsedTimer timer;
qint64 viewVisibleTime = -1;
qint64 loadedWindowVisibleTime = -1;
connect(&view, &QWindow::visibleChanged,
[&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } );
connect(loadedWindow, &QQuickWindowQmlImpl::visibilityChanged,
[&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } );
timer.start();
view.show();
QTest::qWaitForWindowExposed(&view);
QTRY_VERIFY(loadedWindowVisibleTime >= 0);
QVERIFY(viewVisibleTime >= 0);
// now that we're sure they are both visible, which one became visible first?
qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item";
QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0);
QWindowList windows = QGuiApplication::topLevelWindows();
QTRY_COMPARE(windows.size(), 2);
// TODO Ideally we would now close the outer window and make sure the transient window closes too.
// It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed()
// but quitting an autotest doesn't make sense.
}
void tst_QQuickLoader::sourceComponentGarbageCollection()
{
QQmlEngine engine;

View File

@ -6143,9 +6143,17 @@ void tst_qquicktextinput::keypress_inputMask_withValidator_data()
KeyList keys;
// inserting '1111.11' then two backspaces
keys << Qt::Key_Home << "1111.11" << Qt::Key_Backspace << Qt::Key_Backspace;
QTest::newRow("backspaceWithRegExp") << QString("9999.99;_") << 0.0 << 0.0 << 0
QTest::newRow("backspaceWithRegExp") << QString("9999;_") << 0.0 << 0.0 << 0
<< QString("/^[-]?((\\.\\d+)|(\\d+(\\.\\d+)?))$/")
<< keys << QString("1111.") << QString("1111.__");
<< keys << QString("11") << QString("11__");
}
{
KeyList keys;
// inserting '99' - QTBUG-64616
keys << Qt::Key_Home << "99";
QTest::newRow("invalidTextWithRegExp") << QString("X9;_") << 0.0 << 0.0 << 0
<< QString("/[+-][0+9]/")
<< keys << QString("") << QString("__");
}
}