Add a URL interceptor to the QML engine
authorAlan Alpert <aalpert@rim.com>
Fri, 1 Mar 2013 01:03:43 +0000 (17:03 -0800)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Tue, 7 May 2013 17:41:26 +0000 (19:41 +0200)
Allows for custom file handling to a greater extent than the
QNetworkAccessManager.

Change-Id: Ifd3946bf33530c40ca2edeeb9f441f712e4941f6
Reviewed-by: Matthew Vogt <matthew.vogt@qinetic.com.au>

32 files changed:
src/qml/qml/qml.pri
src/qml/qml/qqmlabstracturlinterceptor.cpp [new file with mode: 0644]
src/qml/qml/qqmlabstracturlinterceptor_p.h [new file with mode: 0644]
src/qml/qml/qqmlcompiler.cpp
src/qml/qml/qqmlcontext.cpp
src/qml/qml/qqmlengine.cpp
src/qml/qml/qqmlengine.h
src/qml/qml/qqmlengine_p.h
src/qml/qml/qqmltypeloader.cpp
src/qml/qml/qqmltypeloader_p.h
tests/auto/qml/qqmlengine/data/interception/qmldir/Intercepted.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted.js [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/Intercepted.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/Intercepted2.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/intercepted.js [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/urlInterceptor.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/qmldir/urlInterceptor.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/strings/Intercepted.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/strings/intercepted.js [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/strings/intercepted/Intercepted.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/strings/intercepted/intercepted.js [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/strings/intercepted/urlInterceptor.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/strings/urlInterceptor.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/Intercepted.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/Intercepted2.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/intercepted.js [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/intercepted/Intercepted.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/intercepted/Intercepted2.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/intercepted/intercepted.js [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/intercepted/urlInterceptor.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/data/interception/types/urlInterceptor.qml [new file with mode: 0644]
tests/auto/qml/qqmlengine/tst_qqmlengine.cpp

index aafc50d..9b2926e 100644 (file)
@@ -51,6 +51,7 @@ SOURCES += \
     $$PWD/qqmlmemoryprofiler.cpp \
     $$PWD/qqmlplatform.cpp \
     $$PWD/qqmlbinding.cpp \
+    $$PWD/qqmlabstracturlinterceptor.cpp \
     $$PWD/qqmlapplicationengine.cpp
 
 HEADERS += \
@@ -123,6 +124,7 @@ HEADERS += \
     $$PWD/qqmlplatform_p.h \
     $$PWD/qqmlbinding_p.h \
     $$PWD/qqmlextensionplugin_p.h \
+    $$PWD/qqmlabstracturlinterceptor_p.h \
     $$PWD/qqmlapplicationengine_p.h \
     $$PWD/qqmlapplicationengine.h
 
diff --git a/src/qml/qml/qqmlabstracturlinterceptor.cpp b/src/qml/qml/qqmlabstracturlinterceptor.cpp
new file mode 100644 (file)
index 0000000..a68d5f7
--- /dev/null
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \class QQmlAbstractUrlInterceptor
+ \inmodule QtQml
+ \brief allows you to control QML file loading.
+
+ \note This class is in an extended validation period and still subject to change. It should be treated as private API for 5.1
+
+ QQmlAbstractUrlInterceptor is an interface which can be used to alter URLs
+ before they are used by the QML engine. This is primarily useful for altering
+ file urls into other file urls, such as selecting different graphical assets
+ for the current platform.
+
+ Relative URLs are intercepted after being resolved against the file path of the
+ current QML context. URL interception also occurs after setting the base path for
+ a loaded QML file. This means that the content loaded for that QML file uses the
+ intercepted URL, but inside the file the pre-intercepted URL is used for resolving
+ relative paths. This allows for interception of .qml file loading without needing
+ all paths (or local types) inside intercepted content to insert a different relative path.
+
+ Compared to setNetworkAccessManagerFactory, QQmlAbstractUrlInterceptor affects all URLs
+ and paths, including local files and embedded resource files. QQmlAbstractUrlInterceptor
+ is synchronous, and for asynchronous files must return a url with an asynchronous scheme
+ (such as http or a custom scheme handled by your own custom QNetworkAccessManager). You
+ can use a QQmlAbstractUrlInterceptor to change file URLs into networked URLs which are
+ handled by your own custom QNetworkAccessManager.
+
+ To implement support for a custom networked scheme, see setNetworkAccessManagerFactory.
+*/
+
+/*
+ \enum QQmlAbstractUrlInterceptor::DataType
+
+ Specifies where URL interception is taking place place.
+
+ Because QML loads qmldir files for locating types, there are two URLs involved in loading a QML type. The URL of the (possibly implicit) qmldir used for locating the type and the URL of the file which defines the type. Intercepting
+ both leads to either complex URL replacement or double URL replacements for the same file.
+
+ \value QmldirFile The URL being intercepted is for a Qmldir file. Intercepting this, but not the QmlFile, allows for swapping out entire sub trees.
+ \value JavaScriptFile The URL being intercepted is an import for a Javascript file.
+ \value QmlFile The URL being intercepted is for a Qml file. Intercepting this, but not the Qmldir file, leaves the base dir of a QML file untouched and acts like replacing the file with another file.
+ \value UrlString The URL being intercepted is a url property in a QML file, and not being used to load a file through the engine.
+
+*/
+
+/*!
+ \fn QUrl QQmlAbstractUrlInterceptor::intercept(const QUrl& url, DataType type)
+
+ A pure virtual function where you can intercept the url. The returned value is taken as the
+ new value for the url. The type of url being intercepted is given by the type variable.
+*/
diff --git a/src/qml/qml/qqmlabstracturlinterceptor_p.h b/src/qml/qml/qqmlabstracturlinterceptor_p.h
new file mode 100644 (file)
index 0000000..186d59e
--- /dev/null
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Research In Motion.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+//Private API for 5.1 (at least)
+#ifndef QQMLABSTRACTURLINTERCEPTOR_H
+#define QQMLABSTRACTURLINTERCEPTOR_H
+
+#include <QtCore/qurl.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QML_EXPORT QQmlAbstractUrlInterceptor
+{
+    Q_FLAGS(InterceptionPoint)
+public:
+    enum DataType { //Matches QQmlDataBlob::Type
+        QmlFile = 0,
+        JavaScriptFile = 1,
+        QmldirFile = 2,
+        UrlString = 0x1000
+    };
+
+    QQmlAbstractUrlInterceptor() {}
+    virtual ~QQmlAbstractUrlInterceptor() {}
+    virtual QUrl intercept(const QUrl &path, DataType type) = 0;
+};
+
+QT_END_NAMESPACE
+#endif
index 6951c8c..7b27a4c 100644 (file)
@@ -60,6 +60,7 @@
 #include "qqmlscriptstring.h"
 #include "qqmlglobal_p.h"
 #include "qqmlbinding_p.h"
+#include "qqmlabstracturlinterceptor_p.h"
 #include <private/qv4compiler_p.h>
 
 #include <QDebug>
@@ -517,6 +518,10 @@ void QQmlCompiler::genLiteralAssignment(QQmlScript::Property *prop,
             // Encoded dir-separators defeat QUrl processing - decode them first
             string.replace(QLatin1String("%2f"), QLatin1String("/"), Qt::CaseInsensitive);
             QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(QUrl(string));
+            // Apply URL interceptor
+            if (engine->urlInterceptor())
+                u = engine->urlInterceptor()->intercept(u,
+                        QQmlAbstractUrlInterceptor::UrlString);
             instr.propertyIndex = prop->index;
             instr.value = output->indexForUrl(u);
             output->addInstruction(instr);
index e0a16d1..7fa2472 100644 (file)
@@ -48,6 +48,7 @@
 #include "qqmlengine_p.h"
 #include "qqmlengine.h"
 #include "qqmlinfo.h"
+#include "qqmlabstracturlinterceptor_p.h"
 #include <private/qv4bindings_p.h>
 #include <private/qv8bindings_p.h>
 
@@ -431,6 +432,7 @@ QUrl QQmlContextData::resolvedUrl(const QUrl &src)
 {
     QQmlContextData *ctxt = this;
 
+    QUrl resolved;
     if (src.isRelative() && !src.isEmpty()) {
         if (ctxt) {
             while(ctxt) {
@@ -441,14 +443,20 @@ QUrl QQmlContextData::resolvedUrl(const QUrl &src)
             }
 
             if (ctxt)
-                return ctxt->url.resolved(src);
+                resolved = ctxt->url.resolved(src);
             else if (engine)
-                return engine->baseUrl().resolved(src);
+                resolved = engine->baseUrl().resolved(src);
         }
-        return QUrl();
     } else {
-        return src;
+        resolved = src;
     }
+
+    if (resolved.isEmpty()) //relative but no ctxt
+        return resolved;
+
+    if (engine && engine->urlInterceptor())
+        resolved = engine->urlInterceptor()->intercept(resolved, QQmlAbstractUrlInterceptor::UrlString);
+    return resolved;
 }
 
 
index 1c3aff6..9d2ad8c 100644 (file)
@@ -66,6 +66,7 @@
 #include <private/qv8debugservice_p.h>
 #include <private/qdebugmessageservice_p.h>
 #include "qqmlincubator.h"
+#include "qqmlabstracturlinterceptor_p.h"
 #include <private/qv8profilerservice_p.h>
 #include <private/qqmlboundsignal_p.h>
 
@@ -509,7 +510,7 @@ QQmlEnginePrivate::QQmlEnginePrivate(QQmlEngine *e)
   outputWarningsToStdErr(true), sharedContext(0), sharedScope(0),
   cleanup(0), erroredBindings(0), inProgressCreations(0),
   workerScriptEngine(0), activeVME(0),
-  networkAccessManager(0), networkAccessManagerFactory(0),
+  networkAccessManager(0), networkAccessManagerFactory(0), urlInterceptor(0),
   scarceResourcesRefCount(0), typeLoader(e), importDatabase(e), uniqueId(1),
   incubatorCount(0), incubationController(0), mutex(QMutex::Recursive)
 {
@@ -925,6 +926,35 @@ QQmlContext *QQmlEngine::rootContext() const
 }
 
 /*!
+  \internal
+  This API is private for 5.1
+
+  Sets the \a urlInterceptor to be used when resolving URLs in QML.
+  This also applies to URLs used for loading script files and QML types.
+  This should not be modifed while the engine is loading files, or URL
+  selection may be inconsistent.
+*/
+void QQmlEngine::setUrlInterceptor(QQmlAbstractUrlInterceptor *urlInterceptor)
+{
+    Q_D(QQmlEngine);
+    d->urlInterceptor = urlInterceptor;
+}
+
+/*!
+  \internal
+  This API is private for 5.1
+
+  Returns the current QQmlAbstractUrlInterceptor. It must not be modified outside
+  the GUI thread.
+*/
+QQmlAbstractUrlInterceptor *QQmlEngine::urlInterceptor() const
+{
+    Q_D(const QQmlEngine);
+    return d->urlInterceptor;
+}
+
+
+/*!
   Sets the \a factory to use for creating QNetworkAccessManager(s).
 
   QNetworkAccessManager is used for all network access by QML.  By
index 45826a4..ab25e06 100644 (file)
@@ -51,6 +51,7 @@
 
 QT_BEGIN_NAMESPACE
 
+class QQmlAbstractUrlInterceptor;
 
 class Q_QML_EXPORT QQmlImageProviderBase
 {
@@ -119,6 +120,9 @@ public:
 
     QNetworkAccessManager *networkAccessManager() const;
 
+    void setUrlInterceptor(QQmlAbstractUrlInterceptor* urlInterceptor);
+    QQmlAbstractUrlInterceptor* urlInterceptor() const;
+
     void addImageProvider(const QString &id, QQmlImageProviderBase *);
     QQmlImageProviderBase *imageProvider(const QString &id) const;
     void removeImageProvider(const QString &id);
index b5af0bb..b745d6a 100644 (file)
@@ -173,6 +173,8 @@ public:
 
     QHash<QString,QSharedPointer<QQmlImageProviderBase> > imageProviders;
 
+    QQmlAbstractUrlInterceptor* urlInterceptor;
+
     // Scarce resources are "exceptionally high cost" QVariant types where allowing the
     // normal JavaScript GC to clean them up is likely to lead to out-of-memory or other
     // out-of-resource situations.  When such a resource is passed into JavaScript we
index bbce8e6..8d8503f 100644 (file)
@@ -40,6 +40,7 @@
 ****************************************************************************/
 
 #include "qqmltypeloader_p.h"
+#include "qqmlabstracturlinterceptor_p.h"
 
 #include <private/qqmlengine_p.h>
 #include <private/qqmlglobal_p.h>
@@ -250,6 +251,23 @@ QQmlDataBlob::~QQmlDataBlob()
 }
 
 /*!
+  Sets the manager, and does stuff like selection which needs access to the manager.
+  Must be called before loading can occur.
+*/
+void QQmlDataBlob::startLoading(QQmlDataLoader *manager)
+{
+    Q_ASSERT(status() == QQmlDataBlob::Null);
+    Q_ASSERT(m_manager == 0);
+    m_data.setStatus(QQmlDataBlob::Loading);
+    m_manager = manager;
+
+    //Set here because we need to get the engine from the manager
+    if (manager && manager->engine() && manager->engine()->urlInterceptor())
+        m_url = manager->engine()->urlInterceptor()->intercept(m_url,
+                    (QQmlAbstractUrlInterceptor::DataType)m_type);
+}
+
+/*!
 Returns the type provided to the constructor.
 */
 QQmlDataBlob::Type QQmlDataBlob::type() const
@@ -892,12 +910,7 @@ void QQmlDataLoader::load(QQmlDataBlob *blob, Mode mode)
     qWarning("QQmlDataLoader::load(%s): %s thread", qPrintable(blob->m_url.toString()), 
              m_thread->isThisThread()?"Compile":"Engine");
 #endif
-
-    Q_ASSERT(blob->status() == QQmlDataBlob::Null);
-    Q_ASSERT(blob->m_manager == 0);
-
-    blob->m_data.setStatus(QQmlDataBlob::Loading);
-    blob->m_manager = this;
+    blob->startLoading(this);
 
     if (m_thread->isThisThread()) {
         unlock();
@@ -930,11 +943,7 @@ void QQmlDataLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &da
              m_thread->isThisThread()?"Compile":"Engine");
 #endif
 
-    Q_ASSERT(blob->status() == QQmlDataBlob::Null);
-    Q_ASSERT(blob->m_manager == 0);
-    
-    blob->m_data.setStatus(QQmlDataBlob::Loading);
-    blob->m_manager = this;
+    blob->startLoading(this);
 
     if (m_thread->isThisThread()) {
         unlock();
index 68b8f33..1bd0766 100644 (file)
@@ -68,6 +68,7 @@
 #include <private/qqmldirparser_p.h>
 #include <private/qqmlbundle_p.h>
 #include <private/qflagpointer_p.h>
+#include <private/qqmlabstracturlinterceptor_p.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -92,15 +93,17 @@ public:
         Error                    // Error
     };
 
-    enum Type {
-        QmlFile,
-        JavaScriptFile,
-        QmldirFile
+    enum Type { //Matched in QQmlAbstractUrlInterceptor
+        QmlFile = QQmlAbstractUrlInterceptor::QmlFile,
+        JavaScriptFile = QQmlAbstractUrlInterceptor::JavaScriptFile,
+        QmldirFile = QQmlAbstractUrlInterceptor::QmldirFile
     };
 
     QQmlDataBlob(const QUrl &, Type);
     virtual ~QQmlDataBlob();
 
+    void startLoading(QQmlDataLoader* manager);
+
     Type type() const;
 
     Status status() const;
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/Intercepted.qml b/tests/auto/qml/qqmlengine/data/interception/qmldir/Intercepted.qml
new file mode 100644 (file)
index 0000000..0331a01
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted.js b/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted.js
new file mode 100644 (file)
index 0000000..6f54ebc
--- /dev/null
@@ -0,0 +1 @@
+var myStr = "base file"
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/Intercepted.qml b/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/Intercepted.qml
new file mode 100644 (file)
index 0000000..ef5c28f
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+    property Intercepted2 compilationIsTest: Intercepted2{}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/Intercepted2.qml b/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/Intercepted2.qml
new file mode 100644 (file)
index 0000000..0331a01
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/intercepted.js b/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/intercepted.js
new file mode 100644 (file)
index 0000000..6eeee6e
--- /dev/null
@@ -0,0 +1 @@
+var myStr = "intercepted"
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/urlInterceptor.qml b/tests/auto/qml/qqmlengine/data/interception/qmldir/intercepted/urlInterceptor.qml
new file mode 100644 (file)
index 0000000..bd4aee0
--- /dev/null
@@ -0,0 +1,11 @@
+import QtQml 2.0
+import "intercepted.js" as Script
+
+QtObject {
+    property url filePath: "FailsTest"
+    property url resolvedUrl: Qt.resolvedUrl("FailsTest");
+    property url absoluteUrl: Qt.resolvedUrl("file:///FailsTest");
+    property string childString: child.myStr
+    property string scriptString: Script.myStr
+    property Intercepted child: Intercepted {}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/qmldir/urlInterceptor.qml b/tests/auto/qml/qqmlengine/data/interception/qmldir/urlInterceptor.qml
new file mode 100644 (file)
index 0000000..22a09e5
--- /dev/null
@@ -0,0 +1,12 @@
+import QtQml 2.0
+import "."
+import "intercepted.js" as Script
+
+QtObject {
+    property url filePath: "doesNotExist.file"
+    property url resolvedUrl: Qt.resolvedUrl("doesNotExist.file");
+    property url absoluteUrl: Qt.resolvedUrl("file:///doesNotExist.file");
+    property string childString: child.myStr
+    property string scriptString: Script.myStr
+    property Intercepted child: Intercepted {}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/strings/Intercepted.qml b/tests/auto/qml/qqmlengine/data/interception/strings/Intercepted.qml
new file mode 100644 (file)
index 0000000..449207e
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "base file"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/strings/intercepted.js b/tests/auto/qml/qqmlengine/data/interception/strings/intercepted.js
new file mode 100644 (file)
index 0000000..6f54ebc
--- /dev/null
@@ -0,0 +1 @@
+var myStr = "base file"
diff --git a/tests/auto/qml/qqmlengine/data/interception/strings/intercepted/Intercepted.qml b/tests/auto/qml/qqmlengine/data/interception/strings/intercepted/Intercepted.qml
new file mode 100644 (file)
index 0000000..0331a01
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/strings/intercepted/intercepted.js b/tests/auto/qml/qqmlengine/data/interception/strings/intercepted/intercepted.js
new file mode 100644 (file)
index 0000000..6eeee6e
--- /dev/null
@@ -0,0 +1 @@
+var myStr = "intercepted"
diff --git a/tests/auto/qml/qqmlengine/data/interception/strings/intercepted/urlInterceptor.qml b/tests/auto/qml/qqmlengine/data/interception/strings/intercepted/urlInterceptor.qml
new file mode 100644 (file)
index 0000000..bd4aee0
--- /dev/null
@@ -0,0 +1,11 @@
+import QtQml 2.0
+import "intercepted.js" as Script
+
+QtObject {
+    property url filePath: "FailsTest"
+    property url resolvedUrl: Qt.resolvedUrl("FailsTest");
+    property url absoluteUrl: Qt.resolvedUrl("file:///FailsTest");
+    property string childString: child.myStr
+    property string scriptString: Script.myStr
+    property Intercepted child: Intercepted {}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/strings/urlInterceptor.qml b/tests/auto/qml/qqmlengine/data/interception/strings/urlInterceptor.qml
new file mode 100644 (file)
index 0000000..be86195
--- /dev/null
@@ -0,0 +1,11 @@
+import QtQml 2.0
+import "intercepted.js" as Script
+
+QtObject {
+    property url filePath: "doesNotExist.file"
+    property url resolvedUrl: Qt.resolvedUrl("doesNotExist.file");
+    property url absoluteUrl: Qt.resolvedUrl("file:///doesNotExist.file");
+    property string childString: child.myStr
+    property string scriptString: Script.myStr
+    property Intercepted child: Intercepted {}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/Intercepted.qml b/tests/auto/qml/qqmlengine/data/interception/types/Intercepted.qml
new file mode 100644 (file)
index 0000000..449207e
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "base file"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/Intercepted2.qml b/tests/auto/qml/qqmlengine/data/interception/types/Intercepted2.qml
new file mode 100644 (file)
index 0000000..0331a01
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/intercepted.js b/tests/auto/qml/qqmlengine/data/interception/types/intercepted.js
new file mode 100644 (file)
index 0000000..6f54ebc
--- /dev/null
@@ -0,0 +1 @@
+var myStr = "base file"
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/intercepted/Intercepted.qml b/tests/auto/qml/qqmlengine/data/interception/types/intercepted/Intercepted.qml
new file mode 100644 (file)
index 0000000..ef5c28f
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+    property Intercepted2 compilationIsTest: Intercepted2{}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/intercepted/Intercepted2.qml b/tests/auto/qml/qqmlengine/data/interception/types/intercepted/Intercepted2.qml
new file mode 100644 (file)
index 0000000..0331a01
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQml 2.0
+
+QtObject {
+    property string myStr: "intercepted"
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/intercepted/intercepted.js b/tests/auto/qml/qqmlengine/data/interception/types/intercepted/intercepted.js
new file mode 100644 (file)
index 0000000..6eeee6e
--- /dev/null
@@ -0,0 +1 @@
+var myStr = "intercepted"
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/intercepted/urlInterceptor.qml b/tests/auto/qml/qqmlengine/data/interception/types/intercepted/urlInterceptor.qml
new file mode 100644 (file)
index 0000000..be86195
--- /dev/null
@@ -0,0 +1,11 @@
+import QtQml 2.0
+import "intercepted.js" as Script
+
+QtObject {
+    property url filePath: "doesNotExist.file"
+    property url resolvedUrl: Qt.resolvedUrl("doesNotExist.file");
+    property url absoluteUrl: Qt.resolvedUrl("file:///doesNotExist.file");
+    property string childString: child.myStr
+    property string scriptString: Script.myStr
+    property Intercepted child: Intercepted {}
+}
diff --git a/tests/auto/qml/qqmlengine/data/interception/types/urlInterceptor.qml b/tests/auto/qml/qqmlengine/data/interception/types/urlInterceptor.qml
new file mode 100644 (file)
index 0000000..bd4aee0
--- /dev/null
@@ -0,0 +1,11 @@
+import QtQml 2.0
+import "intercepted.js" as Script
+
+QtObject {
+    property url filePath: "FailsTest"
+    property url resolvedUrl: Qt.resolvedUrl("FailsTest");
+    property url absoluteUrl: Qt.resolvedUrl("file:///FailsTest");
+    property string childString: child.myStr
+    property string scriptString: Script.myStr
+    property Intercepted child: Intercepted {}
+}
index 9177ff5..d604118 100644 (file)
@@ -54,6 +54,7 @@
 #include <QQmlExpression>
 #include <QQmlIncubationController>
 #include <private/qqmlengine_p.h>
+#include <private/qqmlabstracturlinterceptor_p.h>
 
 class tst_qqmlengine : public QQmlDataTest
 {
@@ -79,6 +80,8 @@ private slots:
     void multipleEngines();
     void qtqmlModule_data();
     void qtqmlModule();
+    void urlInterceptor_data();
+    void urlInterceptor();
 
 public slots:
     QObject *createAQObjectForOwnershipTest ()
@@ -674,6 +677,99 @@ void tst_qqmlengine::qtqmlModule()
     }
 }
 
+class CustomSelector : public QQmlAbstractUrlInterceptor
+{
+public:
+    virtual QUrl intercept(const QUrl &url, QQmlAbstractUrlInterceptor::DataType d)
+    {
+        if (url.scheme() != QStringLiteral("file"))
+            return url;
+        if (!m_interceptionPoints.contains(d))
+            return url;
+
+        QString alteredPath = url.path();
+        int a = alteredPath.lastIndexOf('/');
+        if (a < 0)
+            a = 0;
+        alteredPath.insert(a, QStringLiteral("/intercepted"));
+
+        QUrl ret = url;
+        ret.setPath(alteredPath);
+        return ret;
+    }
+    QList<QQmlAbstractUrlInterceptor::DataType> m_interceptionPoints;
+};
+
+Q_DECLARE_METATYPE(QList<QQmlAbstractUrlInterceptor::DataType>);
+void tst_qqmlengine::urlInterceptor_data()
+{
+    QTest::addColumn<QUrl>("testFile");
+    QTest::addColumn<QList<QQmlAbstractUrlInterceptor::DataType> >("interceptionPoint");
+    QTest::addColumn<QString>("expectedFilePath");
+    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)
+        << testFileUrl("interception/types/intercepted/doesNotExist.file").toString()
+        << 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)
+        << testFileUrl("interception/qmldir/intercepted/doesNotExist.file").toString()
+        << QStringLiteral("intercepted")
+        << QStringLiteral("base file")
+        << testFileUrl("interception/qmldir/intercepted/doesNotExist.file").toString()
+        << QStringLiteral("file:///intercepted/doesNotExist.file");
+
+    QTest::newRow("InterceptStrings")
+        << testFileUrl("interception/strings/urlInterceptor.qml")
+        << (QList<QQmlAbstractUrlInterceptor::DataType>() << QQmlAbstractUrlInterceptor::UrlString)
+        << testFileUrl("interception/strings/intercepted/doesNotExist.file").toString()
+        << QStringLiteral("base file")
+        << QStringLiteral("base file")
+        << testFileUrl("interception/strings/intercepted/doesNotExist.file").toString()
+        << QStringLiteral("file:///intercepted/doesNotExist.file");
+}
+
+void tst_qqmlengine::urlInterceptor()
+{
+
+    QFETCH(QUrl, testFile);
+    QFETCH(QList<QQmlAbstractUrlInterceptor::DataType>, interceptionPoint);
+    QFETCH(QString, expectedFilePath);
+    QFETCH(QString, expectedChildString);
+    QFETCH(QString, expectedScriptString);
+    QFETCH(QString, expectedResolvedUrl);
+    QFETCH(QString, expectedAbsoluteUrl);
+
+    QQmlEngine e;
+    CustomSelector cs;
+    cs.m_interceptionPoints = interceptionPoint;
+    e.setUrlInterceptor(&cs);
+    QQmlComponent c(&e, testFile); //Note that this can get intercepted too
+    QObject *o = c.create();
+    if (!o)
+        qDebug() << c.errorString();
+    QVERIFY(o);
+    //Test a URL as a property initialization
+    QCOMPARE(o->property("filePath").toString(), expectedFilePath);
+    //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);
+}
+
 QTEST_MAIN(tst_qqmlengine)
 
 #include "tst_qqmlengine.moc"