2008-08-07 Simon Hausmann <hausmann@webkit.org>
authorhausmann@webkit.org <hausmann@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Aug 2008 11:31:59 +0000 (11:31 +0000)
committerhausmann@webkit.org <hausmann@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Aug 2008 11:31:59 +0000 (11:31 +0000)
        Rubber-stamped by Lars.

        Added API tests for QWebPage/QWebFrame based on QTestLib.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@35621 268f45cc-cd09-0410-ab3c-d52691b4dbfc

ChangeLog
WebKit.pro
WebKit/qt/ChangeLog
WebKit/qt/tests/qwebframe/qwebframe.pro [new file with mode: 0644]
WebKit/qt/tests/qwebframe/tst_qwebframe.cpp [new file with mode: 0644]
WebKit/qt/tests/qwebpage/qwebpage.pro [new file with mode: 0644]
WebKit/qt/tests/qwebpage/tst_qwebpage.cpp [new file with mode: 0644]
WebKit/qt/tests/tests.pro [new file with mode: 0644]

index 68b02ab..b20abef 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2008-08-07  Simon Hausmann  <hausmann@webkit.org>
+
+        Rubber-stamped by Lars.
+
+        Added API tests for QWebPage/QWebFrame based on QTestLib.
+
+        * WebKit.pro: Add WebKit/qt/tests to the build.
+
 2008-08-06  Marco Barisione  <marco.barisione@collabora.co.uk>
 
         Reviewed by Eric Seidel.
index 8c36b62..035f73a 100644 (file)
@@ -5,7 +5,8 @@ SUBDIRS += \
         JavaScriptCore \
         WebCore \
         JavaScriptCore/kjs/jsc.pro \
-        WebKit/qt/QtLauncher
+        WebKit/qt/QtLauncher \
+        WebKit/qt/tests
 
 !win32-*: SUBDIRS += WebKitTools/DumpRenderTree/qt/DumpRenderTree.pro
 
index 7124fad..42ab6a2 100644 (file)
@@ -1,3 +1,15 @@
+2008-08-07  Simon Hausmann  <hausmann@webkit.org>
+
+        Rubber-stamped by Lars.
+
+        Added API tests for QWebPage/QWebFrame based on QTestLib.
+
+        * tests/qwebframe/qwebframe.pro: Added.
+        * tests/qwebframe/tst_qwebframe.cpp: Added.
+        * tests/qwebpage/qwebpage.pro: Added.
+        * tests/qwebpage/tst_qwebpage.cpp: Added.
+        * tests/tests.pro: Added.
+
 2008-08-06  Benjamin C Meyer  <ben@meyerhome.net>
 
         Reviewed by Simon.
