diff options
Diffstat (limited to 'Source/WebKit/qt/tests/qwebframe')
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/qwebframe.pro | 3 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/resources/image.png | bin | 0 -> 14743 bytes | |||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/resources/style.css | 1 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/resources/test1.html | 1 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/resources/test2.html | 1 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/resources/testiframe.html | 54 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/resources/testiframe2.html | 21 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/tst_qwebframe.cpp | 3645 | ||||
| -rw-r--r-- | Source/WebKit/qt/tests/qwebframe/tst_qwebframe.qrc | 10 |
9 files changed, 3736 insertions, 0 deletions
diff --git a/Source/WebKit/qt/tests/qwebframe/qwebframe.pro b/Source/WebKit/qt/tests/qwebframe/qwebframe.pro new file mode 100644 index 0000000..e915d60 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/qwebframe.pro @@ -0,0 +1,3 @@ +isEmpty(OUTPUT_DIR): OUTPUT_DIR = ../../../.. +include(../tests.pri) +exists($${TARGET}.qrc):RESOURCES += $${TARGET}.qrc diff --git a/Source/WebKit/qt/tests/qwebframe/resources/image.png b/Source/WebKit/qt/tests/qwebframe/resources/image.png Binary files differnew file mode 100644 index 0000000..8d70364 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/resources/image.png diff --git a/Source/WebKit/qt/tests/qwebframe/resources/style.css b/Source/WebKit/qt/tests/qwebframe/resources/style.css new file mode 100644 index 0000000..c05b747 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/resources/style.css @@ -0,0 +1 @@ +#idP {color: red !important} diff --git a/Source/WebKit/qt/tests/qwebframe/resources/test1.html b/Source/WebKit/qt/tests/qwebframe/resources/test1.html new file mode 100644 index 0000000..b323f96 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/resources/test1.html @@ -0,0 +1 @@ +<html><body><p>Some text 1</p></body></html> diff --git a/Source/WebKit/qt/tests/qwebframe/resources/test2.html b/Source/WebKit/qt/tests/qwebframe/resources/test2.html new file mode 100644 index 0000000..63ac1f6 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/resources/test2.html @@ -0,0 +1 @@ +<html><body> <p>Some text 2</p></body></html> diff --git a/Source/WebKit/qt/tests/qwebframe/resources/testiframe.html b/Source/WebKit/qt/tests/qwebframe/resources/testiframe.html new file mode 100644 index 0000000..203d3d3 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/resources/testiframe.html @@ -0,0 +1,54 @@ +</html>
+<html>
+<head>
+<title></title>
+<style type="text/css">
+<!--
+#header {
+ background: #0f0;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 800px;
+ height: 100px;
+}
+#content1 {
+ background: #ff0;
+ position: absolute;
+ top: 101px;
+ left: 0px;
+ width: 400px;
+ height: 400px;
+ overflow: scroll;
+}
+#content2 {
+ background: #ff7;
+ position: absolute;
+ top: 101px;
+ left: 401px;
+ width: 400px;
+ height: 400px;
+}
+#footer {
+ background: #0f0;
+ position: absolute;
+ top: 502px;
+ left: 0px;
+ width: 800px;
+ height: 200px;
+}
+-->
+</style>
+</head>
+<body>
+<div id="header"></div>
+<div id="content1">You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.
+You can use the overflow property when you want to have better control of the layout. Try to change the overflow property to: visible, hidden, auto, or inherit and see what happens. The default value is visible.</div>
+<iframe id="content2" name="control" src="testiframe2.html"> </iframe>
+<div id="footer"></div>
+</body>
+</html>
\ No newline at end of file diff --git a/Source/WebKit/qt/tests/qwebframe/resources/testiframe2.html b/Source/WebKit/qt/tests/qwebframe/resources/testiframe2.html new file mode 100644 index 0000000..0d3a22f --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/resources/testiframe2.html @@ -0,0 +1,21 @@ +</html>
+<html>
+<head>
+<title></title>
+<style type="text/css">
+<!--
+#content {
+ background: #fff;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 800px;
+ height: 800px;
+}
+-->
+</style>
+</head>
+<body>
+<div id="content"> </div>
+</body>
+</html>
\ No newline at end of file diff --git a/Source/WebKit/qt/tests/qwebframe/tst_qwebframe.cpp b/Source/WebKit/qt/tests/qwebframe/tst_qwebframe.cpp new file mode 100644 index 0000000..3b9324d --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/tst_qwebframe.cpp @@ -0,0 +1,3645 @@ +/* + Copyright (C) 2008,2009 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include <QtTest/QtTest> + +#include <qwebpage.h> +#include <qwebelement.h> +#include <qwidget.h> +#include <qwebview.h> +#include <qwebframe.h> +#include <qwebhistory.h> +#include <QAbstractItemView> +#include <QApplication> +#include <QComboBox> +#include <QPaintEngine> +#include <QPicture> +#include <QRegExp> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QTextCodec> +#ifndef QT_NO_OPENSSL +#include <qsslerror.h> +#endif +#include "../util.h" + +struct CustomType { + QString string; +}; +Q_DECLARE_METATYPE(CustomType) + +Q_DECLARE_METATYPE(QBrush*) +Q_DECLARE_METATYPE(QObjectList) +Q_DECLARE_METATYPE(QList<int>) +Q_DECLARE_METATYPE(Qt::BrushStyle) +Q_DECLARE_METATYPE(QVariantList) +Q_DECLARE_METATYPE(QVariantMap) + +class MyQObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int intProperty READ intProperty WRITE setIntProperty) + Q_PROPERTY(QVariant variantProperty READ variantProperty WRITE setVariantProperty) + Q_PROPERTY(QVariantList variantListProperty READ variantListProperty WRITE setVariantListProperty) + Q_PROPERTY(QVariantMap variantMapProperty READ variantMapProperty WRITE setVariantMapProperty) + Q_PROPERTY(QString stringProperty READ stringProperty WRITE setStringProperty) + Q_PROPERTY(QStringList stringListProperty READ stringListProperty WRITE setStringListProperty) + Q_PROPERTY(QByteArray byteArrayProperty READ byteArrayProperty WRITE setByteArrayProperty) + Q_PROPERTY(QBrush brushProperty READ brushProperty WRITE setBrushProperty) + Q_PROPERTY(double hiddenProperty READ hiddenProperty WRITE setHiddenProperty SCRIPTABLE false) + Q_PROPERTY(int writeOnlyProperty WRITE setWriteOnlyProperty) + Q_PROPERTY(int readOnlyProperty READ readOnlyProperty) + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) + Q_PROPERTY(CustomType propWithCustomType READ propWithCustomType WRITE setPropWithCustomType) + Q_PROPERTY(QWebElement webElementProperty READ webElementProperty WRITE setWebElementProperty) + Q_PROPERTY(QObject* objectStarProperty READ objectStarProperty WRITE setObjectStarProperty) + Q_ENUMS(Policy Strategy) + Q_FLAGS(Ability) + +public: + enum Policy { + FooPolicy = 0, + BarPolicy, + BazPolicy + }; + + enum Strategy { + FooStrategy = 10, + BarStrategy, + BazStrategy + }; + + enum AbilityFlag { + NoAbility = 0x000, + FooAbility = 0x001, + BarAbility = 0x080, + BazAbility = 0x200, + AllAbility = FooAbility | BarAbility | BazAbility + }; + + Q_DECLARE_FLAGS(Ability, AbilityFlag) + + MyQObject(QObject* parent = 0) + : QObject(parent), + m_intValue(123), + m_variantValue(QLatin1String("foo")), + m_variantListValue(QVariantList() << QVariant(123) << QVariant(QLatin1String("foo"))), + m_stringValue(QLatin1String("bar")), + m_stringListValue(QStringList() << QLatin1String("zig") << QLatin1String("zag")), + m_brushValue(QColor(10, 20, 30, 40)), + m_hiddenValue(456.0), + m_writeOnlyValue(789), + m_readOnlyValue(987), + m_objectStar(0), + m_qtFunctionInvoked(-1) + { + m_variantMapValue.insert("a", QVariant(123)); + m_variantMapValue.insert("b", QVariant(QLatin1String("foo"))); + m_variantMapValue.insert("c", QVariant::fromValue<QObject*>(this)); + } + + ~MyQObject() { } + + int intProperty() const { + return m_intValue; + } + void setIntProperty(int value) { + m_intValue = value; + } + + QVariant variantProperty() const { + return m_variantValue; + } + void setVariantProperty(const QVariant &value) { + m_variantValue = value; + } + + QVariantList variantListProperty() const { + return m_variantListValue; + } + void setVariantListProperty(const QVariantList &value) { + m_variantListValue = value; + } + + QVariantMap variantMapProperty() const { + return m_variantMapValue; + } + void setVariantMapProperty(const QVariantMap &value) { + m_variantMapValue = value; + } + + QString stringProperty() const { + return m_stringValue; + } + void setStringProperty(const QString &value) { + m_stringValue = value; + } + + QStringList stringListProperty() const { + return m_stringListValue; + } + void setStringListProperty(const QStringList &value) { + m_stringListValue = value; + } + + QByteArray byteArrayProperty() const { + return m_byteArrayValue; + } + void setByteArrayProperty(const QByteArray &value) { + m_byteArrayValue = value; + } + + QBrush brushProperty() const { + return m_brushValue; + } + Q_INVOKABLE void setBrushProperty(const QBrush &value) { + m_brushValue = value; + } + + double hiddenProperty() const { + return m_hiddenValue; + } + void setHiddenProperty(double value) { + m_hiddenValue = value; + } + + int writeOnlyProperty() const { + return m_writeOnlyValue; + } + void setWriteOnlyProperty(int value) { + m_writeOnlyValue = value; + } + + int readOnlyProperty() const { + return m_readOnlyValue; + } + + QKeySequence shortcut() const { + return m_shortcut; + } + void setShortcut(const QKeySequence &seq) { + m_shortcut = seq; + } + + QWebElement webElementProperty() const { + return m_webElement; + } + + void setWebElementProperty(const QWebElement& element) { + m_webElement = element; + } + + CustomType propWithCustomType() const { + return m_customType; + } + void setPropWithCustomType(const CustomType &c) { + m_customType = c; + } + + QObject* objectStarProperty() const { + return m_objectStar; + } + + void setObjectStarProperty(QObject* object) { + m_objectStar = object; + } + + + int qtFunctionInvoked() const { + return m_qtFunctionInvoked; + } + + QVariantList qtFunctionActuals() const { + return m_actuals; + } + + void resetQtFunctionInvoked() { + m_qtFunctionInvoked = -1; + m_actuals.clear(); + } + + Q_INVOKABLE void myInvokable() { + m_qtFunctionInvoked = 0; + } + Q_INVOKABLE void myInvokableWithIntArg(int arg) { + m_qtFunctionInvoked = 1; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithLonglongArg(qlonglong arg) { + m_qtFunctionInvoked = 2; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithFloatArg(float arg) { + m_qtFunctionInvoked = 3; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithDoubleArg(double arg) { + m_qtFunctionInvoked = 4; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithStringArg(const QString &arg) { + m_qtFunctionInvoked = 5; + m_actuals << arg; + } + Q_INVOKABLE void myInvokableWithIntArgs(int arg1, int arg2) { + m_qtFunctionInvoked = 6; + m_actuals << arg1 << arg2; + } + Q_INVOKABLE int myInvokableReturningInt() { + m_qtFunctionInvoked = 7; + return 123; + } + Q_INVOKABLE qlonglong myInvokableReturningLongLong() { + m_qtFunctionInvoked = 39; + return 456; + } + Q_INVOKABLE QString myInvokableReturningString() { + m_qtFunctionInvoked = 8; + return QLatin1String("ciao"); + } + Q_INVOKABLE void myInvokableWithIntArg(int arg1, int arg2) { // overload + m_qtFunctionInvoked = 9; + m_actuals << arg1 << arg2; + } + Q_INVOKABLE void myInvokableWithEnumArg(Policy policy) { + m_qtFunctionInvoked = 10; + m_actuals << policy; + } + Q_INVOKABLE void myInvokableWithQualifiedEnumArg(MyQObject::Policy policy) { + m_qtFunctionInvoked = 36; + m_actuals << policy; + } + Q_INVOKABLE Policy myInvokableReturningEnum() { + m_qtFunctionInvoked = 37; + return BazPolicy; + } + Q_INVOKABLE MyQObject::Policy myInvokableReturningQualifiedEnum() { + m_qtFunctionInvoked = 38; + return BazPolicy; + } + Q_INVOKABLE QVector<int> myInvokableReturningVectorOfInt() { + m_qtFunctionInvoked = 11; + return QVector<int>(); + } + Q_INVOKABLE void myInvokableWithVectorOfIntArg(const QVector<int> &) { + m_qtFunctionInvoked = 12; + } + Q_INVOKABLE QObject* myInvokableReturningQObjectStar() { + m_qtFunctionInvoked = 13; + return this; + } + Q_INVOKABLE QObjectList myInvokableWithQObjectListArg(const QObjectList &lst) { + m_qtFunctionInvoked = 14; + m_actuals << QVariant::fromValue(lst); + return lst; + } + Q_INVOKABLE QVariant myInvokableWithVariantArg(const QVariant &v) { + m_qtFunctionInvoked = 15; + m_actuals << v; + return v; + } + Q_INVOKABLE QVariantMap myInvokableWithVariantMapArg(const QVariantMap &vm) { + m_qtFunctionInvoked = 16; + m_actuals << vm; + return vm; + } + Q_INVOKABLE QList<int> myInvokableWithListOfIntArg(const QList<int> &lst) { + m_qtFunctionInvoked = 17; + m_actuals << QVariant::fromValue(lst); + return lst; + } + Q_INVOKABLE QObject* myInvokableWithQObjectStarArg(QObject* obj) { + m_qtFunctionInvoked = 18; + m_actuals << QVariant::fromValue(obj); + return obj; + } + Q_INVOKABLE QBrush myInvokableWithQBrushArg(const QBrush &brush) { + m_qtFunctionInvoked = 19; + m_actuals << QVariant::fromValue(brush); + return brush; + } + Q_INVOKABLE void myInvokableWithBrushStyleArg(Qt::BrushStyle style) { + m_qtFunctionInvoked = 43; + m_actuals << QVariant::fromValue(style); + } + Q_INVOKABLE void myInvokableWithVoidStarArg(void* arg) { + m_qtFunctionInvoked = 44; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithAmbiguousArg(int arg) { + m_qtFunctionInvoked = 45; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithAmbiguousArg(uint arg) { + m_qtFunctionInvoked = 46; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithDefaultArgs(int arg1, const QString &arg2 = "") { + m_qtFunctionInvoked = 47; + m_actuals << QVariant::fromValue(arg1) << qVariantFromValue(arg2); + } + Q_INVOKABLE QObject& myInvokableReturningRef() { + m_qtFunctionInvoked = 48; + return *this; + } + Q_INVOKABLE const QObject& myInvokableReturningConstRef() const { + const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 49; + return *this; + } + Q_INVOKABLE void myInvokableWithPointArg(const QPoint &arg) { + const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 50; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithPointArg(const QPointF &arg) { + const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 51; + m_actuals << QVariant::fromValue(arg); + } + Q_INVOKABLE void myInvokableWithBoolArg(bool arg) { + m_qtFunctionInvoked = 52; + m_actuals << arg; + } + + void emitMySignal() { + emit mySignal(); + } + void emitMySignalWithIntArg(int arg) { + emit mySignalWithIntArg(arg); + } + void emitMySignal2(bool arg) { + emit mySignal2(arg); + } + void emitMySignal2() { + emit mySignal2(); + } + void emitMySignalWithDateTimeArg(QDateTime dt) { + emit mySignalWithDateTimeArg(dt); + } + void emitMySignalWithRegexArg(QRegExp r) { + emit mySignalWithRegexArg(r); + } + +public Q_SLOTS: + void mySlot() { + m_qtFunctionInvoked = 20; + } + void mySlotWithIntArg(int arg) { + m_qtFunctionInvoked = 21; + m_actuals << arg; + } + void mySlotWithDoubleArg(double arg) { + m_qtFunctionInvoked = 22; + m_actuals << arg; + } + void mySlotWithStringArg(const QString &arg) { + m_qtFunctionInvoked = 23; + m_actuals << arg; + } + + void myOverloadedSlot() { + m_qtFunctionInvoked = 24; + } + void myOverloadedSlot(QObject* arg) { + m_qtFunctionInvoked = 41; + m_actuals << QVariant::fromValue(arg); + } + void myOverloadedSlot(bool arg) { + m_qtFunctionInvoked = 25; + m_actuals << arg; + } + void myOverloadedSlot(const QStringList &arg) { + m_qtFunctionInvoked = 42; + m_actuals << arg; + } + void myOverloadedSlot(double arg) { + m_qtFunctionInvoked = 26; + m_actuals << arg; + } + void myOverloadedSlot(float arg) { + m_qtFunctionInvoked = 27; + m_actuals << arg; + } + void myOverloadedSlot(int arg) { + m_qtFunctionInvoked = 28; + m_actuals << arg; + } + void myOverloadedSlot(const QString &arg) { + m_qtFunctionInvoked = 29; + m_actuals << arg; + } + void myOverloadedSlot(const QColor &arg) { + m_qtFunctionInvoked = 30; + m_actuals << arg; + } + void myOverloadedSlot(const QBrush &arg) { + m_qtFunctionInvoked = 31; + m_actuals << arg; + } + void myOverloadedSlot(const QDateTime &arg) { + m_qtFunctionInvoked = 32; + m_actuals << arg; + } + void myOverloadedSlot(const QDate &arg) { + m_qtFunctionInvoked = 33; + m_actuals << arg; + } + void myOverloadedSlot(const QRegExp &arg) { + m_qtFunctionInvoked = 34; + m_actuals << arg; + } + void myOverloadedSlot(const QVariant &arg) { + m_qtFunctionInvoked = 35; + m_actuals << arg; + } + void myOverloadedSlot(const QWebElement &arg) { + m_qtFunctionInvoked = 36; + m_actuals << QVariant::fromValue<QWebElement>(arg); + } + + void qscript_call(int arg) { + m_qtFunctionInvoked = 40; + m_actuals << arg; + } + +protected Q_SLOTS: + void myProtectedSlot() { + m_qtFunctionInvoked = 36; + } + +private Q_SLOTS: + void myPrivateSlot() { } + +Q_SIGNALS: + void mySignal(); + void mySignalWithIntArg(int arg); + void mySignalWithDoubleArg(double arg); + void mySignal2(bool arg = false); + void mySignalWithDateTimeArg(QDateTime dt); + void mySignalWithRegexArg(QRegExp r); + +private: + int m_intValue; + QVariant m_variantValue; + QVariantList m_variantListValue; + QVariantMap m_variantMapValue; + QString m_stringValue; + QStringList m_stringListValue; + QByteArray m_byteArrayValue; + QBrush m_brushValue; + double m_hiddenValue; + int m_writeOnlyValue; + int m_readOnlyValue; + QKeySequence m_shortcut; + QWebElement m_webElement; + CustomType m_customType; + QObject* m_objectStar; + int m_qtFunctionInvoked; + QVariantList m_actuals; +}; + +class MyWebElementSlotOnlyObject : public QObject { + Q_OBJECT + Q_PROPERTY(QString tagName READ tagName) +public slots: + void doSomethingWithWebElement(const QWebElement& element) + { + m_tagName = element.tagName(); + } + +public: + QString tagName() const + { + return m_tagName; + } +private: + QString m_tagName; +}; + +class MyOtherQObject : public MyQObject +{ +public: + MyOtherQObject(QObject* parent = 0) + : MyQObject(parent) { } +}; + +class MyEnumTestQObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString p1 READ p1) + Q_PROPERTY(QString p2 READ p2) + Q_PROPERTY(QString p3 READ p3 SCRIPTABLE false) + Q_PROPERTY(QString p4 READ p4) + Q_PROPERTY(QString p5 READ p5 SCRIPTABLE false) + Q_PROPERTY(QString p6 READ p6) +public: + MyEnumTestQObject(QObject* parent = 0) + : QObject(parent) { } + QString p1() const { + return QLatin1String("p1"); + } + QString p2() const { + return QLatin1String("p2"); + } + QString p3() const { + return QLatin1String("p3"); + } + QString p4() const { + return QLatin1String("p4"); + } + QString p5() const { + return QLatin1String("p5"); + } + QString p6() const { + return QLatin1String("p5"); + } +public Q_SLOTS: + void mySlot() { } + void myOtherSlot() { } +Q_SIGNALS: + void mySignal(); +}; + +class tst_QWebFrame : public QObject +{ + Q_OBJECT + +public: + tst_QWebFrame(); + virtual ~tst_QWebFrame(); + bool eventFilter(QObject* watched, QEvent* event); + +public slots: + void init(); + void cleanup(); + +private slots: + void horizontalScrollAfterBack(); + void getSetStaticProperty(); + void getSetDynamicProperty(); + void getSetChildren(); + void callQtInvokable(); + void connectAndDisconnect(); + void classEnums(); + void classConstructor(); + void overrideInvokable(); + void transferInvokable(); + void findChild(); + void findChildren(); + void overloadedSlots(); + void webElementSlotOnly(); + void enumerate_data(); + void enumerate(); + void objectDeleted(); + void typeConversion(); + void arrayObjectEnumerable(); + void symmetricUrl(); + void progressSignal(); + void urlChange(); + void domCycles(); + void requestedUrl(); + void requestedUrlAfterSetAndLoadFailures(); + void javaScriptWindowObjectCleared_data(); + void javaScriptWindowObjectCleared(); + void javaScriptWindowObjectClearedOnEvaluate(); + void setHtml(); + void setHtmlWithResource(); + void setHtmlWithBaseURL(); + void setHtmlWithJSAlert(); + void ipv6HostEncoding(); + void metaData(); +#if !defined(Q_WS_MAEMO_5) && !defined(Q_OS_SYMBIAN) && !defined(QT_NO_COMBOBOX) + // as maemo 5 && symbian do not use QComboBoxes to implement the popups + // this test does not make sense for it. + void popupFocus(); +#endif + void inputFieldFocus(); + void hitTestContent(); + void jsByteArray(); + void ownership(); + void nullValue(); + void baseUrl_data(); + void baseUrl(); + void hasSetFocus(); + void render(); + void renderHints(); + void scrollPosition(); + void scrollToAnchor(); + void scrollbarsOff(); + void evaluateWillCauseRepaint(); + void qObjectWrapperWithSameIdentity(); + void introspectQtMethods_data(); + void introspectQtMethods(); + void setContent_data(); + void setContent(); + void setCacheLoadControlAttribute(); + void setUrlWithPendingLoads(); + void setUrlWithFragment_data(); + void setUrlWithFragment(); + void setUrlToEmpty(); + void setUrlToInvalid(); + void setUrlHistory(); + void setUrlSameUrl(); + void setUrlThenLoads_data(); + void setUrlThenLoads(); + +private: + QString evalJS(const QString&s) { + // Convert an undefined return variant to the string "undefined" + QVariant ret = evalJSV(s); + if (ret.userType() == QMetaType::Void) + return "undefined"; + else + return ret.toString(); + } + QVariant evalJSV(const QString &s) { + return m_page->mainFrame()->evaluateJavaScript(s); + } + + QString evalJS(const QString&s, QString& type) { + return evalJSV(s, type).toString(); + } + QVariant evalJSV(const QString &s, QString& type) { + // As a special measure, if we get an exception we set the type to 'error' + // (in ecma, an Error object has typeof object, but qtscript has a convenience function) + // Similarly, an array is an object, but we'd prefer to have a type of 'array' + // Also, consider a QMetaType::Void QVariant to be undefined + QString escaped = s; + escaped.replace('\'', "\\'"); // Don't preescape your single quotes! + evalJS("var retvalue;\ + var typevalue; \ + try {\ + retvalue = eval('" + escaped + "'); \ + typevalue = typeof retvalue; \ + if (retvalue instanceof Array) \ + typevalue = 'array'; \ + } \ + catch(e) {\ + retvalue = e.name + ': ' + e.message;\ + typevalue = 'error';\ + }"); + QVariant ret = evalJSV("retvalue"); + if (ret.userType() != QMetaType::Void) + type = evalJS("typevalue"); + else { + ret = QString("undefined"); + type = sUndefined; + } + evalJS("delete retvalue; delete typevalue"); + return ret; + } + QObject* firstChildByClassName(QObject* parent, const char* className) { + const QObjectList & children = parent->children(); + foreach (QObject* child, children) { + if (!strcmp(child->metaObject()->className(), className)) { + return child; + } + } + return 0; + } + + const QString sTrue; + const QString sFalse; + const QString sUndefined; + const QString sArray; + const QString sFunction; + const QString sError; + const QString sString; + const QString sObject; + const QString sNumber; + +private: + QWebView* m_view; + QWebPage* m_page; + MyQObject* m_myObject; + QWebView* m_inputFieldsTestView; + int m_inputFieldTestPaintCount; +}; + +tst_QWebFrame::tst_QWebFrame() + : sTrue("true"), sFalse("false"), sUndefined("undefined"), sArray("array"), sFunction("function"), sError("error"), + sString("string"), sObject("object"), sNumber("number"), m_inputFieldsTestView(0), m_inputFieldTestPaintCount(0) +{ +} + +tst_QWebFrame::~tst_QWebFrame() +{ +} + +bool tst_QWebFrame::eventFilter(QObject* watched, QEvent* event) +{ + // used on the inputFieldFocus test + if (watched == m_inputFieldsTestView) { + if (event->type() == QEvent::Paint) + m_inputFieldTestPaintCount++; + } + return QObject::eventFilter(watched, event); +} + +void tst_QWebFrame::init() +{ + m_view = new QWebView(); + m_page = m_view->page(); + m_myObject = new MyQObject(); + m_page->mainFrame()->addToJavaScriptWindowObject("myObject", m_myObject); +} + +void tst_QWebFrame::cleanup() +{ + delete m_view; + delete m_myObject; +} + +void tst_QWebFrame::getSetStaticProperty() +{ + m_page->mainFrame()->setHtml("<html><head><body></body></html>"); + QCOMPARE(evalJS("typeof myObject.noSuchProperty"), sUndefined); + + // initial value (set in MyQObject constructor) + { + QString type; + QVariant ret = evalJSV("myObject.intProperty", type); + QCOMPARE(type, sNumber); + QCOMPARE(ret.type(), QVariant::Double); + QCOMPARE(ret.toInt(), 123); + } + QCOMPARE(evalJS("myObject.intProperty === 123.0"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.variantProperty", type); + QCOMPARE(type, sString); + QCOMPARE(ret.type(), QVariant::String); + QCOMPARE(ret.toString(), QLatin1String("foo")); + } + QCOMPARE(evalJS("myObject.variantProperty == 'foo'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.stringProperty", type); + QCOMPARE(type, sString); + QCOMPARE(ret.type(), QVariant::String); + QCOMPARE(ret.toString(), QLatin1String("bar")); + } + QCOMPARE(evalJS("myObject.stringProperty === 'bar'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.variantListProperty", type); + QCOMPARE(type, sArray); + QCOMPARE(ret.type(), QVariant::List); + QVariantList vl = ret.value<QVariantList>(); + QCOMPARE(vl.size(), 2); + QCOMPARE(vl.at(0).toInt(), 123); + QCOMPARE(vl.at(1).toString(), QLatin1String("foo")); + } + QCOMPARE(evalJS("myObject.variantListProperty.length === 2"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[0] === 123"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[1] === 'foo'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.variantMapProperty", type); + QCOMPARE(type, sObject); + QCOMPARE(ret.type(), QVariant::Map); + QVariantMap vm = ret.value<QVariantMap>(); + QCOMPARE(vm.size(), 3); + QCOMPARE(vm.value("a").toInt(), 123); + QCOMPARE(vm.value("b").toString(), QLatin1String("foo")); + QCOMPARE(vm.value("c").value<QObject*>(), static_cast<QObject*>(m_myObject)); + } + QCOMPARE(evalJS("myObject.variantMapProperty.a === 123"), sTrue); + QCOMPARE(evalJS("myObject.variantMapProperty.b === 'foo'"), sTrue); + QCOMPARE(evalJS("myObject.variantMapProperty.c.variantMapProperty.b === 'foo'"), sTrue); + + { + QString type; + QVariant ret = evalJSV("myObject.stringListProperty", type); + QCOMPARE(type, sArray); + QCOMPARE(ret.type(), QVariant::List); + QVariantList vl = ret.value<QVariantList>(); + QCOMPARE(vl.size(), 2); + QCOMPARE(vl.at(0).toString(), QLatin1String("zig")); + QCOMPARE(vl.at(1).toString(), QLatin1String("zag")); + } + QCOMPARE(evalJS("myObject.stringListProperty.length === 2"), sTrue); + QCOMPARE(evalJS("typeof myObject.stringListProperty[0]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[0]"), QLatin1String("zig")); + QCOMPARE(evalJS("typeof myObject.stringListProperty[1]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[1]"), QLatin1String("zag")); + + // property change in C++ should be reflected in script + m_myObject->setIntProperty(456); + QCOMPARE(evalJS("myObject.intProperty == 456"), sTrue); + m_myObject->setIntProperty(789); + QCOMPARE(evalJS("myObject.intProperty == 789"), sTrue); + + m_myObject->setVariantProperty(QLatin1String("bar")); + QCOMPARE(evalJS("myObject.variantProperty === 'bar'"), sTrue); + m_myObject->setVariantProperty(42); + QCOMPARE(evalJS("myObject.variantProperty === 42"), sTrue); + m_myObject->setVariantProperty(QVariant::fromValue(QBrush())); +//XFAIL +// QCOMPARE(evalJS("typeof myObject.variantProperty"), sVariant); + + m_myObject->setStringProperty(QLatin1String("baz")); + QCOMPARE(evalJS("myObject.stringProperty === 'baz'"), sTrue); + m_myObject->setStringProperty(QLatin1String("zab")); + QCOMPARE(evalJS("myObject.stringProperty === 'zab'"), sTrue); + + // property change in script should be reflected in C++ + QCOMPARE(evalJS("myObject.intProperty = 123"), QLatin1String("123")); + QCOMPARE(evalJS("myObject.intProperty == 123"), sTrue); + QCOMPARE(m_myObject->intProperty(), 123); + QCOMPARE(evalJS("myObject.intProperty = 'ciao!';" + "myObject.intProperty == 0"), sTrue); + QCOMPARE(m_myObject->intProperty(), 0); + QCOMPARE(evalJS("myObject.intProperty = '123';" + "myObject.intProperty == 123"), sTrue); + QCOMPARE(m_myObject->intProperty(), 123); + + QCOMPARE(evalJS("myObject.stringProperty = 'ciao'"), QLatin1String("ciao")); + QCOMPARE(evalJS("myObject.stringProperty"), QLatin1String("ciao")); + QCOMPARE(m_myObject->stringProperty(), QLatin1String("ciao")); + QCOMPARE(evalJS("myObject.stringProperty = 123;" + "myObject.stringProperty"), QLatin1String("123")); + QCOMPARE(m_myObject->stringProperty(), QLatin1String("123")); + QCOMPARE(evalJS("myObject.stringProperty = null"), QString()); + QCOMPARE(evalJS("myObject.stringProperty"), QString()); + QCOMPARE(m_myObject->stringProperty(), QString()); + QCOMPARE(evalJS("myObject.stringProperty = undefined"), sUndefined); + QCOMPARE(evalJS("myObject.stringProperty"), QString()); + QCOMPARE(m_myObject->stringProperty(), QString()); + + QCOMPARE(evalJS("myObject.variantProperty = new Number(1234);" + "myObject.variantProperty").toDouble(), 1234.0); + QCOMPARE(m_myObject->variantProperty().toDouble(), 1234.0); + + QCOMPARE(evalJS("myObject.variantProperty = new Boolean(1234);" + "myObject.variantProperty"), sTrue); + QCOMPARE(m_myObject->variantProperty().toBool(), true); + + QCOMPARE(evalJS("myObject.variantProperty = null;" + "myObject.variantProperty.valueOf()"), sUndefined); + QCOMPARE(m_myObject->variantProperty(), QVariant()); + QCOMPARE(evalJS("myObject.variantProperty = undefined;" + "myObject.variantProperty.valueOf()"), sUndefined); + QCOMPARE(m_myObject->variantProperty(), QVariant()); + + QCOMPARE(evalJS("myObject.variantProperty = 'foo';" + "myObject.variantProperty.valueOf()"), QLatin1String("foo")); + QCOMPARE(m_myObject->variantProperty(), QVariant(QLatin1String("foo"))); + QCOMPARE(evalJS("myObject.variantProperty = 42;" + "myObject.variantProperty").toDouble(), 42.0); + QCOMPARE(m_myObject->variantProperty().toDouble(), 42.0); + + QCOMPARE(evalJS("myObject.variantListProperty = [1, 'two', true];" + "myObject.variantListProperty.length == 3"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[0] === 1"), sTrue); + QCOMPARE(evalJS("myObject.variantListProperty[1]"), QLatin1String("two")); + QCOMPARE(evalJS("myObject.variantListProperty[2] === true"), sTrue); + + QCOMPARE(evalJS("myObject.stringListProperty = [1, 'two', true];" + "myObject.stringListProperty.length == 3"), sTrue); + QCOMPARE(evalJS("typeof myObject.stringListProperty[0]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[0] == '1'"), sTrue); + QCOMPARE(evalJS("typeof myObject.stringListProperty[1]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[1]"), QLatin1String("two")); + QCOMPARE(evalJS("typeof myObject.stringListProperty[2]"), sString); + QCOMPARE(evalJS("myObject.stringListProperty[2]"), QLatin1String("true")); + evalJS("myObject.webElementProperty=document.body;"); + QCOMPARE(evalJS("myObject.webElementProperty.tagName"), QLatin1String("BODY")); + + // try to delete + QCOMPARE(evalJS("delete myObject.intProperty"), sFalse); + QCOMPARE(evalJS("myObject.intProperty == 123"), sTrue); + + QCOMPARE(evalJS("delete myObject.variantProperty"), sFalse); + QCOMPARE(evalJS("myObject.variantProperty").toDouble(), 42.0); + + // custom property + QCOMPARE(evalJS("myObject.customProperty"), sUndefined); + QCOMPARE(evalJS("myObject.customProperty = 123;" + "myObject.customProperty == 123"), sTrue); + QVariant v = m_page->mainFrame()->evaluateJavaScript("myObject.customProperty"); + QCOMPARE(v.type(), QVariant::Double); + QCOMPARE(v.toInt(), 123); + + // non-scriptable property + QCOMPARE(m_myObject->hiddenProperty(), 456.0); + QCOMPARE(evalJS("myObject.hiddenProperty"), sUndefined); + QCOMPARE(evalJS("myObject.hiddenProperty = 123;" + "myObject.hiddenProperty == 123"), sTrue); + QCOMPARE(m_myObject->hiddenProperty(), 456.0); + + // write-only property + QCOMPARE(m_myObject->writeOnlyProperty(), 789); + QCOMPARE(evalJS("typeof myObject.writeOnlyProperty"), sUndefined); + QCOMPARE(evalJS("myObject.writeOnlyProperty = 123;" + "typeof myObject.writeOnlyProperty"), sUndefined); + QCOMPARE(m_myObject->writeOnlyProperty(), 123); + + // read-only property + QCOMPARE(m_myObject->readOnlyProperty(), 987); + QCOMPARE(evalJS("myObject.readOnlyProperty == 987"), sTrue); + QCOMPARE(evalJS("myObject.readOnlyProperty = 654;" + "myObject.readOnlyProperty == 987"), sTrue); + QCOMPARE(m_myObject->readOnlyProperty(), 987); + + // QObject* property + m_myObject->setObjectStarProperty(0); + QCOMPARE(m_myObject->objectStarProperty(), (QObject*)0); + QCOMPARE(evalJS("myObject.objectStarProperty == null"), sTrue); + QCOMPARE(evalJS("typeof myObject.objectStarProperty"), sObject); + QCOMPARE(evalJS("Boolean(myObject.objectStarProperty)"), sFalse); + QCOMPARE(evalJS("String(myObject.objectStarProperty) == 'null'"), sTrue); + QCOMPARE(evalJS("myObject.objectStarProperty.objectStarProperty"), + sUndefined); + m_myObject->setObjectStarProperty(this); + QCOMPARE(evalJS("myObject.objectStarProperty != null"), sTrue); + QCOMPARE(evalJS("typeof myObject.objectStarProperty"), sObject); + QCOMPARE(evalJS("Boolean(myObject.objectStarProperty)"), sTrue); + QCOMPARE(evalJS("String(myObject.objectStarProperty) != 'null'"), sTrue); +} + +void tst_QWebFrame::getSetDynamicProperty() +{ + // initially the object does not have the property + // In WebKit, RuntimeObjects do not inherit Object, so don't have hasOwnProperty + + //QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sFalse); + QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined); + + // add a dynamic property in C++ + QCOMPARE(m_myObject->setProperty("dynamicProperty", 123), false); + //QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sTrue); + QCOMPARE(evalJS("typeof myObject.dynamicProperty != 'undefined'"), sTrue); + QCOMPARE(evalJS("myObject.dynamicProperty == 123"), sTrue); + + // property change in script should be reflected in C++ + QCOMPARE(evalJS("myObject.dynamicProperty = 'foo';" + "myObject.dynamicProperty"), QLatin1String("foo")); + QCOMPARE(m_myObject->property("dynamicProperty").toString(), QLatin1String("foo")); + + // delete the property (XFAIL - can't delete properties) + QEXPECT_FAIL("", "can't delete properties", Continue); + QCOMPARE(evalJS("delete myObject.dynamicProperty"), sTrue); + /* + QCOMPARE(m_myObject->property("dynamicProperty").isValid(), false); + QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined); + // QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sFalse); + QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined); + */ +} + +void tst_QWebFrame::getSetChildren() +{ + // initially the object does not have the child + // (again, no hasOwnProperty) + + //QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sFalse); + QCOMPARE(evalJS("typeof myObject.child"), sUndefined); + + // add a child + MyQObject* child = new MyQObject(m_myObject); + child->setObjectName("child"); +// QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sTrue); + QCOMPARE(evalJS("typeof myObject.child != 'undefined'"), sTrue); + + // add a grandchild + MyQObject* grandChild = new MyQObject(child); + grandChild->setObjectName("grandChild"); +// QCOMPARE(evalJS("myObject.child.hasOwnProperty('grandChild')"), sTrue); + QCOMPARE(evalJS("typeof myObject.child.grandChild != 'undefined'"), sTrue); + + // delete grandchild + delete grandChild; +// QCOMPARE(evalJS("myObject.child.hasOwnProperty('grandChild')"), sFalse); + QCOMPARE(evalJS("typeof myObject.child.grandChild == 'undefined'"), sTrue); + + // delete child + delete child; +// QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sFalse); + QCOMPARE(evalJS("typeof myObject.child == 'undefined'"), sTrue); +} + +Q_DECLARE_METATYPE(QVector<int>) +Q_DECLARE_METATYPE(QVector<double>) +Q_DECLARE_METATYPE(QVector<QString>) + +void tst_QWebFrame::callQtInvokable() +{ + qRegisterMetaType<QObjectList>(); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokable()"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + // extra arguments should silently be ignored + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokable(10, 20, 30)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg('123')"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithLonglongArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toLongLong(), qlonglong(123)); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithFloatArg(123.5)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 3); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.5); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithDoubleArg(123.5)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 4); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.5); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithDoubleArg(new Number(1234.5))"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 4); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 1234.5); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithBoolArg(new Boolean(true))"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 52); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toBool(), true); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg('ciao')"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("ciao")); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("123")); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(null)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QString()); + QVERIFY(m_myObject->qtFunctionActuals().at(0).toString().isEmpty()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(undefined)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 5); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QString()); + QVERIFY(m_myObject->qtFunctionActuals().at(0).toString().isEmpty()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArgs(123, 456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 6); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toInt(), 456); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableReturningInt()"), QLatin1String("123")); + QCOMPARE(m_myObject->qtFunctionInvoked(), 7); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableReturningLongLong()"), QLatin1String("456")); + QCOMPARE(m_myObject->qtFunctionInvoked(), 39); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableReturningString()"), QLatin1String("ciao")); + QCOMPARE(m_myObject->qtFunctionInvoked(), 8); + QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList()); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg(123, 456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 9); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toInt(), 456); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("typeof myObject.myInvokableWithVoidStarArg(null)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 44); + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithVoidStarArg(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: incompatible type of argument(s) in call to myInvokableWithVoidStarArg(); candidates were\n myInvokableWithVoidStarArg(void*)")); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithAmbiguousArg(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: ambiguous call of overloaded function myInvokableWithAmbiguousArg(); candidates were\n myInvokableWithAmbiguousArg(int)\n myInvokableWithAmbiguousArg(uint)")); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithDefaultArgs(123, 'hello')", type); + QCOMPARE(type, sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 47); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toString(), QLatin1String("hello")); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithDefaultArgs(456)", type); + QCOMPARE(type, sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 47); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 2); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456); + QCOMPARE(m_myObject->qtFunctionActuals().at(1).toString(), QString()); + } + + // calling function that returns (const)ref + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("typeof myObject.myInvokableReturningRef()"); + QCOMPARE(ret, sUndefined); + //QVERIFY(!m_engine->hasUncaughtException()); + QCOMPARE(m_myObject->qtFunctionInvoked(), 48); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("typeof myObject.myInvokableReturningConstRef()"); + QCOMPARE(ret, sUndefined); + //QVERIFY(!m_engine->hasUncaughtException()); + QCOMPARE(m_myObject->qtFunctionInvoked(), 49); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableReturningQObjectStar()", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 13); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 0); + QCOMPARE(type, sObject); + QCOMPARE(ret.userType(), int(QMetaType::QObjectStar)); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithQObjectListArg([myObject])", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 14); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(type, sArray); + QCOMPARE(ret.userType(), int(QVariant::List)); // All lists get downgraded to QVariantList + QVariantList vl = qvariant_cast<QVariantList>(ret); + QCOMPARE(vl.count(), 1); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + m_myObject->setVariantProperty(QVariant(123)); + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(myObject.variantProperty)", type); + QCOMPARE(type, sNumber); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), m_myObject->variantProperty()); + QCOMPARE(ret.userType(), int(QMetaType::Double)); // all JS numbers are doubles, even though this started as an int + QCOMPARE(ret.toInt(),123); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(null)", type); + QCOMPARE(type, sObject); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant()); + QVERIFY(!m_myObject->qtFunctionActuals().at(0).isValid()); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(undefined)", type); + QCOMPARE(type, sObject); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant()); + QVERIFY(!m_myObject->qtFunctionActuals().at(0).isValid()); + } + + /* XFAIL - variant support + m_myObject->resetQtFunctionInvoked(); + { + m_myObject->setVariantProperty(QVariant::fromValue(QBrush())); + QVariant ret = evalJS("myObject.myInvokableWithVariantArg(myObject.variantProperty)"); + QVERIFY(ret.isVariant()); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(ret.toVariant(), m_myObject->qtFunctionActuals().at(0)); + QCOMPARE(ret.toVariant(), m_myObject->variantProperty()); + } + */ + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(123)", type); + QCOMPARE(type, sNumber); + QCOMPARE(m_myObject->qtFunctionInvoked(), 15); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant(123)); + QCOMPARE(ret.userType(), int(QMetaType::Double)); + QCOMPARE(ret.toInt(),123); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithVariantMapArg({ a:123, b:'ciao' })", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 16); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), int(QMetaType::QVariantMap)); + + QVariantMap vmap = qvariant_cast<QVariantMap>(v); + QCOMPARE(vmap.keys().size(), 2); + QCOMPARE(vmap.keys().at(0), QLatin1String("a")); + QCOMPARE(vmap.value("a"), QVariant(123)); + QCOMPARE(vmap.keys().at(1), QLatin1String("b")); + QCOMPARE(vmap.value("b"), QVariant("ciao")); + + QCOMPARE(type, sObject); + + QCOMPARE(ret.userType(), int(QMetaType::QVariantMap)); + vmap = qvariant_cast<QVariantMap>(ret); + QCOMPARE(vmap.keys().size(), 2); + QCOMPARE(vmap.keys().at(0), QLatin1String("a")); + QCOMPARE(vmap.value("a"), QVariant(123)); + QCOMPARE(vmap.keys().at(1), QLatin1String("b")); + QCOMPARE(vmap.value("b"), QVariant("ciao")); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithListOfIntArg([1, 5])", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 17); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), qMetaTypeId<QList<int> >()); + QList<int> ilst = qvariant_cast<QList<int> >(v); + QCOMPARE(ilst.size(), 2); + QCOMPARE(ilst.at(0), 1); + QCOMPARE(ilst.at(1), 5); + + QCOMPARE(type, sArray); + QCOMPARE(ret.userType(), int(QMetaType::QVariantList)); // ints get converted to doubles, so this is a qvariantlist + QVariantList vlst = qvariant_cast<QVariantList>(ret); + QCOMPARE(vlst.size(), 2); + QCOMPARE(vlst.at(0).toInt(), 1); + QCOMPARE(vlst.at(1).toInt(), 5); + } + + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QVariant ret = evalJSV("myObject.myInvokableWithQObjectStarArg(myObject)", type); + QCOMPARE(m_myObject->qtFunctionInvoked(), 18); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), int(QMetaType::QObjectStar)); + QCOMPARE(qvariant_cast<QObject*>(v), (QObject*)m_myObject); + + QCOMPARE(ret.userType(), int(QMetaType::QObjectStar)); + QCOMPARE(qvariant_cast<QObject*>(ret), (QObject*)m_myObject); + + QCOMPARE(type, sObject); + } + + m_myObject->resetQtFunctionInvoked(); + { + // no implicit conversion from integer to QObject* + QString type; + evalJS("myObject.myInvokableWithQObjectStarArg(123)", type); + QCOMPARE(type, sError); + } + + /* + m_myObject->resetQtFunctionInvoked(); + { + QString fun = evalJS("myObject.myInvokableWithQBrushArg"); + Q_ASSERT(fun.isFunction()); + QColor color(10, 20, 30, 40); + // QColor should be converted to a QBrush + QVariant ret = fun.call(QString(), QStringList() + << qScriptValueFromValue(m_engine, color)); + QCOMPARE(m_myObject->qtFunctionInvoked(), 19); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QVariant v = m_myObject->qtFunctionActuals().at(0); + QCOMPARE(v.userType(), int(QMetaType::QBrush)); + QCOMPARE(qvariant_cast<QColor>(v), color); + + QCOMPARE(qscriptvalue_cast<QColor>(ret), color); + } + */ + + // private slots should not be part of the QObject binding + QCOMPARE(evalJS("typeof myObject.myPrivateSlot"), sUndefined); + + // protected slots should be fine + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myProtectedSlot()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 36); + + // call with too few arguments + { + QString type; + QString ret = evalJS("myObject.myInvokableWithIntArg()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("SyntaxError: too few arguments in call to myInvokableWithIntArg(); candidates are\n myInvokableWithIntArg(int,int)\n myInvokableWithIntArg(int)")); + } + + // call function where not all types have been registered + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithBrushStyleArg(0)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: cannot call myInvokableWithBrushStyleArg(): unknown type `Qt::BrushStyle'")); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + } + + // call function with incompatible argument type + m_myObject->resetQtFunctionInvoked(); + { + QString type; + QString ret = evalJS("myObject.myInvokableWithQBrushArg(null)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: incompatible type of argument(s) in call to myInvokableWithQBrushArg(); candidates were\n myInvokableWithQBrushArg(QBrush)")); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + } +} + +void tst_QWebFrame::connectAndDisconnect() +{ + // connect(function) + QCOMPARE(evalJS("typeof myObject.mySignal"), sFunction); + QCOMPARE(evalJS("typeof myObject.mySignal.connect"), sFunction); + QCOMPARE(evalJS("typeof myObject.mySignal.disconnect"), sFunction); + + { + QString type; + evalJS("myObject.mySignal.connect(123)", type); + QCOMPARE(type, sError); + } + + evalJS("myHandler = function() { window.gotSignal = true; window.signalArgs = arguments; window.slotThisObject = this; window.signalSender = __qt_sender__; }"); + + QCOMPARE(evalJS("myObject.mySignal.connect(myHandler)"), sUndefined); + + evalJS("gotSignal = false"); + evalJS("myObject.mySignal()"); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 0"), sTrue); + QCOMPARE(evalJS("signalSender"),evalJS("myObject")); + QCOMPARE(evalJS("slotThisObject == window"), sTrue); + + evalJS("gotSignal = false"); + m_myObject->emitMySignal(); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 0"), sTrue); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myHandler)"), sUndefined); + + evalJS("gotSignal = false"); + m_myObject->emitMySignalWithIntArg(123); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("signalArgs[0] == 123.0"), sTrue); + + QCOMPARE(evalJS("myObject.mySignal.disconnect(myHandler)"), sUndefined); + { + QString type; + evalJS("myObject.mySignal.disconnect(myHandler)", type); + QCOMPARE(type, sError); + } + + evalJS("gotSignal = false"); + QCOMPARE(evalJS("myObject.mySignal2.connect(myHandler)"), sUndefined); + m_myObject->emitMySignal2(true); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("signalArgs[0]"), sTrue); + + QCOMPARE(evalJS("myObject.mySignal2.disconnect(myHandler)"), sUndefined); + + QCOMPARE(evalJS("typeof myObject['mySignal2()']"), sFunction); + QCOMPARE(evalJS("typeof myObject['mySignal2()'].connect"), sFunction); + QCOMPARE(evalJS("typeof myObject['mySignal2()'].disconnect"), sFunction); + + QCOMPARE(evalJS("myObject['mySignal2()'].connect(myHandler)"), sUndefined); + + evalJS("gotSignal = false"); + m_myObject->emitMySignal2(); + QCOMPARE(evalJS("gotSignal"), sTrue); + + QCOMPARE(evalJS("myObject['mySignal2()'].disconnect(myHandler)"), sUndefined); + + // connect(object, function) + evalJS("otherObject = { name:'foo' }"); + QCOMPARE(evalJS("myObject.mySignal.connect(otherObject, myHandler)"), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.disconnect(otherObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal(); + QCOMPARE(evalJS("gotSignal"), sFalse); + + { + QString type; + evalJS("myObject.mySignal.disconnect(otherObject, myHandler)", type); + QCOMPARE(type, sError); + } + + QCOMPARE(evalJS("myObject.mySignal.connect(otherObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal(); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 0"), sTrue); + QCOMPARE(evalJS("slotThisObject"),evalJS("otherObject")); + QCOMPARE(evalJS("signalSender"),evalJS("myObject")); + QCOMPARE(evalJS("slotThisObject.name"), QLatin1String("foo")); + QCOMPARE(evalJS("myObject.mySignal.disconnect(otherObject, myHandler)"), sUndefined); + + evalJS("yetAnotherObject = { name:'bar', func : function() { } }"); + QCOMPARE(evalJS("myObject.mySignal2.connect(yetAnotherObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal2(true); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("slotThisObject == yetAnotherObject"), sTrue); + QCOMPARE(evalJS("signalSender == myObject"), sTrue); + QCOMPARE(evalJS("slotThisObject.name"), QLatin1String("bar")); + QCOMPARE(evalJS("myObject.mySignal2.disconnect(yetAnotherObject, myHandler)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignal2.connect(myObject, myHandler)"), sUndefined); + evalJS("gotSignal = false"); + m_myObject->emitMySignal2(true); + QCOMPARE(evalJS("gotSignal"), sTrue); + QCOMPARE(evalJS("signalArgs.length == 1"), sTrue); + QCOMPARE(evalJS("slotThisObject == myObject"), sTrue); + QCOMPARE(evalJS("signalSender == myObject"), sTrue); + QCOMPARE(evalJS("myObject.mySignal2.disconnect(myObject, myHandler)"), sUndefined); + + // connect(obj, string) + QCOMPARE(evalJS("myObject.mySignal.connect(yetAnotherObject, 'func')"), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.connect(myObject, 'mySlot')"), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.disconnect(yetAnotherObject, 'func')"), sUndefined); + QCOMPARE(evalJS("myObject.mySignal.disconnect(myObject, 'mySlot')"), sUndefined); + + // check that emitting signals from script works + + // no arguments + QCOMPARE(evalJS("myObject.mySignal.connect(myObject.mySlot)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignal()"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 20); + QCOMPARE(evalJS("myObject.mySignal.disconnect(myObject.mySlot)"), sUndefined); + + // one argument + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithIntArg)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 21); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithIntArg)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithDoubleArg)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 22); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.0); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithDoubleArg)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithStringArg)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 23); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("123")); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithStringArg)"), sUndefined); + + // connecting to overloaded slot + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.myOverloadedSlot)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 26); // double overload + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.myOverloadedSlot)"), sUndefined); + + QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject['myOverloadedSlot(int)'])"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.mySignalWithIntArg(456)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 28); // int overload + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456); + QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject['myOverloadedSlot(int)'])"), sUndefined); + + // erroneous input + { + // ### QtScript adds .connect to all functions, WebKit does only to signals/slots + QString type; + QString ret = evalJS("(function() { }).connect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function")); + } + { + QString type; + QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect; o.connect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function")); + } + + { + QString type; + QString ret = evalJS("(function() { }).connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function")); + } + { + QString type; + QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect; o.connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function")); + } + + { + QString type; + QString ret = evalJS("myObject.myInvokable.connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.connect: MyQObject::myInvokable() is not a signal")); + } + { + QString type; + QString ret = evalJS("myObject.myInvokable.connect(function() { })", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.connect: MyQObject::myInvokable() is not a signal")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.connect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.connect: target is not a function")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: no arguments given")); + } + { + QString type; + QString ret = evalJS("var o = { }; o.disconnect = myObject.mySignal.disconnect; o.disconnect()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: no arguments given")); + } + + /* XFAIL - Function.prototype doesn't get connect/disconnect, just signals/slots + { + QString type; + QString ret = evalJS("(function() { }).disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: this object is not a signal")); + } + */ + + { + QString type; + QString ret = evalJS("var o = { }; o.disconnect = myObject.myInvokable.disconnect; o.disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal")); + } + + { + QString type; + QString ret = evalJS("myObject.myInvokable.disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal")); + } + { + QString type; + QString ret = evalJS("myObject.myInvokable.disconnect(function() { })", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect(123)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: target is not a function")); + } + + { + QString type; + QString ret = evalJS("myObject.mySignal.disconnect(function() { })", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: failed to disconnect from MyQObject::mySignal()")); + } + + // when the wrapper dies, the connection stays alive + QCOMPARE(evalJS("myObject.mySignal.connect(myObject.mySlot)"), sUndefined); + m_myObject->resetQtFunctionInvoked(); + m_myObject->emitMySignal(); + QCOMPARE(m_myObject->qtFunctionInvoked(), 20); + evalJS("myObject = null"); + evalJS("gc()"); + m_myObject->resetQtFunctionInvoked(); + m_myObject->emitMySignal(); + QCOMPARE(m_myObject->qtFunctionInvoked(), 20); +} + +void tst_QWebFrame::classEnums() +{ + // We don't do the meta thing currently, unfortunately!!! + /* + QString myClass = m_engine->newQMetaObject(m_myObject->metaObject(), m_engine->undefinedValue()); + m_engine->globalObject().setProperty("MyQObject", myClass); + + QCOMPARE(static_cast<MyQObject::Policy>(evalJS("MyQObject.FooPolicy").toInt()), + MyQObject::FooPolicy); + QCOMPARE(static_cast<MyQObject::Policy>(evalJS("MyQObject.BarPolicy").toInt()), + MyQObject::BarPolicy); + QCOMPARE(static_cast<MyQObject::Policy>(evalJS("MyQObject.BazPolicy").toInt()), + MyQObject::BazPolicy); + + QCOMPARE(static_cast<MyQObject::Strategy>(evalJS("MyQObject.FooStrategy").toInt()), + MyQObject::FooStrategy); + QCOMPARE(static_cast<MyQObject::Strategy>(evalJS("MyQObject.BarStrategy").toInt()), + MyQObject::BarStrategy); + QCOMPARE(static_cast<MyQObject::Strategy>(evalJS("MyQObject.BazStrategy").toInt()), + MyQObject::BazStrategy); + + QCOMPARE(MyQObject::Ability(evalJS("MyQObject.NoAbility").toInt()), + MyQObject::NoAbility); + QCOMPARE(MyQObject::Ability(evalJS("MyQObject.FooAbility").toInt()), + MyQObject::FooAbility); + QCOMPARE(MyQObject::Ability(evalJS("MyQObject.BarAbility").toInt()), + MyQObject::BarAbility); + QCOMPARE(MyQObject::Ability(evalJS("MyQObject.BazAbility").toInt()), + MyQObject::BazAbility); + QCOMPARE(MyQObject::Ability(evalJS("MyQObject.AllAbility").toInt()), + MyQObject::AllAbility); + + // enums from Qt are inherited through prototype + QCOMPARE(static_cast<Qt::FocusPolicy>(evalJS("MyQObject.StrongFocus").toInt()), + Qt::StrongFocus); + QCOMPARE(static_cast<Qt::Key>(evalJS("MyQObject.Key_Left").toInt()), + Qt::Key_Left); + + QCOMPARE(evalJS("MyQObject.className()"), QLatin1String("MyQObject")); + + qRegisterMetaType<MyQObject::Policy>("Policy"); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableWithEnumArg(MyQObject.BazPolicy)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 10); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), int(MyQObject::BazPolicy)); + + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokableWithQualifiedEnumArg(MyQObject.BazPolicy)"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 36); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), int(MyQObject::BazPolicy)); + + m_myObject->resetQtFunctionInvoked(); + { + QVariant ret = evalJS("myObject.myInvokableReturningEnum()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 37); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 0); + QCOMPARE(ret.isVariant()); + } + m_myObject->resetQtFunctionInvoked(); + { + QVariant ret = evalJS("myObject.myInvokableReturningQualifiedEnum()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 38); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 0); + QCOMPARE(ret.isNumber()); + } + */ +} + +void tst_QWebFrame::classConstructor() +{ + /* + QString myClass = qScriptValueFromQMetaObject<MyQObject>(m_engine); + m_engine->globalObject().setProperty("MyQObject", myClass); + + QString myObj = evalJS("myObj = MyQObject()"); + QObject* qobj = myObj.toQObject(); + QVERIFY(qobj != 0); + QCOMPARE(qobj->metaObject()->className(), "MyQObject"); + QCOMPARE(qobj->parent(), (QObject*)0); + + QString qobjectClass = qScriptValueFromQMetaObject<QObject>(m_engine); + m_engine->globalObject().setProperty("QObject", qobjectClass); + + QString otherObj = evalJS("otherObj = QObject(myObj)"); + QObject* qqobj = otherObj.toQObject(); + QVERIFY(qqobj != 0); + QCOMPARE(qqobj->metaObject()->className(), "QObject"); + QCOMPARE(qqobj->parent(), qobj); + + delete qobj; + */ +} + +void tst_QWebFrame::overrideInvokable() +{ + m_myObject->resetQtFunctionInvoked(); + QCOMPARE(evalJS("myObject.myInvokable()"), sUndefined); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + + /* XFAIL - can't write to functions with RuntimeObject + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myInvokable = function() { window.a = 123; }"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + QCOMPARE(evalJS("window.a").toDouble(), 123.0); + + evalJS("myObject.myInvokable = function() { window.a = 456; }"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), -1); + QCOMPARE(evalJS("window.a").toDouble(), 456.0); + */ + + evalJS("delete myObject.myInvokable"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + + /* XFAIL - ditto + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myInvokable = myObject.myInvokableWithIntArg"); + evalJS("myObject.myInvokable(123)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + */ + + evalJS("delete myObject.myInvokable"); + m_myObject->resetQtFunctionInvoked(); + // this form (with the '()') is read-only + evalJS("myObject['myInvokable()'] = function() { window.a = 123; }"); + evalJS("myObject.myInvokable()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); +} + +void tst_QWebFrame::transferInvokable() +{ + /* XFAIL - can't put to functions with RuntimeObject + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.foozball = myObject.myInvokable"); + evalJS("myObject.foozball()"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 0); + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.foozball = myObject.myInvokableWithIntArg"); + evalJS("myObject.foozball(123)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myInvokable = myObject.myInvokableWithIntArg"); + evalJS("myObject.myInvokable(123)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 1); + + MyOtherQObject other; + m_page->mainFrame()->addToJSWindowObject("myOtherObject", &other); + evalJS("myOtherObject.foo = myObject.foozball"); + other.resetQtFunctionInvoked(); + evalJS("myOtherObject.foo(456)"); + QCOMPARE(other.qtFunctionInvoked(), 1); + */ +} + +void tst_QWebFrame::findChild() +{ + /* + QObject* child = new QObject(m_myObject); + child->setObjectName(QLatin1String("myChildObject")); + + { + QString result = evalJS("myObject.findChild('noSuchChild')"); + QCOMPARE(result.isNull()); + } + + { + QString result = evalJS("myObject.findChild('myChildObject')"); + QCOMPARE(result.isQObject()); + QCOMPARE(result.toQObject(), child); + } + + delete child; + */ +} + +void tst_QWebFrame::findChildren() +{ + /* + QObject* child = new QObject(m_myObject); + child->setObjectName(QLatin1String("myChildObject")); + + { + QString result = evalJS("myObject.findChildren('noSuchChild')"); + QCOMPARE(result.isArray()); + QCOMPARE(result.property(QLatin1String("length")).toDouble(), 0.0); + } + + { + QString result = evalJS("myObject.findChildren('myChildObject')"); + QCOMPARE(result.isArray()); + QCOMPARE(result.property(QLatin1String("length")).toDouble(), 1.0); + QCOMPARE(result.property(QLatin1String("0")).toQObject(), child); + } + + QObject* namelessChild = new QObject(m_myObject); + + { + QString result = evalJS("myObject.findChildren('myChildObject')"); + QCOMPARE(result.isArray()); + QCOMPARE(result.property(QLatin1String("length")).toDouble(), 1.0); + QCOMPARE(result.property(QLatin1String("0")).toQObject(), child); + } + + QObject* anotherChild = new QObject(m_myObject); + anotherChild->setObjectName(QLatin1String("anotherChildObject")); + + { + QString result = evalJS("myObject.findChildren('anotherChildObject')"); + QCOMPARE(result.isArray()); + QCOMPARE(result.property(QLatin1String("length")).toDouble(), 1.0); + QCOMPARE(result.property(QLatin1String("0")).toQObject(), anotherChild); + } + + anotherChild->setObjectName(QLatin1String("myChildObject")); + { + QString result = evalJS("myObject.findChildren('myChildObject')"); + QCOMPARE(result.isArray()); + QCOMPARE(result.property(QLatin1String("length")).toDouble(), 2.0); + QObject* o1 = result.property(QLatin1String("0")).toQObject(); + QObject* o2 = result.property(QLatin1String("1")).toQObject(); + if (o1 != child) { + QCOMPARE(o1, anotherChild); + QCOMPARE(o2, child); + } else { + QCOMPARE(o1, child); + QCOMPARE(o2, anotherChild); + } + } + + // find all + { + QString result = evalJS("myObject.findChildren()"); + QVERIFY(result.isArray()); + int count = 3; + QCOMPARE(result.property("length"), QLatin1String(count); + for (int i = 0; i < 3; ++i) { + QObject* o = result.property(i).toQObject(); + if (o == namelessChild || o == child || o == anotherChild) + --count; + } + QVERIFY(count == 0); + } + + delete anotherChild; + delete namelessChild; + delete child; + */ +} + +void tst_QWebFrame::overloadedSlots() +{ + // should pick myOverloadedSlot(double) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(10)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 26); + + // should pick myOverloadedSlot(double) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(10.0)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 26); + + // should pick myOverloadedSlot(QString) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot('10')"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 29); + + // should pick myOverloadedSlot(bool) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(true)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 25); + + // should pick myOverloadedSlot(QDateTime) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(new Date())"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 32); + + // should pick myOverloadedSlot(QRegExp) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(new RegExp())"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 34); + + // should pick myOverloadedSlot(QVariant) + /* XFAIL + m_myObject->resetQtFunctionInvoked(); + QString f = evalJS("myObject.myOverloadedSlot"); + f.call(QString(), QStringList() << m_engine->newVariant(QVariant("ciao"))); + QCOMPARE(m_myObject->qtFunctionInvoked(), 35); + */ + + // should pick myOverloadedSlot(QRegExp) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(document.body)"); + QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=37319", Continue); + QCOMPARE(m_myObject->qtFunctionInvoked(), 36); + + // should pick myOverloadedSlot(QObject*) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(myObject)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 41); + + // should pick myOverloadedSlot(QObject*) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(null)"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 41); + + // should pick myOverloadedSlot(QStringList) + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(['hello'])"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 42); +} + +void tst_QWebFrame::enumerate_data() +{ + QTest::addColumn<QStringList>("expectedNames"); + + QTest::newRow("enumerate all") + << (QStringList() + // meta-object-defined properties: + // inherited + << "objectName" + // non-inherited + << "p1" << "p2" << "p4" << "p6" + // dynamic properties + << "dp1" << "dp2" << "dp3" + // inherited slots + << "destroyed(QObject*)" << "destroyed()" + << "deleteLater()" + // not included because it's private: + // << "_q_reregisterTimers(void*)" + // signals + << "mySignal()" + // slots + << "mySlot()" << "myOtherSlot()"); +} + +void tst_QWebFrame::enumerate() +{ + QFETCH(QStringList, expectedNames); + + MyEnumTestQObject enumQObject; + // give it some dynamic properties + enumQObject.setProperty("dp1", "dp1"); + enumQObject.setProperty("dp2", "dp2"); + enumQObject.setProperty("dp3", "dp3"); + m_page->mainFrame()->addToJavaScriptWindowObject("myEnumObject", &enumQObject); + + // enumerate in script + { + evalJS("var enumeratedProperties = []"); + evalJS("for (var p in myEnumObject) { enumeratedProperties.push(p); }"); + QStringList result = evalJSV("enumeratedProperties").toStringList(); + QCOMPARE(result.size(), expectedNames.size()); + for (int i = 0; i < expectedNames.size(); ++i) + QCOMPARE(result.at(i), expectedNames.at(i)); + } +} + +void tst_QWebFrame::objectDeleted() +{ + MyQObject* qobj = new MyQObject(); + m_page->mainFrame()->addToJavaScriptWindowObject("bar", qobj); + evalJS("bar.objectName = 'foo';"); + QCOMPARE(qobj->objectName(), QLatin1String("foo")); + evalJS("bar.intProperty = 123;"); + QCOMPARE(qobj->intProperty(), 123); + qobj->resetQtFunctionInvoked(); + evalJS("bar.myInvokable.call(bar);"); + QCOMPARE(qobj->qtFunctionInvoked(), 0); + + // do this, to ensure that we cache that it implements call + evalJS("bar()"); + + // now delete the object + delete qobj; + + QCOMPARE(evalJS("typeof bar"), sObject); + + // any attempt to access properties of the object should result in an exception + { + QString type; + QString ret = evalJS("bar.objectName", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject")); + } + { + QString type; + QString ret = evalJS("bar.objectName = 'foo'", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject")); + } + + // myInvokable is stored in member table (since we've accessed it before deletion) + { + QString type; + evalJS("bar.myInvokable", type); + QCOMPARE(type, sFunction); + } + + { + QString type; + QString ret = evalJS("bar.myInvokable.call(bar);", type); + ret = evalJS("bar.myInvokable(bar)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject")); + } + // myInvokableWithIntArg is not stored in member table (since we've not accessed it) + { + QString type; + QString ret = evalJS("bar.myInvokableWithIntArg", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `myInvokableWithIntArg' of deleted QObject")); + } + + // access from script + evalJS("window.o = bar;"); + { + QString type; + QString ret = evalJS("o.objectName", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject")); + } + { + QString type; + QString ret = evalJS("o.myInvokable()", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject")); + } + { + QString type; + QString ret = evalJS("o.myInvokableWithIntArg(10)", type); + QCOMPARE(type, sError); + QCOMPARE(ret, QLatin1String("Error: cannot access member `myInvokableWithIntArg' of deleted QObject")); + } +} + +void tst_QWebFrame::typeConversion() +{ + m_myObject->resetQtFunctionInvoked(); + + QDateTime localdt(QDate(2008,1,18), QTime(12,31,0)); + QDateTime utclocaldt = localdt.toUTC(); + QDateTime utcdt(QDate(2008,1,18), QTime(12,31,0), Qt::UTC); + + // Dates in JS (default to local) + evalJS("myObject.myOverloadedSlot(new Date(2008,0,18,12,31,0))"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 32); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDateTime().toUTC(), utclocaldt); + + m_myObject->resetQtFunctionInvoked(); + evalJS("myObject.myOverloadedSlot(new Date(Date.UTC(2008,0,18,12,31,0)))"); + QCOMPARE(m_myObject->qtFunctionInvoked(), 32); + QCOMPARE(m_myObject->qtFunctionActuals().size(), 1); + QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDateTime().toUTC(), utcdt); + + // Pushing QDateTimes into JS + // Local + evalJS("function checkDate(d) {window.__date_equals = (d.toString() == new Date(2008,0,18,12,31,0))?true:false;}"); + evalJS("myObject.mySignalWithDateTimeArg.connect(checkDate)"); + m_myObject->emitMySignalWithDateTimeArg(localdt); + QCOMPARE(evalJS("window.__date_equals"), sTrue); + evalJS("delete window.__date_equals"); + m_myObject->emitMySignalWithDateTimeArg(utclocaldt); + QCOMPARE(evalJS("window.__date_equals"), sTrue); + evalJS("delete window.__date_equals"); + evalJS("myObject.mySignalWithDateTimeArg.disconnect(checkDate); delete checkDate;"); + + // UTC + evalJS("function checkDate(d) {window.__date_equals = (d.toString() == new Date(Date.UTC(2008,0,18,12,31,0)))?true:false; }"); + evalJS("myObject.mySignalWithDateTimeArg.connect(checkDate)"); + m_myObject->emitMySignalWithDateTimeArg(utcdt); + QCOMPARE(evalJS("window.__date_equals"), sTrue); + evalJS("delete window.__date_equals"); + evalJS("myObject.mySignalWithDateTimeArg.disconnect(checkDate); delete checkDate;"); + + // ### RegExps +} + +class StringListTestObject : public QObject { + Q_OBJECT +public Q_SLOTS: + QVariant stringList() + { + return QStringList() << "Q" << "t"; + }; +}; + +void tst_QWebFrame::arrayObjectEnumerable() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QObject* qobject = new StringListTestObject(); + frame->addToJavaScriptWindowObject("test", qobject, QScriptEngine::ScriptOwnership); + + const QString script("var stringArray = test.stringList();" + "var result = '';" + "for (var i in stringArray) {" + " result += stringArray[i];" + "}" + "result;"); + QCOMPARE(frame->evaluateJavaScript(script).toString(), QString::fromLatin1("Qt")); +} + +void tst_QWebFrame::symmetricUrl() +{ + QVERIFY(m_view->url().isEmpty()); + + QCOMPARE(m_view->history()->count(), 0); + + QUrl dataUrl("data:text/html,<h1>Test"); + + m_view->setUrl(dataUrl); + QCOMPARE(m_view->url(), dataUrl); + QCOMPARE(m_view->history()->count(), 0); + + // loading is _not_ immediate, so the text isn't set just yet. + QVERIFY(m_view->page()->mainFrame()->toPlainText().isEmpty()); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_view->history()->count(), 1); + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test")); + + QUrl dataUrl2("data:text/html,<h1>Test2"); + QUrl dataUrl3("data:text/html,<h1>Test3"); + + m_view->setUrl(dataUrl2); + m_view->setUrl(dataUrl3); + + QCOMPARE(m_view->url(), dataUrl3); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_view->history()->count(), 2); + + QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test3")); +} + +void tst_QWebFrame::progressSignal() +{ + QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_view, SIGNAL(loadFinished(bool))); + + QVERIFY(progressSpy.size() >= 2); + + // WebKit defines initialProgressValue as 10%, not 0% + QCOMPARE(progressSpy.first().first().toInt(), 10); + + // But we always end at 100% + QCOMPARE(progressSpy.last().first().toInt(), 100); +} + +void tst_QWebFrame::urlChange() +{ + QSignalSpy urlSpy(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QUrl dataUrl("data:text/html,<h1>Test"); + m_view->setUrl(dataUrl); + + ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 1); + + QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>"); + m_view->setUrl(dataUrl2); + + ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl))); + + QCOMPARE(urlSpy.size(), 2); +} + + +void tst_QWebFrame::domCycles() +{ + m_view->setHtml("<html><body>"); + QVariant v = m_page->mainFrame()->evaluateJavaScript("document"); + QVERIFY(v.type() == QVariant::Map); +} + +class FakeReply : public QNetworkReply { + Q_OBJECT + +public: + FakeReply(const QNetworkRequest& request, QObject* parent = 0) + : QNetworkReply(parent) + { + setOperation(QNetworkAccessManager::GetOperation); + setRequest(request); + if (request.url() == QUrl("qrc:/test1.html")) { + setHeader(QNetworkRequest::LocationHeader, QString("qrc:/test2.html")); + setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html")); + } +#ifndef QT_NO_OPENSSL + else if (request.url() == QUrl("qrc:/fake-ssl-error.html")) + setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error !")); // force a ssl error +#endif + else if (request.url().host() == QLatin1String("abcdef.abcdef")) + setError(QNetworkReply::HostNotFoundError, tr("Invalid URL")); + + open(QIODevice::ReadOnly); + QTimer::singleShot(0, this, SLOT(timeout())); + } + ~FakeReply() + { + close(); + } + virtual void abort() {} + virtual void close() {} + +protected: + qint64 readData(char*, qint64) + { + return 0; + } + +private slots: + void timeout() + { + if (request().url() == QUrl("qrc://test1.html")) + emit error(this->error()); + else if (request().url() == QUrl("http://abcdef.abcdef/")) + emit metaDataChanged(); +#ifndef QT_NO_OPENSSL + else if (request().url() == QUrl("qrc:/fake-ssl-error.html")) + return; +#endif + + emit readyRead(); + emit finished(); + } +}; + +class FakeNetworkManager : public QNetworkAccessManager { + Q_OBJECT + +public: + FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { } + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData) + { + QString url = request.url().toString(); + if (op == QNetworkAccessManager::GetOperation) { + if (url == "qrc:/test1.html" || url == "http://abcdef.abcdef/") + return new FakeReply(request, this); +#ifndef QT_NO_OPENSSL + else if (url == "qrc:/fake-ssl-error.html") { + FakeReply* reply = new FakeReply(request, this); + QList<QSslError> errors; + emit sslErrors(reply, errors << QSslError(QSslError::UnspecifiedError)); + return reply; + } +#endif + } + + return QNetworkAccessManager::createRequest(op, request, outgoingData); + } +}; + +void tst_QWebFrame::requestedUrl() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completely loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + frame->setUrl(QUrl("qrc:/test1.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/test1.html")); + QCOMPARE(frame->url(), QUrl("qrc:/test2.html")); + + frame->setUrl(QUrl("qrc:/non-existent.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 2); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/non-existent.html")); + QCOMPARE(frame->url(), QUrl("qrc:/non-existent.html")); + + frame->setUrl(QUrl("http://abcdef.abcdef")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 3); + QCOMPARE(frame->requestedUrl(), QUrl("http://abcdef.abcdef/")); + QCOMPARE(frame->url(), QUrl("http://abcdef.abcdef/")); + +#ifndef QT_NO_OPENSSL + qRegisterMetaType<QList<QSslError> >("QList<QSslError>"); + qRegisterMetaType<QNetworkReply* >("QNetworkReply*"); + + QSignalSpy spy2(page.networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>))); + frame->setUrl(QUrl("qrc:/fake-ssl-error.html")); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy2.count(), 1); + QCOMPARE(frame->requestedUrl(), QUrl("qrc:/fake-ssl-error.html")); + QCOMPARE(frame->url(), QUrl("qrc:/fake-ssl-error.html")); +#endif +} + +void tst_QWebFrame::requestedUrlAfterSetAndLoadFailures() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + const QUrl first("http://abcdef.abcdef/"); + frame->setUrl(first); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), first); + QCOMPARE(frame->requestedUrl(), first); + QVERIFY(!spy.at(0).first().toBool()); + + const QUrl second("http://abcdef.abcdef/another_page.html"); + QVERIFY(first != second); + + frame->load(second); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), first); + QCOMPARE(frame->requestedUrl(), second); + QVERIFY(!spy.at(1).first().toBool()); +} + +void tst_QWebFrame::javaScriptWindowObjectCleared_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<int>("signalCount"); + QTest::newRow("with <script>") << "<html><body><script>i=0</script><p>hello world</p></body></html>" << 1; + // NOTE: Empty scripts no longer cause this signal to be emitted. + QTest::newRow("with empty <script>") << "<html><body><script></script><p>hello world</p></body></html>" << 0; + QTest::newRow("without <script>") << "<html><body><p>hello world</p></body></html>" << 0; +} + +void tst_QWebFrame::javaScriptWindowObjectCleared() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared())); + QFETCH(QString, html); + frame->setHtml(html); + + QFETCH(int, signalCount); + QCOMPARE(spy.count(), signalCount); +} + +void tst_QWebFrame::javaScriptWindowObjectClearedOnEvaluate() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared())); + frame->setHtml("<html></html>"); + QCOMPARE(spy.count(), 0); + frame->evaluateJavaScript("var a = 'a';"); + QCOMPARE(spy.count(), 1); + // no new clear for a new script: + frame->evaluateJavaScript("var a = 1;"); + QCOMPARE(spy.count(), 1); +} + +void tst_QWebFrame::setHtml() +{ + QString html("<html><head></head><body><p>hello world</p></body></html>"); + QSignalSpy spy(m_view->page(), SIGNAL(loadFinished(bool))); + m_view->page()->mainFrame()->setHtml(html); + QCOMPARE(m_view->page()->mainFrame()->toHtml(), html); + QCOMPARE(spy.count(), 1); +} + +void tst_QWebFrame::setHtmlWithResource() +{ + QString html("<html><body><p>hello world</p><img src='qrc:/image.png'/></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completey loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + frame->setHtml(html); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128); + + QString html2 = + "<html>" + "<head>" + "<link rel='stylesheet' href='qrc:/style.css' type='text/css' />" + "</head>" + "<body>" + "<p id='idP'>some text</p>" + "</body>" + "</html>"; + + // in few seconds, the CSS should be completey loaded + frame->setHtml(html2); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.size(), 2); + + QWebElement p = frame->documentElement().findAll("p").at(0); + QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("red")); +} + +void tst_QWebFrame::setHtmlWithBaseURL() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QDir::setCurrent(TESTS_SOURCE_DIR); + + QString html("<html><body><p>hello world</p><img src='resources/image2.png'/></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + // in few seconds, the image should be completey loaded + QSignalSpy spy(&page, SIGNAL(loadFinished(bool))); + + frame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR)); + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(spy.count(), 1); + + QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1); + QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128); + QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128); + + // no history item has to be added. + QCOMPARE(m_view->page()->history()->count(), 0); +} + +class MyPage : public QWebPage +{ +public: + MyPage() : QWebPage(), alerts(0) {} + int alerts; + +protected: + virtual void javaScriptAlert(QWebFrame*, const QString& msg) + { + alerts++; + QCOMPARE(msg, QString("foo")); + // Should not be enough to trigger deferred loading, since we've upped the HTML + // tokenizer delay in the Qt frameloader. See HTMLTokenizer::continueProcessing() + QTest::qWait(1000); + } +}; + +void tst_QWebFrame::setHtmlWithJSAlert() +{ + QString html("<html><head></head><body><script>alert('foo');</script><p>hello world</p></body></html>"); + MyPage page; + m_view->setPage(&page); + page.mainFrame()->setHtml(html); + QCOMPARE(page.alerts, 1); + QCOMPARE(m_view->page()->mainFrame()->toHtml(), html); +} + +class TestNetworkManager : public QNetworkAccessManager +{ +public: + TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {} + + QList<QUrl> requestedUrls; + +protected: + virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) { + requestedUrls.append(request.url()); + QNetworkRequest redirectedRequest = request; + redirectedRequest.setUrl(QUrl("data:text/html,<p>hello")); + return QNetworkAccessManager::createRequest(op, redirectedRequest, outgoingData); + } +}; + +void tst_QWebFrame::ipv6HostEncoding() +{ + TestNetworkManager* networkManager = new TestNetworkManager(m_page); + m_page->setNetworkAccessManager(networkManager); + networkManager->requestedUrls.clear(); + + QUrl baseUrl = QUrl::fromEncoded("http://[::1]/index.html"); + m_view->setHtml("<p>Hi", baseUrl); + m_view->page()->mainFrame()->evaluateJavaScript("var r = new XMLHttpRequest();" + "r.open('GET', 'http://[::1]/test.xml', false);" + "r.send(null);" + ); + QCOMPARE(networkManager->requestedUrls.count(), 1); + QCOMPARE(networkManager->requestedUrls.at(0), QUrl::fromEncoded("http://[::1]/test.xml")); +} + +void tst_QWebFrame::metaData() +{ + m_view->setHtml("<html>" + " <head>" + " <meta name=\"description\" content=\"Test description\">" + " <meta name=\"keywords\" content=\"HTML, JavaScript, Css\">" + " </head>" + "</html>"); + + QMultiMap<QString, QString> metaData = m_view->page()->mainFrame()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QCOMPARE(metaData.value("description"), QString("Test description")); + QCOMPARE(metaData.value("keywords"), QString("HTML, JavaScript, Css")); + QCOMPARE(metaData.value("nonexistant"), QString()); + + m_view->setHtml("<html>" + " <head>" + " <meta name=\"samekey\" content=\"FirstValue\">" + " <meta name=\"samekey\" content=\"SecondValue\">" + " </head>" + "</html>"); + + metaData = m_view->page()->mainFrame()->metaData(); + + QCOMPARE(metaData.count(), 2); + + QStringList values = metaData.values("samekey"); + QCOMPARE(values.count(), 2); + + QVERIFY(values.contains("FirstValue")); + QVERIFY(values.contains("SecondValue")); + + QCOMPARE(metaData.value("nonexistant"), QString()); +} + +#if !defined(Q_WS_MAEMO_5) && !defined(Q_OS_SYMBIAN) && !defined(QT_NO_COMBOBOX) +void tst_QWebFrame::popupFocus() +{ + QWebView view; + view.setHtml("<html>" + " <body>" + " <select name=\"select\">" + " <option>1</option>" + " <option>2</option>" + " </select>" + " <input type=\"text\"> </input>" + " <textarea name=\"text_area\" rows=\"3\" cols=\"40\">" + "This test checks whether showing and hiding a popup" + "takes the focus away from the webpage." + " </textarea>" + " </body>" + "</html>"); + view.resize(400, 100); + // Call setFocus before show to work around http://bugreports.qt.nokia.com/browse/QTBUG-14762 + view.setFocus(); + view.show(); + QTest::qWaitForWindowShown(&view); + view.activateWindow(); + QTRY_VERIFY(view.hasFocus()); + + // open the popup by clicking. check if focus is on the popup + const QWebElement webCombo = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("select[name=select]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center()); + QObject* webpopup = firstChildByClassName(&view, "QComboBox"); + QComboBox* combo = qobject_cast<QComboBox*>(webpopup); + QVERIFY(combo != 0); + QTRY_VERIFY(!view.hasFocus() && combo->view()->hasFocus()); // Focus should be on the popup + + // hide the popup and check if focus is on the page + combo->hidePopup(); + QTRY_VERIFY(view.hasFocus()); // Focus should be back on the WebView +} +#endif + +void tst_QWebFrame::inputFieldFocus() +{ + QWebView view; + view.setHtml("<html><body><input type=\"text\"></input></body></html>"); + view.resize(400, 100); + view.show(); + QTest::qWaitForWindowShown(&view); + view.activateWindow(); + view.setFocus(); + QTRY_VERIFY(view.hasFocus()); + + // double the flashing time, should at least blink once already + int delay = qApp->cursorFlashTime() * 2; + + // focus the lineedit and check if it blinks + bool autoSipEnabled = qApp->autoSipEnabled(); + qApp->setAutoSipEnabled(false); + const QWebElement inputElement = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("input[type=text]")); + QTest::mouseClick(&view, Qt::LeftButton, 0, inputElement.geometry().center()); + m_inputFieldsTestView = &view; + view.installEventFilter( this ); + QTest::qWait(delay); + QVERIFY2(m_inputFieldTestPaintCount >= 3, + "The input field should have a blinking caret"); + qApp->setAutoSipEnabled(autoSipEnabled); +} + +void tst_QWebFrame::hitTestContent() +{ + QString html("<html><body><p>A paragraph</p><br/><br/><br/><a href=\"about:blank\" target=\"_foo\" id=\"link\">link text</a></body></html>"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->setHtml(html); + page.setViewportSize(QSize(200, 0)); //no height so link is not visible + const QWebElement linkElement = frame->documentElement().findFirst(QLatin1String("a#link")); + QWebHitTestResult result = frame->hitTestContent(linkElement.geometry().center()); + QCOMPARE(result.linkText(), QString("link text")); + QWebElement link = result.linkElement(); + QCOMPARE(link.attribute("target"), QString("_foo")); +} + +void tst_QWebFrame::jsByteArray() +{ + QByteArray ba("hello world"); + m_myObject->setByteArrayProperty(ba); + + // read-only property + QCOMPARE(m_myObject->byteArrayProperty(), ba); + QString type; + QVariant v = evalJSV("myObject.byteArrayProperty"); + QCOMPARE(int(v.type()), int(QVariant::ByteArray)); + + QCOMPARE(v.toByteArray(), ba); +} + +void tst_QWebFrame::ownership() +{ + // test ownership + { + QPointer<QObject> ptr = new QObject(); + QVERIFY(ptr != 0); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", ptr, QScriptEngine::ScriptOwnership); + } + QVERIFY(ptr == 0); + } + { + QPointer<QObject> ptr = new QObject(); + QVERIFY(ptr != 0); + QObject* before = ptr; + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", ptr, QScriptEngine::QtOwnership); + } + QVERIFY(ptr == before); + delete ptr; + } + { + QObject* parent = new QObject(); + QObject* child = new QObject(parent); + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", child, QScriptEngine::QtOwnership); + QVariant v = frame->evaluateJavaScript("test"); + QCOMPARE(qvariant_cast<QObject*>(v), child); + delete parent; + v = frame->evaluateJavaScript("test"); + QCOMPARE(qvariant_cast<QObject*>(v), (QObject *)0); + } + { + QPointer<QObject> ptr = new QObject(); + QVERIFY(ptr != 0); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", ptr, QScriptEngine::AutoOwnership); + } + // no parent, so it should be like ScriptOwnership + QVERIFY(ptr == 0); + } + { + QObject* parent = new QObject(); + QPointer<QObject> child = new QObject(parent); + QVERIFY(child != 0); + { + QWebPage page; + QWebFrame* frame = page.mainFrame(); + frame->addToJavaScriptWindowObject("test", child, QScriptEngine::AutoOwnership); + } + // has parent, so it should be like QtOwnership + QVERIFY(child != 0); + delete parent; + } +} + +void tst_QWebFrame::nullValue() +{ + QVariant v = m_view->page()->mainFrame()->evaluateJavaScript("null"); + QVERIFY(v.isNull()); +} + +void tst_QWebFrame::baseUrl_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QUrl>("loadUrl"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("null") << QString() << QUrl() + << QUrl("about:blank") << QUrl("about:blank"); + + QTest::newRow("foo") << QString() << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/"); + + QString html = "<html>" + "<head>" + "<base href=\"http://foobaz.bar/\" />" + "</head>" + "</html>"; + QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/") + << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/"); +} + +void tst_QWebFrame::baseUrl() +{ + QFETCH(QString, html); + QFETCH(QUrl, loadUrl); + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + + m_page->mainFrame()->setHtml(html, loadUrl); + QCOMPARE(m_page->mainFrame()->url(), url); + QCOMPARE(m_page->mainFrame()->baseUrl(), baseUrl); +} + +void tst_QWebFrame::hasSetFocus() +{ + QString html("<html><body><p>top</p>" \ + "<iframe width='80%' height='30%'/>" \ + "</body></html>"); + + QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool))); + m_page->mainFrame()->setHtml(html); + + waitForSignal(m_page->mainFrame(), SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.size(), 1); + + QList<QWebFrame*> children = m_page->mainFrame()->childFrames(); + QWebFrame* frame = children.at(0); + QString innerHtml("<html><body><p>another iframe</p>" \ + "<iframe width='80%' height='30%'/>" \ + "</body></html>"); + frame->setHtml(innerHtml); + + waitForSignal(frame, SIGNAL(loadFinished(bool)), 200); + QCOMPARE(loadSpy.size(), 2); + + m_page->mainFrame()->setFocus(); + QTRY_VERIFY(m_page->mainFrame()->hasFocus()); + + for (int i = 0; i < children.size(); ++i) { + children.at(i)->setFocus(); + QTRY_VERIFY(children.at(i)->hasFocus()); + QVERIFY(!m_page->mainFrame()->hasFocus()); + } + + m_page->mainFrame()->setFocus(); + QTRY_VERIFY(m_page->mainFrame()->hasFocus()); +} + +void tst_QWebFrame::render() +{ + QString html("<html>" \ + "<head><style>" \ + "body, iframe { margin: 0px; border: none; }" \ + "</style></head>" \ + "<body><iframe width='100px' height='100px'/></body>" \ + "</html>"); + + QWebPage page; + page.mainFrame()->setHtml(html); + + QList<QWebFrame*> frames = page.mainFrame()->childFrames(); + QWebFrame *frame = frames.at(0); + QString innerHtml("<body style='margin: 0px;'><img src='qrc:/image.png'/></body>"); + frame->setHtml(innerHtml); + + QPicture picture; + + QSize size = page.mainFrame()->contentsSize(); + page.setViewportSize(size); + + // render contents layer only (the iframe is smaller than the image, so it will have scrollbars) + QPainter painter1(&picture); + frame->render(&painter1, QWebFrame::ContentsLayer); + painter1.end(); + + QCOMPARE(size.width(), picture.boundingRect().width() + frame->scrollBarGeometry(Qt::Vertical).width()); + QCOMPARE(size.height(), picture.boundingRect().height() + frame->scrollBarGeometry(Qt::Horizontal).height()); + + // render everything, should be the size of the iframe + QPainter painter2(&picture); + frame->render(&painter2, QWebFrame::AllLayers); + painter2.end(); + + QCOMPARE(size.width(), picture.boundingRect().width()); // width: 100px + QCOMPARE(size.height(), picture.boundingRect().height()); // height: 100px +} + + +class DummyPaintEngine: public QPaintEngine { +public: + + DummyPaintEngine() + : QPaintEngine(QPaintEngine::AllFeatures) + , renderHints(0) + { + } + + bool begin(QPaintDevice*) + { + setActive(true); + return true; + } + + bool end() + { + setActive(false); + return false; + } + + void updateState(const QPaintEngineState& state) + { + renderHints = state.renderHints(); + } + + void drawPath(const QPainterPath&) { } + void drawPixmap(const QRectF&, const QPixmap&, const QRectF&) { } + + QPaintEngine::Type type() const + { + return static_cast<QPaintEngine::Type>(QPaintEngine::User + 2); + } + + QPainter::RenderHints renderHints; +}; + +class DummyPaintDevice: public QPaintDevice { +public: + DummyPaintDevice() + : QPaintDevice() + , m_engine(new DummyPaintEngine) + { + } + + ~DummyPaintDevice() + { + delete m_engine; + } + + QPaintEngine* paintEngine() const + { + return m_engine; + } + + QPainter::RenderHints renderHints() const + { + return m_engine->renderHints; + } + +protected: + int metric(PaintDeviceMetric metric) const; + +private: + DummyPaintEngine* m_engine; + friend class DummyPaintEngine; +}; + + +int DummyPaintDevice::metric(PaintDeviceMetric metric) const +{ + switch (metric) { + case PdmWidth: + return 400; + break; + + case PdmHeight: + return 200; + break; + + case PdmNumColors: + return INT_MAX; + break; + + case PdmDepth: + return 32; + break; + + default: + break; + } + return 0; +} + +void tst_QWebFrame::renderHints() +{ + QString html("<html><body><p>Hello, world!</p></body></html>"); + + QWebPage page; + page.mainFrame()->setHtml(html); + page.setViewportSize(page.mainFrame()->contentsSize()); + + // We will call frame->render and trap the paint engine state changes + // to ensure that GraphicsContext does not clobber the render hints. + DummyPaintDevice buffer; + QPainter painter(&buffer); + + painter.setRenderHint(QPainter::TextAntialiasing, false); + page.mainFrame()->render(&painter); + QVERIFY(!(buffer.renderHints() & QPainter::TextAntialiasing)); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::TextAntialiasing, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform)); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing)); + + painter.setRenderHint(QPainter::HighQualityAntialiasing, true); + page.mainFrame()->render(&painter); + QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing); + QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform); + QVERIFY(buffer.renderHints() & QPainter::HighQualityAntialiasing); +} + +void tst_QWebFrame::scrollPosition() +{ + // enlarged image in a small viewport, to provoke the scrollbars to appear + QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>"); + + QWebPage page; + page.setViewportSize(QSize(200, 200)); + + QWebFrame* frame = page.mainFrame(); + frame->setHtml(html); + frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + // try to set the scroll offset programmatically + frame->setScrollPosition(QPoint(23, 29)); + QCOMPARE(frame->scrollPosition().x(), 23); + QCOMPARE(frame->scrollPosition().y(), 29); + + int x = frame->evaluateJavaScript("window.scrollX").toInt(); + int y = frame->evaluateJavaScript("window.scrollY").toInt(); + QCOMPARE(x, 23); + QCOMPARE(y, 29); +} + +void tst_QWebFrame::scrollToAnchor() +{ + QWebPage page; + page.setViewportSize(QSize(480, 800)); + QWebFrame* frame = page.mainFrame(); + + QString html("<html><body><p style=\"margin-bottom: 1500px;\">Hello.</p>" + "<p><a id=\"foo\">This</a> is an anchor</p>" + "<p style=\"margin-bottom: 1500px;\"><a id=\"bar\">This</a> is another anchor</p>" + "</body></html>"); + frame->setHtml(html); + frame->setScrollPosition(QPoint(0, 0)); + QCOMPARE(frame->scrollPosition().x(), 0); + QCOMPARE(frame->scrollPosition().y(), 0); + + QWebElement fooAnchor = frame->findFirstElement("a[id=foo]"); + + frame->scrollToAnchor("foo"); + QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top()); + + frame->scrollToAnchor("bar"); + frame->scrollToAnchor("foo"); + QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top()); + + frame->scrollToAnchor("top"); + QCOMPARE(frame->scrollPosition().y(), 0); + + frame->scrollToAnchor("bar"); + frame->scrollToAnchor("notexist"); + QVERIFY(frame->scrollPosition().y() != 0); +} + + +void tst_QWebFrame::scrollbarsOff() +{ + QWebView view; + QWebFrame* mainFrame = view.page()->mainFrame(); + + mainFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); + mainFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); + + QString html("<script>" \ + " function checkScrollbar() {" \ + " if (innerWidth === document.documentElement.offsetWidth)" \ + " document.getElementById('span1').innerText = 'SUCCESS';" \ + " else" \ + " document.getElementById('span1').innerText = 'FAIL';" \ + " }" \ + "</script>" \ + "<body>" \ + " <div style='margin-top:1000px ; margin-left:1000px'>" \ + " <a id='offscreen' href='a'>End</a>" \ + " </div>" \ + "<span id='span1'></span>" \ + "</body>"); + + + view.setHtml(html); + ::waitForSignal(&view, SIGNAL(loadFinished(bool))); + + mainFrame->evaluateJavaScript("checkScrollbar();"); + QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS")); +} + +void tst_QWebFrame::horizontalScrollAfterBack() +{ + QWebView view; + QWebFrame* frame = view.page()->mainFrame(); + QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool))); + + view.page()->settings()->setMaximumPagesInCache(2); + frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded); + frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded); + + view.load(QUrl("qrc:/testiframe2.html")); + view.resize(200, 200); + QTRY_COMPARE(loadSpy.count(), 1); + QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height()); + + view.load(QUrl("qrc:/testiframe.html")); + QTRY_COMPARE(loadSpy.count(), 2); + + view.page()->triggerAction(QWebPage::Back); + QTRY_COMPARE(loadSpy.count(), 3); + QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height()); +} + +void tst_QWebFrame::evaluateWillCauseRepaint() +{ + QWebView view; + QString html("<html><body>top<div id=\"junk\" style=\"display: block;\">" + "junk</div>bottom</body></html>"); + view.setHtml(html); + view.show(); + + QTest::qWaitForWindowShown(&view); + view.page()->mainFrame()->evaluateJavaScript( + "document.getElementById('junk').style.display = 'none';"); + + ::waitForSignal(view.page(), SIGNAL(repaintRequested(QRect))); +} + +class TestFactory : public QObject +{ + Q_OBJECT +public: + TestFactory() + : obj(0), counter(0) + {} + + Q_INVOKABLE QObject* getNewObject() + { + delete obj; + obj = new QObject(this); + obj->setObjectName(QLatin1String("test") + QString::number(++counter)); + return obj; + + } + + QObject* obj; + int counter; +}; + +void tst_QWebFrame::qObjectWrapperWithSameIdentity() +{ + m_view->setHtml("<script>function triggerBug() { document.getElementById('span1').innerText = test.getNewObject().objectName; }</script>" + "<body><span id='span1'>test</span></body>"); + + QWebFrame* mainFrame = m_view->page()->mainFrame(); + QCOMPARE(mainFrame->toPlainText(), QString("test")); + + mainFrame->addToJavaScriptWindowObject("test", new TestFactory, QScriptEngine::ScriptOwnership); + + mainFrame->evaluateJavaScript("triggerBug();"); + QCOMPARE(mainFrame->toPlainText(), QString("test1")); + + mainFrame->evaluateJavaScript("triggerBug();"); + QCOMPARE(mainFrame->toPlainText(), QString("test2")); +} + +void tst_QWebFrame::introspectQtMethods_data() +{ + QTest::addColumn<QString>("objectExpression"); + QTest::addColumn<QString>("methodName"); + QTest::addColumn<QStringList>("expectedPropertyNames"); + + QTest::newRow("myObject.mySignal") + << "myObject" << "mySignal" << (QStringList() << "connect" << "disconnect" << "length" << "name"); + QTest::newRow("myObject.mySlot") + << "myObject" << "mySlot" << (QStringList() << "connect" << "disconnect" << "length" << "name"); + QTest::newRow("myObject.myInvokable") + << "myObject" << "myInvokable" << (QStringList() << "connect" << "disconnect" << "length" << "name"); + QTest::newRow("myObject.mySignal.connect") + << "myObject.mySignal" << "connect" << (QStringList() << "length" << "name"); + QTest::newRow("myObject.mySignal.disconnect") + << "myObject.mySignal" << "disconnect" << (QStringList() << "length" << "name"); +} + +void tst_QWebFrame::introspectQtMethods() +{ + QFETCH(QString, objectExpression); + QFETCH(QString, methodName); + QFETCH(QStringList, expectedPropertyNames); + + QString methodLookup = QString::fromLatin1("%0['%1']").arg(objectExpression).arg(methodName); + QCOMPARE(evalJSV(QString::fromLatin1("Object.getOwnPropertyNames(%0).sort()").arg(methodLookup)).toStringList(), expectedPropertyNames); + + for (int i = 0; i < expectedPropertyNames.size(); ++i) { + QString name = expectedPropertyNames.at(i); + QCOMPARE(evalJS(QString::fromLatin1("%0.hasOwnProperty('%1')").arg(methodLookup).arg(name)), sTrue); + evalJS(QString::fromLatin1("var descriptor = Object.getOwnPropertyDescriptor(%0, '%1')").arg(methodLookup).arg(name)); + QCOMPARE(evalJS("typeof descriptor"), QString::fromLatin1("object")); + QCOMPARE(evalJS("descriptor.get"), sUndefined); + QCOMPARE(evalJS("descriptor.set"), sUndefined); + QCOMPARE(evalJS(QString::fromLatin1("descriptor.value === %0['%1']").arg(methodLookup).arg(name)), sTrue); + QCOMPARE(evalJS(QString::fromLatin1("descriptor.enumerable")), sFalse); + QCOMPARE(evalJS(QString::fromLatin1("descriptor.configurable")), sFalse); + } + + QVERIFY(evalJSV("var props=[]; for (var p in myObject.deleteLater) {props.push(p);}; props.sort()").toStringList().isEmpty()); +} + +void tst_QWebFrame::setContent_data() +{ + QTest::addColumn<QString>("mimeType"); + QTest::addColumn<QByteArray>("testContents"); + QTest::addColumn<QString>("expected"); + + QString str = QString::fromUtf8("ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει"); + QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str; + + QTextCodec *utf16 = QTextCodec::codecForName("UTF-16"); + if (utf16) + QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str; + + str = QString::fromUtf8("Une chaîne de caractères à sa façon."); + QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str; + + +} + +void tst_QWebFrame::setContent() +{ + QFETCH(QString, mimeType); + QFETCH(QByteArray, testContents); + QFETCH(QString, expected); + m_view->setContent(testContents, mimeType); + QWebFrame* mainFrame = m_view->page()->mainFrame(); + QCOMPARE(expected , mainFrame->toPlainText()); +} + +class CacheNetworkAccessManager : public QNetworkAccessManager { +public: + CacheNetworkAccessManager(QObject* parent = 0) + : QNetworkAccessManager(parent) + , m_lastCacheLoad(QNetworkRequest::PreferNetwork) + { + } + + virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*) + { + QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute); + if (cacheLoad.isValid()) + m_lastCacheLoad = static_cast<QNetworkRequest::CacheLoadControl>(cacheLoad.toUInt()); + else + m_lastCacheLoad = QNetworkRequest::PreferNetwork; // default value + return new FakeReply(request, this); + } + + QNetworkRequest::CacheLoadControl lastCacheLoad() const + { + return m_lastCacheLoad; + } + +private: + QNetworkRequest::CacheLoadControl m_lastCacheLoad; +}; + +void tst_QWebFrame::setCacheLoadControlAttribute() +{ + QWebPage page; + CacheNetworkAccessManager* manager = new CacheNetworkAccessManager(&page); + page.setNetworkAccessManager(manager); + QWebFrame* frame = page.mainFrame(); + + QNetworkRequest request(QUrl("http://abcdef.abcdef/")); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferCache); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysNetwork); + + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); + frame->load(request); + QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferNetwork); +} + +void tst_QWebFrame::webElementSlotOnly() +{ + MyWebElementSlotOnlyObject object; + m_page->mainFrame()->setHtml("<html><head><body></body></html>"); + m_page->mainFrame()->addToJavaScriptWindowObject("myWebElementSlotObject", &object); + evalJS("myWebElementSlotObject.doSomethingWithWebElement(document.body)"); + QCOMPARE(evalJS("myWebElementSlotObject.tagName"), QString("BODY")); +} + +void tst_QWebFrame::setUrlWithPendingLoads() +{ + QWebPage page; + page.mainFrame()->setHtml("<img src='dummy:'/>"); + page.mainFrame()->setUrl(QUrl("about:blank")); +} + +void tst_QWebFrame::setUrlWithFragment_data() +{ + QTest::addColumn<QUrl>("previousUrl"); + QTest::newRow("empty") << QUrl(); + QTest::newRow("same URL no fragment") << QUrl("qrc:/test1.html"); + // See comments in setUrlSameUrl about using setUrl() with the same url(). + QTest::newRow("same URL with same fragment") << QUrl("qrc:/test1.html#"); + QTest::newRow("same URL with different fragment") << QUrl("qrc:/test1.html#anotherFragment"); + QTest::newRow("another URL") << QUrl("qrc:/test2.html"); +} + +// Based on bug report https://bugs.webkit.org/show_bug.cgi?id=32723 +void tst_QWebFrame::setUrlWithFragment() +{ + QFETCH(QUrl, previousUrl); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + if (!previousUrl.isEmpty()) { + frame->load(previousUrl); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), previousUrl); + } + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + const QUrl url("qrc:/test1.html#"); + QVERIFY(!url.fragment().isNull()); + + frame->setUrl(url); + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), 1); + QVERIFY(!frame->toPlainText().isEmpty()); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->url(), url); +} + +void tst_QWebFrame::setUrlToEmpty() +{ + int expectedLoadFinishedCount = 0; + const QUrl aboutBlank("about:blank"); + const QUrl url("qrc:/test2.html"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + QCOMPARE(frame->url(), QUrl()); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), QUrl()); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + // Set existing url + frame->setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), url); + + // Set empty url + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), aboutBlank); + + // Set existing url + frame->setUrl(url); + expectedLoadFinishedCount++; + ::waitForSignal(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), url); + + // Load empty url + frame->load(QUrl()); + expectedLoadFinishedCount++; + + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(frame->baseUrl(), aboutBlank); +} + +void tst_QWebFrame::setUrlToInvalid() +{ + QWebPage page; + QWebFrame* frame = page.mainFrame(); + + const QUrl invalidUrl("http://strange;hostname/here"); + QVERIFY(!invalidUrl.isEmpty()); + QVERIFY(!invalidUrl.isValid()); + QVERIFY(invalidUrl != QUrl()); + + frame->setUrl(invalidUrl); + QCOMPARE(frame->url(), invalidUrl); + QCOMPARE(frame->requestedUrl(), invalidUrl); + QCOMPARE(frame->baseUrl(), invalidUrl); + + // QUrls equivalent to QUrl() will be treated as such. + const QUrl aboutBlank("about:blank"); + const QUrl anotherInvalidUrl("1http://bugs.webkit.org"); + QVERIFY(!anotherInvalidUrl.isEmpty()); // and they are not necessarily empty. + QVERIFY(!anotherInvalidUrl.isValid()); + QCOMPARE(anotherInvalidUrl, QUrl()); + + frame->setUrl(anotherInvalidUrl); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), anotherInvalidUrl); + QCOMPARE(frame->baseUrl(), aboutBlank); +} + +void tst_QWebFrame::setUrlHistory() +{ + const QUrl aboutBlank("about:blank"); + QUrl url; + int expectedLoadFinishedCount = 0; + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + QCOMPARE(m_page->history()->count(), 0); + + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("http://non.existant/"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 0); + + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 1); + + frame->setUrl(QUrl()); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), aboutBlank); + QCOMPARE(frame->requestedUrl(), QUrl()); + QCOMPARE(m_page->history()->count(), 1); + + // Loading same page as current in history, so history count doesn't change. + url = QUrl("qrc:/test1.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 1); + + url = QUrl("qrc:/test2.html"); + frame->setUrl(url); + ::waitForSignal(m_page, SIGNAL(loadFinished(bool))); + expectedLoadFinishedCount++; + QCOMPARE(spy.count(), expectedLoadFinishedCount); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(m_page->history()->count(), 2); +} + +void tst_QWebFrame::setUrlSameUrl() +{ + const QUrl url1("qrc:/test1.html"); + const QUrl url2("qrc:/test2.html"); + + QWebPage page; + QWebFrame* frame = page.mainFrame(); + FakeNetworkManager* networkManager = new FakeNetworkManager(&page); + page.setNetworkAccessManager(networkManager); + + QSignalSpy spy(frame, SIGNAL(loadFinished(bool))); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QVERIFY(frame->url() != url1); // Nota bene: our QNAM redirects url1 to url2 + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 1); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QVERIFY(frame->url() != url1); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 2); + + // Now a case without redirect. The existing behavior we have for setUrl() + // is more like a "clear(); load()", so the page will be loaded again, even + // if urlToBeLoaded == url(). This test should be changed if we want to + // make setUrl() early return in this case. + frame->setUrl(url2); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 3); + + frame->setUrl(url1); + waitForSignal(frame, SIGNAL(loadFinished(bool))); + QCOMPARE(frame->url(), url2); + QCOMPARE(spy.count(), 4); +} + +static inline QUrl extractBaseUrl(const QUrl& url) +{ + return url.resolved(QUrl()); +} + +void tst_QWebFrame::setUrlThenLoads_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QUrl>("baseUrl"); + + QTest::newRow("resource file") << QUrl("qrc:/test1.html") << extractBaseUrl(QUrl("qrc:/test1.html")); + QTest::newRow("base specified in HTML") << QUrl("data:text/html,<head><base href=\"http://different.base/\"></head>") << QUrl("http://different.base/"); +} + +void tst_QWebFrame::setUrlThenLoads() +{ + QFETCH(QUrl, url); + QFETCH(QUrl, baseUrl); + QWebFrame* frame = m_page->mainFrame(); + QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl))); + QSignalSpy startedSpy(frame, SIGNAL(loadStarted())); + QSignalSpy finishedSpy(frame, SIGNAL(loadFinished(bool))); + + frame->setUrl(url); + QCOMPARE(startedSpy.count(), 1); + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 1); + QVERIFY(finishedSpy.at(0).first().toBool()); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), url); + QCOMPARE(frame->baseUrl(), baseUrl); + + const QUrl urlToLoad1("qrc:/test2.html"); + const QUrl urlToLoad2("qrc:/test1.html"); + + // Just after first load. URL didn't changed yet. + frame->load(urlToLoad1); + QCOMPARE(startedSpy.count(), 2); + QCOMPARE(frame->url(), url); + QCOMPARE(frame->requestedUrl(), urlToLoad1); + QCOMPARE(frame->baseUrl(), baseUrl); + + // After first URL changed. + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 2); + QVERIFY(finishedSpy.at(1).first().toBool()); + QCOMPARE(frame->url(), urlToLoad1); + QCOMPARE(frame->requestedUrl(), urlToLoad1); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1)); + + // Just after second load. URL didn't changed yet. + frame->load(urlToLoad2); + QCOMPARE(startedSpy.count(), 3); + QCOMPARE(frame->url(), urlToLoad1); + QCOMPARE(frame->requestedUrl(), urlToLoad2); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1)); + + // After second URL changed. + ::waitForSignal(frame, SIGNAL(urlChanged(QUrl))); + QCOMPARE(urlChangedSpy.count(), 3); + QVERIFY(finishedSpy.at(2).first().toBool()); + QCOMPARE(frame->url(), urlToLoad2); + QCOMPARE(frame->requestedUrl(), urlToLoad2); + QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad2)); +} + +QTEST_MAIN(tst_QWebFrame) +#include "tst_qwebframe.moc" diff --git a/Source/WebKit/qt/tests/qwebframe/tst_qwebframe.qrc b/Source/WebKit/qt/tests/qwebframe/tst_qwebframe.qrc new file mode 100644 index 0000000..2a7d0b9 --- /dev/null +++ b/Source/WebKit/qt/tests/qwebframe/tst_qwebframe.qrc @@ -0,0 +1,10 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> +<file alias="image.png">resources/image.png</file> +<file alias="style.css">resources/style.css</file> +<file alias="test1.html">resources/test1.html</file> +<file alias="test2.html">resources/test2.html</file> +<file alias="testiframe.html">resources/testiframe.html</file> +<file alias="testiframe2.html">resources/testiframe2.html</file> +</qresource> +</RCC> |
