aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/qml/qqmlimport.cpp27
-rw-r--r--src/qml/qml/qqmlimport_p.h1
-rw-r--r--src/qml/qml/qqmltypeloader.cpp48
-rw-r--r--tests/auto/qml/qqmltypeloader/data/Intercept.qml41
-rw-r--r--tests/auto/qml/qqmltypeloader/data/test_intercept.qml53
-rw-r--r--tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp168
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"