diff --git a/WebKit/qt/tests/qwebframe/qwebframe.pro b/WebKit/qt/tests/qwebframe/qwebframe.pro
new file mode 100644 (file)
index 0000000..9715fd6
--- /dev/null
@@ -0,0 +1,6 @@
+TEMPLATE = app
+TARGET = tst_qwebframe
+include(../../../../WebKit.pri)
+SOURCES  += tst_qwebframe.cpp
+QT += testlib network
+QMAKE_RPATHDIR = $$OUTPUT_DIR/lib $$QMAKE_RPATHDIR
diff --git a/WebKit/qt/tests/qwebframe/tst_qwebframe.cpp b/WebKit/qt/tests/qwebframe/tst_qwebframe.cpp
new file mode 100644 (file)
index 0000000..d29a224
--- /dev/null
@@ -0,0 +1,2064 @@
+/*
+    Copyright (C) 2008 Trolltech ASA
+
+    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 <qwidget.h>
+#include <qwebview.h>
+#include <qwebframe.h>
+#include <qwebhistory.h>
+#include <QRegExp>
+//TESTED_CLASS=
+//TESTED_FILES=
+
+// Task 160192
+/**
+ * Starts an event loop that runs until the given signal is received.
+ Optionally the event loop
+ * can return earlier on a timeout.
+ *
+ * \return \p true if the requested signal was received
+ *         \p false on timeout
+ */
+static bool waitForSignal(QObject* obj, const char* signal, int timeout = 0)
+{
+    QEventLoop loop;
+    QObject::connect(obj, signal, &loop, SLOT(quit()));
+    QTimer timer;
+    QSignalSpy timeoutSpy(&timer, SIGNAL(timeout()));
+    if (timeout > 0) {
+        QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
+        timer.setSingleShot(true);
+        timer.start(timeout);
+    }
+    loop.exec();
+    return timeoutSpy.isEmpty();
+}
+
+/* Mostly a test for the JavaScript related parts of QWebFrame */
+
+
+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(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_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_qtFunctionInvoked(-1) { }
+
+    ~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;
+    }
+
+    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;
+    }
+
+    CustomType propWithCustomType() const {
+        return m_customType;
+    }
+    void setPropWithCustomType(const CustomType &c) {
+        m_customType = c;
+    }
+
+    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 << qVariantFromValue(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 << qVariantFromValue(lst);
+        return lst;
+    }
+    Q_INVOKABLE QObject* myInvokableWithQObjectStarArg(QObject* obj) {
+        m_qtFunctionInvoked = 18;
+        m_actuals << qVariantFromValue(obj);
+        return obj;
+    }
+    Q_INVOKABLE QBrush myInvokableWithQBrushArg(const QBrush &brush) {
+        m_qtFunctionInvoked = 19;
+        m_actuals << qVariantFromValue(brush);
+        return brush;
+    }
+    Q_INVOKABLE void myInvokableWithBrushStyleArg(Qt::BrushStyle style) {
+        m_qtFunctionInvoked = 43;
+        m_actuals << qVariantFromValue(style);
+    }
+    Q_INVOKABLE void myInvokableWithVoidStarArg(void* arg) {
+        m_qtFunctionInvoked = 44;
+        m_actuals << qVariantFromValue(arg);
+    }
+    Q_INVOKABLE void myInvokableWithAmbiguousArg(int arg) {
+        m_qtFunctionInvoked = 45;
+        m_actuals << qVariantFromValue(arg);
+    }
+    Q_INVOKABLE void myInvokableWithAmbiguousArg(uint arg) {
+        m_qtFunctionInvoked = 46;
+        m_actuals << qVariantFromValue(arg);
+    }
+    Q_INVOKABLE void myInvokableWithDefaultArgs(int arg1, const QString &arg2 = "") {
+        m_qtFunctionInvoked = 47;
+        m_actuals << qVariantFromValue(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 << qVariantFromValue(arg);
+    }
+    Q_INVOKABLE void myInvokableWithPointArg(const QPointF &arg) {
+        const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 51;
+        m_actuals << qVariantFromValue(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 << 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 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;
+    QString m_stringValue;
+    QStringList m_stringListValue;
+    QByteArray m_byteArrayValue;
+    QBrush m_brushValue;
+    double m_hiddenValue;
+    int m_writeOnlyValue;
+    int m_readOnlyValue;
+    QKeySequence m_shortcut;
+    CustomType m_customType;
+    int m_qtFunctionInvoked;
+    QVariantList m_actuals;
+};
+
+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();
+
+public slots:
+    void init();
+    void cleanup();
+
+private slots:
+    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 enumerate_data();
+    void enumerate();
+    void objectDeleted();
+    void typeConversion();
+    void symmetricUrl();
+    void progressSignal();
+    void domCycles();
+    void setHtml();
+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;
+    }
+
+    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;
+};
+
+tst_QWebFrame::tst_QWebFrame()
+    : sTrue("true"), sFalse("false"), sUndefined("undefined"), sArray("array"), sFunction("function"), sError("error"),
+        sString("string"), sObject("object"), sNumber("number")
+{
+}
+
+tst_QWebFrame::~tst_QWebFrame()
+{
+}
+
+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()
+{
+    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.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(qVariantFromValue(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.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"));
+
+    // 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);
+
+    // non-scriptable property
+    QCOMPARE(m_myObject->hiddenProperty(), 456.0);
+    QCOMPARE(evalJS("myObject.hiddenProperty"), sUndefined);
+    QEXPECT_FAIL("", "undefined properties not supported", Continue);
+    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);
+}
+
+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.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.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, sFunction);
+        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);
+    }
+
+    /* XFAIL - variant support
+    m_myObject->resetQtFunctionInvoked();
+    {
+        m_myObject->setVariantProperty(qVariantFromValue(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, sFunction); // implements call, so Function, not object
+    }
+
+    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);
+    }
+
+    // qscript_call()
+    {
+        m_myObject->resetQtFunctionInvoked();
+        QString type;
+        evalJS("new myObject(123)", type);
+        QCOMPARE(type, sObject);
+        QCOMPARE(m_myObject->qtFunctionInvoked(), 40);
+        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
+        QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
+    }
+    {
+        m_myObject->resetQtFunctionInvoked();
+        QString type;
+        evalJS("myObject(123)", type);
+        QCOMPARE(type, sUndefined);
+        QCOMPARE(m_myObject->qtFunctionInvoked(), 40);
+        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
+        QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
+    }
+}
+
+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: Value undefined (result of expression (function ()\n{\n}).connect) is not object."));
+    }
+    {
+        QString type;
+        QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect;  o.connect()", type);
+        QCOMPARE(type, sError);
+        QCOMPARE(ret, QLatin1String("TypeError: Value undefined (result of expression o.connect) is not object."));
+    }
+
+    {
+        QString type;
+        QString ret = evalJS("(function() { }).connect(123)", type);
+        QCOMPARE(type, sError);
+        QCOMPARE(ret, QLatin1String("TypeError: Value undefined (result of expression (function ()\n{\n}).connect) is not object."));
+    }
+    {
+        QString type;
+        QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect;  o.connect(123)", type);
+        QCOMPARE(type, sError);
+        QCOMPARE(ret, QLatin1String("TypeError: Value undefined (result of expression o.connect) is not object."));
+    }
+
+    {
+        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(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"), sFunction);
+
+    // 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"));
+    }
+
+    {
+        QString type;
+        QString ret = evalJS("bar()", type);
+        QCOMPARE(type, sError);
+        QCOMPARE(ret, QLatin1String("Error: cannot call function 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()", type);
+        QCOMPARE(type, sError);
+        QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject"));
+    }
+    {
+        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
+}
+
+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::domCycles()
+{
+    m_view->setHtml("<html><body>");
+    QVariant v = m_page->mainFrame()->evaluateJavaScript("document");
+    QVERIFY(v.type() == QVariant::Map);
+}
+
+void tst_QWebFrame::setHtml()
+{
+    QString html("<html><body><p>hello world</p></body></html>");
+    m_view->page()->mainFrame()->setHtml(html);
+    QCOMPARE(m_view->page()->mainFrame()->toHtml(), html);
+}
+
+QTEST_MAIN(tst_QWebFrame)
+#include "tst_qwebframe.moc"
diff --git a/WebKit/qt/tests/qwebpage/qwebpage.pro b/WebKit/qt/tests/qwebpage/qwebpage.pro
new file mode 100644 (file)
index 0000000..bbd98c6
--- /dev/null
@@ -0,0 +1,6 @@
+TEMPLATE = app
+TARGET = tst_qwebpage
+include(../../../../WebKit.pri)
+SOURCES  += tst_qwebpage.cpp
+QT += testlib network
+QMAKE_RPATHDIR = $$OUTPUT_DIR/lib $$QMAKE_RPATHDIR
diff --git a/WebKit/qt/tests/qwebpage/tst_qwebpage.cpp b/WebKit/qt/tests/qwebpage/tst_qwebpage.cpp
new file mode 100644 (file)
index 0000000..622e9fb
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+    Copyright (C) 2008 Trolltech ASA
+
+    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 <qwidget.h>
+#include <qwebview.h>
+#include <qwebframe.h>
+#include <qwebhistory.h>
+#include <qnetworkrequest.h>
+#include <QDebug>
+
+// Will try to wait for the condition while allowing event processing
+#define QTRY_COMPARE(__expr, __expected) \
+    do { \
+        const int __step = 50; \
+        const int __timeout = 5000; \
+        if ((__expr) != (__expected)) { \
+            QTest::qWait(0); \
+        } \
+        for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \
+            QTest::qWait(__step); \
+        } \
+        QCOMPARE(__expr, __expected); \
+    } while(0)
+
+//TESTED_CLASS=
+//TESTED_FILES=
+
+// Task 160192
+/**
+ * Starts an event loop that runs until the given signal is received.
+ Optionally the event loop
+ * can return earlier on a timeout.
+ *
+ * \return \p true if the requested signal was received
+ *         \p false on timeout
+ */
+static bool waitForSignal(QObject* obj, const char* signal, int timeout = 0)
+{
+    QEventLoop loop;
+    QObject::connect(obj, signal, &loop, SLOT(quit()));
+    QTimer timer;
+    QSignalSpy timeoutSpy(&timer, SIGNAL(timeout()));
+    if (timeout > 0) {
+        QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
+        timer.setSingleShot(true);
+        timer.start(timeout);
+    }
+    loop.exec();
+    return timeoutSpy.isEmpty();
+}
+
+class tst_QWebPage : public QObject
+{
+    Q_OBJECT
+
+public:
+    tst_QWebPage();
+    virtual ~tst_QWebPage();
+
+public slots:
+    void init();
+    void cleanup();
+
+private slots:
+    void acceptNavigationRequest();
+    void loadFinished();
+    void acceptNavigationRequestWithNewWindow();
+    void userStyleSheet();
+    void modified();
+
+private:
+
+
+private:
+    QWebView* m_view;
+    QWebPage* m_page;
+};
+
+tst_QWebPage::tst_QWebPage()
+{
+}
+
+tst_QWebPage::~tst_QWebPage()
+{
+}
+
+void tst_QWebPage::init()
+{
+    m_view = new QWebView();
+    m_page = m_view->page();
+}
+
+void tst_QWebPage::cleanup()
+{
+    delete m_view;
+}
+
+class NavigationRequestOverride : public QWebPage
+{
+public:
+    NavigationRequestOverride(QWebView* parent, bool initialValue) : QWebPage(parent), m_acceptNavigationRequest(initialValue) {}
+
+    bool m_acceptNavigationRequest;
+protected:
+    virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, QWebPage::NavigationType type) {
+        Q_UNUSED(frame);
+        Q_UNUSED(request);
+        Q_UNUSED(type);
+
+        return m_acceptNavigationRequest;
+    }
+};
+
+void tst_QWebPage::acceptNavigationRequest()
+{
+    QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool)));
+
+    NavigationRequestOverride* newPage = new NavigationRequestOverride(m_view, false);
+    m_view->setPage(newPage);
+
+    m_view->setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>"
+                            "<input type='text'><input type='submit'></form></body></html>"), QUrl());
+    QTRY_COMPARE(loadSpy.count(), 1);
+
+    m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();");
+
+    newPage->m_acceptNavigationRequest = true;
+    m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();");
+    QTRY_COMPARE(loadSpy.count(), 2);
+
+    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("foo?"));
+
+    // Restore default page
+    m_view->setPage(0);
+}
+
+
+void tst_QWebPage::loadFinished()
+{
+    QSignalSpy spyLoadStarted(m_view, SIGNAL(loadStarted()));
+    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
+
+    m_view->setHtml(QString("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
+                            "<head><meta http-equiv='refresh' content='1'></head>foo \">"
+                            "<frame src=\"data:text/html,bar\"></frameset>"), QUrl());
+    QTRY_COMPARE(spyLoadFinished.count(), 1);
+
+    QTest::qWait(3000);
+
+    QVERIFY(spyLoadStarted.count() > 1);
+    QVERIFY(spyLoadFinished.count() > 1);
+
+    spyLoadFinished.clear();
+
+    m_view->setHtml(QString("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
+                            "foo \"><frame src=\"data:text/html,bar\"></frameset>"), QUrl());
+    QTRY_COMPARE(spyLoadFinished.count(), 1);
+    QCOMPARE(spyLoadFinished.count(), 1);
+}
+
+class TestPage : public QWebPage
+{
+public:
+    TestPage(QObject* parent = 0) : QWebPage(parent) {}
+
+    struct Navigation {
+        QPointer<QWebFrame> frame;
+        QNetworkRequest request;
+        NavigationType type;
+    };
+
+    QList<Navigation> navigations;
+    QList<QWebPage*> createdWindows;
+
+    virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, NavigationType type) {
+        Navigation n;
+        n.frame = frame;
+        n.request = request;
+        n.type = type;
+        navigations.append(n);
+        return true;
+    }
+
+    virtual QWebPage* createWindow(WebWindowType type) {
+        QWebPage* page = new TestPage(this);
+        createdWindows.append(page);
+        return page;
+    }
+};
+
+void tst_QWebPage::acceptNavigationRequestWithNewWindow()
+{
+    TestPage* page = new TestPage;
+    page->settings()->setAttribute(QWebSettings::LinksIncludedInFocusChain, true);
+    m_page = page;
+    m_view->setPage(m_page);
+
+    m_view->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>"));
+    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
+
+    QFocusEvent fe(QEvent::FocusIn);
+    m_page->event(&fe);
+
+    QVERIFY(m_page->focusNextPrevChild(/*next*/ true));
+
+    QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
+    m_page->event(&keyEnter);
+
+    QCOMPARE(page->navigations.count(), 2);
+
+    TestPage::Navigation n = page->navigations.at(1);
+    QVERIFY(n.frame.isNull());
+    QCOMPARE(n.request.url().toString(), QString("data:text/html,Reached"));
+    QVERIFY(n.type == QWebPage::NavigationTypeLinkClicked);
+
+    QCOMPARE(page->createdWindows.count(), 1);
+}
+
+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());
+        return QNetworkAccessManager::createRequest(op, request, outgoingData);
+    }
+};
+
+void tst_QWebPage::userStyleSheet()
+{
+    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
+    m_page->setNetworkAccessManager(networkManager);
+    networkManager->requestedUrls.clear();
+
+    m_page->settings()->setUserStyleSheetUrl(QUrl::fromEncoded("data:text/css,p { background-image: url(qrc:/does/not/exist.png);}"));
+    m_view->setHtml("<p>hello world</p>");
+    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
+
+    QCOMPARE(networkManager->requestedUrls.count(), 2);
+    QCOMPARE(networkManager->requestedUrls.at(0), QUrl::fromEncoded("data:text/css,p { background-image: url(qrc:/does/not/exist.png);}"));
+    QCOMPARE(networkManager->requestedUrls.at(1), QUrl::fromEncoded("qrc:/does/not/exist.png"));
+}
+
+void tst_QWebPage::modified()
+{
+    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>blub"));
+    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
+
+    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body id=foo contenteditable>blah"));
+    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
+
+    QVERIFY(!m_page->isModified());
+
+//    m_page->mainFrame()->evaluateJavaScript("alert(document.getElementById('foo'))");
+    m_page->mainFrame()->evaluateJavaScript("document.getElementById('foo').focus()");
+    m_page->mainFrame()->evaluateJavaScript("document.execCommand('InsertText', true, 'Test');");
+
+    QVERIFY(m_page->isModified());
+
+    m_page->mainFrame()->evaluateJavaScript("document.execCommand('Undo', true);");
+
+    QVERIFY(!m_page->isModified());
+
+    m_page->mainFrame()->evaluateJavaScript("document.execCommand('Redo', true);");
+
+    QVERIFY(m_page->isModified());
+
+    QVERIFY(m_page->history()->canGoBack());
+    QVERIFY(!m_page->history()->canGoForward());
+    QCOMPARE(m_page->history()->count(), 2);
+    m_page->history()->back();
+    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
+
+    QVERIFY(!m_page->history()->canGoBack());
+    QVERIFY(m_page->history()->canGoForward());
+
+    QVERIFY(!m_page->isModified());
+}
+
+QTEST_MAIN(tst_QWebPage)
+#include "tst_qwebpage.moc"
diff --git a/WebKit/qt/tests/tests.pro b/WebKit/qt/tests/tests.pro
new file mode 100644 (file)
index 0000000..f08830d
--- /dev/null
@@ -0,0 +1,3 @@
+
+TEMPLATE = subdirs
+SUBDIRS = qwebframe qwebpage