diff options
-rw-r--r-- | src/qml/qml/qqmlimport.cpp | 27 | ||||
-rw-r--r-- | src/qml/qml/qqmlimport_p.h | 1 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 48 | ||||
-rw-r--r-- | tests/auto/qml/qqmltypeloader/data/Intercept.qml | 41 | ||||
-rw-r--r-- | tests/auto/qml/qqmltypeloader/data/test_intercept.qml | 53 | ||||
-rw-r--r-- | tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp | 168 |
6 files changed, 321 insertions, 17 deletions
diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 4e3b25070f..18dc8e4b28 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1266,11 +1266,20 @@ bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin, QQ QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader; - QStringList localImportPaths = database->importPathList(QQmlImportDatabase::Local); + // Interceptor might redirect remote files to local ones. + QQmlAbstractUrlInterceptor *interceptor = typeLoader.engine()->urlInterceptor(); + QStringList localImportPaths = database->importPathList( + interceptor ? QQmlImportDatabase::LocalOrRemote : QQmlImportDatabase::Local); // Search local import paths for a matching version const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(uri, localImportPaths, vmaj, vmin); - for (const QString &qmldirPath : qmlDirPaths) { + for (QString qmldirPath : qmlDirPaths) { + if (interceptor) { + qmldirPath = QQmlFile::urlToLocalFileOrQrc( + interceptor->intercept(QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), + QQmlAbstractUrlInterceptor::QmldirFile)); + } + QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirPath); if (!absoluteFilePath.isEmpty()) { QString url; @@ -1479,6 +1488,10 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix QString qmldirUrl = resolveLocalUrl(base, importUri + (importUri.endsWith(Slash) ? String_qmldir : Slash_qmldir)); + if (QQmlAbstractUrlInterceptor *interceptor = typeLoader->engine()->urlInterceptor()) { + qmldirUrl = interceptor->intercept(QUrl(qmldirUrl), + QQmlAbstractUrlInterceptor::QmldirFile).toString(); + } QString qmldirIdentifier; if (QQmlFile::isLocalFile(qmldirUrl)) { @@ -1693,6 +1706,16 @@ bool QQmlImports::isLocal(const QUrl &url) return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty(); } +QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file) +{ + QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file); + + // We don't support single character schemes as those conflict with windows drive letters. + if (url.scheme().length() < 2) + return QUrl::fromLocalFile(file); + return url; +} + void QQmlImports::setDesignerSupportRequired(bool b) { designerSupportRequired = b; diff --git a/src/qml/qml/qqmlimport_p.h b/src/qml/qml/qqmlimport_p.h index 1bdd287690..9cb5340c68 100644 --- a/src/qml/qml/qqmlimport_p.h +++ b/src/qml/qml/qqmlimport_p.h @@ -184,6 +184,7 @@ public: static bool isLocal(const QString &url); static bool isLocal(const QUrl &url); + static QUrl urlFromLocalFileOrQrcOrUrl(const QString &); static void setDesignerSupportRequired(bool b); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index d9d7c19312..1a7b8250e7 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -1436,8 +1436,13 @@ bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QL // We haven't yet resolved this import m_unresolvedImports.insert(import, 0); - // Query any network import paths for this library - QStringList remotePathList = importDatabase->importPathList(QQmlImportDatabase::Remote); + QQmlAbstractUrlInterceptor *interceptor = typeLoader()->engine()->urlInterceptor(); + + // Query any network import paths for this library. + // Interceptor might redirect local paths. + QStringList remotePathList = importDatabase->importPathList( + interceptor ? QQmlImportDatabase::LocalOrRemote + : QQmlImportDatabase::Remote); if (!remotePathList.isEmpty()) { // Add this library and request the possible locations for it if (!m_importCache.addLibraryImport(importDatabase, importUri, importQualifier, import->majorVersion, @@ -1448,8 +1453,18 @@ bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QL int priority = 0; const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(importUri, remotePathList, import->majorVersion, import->minorVersion); for (const QString &qmldirPath : qmlDirPaths) { - if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) + if (interceptor) { + QUrl url = interceptor->intercept( + QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), + QQmlAbstractUrlInterceptor::QmldirFile); + if (!QQmlFile::isLocalFile(url) + && !fetchQmldir(url, import, ++priority, errors)) { + return false; + } + } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) { return false; + } + } } } @@ -1872,19 +1887,22 @@ It can also be a remote path for a remote directory import, but it will have bee */ const QQmlTypeLoaderQmldirContent *QQmlTypeLoader::qmldirContent(const QString &filePathIn) { - QUrl url(filePathIn); //May already contain http scheme - if (url.scheme() == QLatin1String("http") || url.scheme() == QLatin1String("https")) - return *(m_importQmlDirCache.value(filePathIn)); //Can't load the remote here, but should be cached - else - url = QUrl::fromLocalFile(filePathIn); - if (engine() && engine()->urlInterceptor()) - url = engine()->urlInterceptor()->intercept(url, QQmlAbstractUrlInterceptor::QmldirFile); - Q_ASSERT(url.scheme() == QLatin1String("file")); QString filePath; - if (url.scheme() == QLatin1String("file")) - filePath = url.toLocalFile(); - else - filePath = url.path(); + + // Try to guess if filePathIn is already a URL. This is necessarily fragile, because + // - paths can contain ':', which might make them appear as URLs with schemes. + // - windows drive letters appear as schemes (thus "< 2" below). + // - a "file:" URL is equivalent to the respective file, but will be treated differently. + // Yet, this heuristic is the best we can do until we pass more structured information here, + // for example a QUrl also for local files. + QUrl url(filePathIn); + if (url.scheme().length() < 2) { + filePath = filePathIn; + } else { + filePath = QQmlFile::urlToLocalFileOrQrc(url); + if (filePath.isEmpty()) // Can't load the remote here, but should be cached + return *(m_importQmlDirCache.value(filePathIn)); + } QQmlTypeLoaderQmldirContent *qmldir; QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(filePath); diff --git a/tests/auto/qml/qqmltypeloader/data/Intercept.qml b/tests/auto/qml/qqmltypeloader/data/Intercept.qml new file mode 100644 index 0000000000..b557b4b941 --- /dev/null +++ b/tests/auto/qml/qqmltypeloader/data/Intercept.qml @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Fast 1.0 + +Item { + Rectangle { + color: "red" + width: 100 + height: 100 + } + Fast { + + } +} diff --git a/tests/auto/qml/qqmltypeloader/data/test_intercept.qml b/tests/auto/qml/qqmltypeloader/data/test_intercept.qml new file mode 100644 index 0000000000..091fbe7f49 --- /dev/null +++ b/tests/auto/qml/qqmltypeloader/data/test_intercept.qml @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +ListView { + width: 400 + height: 500 + model: 2 + + id: test + property int created: 0 + property int loaded: 0 + + delegate: Loader { + width: ListView.view.width + height: 100 + asynchronous: true + source: "Intercept.qml" + + onLoaded: { + test.loaded++ + } + Component.onCompleted: { + test.created++ + } + } +} diff --git a/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp b/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp index 68b450ab26..12aeff7591 100644 --- a/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp +++ b/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp @@ -28,6 +28,7 @@ #include <QtTest/QtTest> #include <QtQml/qqmlengine.h> +#include <QtQml/qqmlnetworkaccessmanagerfactory.h> #include <QtQuick/qquickview.h> #include <QtQuick/qquickitem.h> #include <QtQml/private/qqmlengine_p.h> @@ -45,6 +46,7 @@ private slots: void trimCache2(); void keepSingleton(); void keepRegistrations(); + void intercept(); }; void tst_QQMLTypeLoader::testLoadComplete() @@ -192,6 +194,172 @@ void tst_QQMLTypeLoader::keepRegistrations() verifyTypes(true, false); // qmlRegisterType creates an undeletable type. } +class NetworkReply : public QNetworkReply +{ +public: + NetworkReply() + { + open(QIODevice::ReadOnly); + } + + void setData(const QByteArray &data) + { + if (isFinished()) + return; + m_buffer = data; + emit readyRead(); + setFinished(true); + emit finished(); + } + + void fail() + { + if (isFinished()) + return; + m_buffer.clear(); + setError(ContentNotFoundError, "content not found"); + emit error(ContentNotFoundError); + setFinished(true); + emit finished(); + } + + qint64 bytesAvailable() const override + { + return m_buffer.size(); + } + + qint64 readData(char *data, qint64 maxlen) override + { + if (m_buffer.length() < maxlen) + maxlen = m_buffer.length(); + std::memcpy(data, m_buffer.data(), maxlen); + m_buffer.remove(0, maxlen); + return maxlen; + } + + void abort() override + { + if (isFinished()) + return; + m_buffer.clear(); + setFinished(true); + emit finished(); + } + +private: + QByteArray m_buffer; +}; + +class NetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT +public: + + NetworkAccessManager(QObject *parent) : QNetworkAccessManager(parent) + { + } + + QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, + QIODevice *outgoingData) override + { + QUrl url = request.url(); + QString scheme = url.scheme(); + if (op != GetOperation || !scheme.endsWith("+debug")) + return QNetworkAccessManager::createRequest(op, request, outgoingData); + + scheme.chop(sizeof("+debug") - 1); + url.setScheme(scheme); + + NetworkReply *reply = new NetworkReply; + QString filename = QQmlFile::urlToLocalFileOrQrc(url); + QTimer::singleShot(10, reply, [this, reply, filename]() { + if (filename.isEmpty()) { + reply->fail(); + } else { + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + emit loaded(filename); + reply->setData(file.readAll()); + } else + reply->fail(); + } + }); + return reply; + } + +signals: + void loaded(const QString &filename); +}; + +class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory +{ +public: + QStringList loadedFiles; + + QNetworkAccessManager *create(QObject *parent) override + { + NetworkAccessManager *manager = new NetworkAccessManager(parent); + QObject::connect(manager, &NetworkAccessManager::loaded, [this](const QString &filename) { + loadedFiles.append(filename); + }); + return manager; + } +}; + +class UrlInterceptor : public QQmlAbstractUrlInterceptor +{ +public: + QUrl intercept(const QUrl &path, DataType type) override + { + Q_UNUSED(type); + if (!QQmlFile::isLocalFile(path)) + return path; + + // Don't rewrite internal Qt paths. We'd hit C++ plugins there. + QString filename = QQmlFile::urlToLocalFileOrQrc(path); + if (filename.startsWith(":/qt-project.org/") + || filename.startsWith(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath))) { + return path; + } + + QUrl result = path; + QString scheme = result.scheme(); + if (!scheme.endsWith("+debug")) + result.setScheme(scheme + "+debug"); + return result; + } +}; + +void tst_QQMLTypeLoader::intercept() +{ + QQmlEngine engine; + engine.addImportPath(dataDirectory()); + + UrlInterceptor interceptor; + NetworkAccessManagerFactory factory; + + engine.setUrlInterceptor(&interceptor); + engine.setNetworkAccessManagerFactory(&factory); + + QQmlComponent component(&engine, testFileUrl("test_intercept.qml")); + + QVERIFY(component.status() != QQmlComponent::Ready); + QTRY_VERIFY2(component.status() == QQmlComponent::Ready, + component.errorString().toUtf8().constData()); + + QScopedPointer<QObject> o(component.create()); + QVERIFY(o.data()); + + QTRY_COMPARE(o->property("created").toInt(), 2); + QTRY_COMPARE(o->property("loaded").toInt(), 2); + + QCOMPARE(factory.loadedFiles.length(), 4); + QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/test_intercept.qml")); + QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/Intercept.qml")); + QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/Fast/qmldir")); + QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/Fast/Fast.qml")); +} + QTEST_MAIN(tst_QQMLTypeLoader) #include "tst_qqmltypeloader.moc" |