1/*
2    Copyright (C) 2008,2009 Nokia Corporation and/or its subsidiary(-ies)
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20
21#include <QtTest/QtTest>
22
23#include <qwebpage.h>
24#include <qwebelement.h>
25#include <qwidget.h>
26#include <qwebview.h>
27#include <qwebframe.h>
28#include <qwebhistory.h>
29#include <QAbstractItemView>
30#include <QApplication>
31#include <QComboBox>
32#include <QPaintEngine>
33#include <QPicture>
34#include <QRegExp>
35#include <QNetworkRequest>
36#include <QNetworkReply>
37#include <QTextCodec>
38#ifndef QT_NO_OPENSSL
39#include <qsslerror.h>
40#endif
41#include "../util.h"
42
43struct CustomType {
44    QString string;
45};
46Q_DECLARE_METATYPE(CustomType)
47
48Q_DECLARE_METATYPE(QBrush*)
49Q_DECLARE_METATYPE(QObjectList)
50Q_DECLARE_METATYPE(QList<int>)
51Q_DECLARE_METATYPE(Qt::BrushStyle)
52Q_DECLARE_METATYPE(QVariantList)
53Q_DECLARE_METATYPE(QVariantMap)
54
55class MyQObject : public QObject
56{
57    Q_OBJECT
58
59    Q_PROPERTY(int intProperty READ intProperty WRITE setIntProperty)
60    Q_PROPERTY(QVariant variantProperty READ variantProperty WRITE setVariantProperty)
61    Q_PROPERTY(QVariantList variantListProperty READ variantListProperty WRITE setVariantListProperty)
62    Q_PROPERTY(QVariantMap variantMapProperty READ variantMapProperty WRITE setVariantMapProperty)
63    Q_PROPERTY(QString stringProperty READ stringProperty WRITE setStringProperty)
64    Q_PROPERTY(QStringList stringListProperty READ stringListProperty WRITE setStringListProperty)
65    Q_PROPERTY(QByteArray byteArrayProperty READ byteArrayProperty WRITE setByteArrayProperty)
66    Q_PROPERTY(QBrush brushProperty READ brushProperty WRITE setBrushProperty)
67    Q_PROPERTY(double hiddenProperty READ hiddenProperty WRITE setHiddenProperty SCRIPTABLE false)
68    Q_PROPERTY(int writeOnlyProperty WRITE setWriteOnlyProperty)
69    Q_PROPERTY(int readOnlyProperty READ readOnlyProperty)
70    Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut)
71    Q_PROPERTY(CustomType propWithCustomType READ propWithCustomType WRITE setPropWithCustomType)
72    Q_PROPERTY(QWebElement webElementProperty READ webElementProperty WRITE setWebElementProperty)
73    Q_PROPERTY(QObject* objectStarProperty READ objectStarProperty WRITE setObjectStarProperty)
74    Q_ENUMS(Policy Strategy)
75    Q_FLAGS(Ability)
76
77public:
78    enum Policy {
79        FooPolicy = 0,
80        BarPolicy,
81        BazPolicy
82    };
83
84    enum Strategy {
85        FooStrategy = 10,
86        BarStrategy,
87        BazStrategy
88    };
89
90    enum AbilityFlag {
91        NoAbility  = 0x000,
92        FooAbility = 0x001,
93        BarAbility = 0x080,
94        BazAbility = 0x200,
95        AllAbility = FooAbility | BarAbility | BazAbility
96    };
97
98    Q_DECLARE_FLAGS(Ability, AbilityFlag)
99
100    MyQObject(QObject* parent = 0)
101        : QObject(parent),
102            m_intValue(123),
103            m_variantValue(QLatin1String("foo")),
104            m_variantListValue(QVariantList() << QVariant(123) << QVariant(QLatin1String("foo"))),
105            m_stringValue(QLatin1String("bar")),
106            m_stringListValue(QStringList() << QLatin1String("zig") << QLatin1String("zag")),
107            m_brushValue(QColor(10, 20, 30, 40)),
108            m_hiddenValue(456.0),
109            m_writeOnlyValue(789),
110            m_readOnlyValue(987),
111            m_objectStar(0),
112            m_qtFunctionInvoked(-1)
113    {
114        m_variantMapValue.insert("a", QVariant(123));
115        m_variantMapValue.insert("b", QVariant(QLatin1String("foo")));
116        m_variantMapValue.insert("c", QVariant::fromValue<QObject*>(this));
117    }
118
119    ~MyQObject() { }
120
121    int intProperty() const {
122        return m_intValue;
123    }
124    void setIntProperty(int value) {
125        m_intValue = value;
126    }
127
128    QVariant variantProperty() const {
129        return m_variantValue;
130    }
131    void setVariantProperty(const QVariant &value) {
132        m_variantValue = value;
133    }
134
135    QVariantList variantListProperty() const {
136        return m_variantListValue;
137    }
138    void setVariantListProperty(const QVariantList &value) {
139        m_variantListValue = value;
140    }
141
142    QVariantMap variantMapProperty() const {
143        return m_variantMapValue;
144    }
145    void setVariantMapProperty(const QVariantMap &value) {
146        m_variantMapValue = value;
147    }
148
149    QString stringProperty() const {
150        return m_stringValue;
151    }
152    void setStringProperty(const QString &value) {
153        m_stringValue = value;
154    }
155
156    QStringList stringListProperty() const {
157        return m_stringListValue;
158    }
159    void setStringListProperty(const QStringList &value) {
160        m_stringListValue = value;
161    }
162
163    QByteArray byteArrayProperty() const {
164        return m_byteArrayValue;
165    }
166    void setByteArrayProperty(const QByteArray &value) {
167        m_byteArrayValue = value;
168    }
169
170    QBrush brushProperty() const {
171        return m_brushValue;
172    }
173    Q_INVOKABLE void setBrushProperty(const QBrush &value) {
174        m_brushValue = value;
175    }
176
177    double hiddenProperty() const {
178        return m_hiddenValue;
179    }
180    void setHiddenProperty(double value) {
181        m_hiddenValue = value;
182    }
183
184    int writeOnlyProperty() const {
185        return m_writeOnlyValue;
186    }
187    void setWriteOnlyProperty(int value) {
188        m_writeOnlyValue = value;
189    }
190
191    int readOnlyProperty() const {
192        return m_readOnlyValue;
193    }
194
195    QKeySequence shortcut() const {
196        return m_shortcut;
197    }
198    void setShortcut(const QKeySequence &seq) {
199        m_shortcut = seq;
200    }
201
202    QWebElement webElementProperty() const {
203        return m_webElement;
204    }
205
206    void setWebElementProperty(const QWebElement& element) {
207        m_webElement = element;
208    }
209
210    CustomType propWithCustomType() const {
211        return m_customType;
212    }
213    void setPropWithCustomType(const CustomType &c) {
214        m_customType = c;
215    }
216
217    QObject* objectStarProperty() const {
218        return m_objectStar;
219    }
220
221    void setObjectStarProperty(QObject* object) {
222        m_objectStar = object;
223    }
224
225
226    int qtFunctionInvoked() const {
227        return m_qtFunctionInvoked;
228    }
229
230    QVariantList qtFunctionActuals() const {
231        return m_actuals;
232    }
233
234    void resetQtFunctionInvoked() {
235        m_qtFunctionInvoked = -1;
236        m_actuals.clear();
237    }
238
239    Q_INVOKABLE void myInvokable() {
240        m_qtFunctionInvoked = 0;
241    }
242    Q_INVOKABLE void myInvokableWithIntArg(int arg) {
243        m_qtFunctionInvoked = 1;
244        m_actuals << arg;
245    }
246    Q_INVOKABLE void myInvokableWithLonglongArg(qlonglong arg) {
247        m_qtFunctionInvoked = 2;
248        m_actuals << arg;
249    }
250    Q_INVOKABLE void myInvokableWithFloatArg(float arg) {
251        m_qtFunctionInvoked = 3;
252        m_actuals << arg;
253    }
254    Q_INVOKABLE void myInvokableWithDoubleArg(double arg) {
255        m_qtFunctionInvoked = 4;
256        m_actuals << arg;
257    }
258    Q_INVOKABLE void myInvokableWithStringArg(const QString &arg) {
259        m_qtFunctionInvoked = 5;
260        m_actuals << arg;
261    }
262    Q_INVOKABLE void myInvokableWithIntArgs(int arg1, int arg2) {
263        m_qtFunctionInvoked = 6;
264        m_actuals << arg1 << arg2;
265    }
266    Q_INVOKABLE int myInvokableReturningInt() {
267        m_qtFunctionInvoked = 7;
268        return 123;
269    }
270    Q_INVOKABLE qlonglong myInvokableReturningLongLong() {
271        m_qtFunctionInvoked = 39;
272        return 456;
273    }
274    Q_INVOKABLE QString myInvokableReturningString() {
275        m_qtFunctionInvoked = 8;
276        return QLatin1String("ciao");
277    }
278    Q_INVOKABLE void myInvokableWithIntArg(int arg1, int arg2) { // overload
279        m_qtFunctionInvoked = 9;
280        m_actuals << arg1 << arg2;
281    }
282    Q_INVOKABLE void myInvokableWithEnumArg(Policy policy) {
283        m_qtFunctionInvoked = 10;
284        m_actuals << policy;
285    }
286    Q_INVOKABLE void myInvokableWithQualifiedEnumArg(MyQObject::Policy policy) {
287        m_qtFunctionInvoked = 36;
288        m_actuals << policy;
289    }
290    Q_INVOKABLE Policy myInvokableReturningEnum() {
291        m_qtFunctionInvoked = 37;
292        return BazPolicy;
293    }
294    Q_INVOKABLE MyQObject::Policy myInvokableReturningQualifiedEnum() {
295        m_qtFunctionInvoked = 38;
296        return BazPolicy;
297    }
298    Q_INVOKABLE QVector<int> myInvokableReturningVectorOfInt() {
299        m_qtFunctionInvoked = 11;
300        return QVector<int>();
301    }
302    Q_INVOKABLE void myInvokableWithVectorOfIntArg(const QVector<int> &) {
303        m_qtFunctionInvoked = 12;
304    }
305    Q_INVOKABLE QObject* myInvokableReturningQObjectStar() {
306        m_qtFunctionInvoked = 13;
307        return this;
308    }
309    Q_INVOKABLE QObjectList myInvokableWithQObjectListArg(const QObjectList &lst) {
310        m_qtFunctionInvoked = 14;
311        m_actuals << QVariant::fromValue(lst);
312        return lst;
313    }
314    Q_INVOKABLE QVariant myInvokableWithVariantArg(const QVariant &v) {
315        m_qtFunctionInvoked = 15;
316        m_actuals << v;
317        return v;
318    }
319    Q_INVOKABLE QVariantMap myInvokableWithVariantMapArg(const QVariantMap &vm) {
320        m_qtFunctionInvoked = 16;
321        m_actuals << vm;
322        return vm;
323    }
324    Q_INVOKABLE QList<int> myInvokableWithListOfIntArg(const QList<int> &lst) {
325        m_qtFunctionInvoked = 17;
326        m_actuals << QVariant::fromValue(lst);
327        return lst;
328    }
329    Q_INVOKABLE QObject* myInvokableWithQObjectStarArg(QObject* obj) {
330        m_qtFunctionInvoked = 18;
331        m_actuals << QVariant::fromValue(obj);
332        return obj;
333    }
334    Q_INVOKABLE QBrush myInvokableWithQBrushArg(const QBrush &brush) {
335        m_qtFunctionInvoked = 19;
336        m_actuals << QVariant::fromValue(brush);
337        return brush;
338    }
339    Q_INVOKABLE void myInvokableWithBrushStyleArg(Qt::BrushStyle style) {
340        m_qtFunctionInvoked = 43;
341        m_actuals << QVariant::fromValue(style);
342    }
343    Q_INVOKABLE void myInvokableWithVoidStarArg(void* arg) {
344        m_qtFunctionInvoked = 44;
345        m_actuals << QVariant::fromValue(arg);
346    }
347    Q_INVOKABLE void myInvokableWithAmbiguousArg(int arg) {
348        m_qtFunctionInvoked = 45;
349        m_actuals << QVariant::fromValue(arg);
350    }
351    Q_INVOKABLE void myInvokableWithAmbiguousArg(uint arg) {
352        m_qtFunctionInvoked = 46;
353        m_actuals << QVariant::fromValue(arg);
354    }
355    Q_INVOKABLE void myInvokableWithDefaultArgs(int arg1, const QString &arg2 = "") {
356        m_qtFunctionInvoked = 47;
357        m_actuals << QVariant::fromValue(arg1) << qVariantFromValue(arg2);
358    }
359    Q_INVOKABLE QObject& myInvokableReturningRef() {
360        m_qtFunctionInvoked = 48;
361        return *this;
362    }
363    Q_INVOKABLE const QObject& myInvokableReturningConstRef() const {
364        const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 49;
365        return *this;
366    }
367    Q_INVOKABLE void myInvokableWithPointArg(const QPoint &arg) {
368        const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 50;
369        m_actuals << QVariant::fromValue(arg);
370    }
371    Q_INVOKABLE void myInvokableWithPointArg(const QPointF &arg) {
372        const_cast<MyQObject*>(this)->m_qtFunctionInvoked = 51;
373        m_actuals << QVariant::fromValue(arg);
374    }
375    Q_INVOKABLE void myInvokableWithBoolArg(bool arg) {
376        m_qtFunctionInvoked = 52;
377        m_actuals << arg;
378    }
379
380    void emitMySignal() {
381        emit mySignal();
382    }
383    void emitMySignalWithIntArg(int arg) {
384        emit mySignalWithIntArg(arg);
385    }
386    void emitMySignal2(bool arg) {
387        emit mySignal2(arg);
388    }
389    void emitMySignal2() {
390        emit mySignal2();
391    }
392    void emitMySignalWithDateTimeArg(QDateTime dt) {
393        emit mySignalWithDateTimeArg(dt);
394    }
395    void emitMySignalWithRegexArg(QRegExp r) {
396        emit mySignalWithRegexArg(r);
397    }
398
399public Q_SLOTS:
400    void mySlot() {
401        m_qtFunctionInvoked = 20;
402    }
403    void mySlotWithIntArg(int arg) {
404        m_qtFunctionInvoked = 21;
405        m_actuals << arg;
406    }
407    void mySlotWithDoubleArg(double arg) {
408        m_qtFunctionInvoked = 22;
409        m_actuals << arg;
410    }
411    void mySlotWithStringArg(const QString &arg) {
412        m_qtFunctionInvoked = 23;
413        m_actuals << arg;
414    }
415
416    void myOverloadedSlot() {
417        m_qtFunctionInvoked = 24;
418    }
419    void myOverloadedSlot(QObject* arg) {
420        m_qtFunctionInvoked = 41;
421        m_actuals << QVariant::fromValue(arg);
422    }
423    void myOverloadedSlot(bool arg) {
424        m_qtFunctionInvoked = 25;
425        m_actuals << arg;
426    }
427    void myOverloadedSlot(const QStringList &arg) {
428        m_qtFunctionInvoked = 42;
429        m_actuals << arg;
430    }
431    void myOverloadedSlot(double arg) {
432        m_qtFunctionInvoked = 26;
433        m_actuals << arg;
434    }
435    void myOverloadedSlot(float arg) {
436        m_qtFunctionInvoked = 27;
437        m_actuals << arg;
438    }
439    void myOverloadedSlot(int arg) {
440        m_qtFunctionInvoked = 28;
441        m_actuals << arg;
442    }
443    void myOverloadedSlot(const QString &arg) {
444        m_qtFunctionInvoked = 29;
445        m_actuals << arg;
446    }
447    void myOverloadedSlot(const QColor &arg) {
448        m_qtFunctionInvoked = 30;
449        m_actuals << arg;
450    }
451    void myOverloadedSlot(const QBrush &arg) {
452        m_qtFunctionInvoked = 31;
453        m_actuals << arg;
454    }
455    void myOverloadedSlot(const QDateTime &arg) {
456        m_qtFunctionInvoked = 32;
457        m_actuals << arg;
458    }
459    void myOverloadedSlot(const QDate &arg) {
460        m_qtFunctionInvoked = 33;
461        m_actuals << arg;
462    }
463    void myOverloadedSlot(const QRegExp &arg) {
464        m_qtFunctionInvoked = 34;
465        m_actuals << arg;
466    }
467    void myOverloadedSlot(const QVariant &arg) {
468        m_qtFunctionInvoked = 35;
469        m_actuals << arg;
470    }
471    void myOverloadedSlot(const QWebElement &arg) {
472        m_qtFunctionInvoked = 36;
473        m_actuals << QVariant::fromValue<QWebElement>(arg);
474    }
475
476    void qscript_call(int arg) {
477        m_qtFunctionInvoked = 40;
478        m_actuals << arg;
479    }
480
481protected Q_SLOTS:
482    void myProtectedSlot() {
483        m_qtFunctionInvoked = 36;
484    }
485
486private Q_SLOTS:
487    void myPrivateSlot() { }
488
489Q_SIGNALS:
490    void mySignal();
491    void mySignalWithIntArg(int arg);
492    void mySignalWithDoubleArg(double arg);
493    void mySignal2(bool arg = false);
494    void mySignalWithDateTimeArg(QDateTime dt);
495    void mySignalWithRegexArg(QRegExp r);
496
497private:
498    int m_intValue;
499    QVariant m_variantValue;
500    QVariantList m_variantListValue;
501    QVariantMap m_variantMapValue;
502    QString m_stringValue;
503    QStringList m_stringListValue;
504    QByteArray m_byteArrayValue;
505    QBrush m_brushValue;
506    double m_hiddenValue;
507    int m_writeOnlyValue;
508    int m_readOnlyValue;
509    QKeySequence m_shortcut;
510    QWebElement m_webElement;
511    CustomType m_customType;
512    QObject* m_objectStar;
513    int m_qtFunctionInvoked;
514    QVariantList m_actuals;
515};
516
517class MyWebElementSlotOnlyObject : public QObject {
518    Q_OBJECT
519    Q_PROPERTY(QString tagName READ tagName)
520public slots:
521    void doSomethingWithWebElement(const QWebElement& element)
522    {
523        m_tagName = element.tagName();
524    }
525
526public:
527    QString tagName() const
528    {
529        return m_tagName;
530    }
531private:
532    QString m_tagName;
533};
534
535class MyOtherQObject : public MyQObject
536{
537public:
538    MyOtherQObject(QObject* parent = 0)
539        : MyQObject(parent) { }
540};
541
542class MyEnumTestQObject : public QObject
543{
544    Q_OBJECT
545    Q_PROPERTY(QString p1 READ p1)
546    Q_PROPERTY(QString p2 READ p2)
547    Q_PROPERTY(QString p3 READ p3 SCRIPTABLE false)
548    Q_PROPERTY(QString p4 READ p4)
549    Q_PROPERTY(QString p5 READ p5 SCRIPTABLE false)
550    Q_PROPERTY(QString p6 READ p6)
551public:
552    MyEnumTestQObject(QObject* parent = 0)
553        : QObject(parent) { }
554    QString p1() const {
555        return QLatin1String("p1");
556    }
557    QString p2() const {
558        return QLatin1String("p2");
559    }
560    QString p3() const {
561        return QLatin1String("p3");
562    }
563    QString p4() const {
564        return QLatin1String("p4");
565    }
566    QString p5() const {
567        return QLatin1String("p5");
568    }
569    QString p6() const {
570        return QLatin1String("p5");
571    }
572public Q_SLOTS:
573    void mySlot() { }
574    void myOtherSlot() { }
575Q_SIGNALS:
576    void mySignal();
577};
578
579class tst_QWebFrame : public QObject
580{
581    Q_OBJECT
582
583public:
584    tst_QWebFrame();
585    virtual ~tst_QWebFrame();
586    bool eventFilter(QObject* watched, QEvent* event);
587
588public slots:
589    void init();
590    void cleanup();
591
592private slots:
593    void horizontalScrollAfterBack();
594    void getSetStaticProperty();
595    void getSetDynamicProperty();
596    void getSetChildren();
597    void callQtInvokable();
598    void connectAndDisconnect();
599    void classEnums();
600    void classConstructor();
601    void overrideInvokable();
602    void transferInvokable();
603    void findChild();
604    void findChildren();
605    void overloadedSlots();
606    void webElementSlotOnly();
607    void enumerate_data();
608    void enumerate();
609    void objectDeleted();
610    void typeConversion();
611    void arrayObjectEnumerable();
612    void symmetricUrl();
613    void progressSignal();
614    void urlChange();
615    void domCycles();
616    void requestedUrl();
617    void requestedUrlAfterSetAndLoadFailures();
618    void javaScriptWindowObjectCleared_data();
619    void javaScriptWindowObjectCleared();
620    void javaScriptWindowObjectClearedOnEvaluate();
621    void setHtml();
622    void setHtmlWithResource();
623    void setHtmlWithBaseURL();
624    void setHtmlWithJSAlert();
625    void ipv6HostEncoding();
626    void metaData();
627#if !defined(Q_WS_MAEMO_5) && !defined(Q_OS_SYMBIAN) && !defined(QT_NO_COMBOBOX)
628    // as maemo 5 && symbian do not use QComboBoxes to implement the popups
629    // this test does not make sense for it.
630    void popupFocus();
631#endif
632    void inputFieldFocus();
633    void hitTestContent();
634    void jsByteArray();
635    void ownership();
636    void nullValue();
637    void baseUrl_data();
638    void baseUrl();
639    void hasSetFocus();
640    void render();
641    void renderHints();
642    void scrollPosition();
643    void scrollToAnchor();
644    void scrollbarsOff();
645    void evaluateWillCauseRepaint();
646    void qObjectWrapperWithSameIdentity();
647    void introspectQtMethods_data();
648    void introspectQtMethods();
649    void setContent_data();
650    void setContent();
651    void setCacheLoadControlAttribute();
652    void setUrlWithPendingLoads();
653    void setUrlWithFragment_data();
654    void setUrlWithFragment();
655    void setUrlToEmpty();
656    void setUrlToInvalid();
657    void setUrlHistory();
658    void setUrlSameUrl();
659    void setUrlThenLoads_data();
660    void setUrlThenLoads();
661
662private:
663    QString  evalJS(const QString&s) {
664        // Convert an undefined return variant to the string "undefined"
665        QVariant ret = evalJSV(s);
666        if (ret.userType() == QMetaType::Void)
667            return "undefined";
668        else
669            return ret.toString();
670    }
671    QVariant evalJSV(const QString &s) {
672        return m_page->mainFrame()->evaluateJavaScript(s);
673    }
674
675    QString  evalJS(const QString&s, QString& type) {
676        return evalJSV(s, type).toString();
677    }
678    QVariant evalJSV(const QString &s, QString& type) {
679        // As a special measure, if we get an exception we set the type to 'error'
680        // (in ecma, an Error object has typeof object, but qtscript has a convenience function)
681        // Similarly, an array is an object, but we'd prefer to have a type of 'array'
682        // Also, consider a QMetaType::Void QVariant to be undefined
683        QString escaped = s;
684        escaped.replace('\'', "\\'"); // Don't preescape your single quotes!
685        evalJS("var retvalue;\
686               var typevalue; \
687               try {\
688               retvalue = eval('" + escaped + "'); \
689               typevalue = typeof retvalue; \
690               if (retvalue instanceof Array) \
691               typevalue = 'array'; \
692           } \
693               catch(e) {\
694               retvalue = e.name + ': ' + e.message;\
695               typevalue = 'error';\
696           }");
697        QVariant ret = evalJSV("retvalue");
698        if (ret.userType() != QMetaType::Void)
699            type = evalJS("typevalue");
700        else {
701            ret = QString("undefined");
702            type = sUndefined;
703        }
704        evalJS("delete retvalue; delete typevalue");
705        return ret;
706    }
707    QObject* firstChildByClassName(QObject* parent, const char* className) {
708        const QObjectList & children = parent->children();
709        foreach (QObject* child, children) {
710            if (!strcmp(child->metaObject()->className(), className)) {
711                return child;
712            }
713        }
714        return 0;
715    }
716
717    const QString sTrue;
718    const QString sFalse;
719    const QString sUndefined;
720    const QString sArray;
721    const QString sFunction;
722    const QString sError;
723    const QString sString;
724    const QString sObject;
725    const QString sNumber;
726
727private:
728    QWebView* m_view;
729    QWebPage* m_page;
730    MyQObject* m_myObject;
731    QWebView* m_inputFieldsTestView;
732    int m_inputFieldTestPaintCount;
733};
734
735tst_QWebFrame::tst_QWebFrame()
736    : sTrue("true"), sFalse("false"), sUndefined("undefined"), sArray("array"), sFunction("function"), sError("error"),
737        sString("string"), sObject("object"), sNumber("number"), m_inputFieldsTestView(0), m_inputFieldTestPaintCount(0)
738{
739}
740
741tst_QWebFrame::~tst_QWebFrame()
742{
743}
744
745bool tst_QWebFrame::eventFilter(QObject* watched, QEvent* event)
746{
747    // used on the inputFieldFocus test
748    if (watched == m_inputFieldsTestView) {
749        if (event->type() == QEvent::Paint)
750            m_inputFieldTestPaintCount++;
751    }
752    return QObject::eventFilter(watched, event);
753}
754
755void tst_QWebFrame::init()
756{
757    m_view = new QWebView();
758    m_page = m_view->page();
759    m_myObject = new MyQObject();
760    m_page->mainFrame()->addToJavaScriptWindowObject("myObject", m_myObject);
761}
762
763void tst_QWebFrame::cleanup()
764{
765    delete m_view;
766    delete m_myObject;
767}
768
769void tst_QWebFrame::getSetStaticProperty()
770{
771    m_page->mainFrame()->setHtml("<html><head><body></body></html>");
772    QCOMPARE(evalJS("typeof myObject.noSuchProperty"), sUndefined);
773
774    // initial value (set in MyQObject constructor)
775    {
776        QString type;
777        QVariant ret = evalJSV("myObject.intProperty", type);
778        QCOMPARE(type, sNumber);
779        QCOMPARE(ret.type(), QVariant::Double);
780        QCOMPARE(ret.toInt(), 123);
781    }
782    QCOMPARE(evalJS("myObject.intProperty === 123.0"), sTrue);
783
784    {
785        QString type;
786        QVariant ret = evalJSV("myObject.variantProperty", type);
787        QCOMPARE(type, sString);
788        QCOMPARE(ret.type(), QVariant::String);
789        QCOMPARE(ret.toString(), QLatin1String("foo"));
790    }
791    QCOMPARE(evalJS("myObject.variantProperty == 'foo'"), sTrue);
792
793    {
794        QString type;
795        QVariant ret = evalJSV("myObject.stringProperty", type);
796        QCOMPARE(type, sString);
797        QCOMPARE(ret.type(), QVariant::String);
798        QCOMPARE(ret.toString(), QLatin1String("bar"));
799    }
800    QCOMPARE(evalJS("myObject.stringProperty === 'bar'"), sTrue);
801
802    {
803        QString type;
804        QVariant ret = evalJSV("myObject.variantListProperty", type);
805        QCOMPARE(type, sArray);
806        QCOMPARE(ret.type(), QVariant::List);
807        QVariantList vl = ret.value<QVariantList>();
808        QCOMPARE(vl.size(), 2);
809        QCOMPARE(vl.at(0).toInt(), 123);
810        QCOMPARE(vl.at(1).toString(), QLatin1String("foo"));
811    }
812    QCOMPARE(evalJS("myObject.variantListProperty.length === 2"), sTrue);
813    QCOMPARE(evalJS("myObject.variantListProperty[0] === 123"), sTrue);
814    QCOMPARE(evalJS("myObject.variantListProperty[1] === 'foo'"), sTrue);
815
816    {
817        QString type;
818        QVariant ret = evalJSV("myObject.variantMapProperty", type);
819        QCOMPARE(type, sObject);
820        QCOMPARE(ret.type(), QVariant::Map);
821        QVariantMap vm = ret.value<QVariantMap>();
822        QCOMPARE(vm.size(), 3);
823        QCOMPARE(vm.value("a").toInt(), 123);
824        QCOMPARE(vm.value("b").toString(), QLatin1String("foo"));
825        QCOMPARE(vm.value("c").value<QObject*>(), static_cast<QObject*>(m_myObject));
826    }
827    QCOMPARE(evalJS("myObject.variantMapProperty.a === 123"), sTrue);
828    QCOMPARE(evalJS("myObject.variantMapProperty.b === 'foo'"), sTrue);
829    QCOMPARE(evalJS("myObject.variantMapProperty.c.variantMapProperty.b === 'foo'"), sTrue);
830
831    {
832        QString type;
833        QVariant ret = evalJSV("myObject.stringListProperty", type);
834        QCOMPARE(type, sArray);
835        QCOMPARE(ret.type(), QVariant::List);
836        QVariantList vl = ret.value<QVariantList>();
837        QCOMPARE(vl.size(), 2);
838        QCOMPARE(vl.at(0).toString(), QLatin1String("zig"));
839        QCOMPARE(vl.at(1).toString(), QLatin1String("zag"));
840    }
841    QCOMPARE(evalJS("myObject.stringListProperty.length === 2"), sTrue);
842    QCOMPARE(evalJS("typeof myObject.stringListProperty[0]"), sString);
843    QCOMPARE(evalJS("myObject.stringListProperty[0]"), QLatin1String("zig"));
844    QCOMPARE(evalJS("typeof myObject.stringListProperty[1]"), sString);
845    QCOMPARE(evalJS("myObject.stringListProperty[1]"), QLatin1String("zag"));
846
847    // property change in C++ should be reflected in script
848    m_myObject->setIntProperty(456);
849    QCOMPARE(evalJS("myObject.intProperty == 456"), sTrue);
850    m_myObject->setIntProperty(789);
851    QCOMPARE(evalJS("myObject.intProperty == 789"), sTrue);
852
853    m_myObject->setVariantProperty(QLatin1String("bar"));
854    QCOMPARE(evalJS("myObject.variantProperty === 'bar'"), sTrue);
855    m_myObject->setVariantProperty(42);
856    QCOMPARE(evalJS("myObject.variantProperty === 42"), sTrue);
857    m_myObject->setVariantProperty(QVariant::fromValue(QBrush()));
858//XFAIL
859//  QCOMPARE(evalJS("typeof myObject.variantProperty"), sVariant);
860
861    m_myObject->setStringProperty(QLatin1String("baz"));
862    QCOMPARE(evalJS("myObject.stringProperty === 'baz'"), sTrue);
863    m_myObject->setStringProperty(QLatin1String("zab"));
864    QCOMPARE(evalJS("myObject.stringProperty === 'zab'"), sTrue);
865
866    // property change in script should be reflected in C++
867    QCOMPARE(evalJS("myObject.intProperty = 123"), QLatin1String("123"));
868    QCOMPARE(evalJS("myObject.intProperty == 123"), sTrue);
869    QCOMPARE(m_myObject->intProperty(), 123);
870    QCOMPARE(evalJS("myObject.intProperty = 'ciao!';"
871                    "myObject.intProperty == 0"), sTrue);
872    QCOMPARE(m_myObject->intProperty(), 0);
873    QCOMPARE(evalJS("myObject.intProperty = '123';"
874                    "myObject.intProperty == 123"), sTrue);
875    QCOMPARE(m_myObject->intProperty(), 123);
876
877    QCOMPARE(evalJS("myObject.stringProperty = 'ciao'"), QLatin1String("ciao"));
878    QCOMPARE(evalJS("myObject.stringProperty"), QLatin1String("ciao"));
879    QCOMPARE(m_myObject->stringProperty(), QLatin1String("ciao"));
880    QCOMPARE(evalJS("myObject.stringProperty = 123;"
881                    "myObject.stringProperty"), QLatin1String("123"));
882    QCOMPARE(m_myObject->stringProperty(), QLatin1String("123"));
883    QCOMPARE(evalJS("myObject.stringProperty = null"), QString());
884    QCOMPARE(evalJS("myObject.stringProperty"), QString());
885    QCOMPARE(m_myObject->stringProperty(), QString());
886    QCOMPARE(evalJS("myObject.stringProperty = undefined"), sUndefined);
887    QCOMPARE(evalJS("myObject.stringProperty"), QString());
888    QCOMPARE(m_myObject->stringProperty(), QString());
889
890    QCOMPARE(evalJS("myObject.variantProperty = new Number(1234);"
891                    "myObject.variantProperty").toDouble(), 1234.0);
892    QCOMPARE(m_myObject->variantProperty().toDouble(), 1234.0);
893
894    QCOMPARE(evalJS("myObject.variantProperty = new Boolean(1234);"
895                    "myObject.variantProperty"), sTrue);
896    QCOMPARE(m_myObject->variantProperty().toBool(), true);
897
898    QCOMPARE(evalJS("myObject.variantProperty = null;"
899                    "myObject.variantProperty.valueOf()"), sUndefined);
900    QCOMPARE(m_myObject->variantProperty(), QVariant());
901    QCOMPARE(evalJS("myObject.variantProperty = undefined;"
902                    "myObject.variantProperty.valueOf()"), sUndefined);
903    QCOMPARE(m_myObject->variantProperty(), QVariant());
904
905    QCOMPARE(evalJS("myObject.variantProperty = 'foo';"
906                    "myObject.variantProperty.valueOf()"), QLatin1String("foo"));
907    QCOMPARE(m_myObject->variantProperty(), QVariant(QLatin1String("foo")));
908    QCOMPARE(evalJS("myObject.variantProperty = 42;"
909                    "myObject.variantProperty").toDouble(), 42.0);
910    QCOMPARE(m_myObject->variantProperty().toDouble(), 42.0);
911
912    QCOMPARE(evalJS("myObject.variantListProperty = [1, 'two', true];"
913                    "myObject.variantListProperty.length == 3"), sTrue);
914    QCOMPARE(evalJS("myObject.variantListProperty[0] === 1"), sTrue);
915    QCOMPARE(evalJS("myObject.variantListProperty[1]"), QLatin1String("two"));
916    QCOMPARE(evalJS("myObject.variantListProperty[2] === true"), sTrue);
917
918    QCOMPARE(evalJS("myObject.stringListProperty = [1, 'two', true];"
919                    "myObject.stringListProperty.length == 3"), sTrue);
920    QCOMPARE(evalJS("typeof myObject.stringListProperty[0]"), sString);
921    QCOMPARE(evalJS("myObject.stringListProperty[0] == '1'"), sTrue);
922    QCOMPARE(evalJS("typeof myObject.stringListProperty[1]"), sString);
923    QCOMPARE(evalJS("myObject.stringListProperty[1]"), QLatin1String("two"));
924    QCOMPARE(evalJS("typeof myObject.stringListProperty[2]"), sString);
925    QCOMPARE(evalJS("myObject.stringListProperty[2]"), QLatin1String("true"));
926    evalJS("myObject.webElementProperty=document.body;");
927    QCOMPARE(evalJS("myObject.webElementProperty.tagName"), QLatin1String("BODY"));
928
929    // try to delete
930    QCOMPARE(evalJS("delete myObject.intProperty"), sFalse);
931    QCOMPARE(evalJS("myObject.intProperty == 123"), sTrue);
932
933    QCOMPARE(evalJS("delete myObject.variantProperty"), sFalse);
934    QCOMPARE(evalJS("myObject.variantProperty").toDouble(), 42.0);
935
936    // custom property
937    QCOMPARE(evalJS("myObject.customProperty"), sUndefined);
938    QCOMPARE(evalJS("myObject.customProperty = 123;"
939                    "myObject.customProperty == 123"), sTrue);
940    QVariant v = m_page->mainFrame()->evaluateJavaScript("myObject.customProperty");
941    QCOMPARE(v.type(), QVariant::Double);
942    QCOMPARE(v.toInt(), 123);
943
944    // non-scriptable property
945    QCOMPARE(m_myObject->hiddenProperty(), 456.0);
946    QCOMPARE(evalJS("myObject.hiddenProperty"), sUndefined);
947    QCOMPARE(evalJS("myObject.hiddenProperty = 123;"
948                    "myObject.hiddenProperty == 123"), sTrue);
949    QCOMPARE(m_myObject->hiddenProperty(), 456.0);
950
951    // write-only property
952    QCOMPARE(m_myObject->writeOnlyProperty(), 789);
953    QCOMPARE(evalJS("typeof myObject.writeOnlyProperty"), sUndefined);
954    QCOMPARE(evalJS("myObject.writeOnlyProperty = 123;"
955                    "typeof myObject.writeOnlyProperty"), sUndefined);
956    QCOMPARE(m_myObject->writeOnlyProperty(), 123);
957
958    // read-only property
959    QCOMPARE(m_myObject->readOnlyProperty(), 987);
960    QCOMPARE(evalJS("myObject.readOnlyProperty == 987"), sTrue);
961    QCOMPARE(evalJS("myObject.readOnlyProperty = 654;"
962                    "myObject.readOnlyProperty == 987"), sTrue);
963    QCOMPARE(m_myObject->readOnlyProperty(), 987);
964
965    // QObject* property
966    m_myObject->setObjectStarProperty(0);
967    QCOMPARE(m_myObject->objectStarProperty(), (QObject*)0);
968    QCOMPARE(evalJS("myObject.objectStarProperty == null"), sTrue);
969    QCOMPARE(evalJS("typeof myObject.objectStarProperty"), sObject);
970    QCOMPARE(evalJS("Boolean(myObject.objectStarProperty)"), sFalse);
971    QCOMPARE(evalJS("String(myObject.objectStarProperty) == 'null'"), sTrue);
972    QCOMPARE(evalJS("myObject.objectStarProperty.objectStarProperty"),
973        sUndefined);
974    m_myObject->setObjectStarProperty(this);
975    QCOMPARE(evalJS("myObject.objectStarProperty != null"), sTrue);
976    QCOMPARE(evalJS("typeof myObject.objectStarProperty"), sObject);
977    QCOMPARE(evalJS("Boolean(myObject.objectStarProperty)"), sTrue);
978    QCOMPARE(evalJS("String(myObject.objectStarProperty) != 'null'"), sTrue);
979}
980
981void tst_QWebFrame::getSetDynamicProperty()
982{
983    // initially the object does not have the property
984    // In WebKit, RuntimeObjects do not inherit Object, so don't have hasOwnProperty
985
986    //QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sFalse);
987    QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined);
988
989    // add a dynamic property in C++
990    QCOMPARE(m_myObject->setProperty("dynamicProperty", 123), false);
991    //QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sTrue);
992    QCOMPARE(evalJS("typeof myObject.dynamicProperty != 'undefined'"), sTrue);
993    QCOMPARE(evalJS("myObject.dynamicProperty == 123"), sTrue);
994
995    // property change in script should be reflected in C++
996    QCOMPARE(evalJS("myObject.dynamicProperty = 'foo';"
997                    "myObject.dynamicProperty"), QLatin1String("foo"));
998    QCOMPARE(m_myObject->property("dynamicProperty").toString(), QLatin1String("foo"));
999
1000    // delete the property (XFAIL - can't delete properties)
1001    QEXPECT_FAIL("", "can't delete properties", Continue);
1002    QCOMPARE(evalJS("delete myObject.dynamicProperty"), sTrue);
1003    /*
1004    QCOMPARE(m_myObject->property("dynamicProperty").isValid(), false);
1005    QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined);
1006    //    QCOMPARE(evalJS("myObject.hasOwnProperty('dynamicProperty')"), sFalse);
1007    QCOMPARE(evalJS("typeof myObject.dynamicProperty"), sUndefined);
1008    */
1009}
1010
1011void tst_QWebFrame::getSetChildren()
1012{
1013    // initially the object does not have the child
1014    // (again, no hasOwnProperty)
1015
1016    //QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sFalse);
1017    QCOMPARE(evalJS("typeof myObject.child"), sUndefined);
1018
1019    // add a child
1020    MyQObject* child = new MyQObject(m_myObject);
1021    child->setObjectName("child");
1022//  QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sTrue);
1023    QCOMPARE(evalJS("typeof myObject.child != 'undefined'"), sTrue);
1024
1025    // add a grandchild
1026    MyQObject* grandChild = new MyQObject(child);
1027    grandChild->setObjectName("grandChild");
1028//  QCOMPARE(evalJS("myObject.child.hasOwnProperty('grandChild')"), sTrue);
1029    QCOMPARE(evalJS("typeof myObject.child.grandChild != 'undefined'"), sTrue);
1030
1031    // delete grandchild
1032    delete grandChild;
1033//  QCOMPARE(evalJS("myObject.child.hasOwnProperty('grandChild')"), sFalse);
1034    QCOMPARE(evalJS("typeof myObject.child.grandChild == 'undefined'"), sTrue);
1035
1036    // delete child
1037    delete child;
1038//  QCOMPARE(evalJS("myObject.hasOwnProperty('child')"), sFalse);
1039    QCOMPARE(evalJS("typeof myObject.child == 'undefined'"), sTrue);
1040}
1041
1042Q_DECLARE_METATYPE(QVector<int>)
1043Q_DECLARE_METATYPE(QVector<double>)
1044Q_DECLARE_METATYPE(QVector<QString>)
1045
1046void tst_QWebFrame::callQtInvokable()
1047{
1048    qRegisterMetaType<QObjectList>();
1049
1050    m_myObject->resetQtFunctionInvoked();
1051    QCOMPARE(evalJS("typeof myObject.myInvokable()"), sUndefined);
1052    QCOMPARE(m_myObject->qtFunctionInvoked(), 0);
1053    QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList());
1054
1055    // extra arguments should silently be ignored
1056    m_myObject->resetQtFunctionInvoked();
1057    QCOMPARE(evalJS("typeof myObject.myInvokable(10, 20, 30)"), sUndefined);
1058    QCOMPARE(m_myObject->qtFunctionInvoked(), 0);
1059    QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList());
1060
1061    m_myObject->resetQtFunctionInvoked();
1062    QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg(123)"), sUndefined);
1063    QCOMPARE(m_myObject->qtFunctionInvoked(), 1);
1064    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1065    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1066
1067    m_myObject->resetQtFunctionInvoked();
1068    QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg('123')"), sUndefined);
1069    QCOMPARE(m_myObject->qtFunctionInvoked(), 1);
1070    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1071    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1072
1073    m_myObject->resetQtFunctionInvoked();
1074    QCOMPARE(evalJS("typeof myObject.myInvokableWithLonglongArg(123)"), sUndefined);
1075    QCOMPARE(m_myObject->qtFunctionInvoked(), 2);
1076    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1077    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toLongLong(), qlonglong(123));
1078
1079    m_myObject->resetQtFunctionInvoked();
1080    QCOMPARE(evalJS("typeof myObject.myInvokableWithFloatArg(123.5)"), sUndefined);
1081    QCOMPARE(m_myObject->qtFunctionInvoked(), 3);
1082    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1083    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.5);
1084
1085    m_myObject->resetQtFunctionInvoked();
1086    QCOMPARE(evalJS("typeof myObject.myInvokableWithDoubleArg(123.5)"), sUndefined);
1087    QCOMPARE(m_myObject->qtFunctionInvoked(), 4);
1088    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1089    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.5);
1090
1091    m_myObject->resetQtFunctionInvoked();
1092    QCOMPARE(evalJS("typeof myObject.myInvokableWithDoubleArg(new Number(1234.5))"), sUndefined);
1093    QCOMPARE(m_myObject->qtFunctionInvoked(), 4);
1094    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1095    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 1234.5);
1096
1097    m_myObject->resetQtFunctionInvoked();
1098    QCOMPARE(evalJS("typeof myObject.myInvokableWithBoolArg(new Boolean(true))"), sUndefined);
1099    QCOMPARE(m_myObject->qtFunctionInvoked(), 52);
1100    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1101    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toBool(), true);
1102
1103    m_myObject->resetQtFunctionInvoked();
1104    QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg('ciao')"), sUndefined);
1105    QCOMPARE(m_myObject->qtFunctionInvoked(), 5);
1106    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1107    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("ciao"));
1108
1109    m_myObject->resetQtFunctionInvoked();
1110    QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(123)"), sUndefined);
1111    QCOMPARE(m_myObject->qtFunctionInvoked(), 5);
1112    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1113    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("123"));
1114
1115    m_myObject->resetQtFunctionInvoked();
1116    QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(null)"), sUndefined);
1117    QCOMPARE(m_myObject->qtFunctionInvoked(), 5);
1118    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1119    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QString());
1120    QVERIFY(m_myObject->qtFunctionActuals().at(0).toString().isEmpty());
1121
1122    m_myObject->resetQtFunctionInvoked();
1123    QCOMPARE(evalJS("typeof myObject.myInvokableWithStringArg(undefined)"), sUndefined);
1124    QCOMPARE(m_myObject->qtFunctionInvoked(), 5);
1125    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1126    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QString());
1127    QVERIFY(m_myObject->qtFunctionActuals().at(0).toString().isEmpty());
1128
1129    m_myObject->resetQtFunctionInvoked();
1130    QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArgs(123, 456)"), sUndefined);
1131    QCOMPARE(m_myObject->qtFunctionInvoked(), 6);
1132    QCOMPARE(m_myObject->qtFunctionActuals().size(), 2);
1133    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1134    QCOMPARE(m_myObject->qtFunctionActuals().at(1).toInt(), 456);
1135
1136    m_myObject->resetQtFunctionInvoked();
1137    QCOMPARE(evalJS("myObject.myInvokableReturningInt()"), QLatin1String("123"));
1138    QCOMPARE(m_myObject->qtFunctionInvoked(), 7);
1139    QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList());
1140
1141    m_myObject->resetQtFunctionInvoked();
1142    QCOMPARE(evalJS("myObject.myInvokableReturningLongLong()"), QLatin1String("456"));
1143    QCOMPARE(m_myObject->qtFunctionInvoked(), 39);
1144    QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList());
1145
1146    m_myObject->resetQtFunctionInvoked();
1147    QCOMPARE(evalJS("myObject.myInvokableReturningString()"), QLatin1String("ciao"));
1148    QCOMPARE(m_myObject->qtFunctionInvoked(), 8);
1149    QCOMPARE(m_myObject->qtFunctionActuals(), QVariantList());
1150
1151    m_myObject->resetQtFunctionInvoked();
1152    QCOMPARE(evalJS("typeof myObject.myInvokableWithIntArg(123, 456)"), sUndefined);
1153    QCOMPARE(m_myObject->qtFunctionInvoked(), 9);
1154    QCOMPARE(m_myObject->qtFunctionActuals().size(), 2);
1155    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1156    QCOMPARE(m_myObject->qtFunctionActuals().at(1).toInt(), 456);
1157
1158    m_myObject->resetQtFunctionInvoked();
1159    QCOMPARE(evalJS("typeof myObject.myInvokableWithVoidStarArg(null)"), sUndefined);
1160    QCOMPARE(m_myObject->qtFunctionInvoked(), 44);
1161    m_myObject->resetQtFunctionInvoked();
1162    {
1163        QString type;
1164        QString ret = evalJS("myObject.myInvokableWithVoidStarArg(123)", type);
1165        QCOMPARE(type, sError);
1166        QCOMPARE(ret, QLatin1String("TypeError: incompatible type of argument(s) in call to myInvokableWithVoidStarArg(); candidates were\n    myInvokableWithVoidStarArg(void*)"));
1167        QCOMPARE(m_myObject->qtFunctionInvoked(), -1);
1168    }
1169
1170    m_myObject->resetQtFunctionInvoked();
1171    {
1172        QString type;
1173        QString ret = evalJS("myObject.myInvokableWithAmbiguousArg(123)", type);
1174        QCOMPARE(type, sError);
1175        QCOMPARE(ret, QLatin1String("TypeError: ambiguous call of overloaded function myInvokableWithAmbiguousArg(); candidates were\n    myInvokableWithAmbiguousArg(int)\n    myInvokableWithAmbiguousArg(uint)"));
1176    }
1177
1178    m_myObject->resetQtFunctionInvoked();
1179    {
1180        QString type;
1181        QString ret = evalJS("myObject.myInvokableWithDefaultArgs(123, 'hello')", type);
1182        QCOMPARE(type, sUndefined);
1183        QCOMPARE(m_myObject->qtFunctionInvoked(), 47);
1184        QCOMPARE(m_myObject->qtFunctionActuals().size(), 2);
1185        QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1186        QCOMPARE(m_myObject->qtFunctionActuals().at(1).toString(), QLatin1String("hello"));
1187    }
1188
1189    m_myObject->resetQtFunctionInvoked();
1190    {
1191        QString type;
1192        QString ret = evalJS("myObject.myInvokableWithDefaultArgs(456)", type);
1193        QCOMPARE(type, sUndefined);
1194        QCOMPARE(m_myObject->qtFunctionInvoked(), 47);
1195        QCOMPARE(m_myObject->qtFunctionActuals().size(), 2);
1196        QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456);
1197        QCOMPARE(m_myObject->qtFunctionActuals().at(1).toString(), QString());
1198    }
1199
1200    // calling function that returns (const)ref
1201    m_myObject->resetQtFunctionInvoked();
1202    {
1203        QString type;
1204        QString ret = evalJS("typeof myObject.myInvokableReturningRef()");
1205        QCOMPARE(ret, sUndefined);
1206        //QVERIFY(!m_engine->hasUncaughtException());
1207        QCOMPARE(m_myObject->qtFunctionInvoked(), 48);
1208    }
1209
1210    m_myObject->resetQtFunctionInvoked();
1211    {
1212        QString type;
1213        QString ret = evalJS("typeof myObject.myInvokableReturningConstRef()");
1214        QCOMPARE(ret, sUndefined);
1215        //QVERIFY(!m_engine->hasUncaughtException());
1216        QCOMPARE(m_myObject->qtFunctionInvoked(), 49);
1217    }
1218
1219    m_myObject->resetQtFunctionInvoked();
1220    {
1221        QString type;
1222        QVariant ret = evalJSV("myObject.myInvokableReturningQObjectStar()", type);
1223        QCOMPARE(m_myObject->qtFunctionInvoked(), 13);
1224        QCOMPARE(m_myObject->qtFunctionActuals().size(), 0);
1225        QCOMPARE(type, sObject);
1226        QCOMPARE(ret.userType(), int(QMetaType::QObjectStar));
1227    }
1228
1229    m_myObject->resetQtFunctionInvoked();
1230    {
1231        QString type;
1232        QVariant ret = evalJSV("myObject.myInvokableWithQObjectListArg([myObject])", type);
1233        QCOMPARE(m_myObject->qtFunctionInvoked(), 14);
1234        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1235        QCOMPARE(type, sArray);
1236        QCOMPARE(ret.userType(), int(QVariant::List)); // All lists get downgraded to QVariantList
1237        QVariantList vl = qvariant_cast<QVariantList>(ret);
1238        QCOMPARE(vl.count(), 1);
1239    }
1240
1241    m_myObject->resetQtFunctionInvoked();
1242    {
1243        QString type;
1244        m_myObject->setVariantProperty(QVariant(123));
1245        QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(myObject.variantProperty)", type);
1246        QCOMPARE(type, sNumber);
1247        QCOMPARE(m_myObject->qtFunctionInvoked(), 15);
1248        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1249        QCOMPARE(m_myObject->qtFunctionActuals().at(0), m_myObject->variantProperty());
1250        QCOMPARE(ret.userType(), int(QMetaType::Double)); // all JS numbers are doubles, even though this started as an int
1251        QCOMPARE(ret.toInt(),123);
1252    }
1253
1254    m_myObject->resetQtFunctionInvoked();
1255    {
1256        QString type;
1257        QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(null)", type);
1258        QCOMPARE(type, sObject);
1259        QCOMPARE(m_myObject->qtFunctionInvoked(), 15);
1260        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1261        QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant());
1262        QVERIFY(!m_myObject->qtFunctionActuals().at(0).isValid());
1263    }
1264
1265    m_myObject->resetQtFunctionInvoked();
1266    {
1267        QString type;
1268        QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(undefined)", type);
1269        QCOMPARE(type, sObject);
1270        QCOMPARE(m_myObject->qtFunctionInvoked(), 15);
1271        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1272        QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant());
1273        QVERIFY(!m_myObject->qtFunctionActuals().at(0).isValid());
1274    }
1275
1276    /* XFAIL - variant support
1277    m_myObject->resetQtFunctionInvoked();
1278    {
1279        m_myObject->setVariantProperty(QVariant::fromValue(QBrush()));
1280        QVariant ret = evalJS("myObject.myInvokableWithVariantArg(myObject.variantProperty)");
1281        QVERIFY(ret.isVariant());
1282        QCOMPARE(m_myObject->qtFunctionInvoked(), 15);
1283        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1284        QCOMPARE(ret.toVariant(), m_myObject->qtFunctionActuals().at(0));
1285        QCOMPARE(ret.toVariant(), m_myObject->variantProperty());
1286    }
1287    */
1288
1289    m_myObject->resetQtFunctionInvoked();
1290    {
1291        QString type;
1292        QVariant ret = evalJSV("myObject.myInvokableWithVariantArg(123)", type);
1293        QCOMPARE(type, sNumber);
1294        QCOMPARE(m_myObject->qtFunctionInvoked(), 15);
1295        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1296        QCOMPARE(m_myObject->qtFunctionActuals().at(0), QVariant(123));
1297        QCOMPARE(ret.userType(), int(QMetaType::Double));
1298        QCOMPARE(ret.toInt(),123);
1299    }
1300
1301    m_myObject->resetQtFunctionInvoked();
1302    {
1303        QString type;
1304        QVariant ret = evalJSV("myObject.myInvokableWithVariantMapArg({ a:123, b:'ciao' })", type);
1305        QCOMPARE(m_myObject->qtFunctionInvoked(), 16);
1306        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1307
1308        QVariant v = m_myObject->qtFunctionActuals().at(0);
1309        QCOMPARE(v.userType(), int(QMetaType::QVariantMap));
1310
1311        QVariantMap vmap = qvariant_cast<QVariantMap>(v);
1312        QCOMPARE(vmap.keys().size(), 2);
1313        QCOMPARE(vmap.keys().at(0), QLatin1String("a"));
1314        QCOMPARE(vmap.value("a"), QVariant(123));
1315        QCOMPARE(vmap.keys().at(1), QLatin1String("b"));
1316        QCOMPARE(vmap.value("b"), QVariant("ciao"));
1317
1318        QCOMPARE(type, sObject);
1319
1320        QCOMPARE(ret.userType(), int(QMetaType::QVariantMap));
1321        vmap = qvariant_cast<QVariantMap>(ret);
1322        QCOMPARE(vmap.keys().size(), 2);
1323        QCOMPARE(vmap.keys().at(0), QLatin1String("a"));
1324        QCOMPARE(vmap.value("a"), QVariant(123));
1325        QCOMPARE(vmap.keys().at(1), QLatin1String("b"));
1326        QCOMPARE(vmap.value("b"), QVariant("ciao"));
1327    }
1328
1329    m_myObject->resetQtFunctionInvoked();
1330    {
1331        QString type;
1332        QVariant ret = evalJSV("myObject.myInvokableWithListOfIntArg([1, 5])", type);
1333        QCOMPARE(m_myObject->qtFunctionInvoked(), 17);
1334        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1335        QVariant v = m_myObject->qtFunctionActuals().at(0);
1336        QCOMPARE(v.userType(), qMetaTypeId<QList<int> >());
1337        QList<int> ilst = qvariant_cast<QList<int> >(v);
1338        QCOMPARE(ilst.size(), 2);
1339        QCOMPARE(ilst.at(0), 1);
1340        QCOMPARE(ilst.at(1), 5);
1341
1342        QCOMPARE(type, sArray);
1343        QCOMPARE(ret.userType(), int(QMetaType::QVariantList)); // ints get converted to doubles, so this is a qvariantlist
1344        QVariantList vlst = qvariant_cast<QVariantList>(ret);
1345        QCOMPARE(vlst.size(), 2);
1346        QCOMPARE(vlst.at(0).toInt(), 1);
1347        QCOMPARE(vlst.at(1).toInt(), 5);
1348    }
1349
1350    m_myObject->resetQtFunctionInvoked();
1351    {
1352        QString type;
1353        QVariant ret = evalJSV("myObject.myInvokableWithQObjectStarArg(myObject)", type);
1354        QCOMPARE(m_myObject->qtFunctionInvoked(), 18);
1355        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1356        QVariant v = m_myObject->qtFunctionActuals().at(0);
1357        QCOMPARE(v.userType(), int(QMetaType::QObjectStar));
1358        QCOMPARE(qvariant_cast<QObject*>(v), (QObject*)m_myObject);
1359
1360        QCOMPARE(ret.userType(), int(QMetaType::QObjectStar));
1361        QCOMPARE(qvariant_cast<QObject*>(ret), (QObject*)m_myObject);
1362
1363        QCOMPARE(type, sObject);
1364    }
1365
1366    m_myObject->resetQtFunctionInvoked();
1367    {
1368        // no implicit conversion from integer to QObject*
1369        QString type;
1370        evalJS("myObject.myInvokableWithQObjectStarArg(123)", type);
1371        QCOMPARE(type, sError);
1372    }
1373
1374    /*
1375    m_myObject->resetQtFunctionInvoked();
1376    {
1377        QString fun = evalJS("myObject.myInvokableWithQBrushArg");
1378        Q_ASSERT(fun.isFunction());
1379        QColor color(10, 20, 30, 40);
1380        // QColor should be converted to a QBrush
1381        QVariant ret = fun.call(QString(), QStringList()
1382                                    << qScriptValueFromValue(m_engine, color));
1383        QCOMPARE(m_myObject->qtFunctionInvoked(), 19);
1384        QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1385        QVariant v = m_myObject->qtFunctionActuals().at(0);
1386        QCOMPARE(v.userType(), int(QMetaType::QBrush));
1387        QCOMPARE(qvariant_cast<QColor>(v), color);
1388
1389        QCOMPARE(qscriptvalue_cast<QColor>(ret), color);
1390    }
1391    */
1392
1393    // private slots should not be part of the QObject binding
1394    QCOMPARE(evalJS("typeof myObject.myPrivateSlot"), sUndefined);
1395
1396    // protected slots should be fine
1397    m_myObject->resetQtFunctionInvoked();
1398    evalJS("myObject.myProtectedSlot()");
1399    QCOMPARE(m_myObject->qtFunctionInvoked(), 36);
1400
1401    // call with too few arguments
1402    {
1403        QString type;
1404        QString ret = evalJS("myObject.myInvokableWithIntArg()", type);
1405        QCOMPARE(type, sError);
1406        QCOMPARE(ret, QLatin1String("SyntaxError: too few arguments in call to myInvokableWithIntArg(); candidates are\n    myInvokableWithIntArg(int,int)\n    myInvokableWithIntArg(int)"));
1407    }
1408
1409    // call function where not all types have been registered
1410    m_myObject->resetQtFunctionInvoked();
1411    {
1412        QString type;
1413        QString ret = evalJS("myObject.myInvokableWithBrushStyleArg(0)", type);
1414        QCOMPARE(type, sError);
1415        QCOMPARE(ret, QLatin1String("TypeError: cannot call myInvokableWithBrushStyleArg(): unknown type `Qt::BrushStyle'"));
1416        QCOMPARE(m_myObject->qtFunctionInvoked(), -1);
1417    }
1418
1419    // call function with incompatible argument type
1420    m_myObject->resetQtFunctionInvoked();
1421    {
1422        QString type;
1423        QString ret = evalJS("myObject.myInvokableWithQBrushArg(null)", type);
1424        QCOMPARE(type, sError);
1425        QCOMPARE(ret, QLatin1String("TypeError: incompatible type of argument(s) in call to myInvokableWithQBrushArg(); candidates were\n    myInvokableWithQBrushArg(QBrush)"));
1426        QCOMPARE(m_myObject->qtFunctionInvoked(), -1);
1427    }
1428}
1429
1430void tst_QWebFrame::connectAndDisconnect()
1431{
1432    // connect(function)
1433    QCOMPARE(evalJS("typeof myObject.mySignal"), sFunction);
1434    QCOMPARE(evalJS("typeof myObject.mySignal.connect"), sFunction);
1435    QCOMPARE(evalJS("typeof myObject.mySignal.disconnect"), sFunction);
1436
1437    {
1438        QString type;
1439        evalJS("myObject.mySignal.connect(123)", type);
1440        QCOMPARE(type, sError);
1441    }
1442
1443    evalJS("myHandler = function() { window.gotSignal = true; window.signalArgs = arguments; window.slotThisObject = this; window.signalSender = __qt_sender__; }");
1444
1445    QCOMPARE(evalJS("myObject.mySignal.connect(myHandler)"), sUndefined);
1446
1447    evalJS("gotSignal = false");
1448    evalJS("myObject.mySignal()");
1449    QCOMPARE(evalJS("gotSignal"), sTrue);
1450    QCOMPARE(evalJS("signalArgs.length == 0"), sTrue);
1451    QCOMPARE(evalJS("signalSender"),evalJS("myObject"));
1452    QCOMPARE(evalJS("slotThisObject == window"), sTrue);
1453
1454    evalJS("gotSignal = false");
1455    m_myObject->emitMySignal();
1456    QCOMPARE(evalJS("gotSignal"), sTrue);
1457    QCOMPARE(evalJS("signalArgs.length == 0"), sTrue);
1458
1459    QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myHandler)"), sUndefined);
1460
1461    evalJS("gotSignal = false");
1462    m_myObject->emitMySignalWithIntArg(123);
1463    QCOMPARE(evalJS("gotSignal"), sTrue);
1464    QCOMPARE(evalJS("signalArgs.length == 1"), sTrue);
1465    QCOMPARE(evalJS("signalArgs[0] == 123.0"), sTrue);
1466
1467    QCOMPARE(evalJS("myObject.mySignal.disconnect(myHandler)"), sUndefined);
1468    {
1469        QString type;
1470        evalJS("myObject.mySignal.disconnect(myHandler)", type);
1471        QCOMPARE(type, sError);
1472    }
1473
1474    evalJS("gotSignal = false");
1475    QCOMPARE(evalJS("myObject.mySignal2.connect(myHandler)"), sUndefined);
1476    m_myObject->emitMySignal2(true);
1477    QCOMPARE(evalJS("gotSignal"), sTrue);
1478    QCOMPARE(evalJS("signalArgs.length == 1"), sTrue);
1479    QCOMPARE(evalJS("signalArgs[0]"), sTrue);
1480
1481    QCOMPARE(evalJS("myObject.mySignal2.disconnect(myHandler)"), sUndefined);
1482
1483    QCOMPARE(evalJS("typeof myObject['mySignal2()']"), sFunction);
1484    QCOMPARE(evalJS("typeof myObject['mySignal2()'].connect"), sFunction);
1485    QCOMPARE(evalJS("typeof myObject['mySignal2()'].disconnect"), sFunction);
1486
1487    QCOMPARE(evalJS("myObject['mySignal2()'].connect(myHandler)"), sUndefined);
1488
1489    evalJS("gotSignal = false");
1490    m_myObject->emitMySignal2();
1491    QCOMPARE(evalJS("gotSignal"), sTrue);
1492
1493    QCOMPARE(evalJS("myObject['mySignal2()'].disconnect(myHandler)"), sUndefined);
1494
1495    // connect(object, function)
1496    evalJS("otherObject = { name:'foo' }");
1497    QCOMPARE(evalJS("myObject.mySignal.connect(otherObject, myHandler)"), sUndefined);
1498    QCOMPARE(evalJS("myObject.mySignal.disconnect(otherObject, myHandler)"), sUndefined);
1499    evalJS("gotSignal = false");
1500    m_myObject->emitMySignal();
1501    QCOMPARE(evalJS("gotSignal"), sFalse);
1502
1503    {
1504        QString type;
1505        evalJS("myObject.mySignal.disconnect(otherObject, myHandler)", type);
1506        QCOMPARE(type, sError);
1507    }
1508
1509    QCOMPARE(evalJS("myObject.mySignal.connect(otherObject, myHandler)"), sUndefined);
1510    evalJS("gotSignal = false");
1511    m_myObject->emitMySignal();
1512    QCOMPARE(evalJS("gotSignal"), sTrue);
1513    QCOMPARE(evalJS("signalArgs.length == 0"), sTrue);
1514    QCOMPARE(evalJS("slotThisObject"),evalJS("otherObject"));
1515    QCOMPARE(evalJS("signalSender"),evalJS("myObject"));
1516    QCOMPARE(evalJS("slotThisObject.name"), QLatin1String("foo"));
1517    QCOMPARE(evalJS("myObject.mySignal.disconnect(otherObject, myHandler)"), sUndefined);
1518
1519    evalJS("yetAnotherObject = { name:'bar', func : function() { } }");
1520    QCOMPARE(evalJS("myObject.mySignal2.connect(yetAnotherObject, myHandler)"), sUndefined);
1521    evalJS("gotSignal = false");
1522    m_myObject->emitMySignal2(true);
1523    QCOMPARE(evalJS("gotSignal"), sTrue);
1524    QCOMPARE(evalJS("signalArgs.length == 1"), sTrue);
1525    QCOMPARE(evalJS("slotThisObject == yetAnotherObject"), sTrue);
1526    QCOMPARE(evalJS("signalSender == myObject"), sTrue);
1527    QCOMPARE(evalJS("slotThisObject.name"), QLatin1String("bar"));
1528    QCOMPARE(evalJS("myObject.mySignal2.disconnect(yetAnotherObject, myHandler)"), sUndefined);
1529
1530    QCOMPARE(evalJS("myObject.mySignal2.connect(myObject, myHandler)"), sUndefined);
1531    evalJS("gotSignal = false");
1532    m_myObject->emitMySignal2(true);
1533    QCOMPARE(evalJS("gotSignal"), sTrue);
1534    QCOMPARE(evalJS("signalArgs.length == 1"), sTrue);
1535    QCOMPARE(evalJS("slotThisObject == myObject"), sTrue);
1536    QCOMPARE(evalJS("signalSender == myObject"), sTrue);
1537    QCOMPARE(evalJS("myObject.mySignal2.disconnect(myObject, myHandler)"), sUndefined);
1538
1539    // connect(obj, string)
1540    QCOMPARE(evalJS("myObject.mySignal.connect(yetAnotherObject, 'func')"), sUndefined);
1541    QCOMPARE(evalJS("myObject.mySignal.connect(myObject, 'mySlot')"), sUndefined);
1542    QCOMPARE(evalJS("myObject.mySignal.disconnect(yetAnotherObject, 'func')"), sUndefined);
1543    QCOMPARE(evalJS("myObject.mySignal.disconnect(myObject, 'mySlot')"), sUndefined);
1544
1545    // check that emitting signals from script works
1546
1547    // no arguments
1548    QCOMPARE(evalJS("myObject.mySignal.connect(myObject.mySlot)"), sUndefined);
1549    m_myObject->resetQtFunctionInvoked();
1550    QCOMPARE(evalJS("myObject.mySignal()"), sUndefined);
1551    QCOMPARE(m_myObject->qtFunctionInvoked(), 20);
1552    QCOMPARE(evalJS("myObject.mySignal.disconnect(myObject.mySlot)"), sUndefined);
1553
1554    // one argument
1555    QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithIntArg)"), sUndefined);
1556    m_myObject->resetQtFunctionInvoked();
1557    QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined);
1558    QCOMPARE(m_myObject->qtFunctionInvoked(), 21);
1559    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1560    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1561    QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithIntArg)"), sUndefined);
1562
1563    QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithDoubleArg)"), sUndefined);
1564    m_myObject->resetQtFunctionInvoked();
1565    QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined);
1566    QCOMPARE(m_myObject->qtFunctionInvoked(), 22);
1567    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1568    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDouble(), 123.0);
1569    QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithDoubleArg)"), sUndefined);
1570
1571    QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.mySlotWithStringArg)"), sUndefined);
1572    m_myObject->resetQtFunctionInvoked();
1573    QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined);
1574    QCOMPARE(m_myObject->qtFunctionInvoked(), 23);
1575    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1576    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toString(), QLatin1String("123"));
1577    QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.mySlotWithStringArg)"), sUndefined);
1578
1579    // connecting to overloaded slot
1580    QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject.myOverloadedSlot)"), sUndefined);
1581    m_myObject->resetQtFunctionInvoked();
1582    QCOMPARE(evalJS("myObject.mySignalWithIntArg(123)"), sUndefined);
1583    QCOMPARE(m_myObject->qtFunctionInvoked(), 26); // double overload
1584    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1585    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 123);
1586    QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject.myOverloadedSlot)"), sUndefined);
1587
1588    QCOMPARE(evalJS("myObject.mySignalWithIntArg.connect(myObject['myOverloadedSlot(int)'])"), sUndefined);
1589    m_myObject->resetQtFunctionInvoked();
1590    QCOMPARE(evalJS("myObject.mySignalWithIntArg(456)"), sUndefined);
1591    QCOMPARE(m_myObject->qtFunctionInvoked(), 28); // int overload
1592    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1593    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), 456);
1594    QCOMPARE(evalJS("myObject.mySignalWithIntArg.disconnect(myObject['myOverloadedSlot(int)'])"), sUndefined);
1595
1596    // erroneous input
1597    {
1598        // ### QtScript adds .connect to all functions, WebKit does only to signals/slots
1599        QString type;
1600        QString ret = evalJS("(function() { }).connect()", type);
1601        QCOMPARE(type, sError);
1602        QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function"));
1603    }
1604    {
1605        QString type;
1606        QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect;  o.connect()", type);
1607        QCOMPARE(type, sError);
1608        QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function"));
1609    }
1610
1611    {
1612        QString type;
1613        QString ret = evalJS("(function() { }).connect(123)", type);
1614        QCOMPARE(type, sError);
1615        QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function"));
1616    }
1617    {
1618        QString type;
1619        QString ret = evalJS("var o = { }; o.connect = Function.prototype.connect;  o.connect(123)", type);
1620        QCOMPARE(type, sError);
1621        QCOMPARE(ret, QLatin1String("TypeError: 'undefined' is not a function"));
1622    }
1623
1624    {
1625        QString type;
1626        QString ret = evalJS("myObject.myInvokable.connect(123)", type);
1627        QCOMPARE(type, sError);
1628        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.connect: MyQObject::myInvokable() is not a signal"));
1629    }
1630    {
1631        QString type;
1632        QString ret = evalJS("myObject.myInvokable.connect(function() { })", type);
1633        QCOMPARE(type, sError);
1634        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.connect: MyQObject::myInvokable() is not a signal"));
1635    }
1636
1637    {
1638        QString type;
1639        QString ret = evalJS("myObject.mySignal.connect(123)", type);
1640        QCOMPARE(type, sError);
1641        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.connect: target is not a function"));
1642    }
1643
1644    {
1645        QString type;
1646        QString ret = evalJS("myObject.mySignal.disconnect()", type);
1647        QCOMPARE(type, sError);
1648        QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: no arguments given"));
1649    }
1650    {
1651        QString type;
1652        QString ret = evalJS("var o = { }; o.disconnect = myObject.mySignal.disconnect;  o.disconnect()", type);
1653        QCOMPARE(type, sError);
1654        QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: no arguments given"));
1655    }
1656
1657    /* XFAIL - Function.prototype doesn't get connect/disconnect, just signals/slots
1658    {
1659        QString type;
1660        QString ret = evalJS("(function() { }).disconnect(123)", type);
1661        QCOMPARE(type, sError);
1662        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: this object is not a signal"));
1663    }
1664    */
1665
1666    {
1667        QString type;
1668        QString ret = evalJS("var o = { }; o.disconnect = myObject.myInvokable.disconnect; o.disconnect(123)", type);
1669        QCOMPARE(type, sError);
1670        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal"));
1671    }
1672
1673    {
1674        QString type;
1675        QString ret = evalJS("myObject.myInvokable.disconnect(123)", type);
1676        QCOMPARE(type, sError);
1677        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal"));
1678    }
1679    {
1680        QString type;
1681        QString ret = evalJS("myObject.myInvokable.disconnect(function() { })", type);
1682        QCOMPARE(type, sError);
1683        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: MyQObject::myInvokable() is not a signal"));
1684    }
1685
1686    {
1687        QString type;
1688        QString ret = evalJS("myObject.mySignal.disconnect(123)", type);
1689        QCOMPARE(type, sError);
1690        QCOMPARE(ret, QLatin1String("TypeError: QtMetaMethod.disconnect: target is not a function"));
1691    }
1692
1693    {
1694        QString type;
1695        QString ret = evalJS("myObject.mySignal.disconnect(function() { })", type);
1696        QCOMPARE(type, sError);
1697        QCOMPARE(ret, QLatin1String("Error: QtMetaMethod.disconnect: failed to disconnect from MyQObject::mySignal()"));
1698    }
1699
1700    // when the wrapper dies, the connection stays alive
1701    QCOMPARE(evalJS("myObject.mySignal.connect(myObject.mySlot)"), sUndefined);
1702    m_myObject->resetQtFunctionInvoked();
1703    m_myObject->emitMySignal();
1704    QCOMPARE(m_myObject->qtFunctionInvoked(), 20);
1705    evalJS("myObject = null");
1706    evalJS("gc()");
1707    m_myObject->resetQtFunctionInvoked();
1708    m_myObject->emitMySignal();
1709    QCOMPARE(m_myObject->qtFunctionInvoked(), 20);
1710}
1711
1712void tst_QWebFrame::classEnums()
1713{
1714    // We don't do the meta thing currently, unfortunately!!!
1715    /*
1716    QString myClass = m_engine->newQMetaObject(m_myObject->metaObject(), m_engine->undefinedValue());
1717    m_engine->globalObject().setProperty("MyQObject", myClass);
1718
1719    QCOMPARE(static_cast<MyQObject::Policy>(evalJS("MyQObject.FooPolicy").toInt()),
1720             MyQObject::FooPolicy);
1721    QCOMPARE(static_cast<MyQObject::Policy>(evalJS("MyQObject.BarPolicy").toInt()),
1722             MyQObject::BarPolicy);
1723    QCOMPARE(static_cast<MyQObject::Policy>(evalJS("MyQObject.BazPolicy").toInt()),
1724             MyQObject::BazPolicy);
1725
1726    QCOMPARE(static_cast<MyQObject::Strategy>(evalJS("MyQObject.FooStrategy").toInt()),
1727             MyQObject::FooStrategy);
1728    QCOMPARE(static_cast<MyQObject::Strategy>(evalJS("MyQObject.BarStrategy").toInt()),
1729             MyQObject::BarStrategy);
1730    QCOMPARE(static_cast<MyQObject::Strategy>(evalJS("MyQObject.BazStrategy").toInt()),
1731             MyQObject::BazStrategy);
1732
1733    QCOMPARE(MyQObject::Ability(evalJS("MyQObject.NoAbility").toInt()),
1734             MyQObject::NoAbility);
1735    QCOMPARE(MyQObject::Ability(evalJS("MyQObject.FooAbility").toInt()),
1736             MyQObject::FooAbility);
1737    QCOMPARE(MyQObject::Ability(evalJS("MyQObject.BarAbility").toInt()),
1738             MyQObject::BarAbility);
1739    QCOMPARE(MyQObject::Ability(evalJS("MyQObject.BazAbility").toInt()),
1740             MyQObject::BazAbility);
1741    QCOMPARE(MyQObject::Ability(evalJS("MyQObject.AllAbility").toInt()),
1742             MyQObject::AllAbility);
1743
1744    // enums from Qt are inherited through prototype
1745    QCOMPARE(static_cast<Qt::FocusPolicy>(evalJS("MyQObject.StrongFocus").toInt()),
1746             Qt::StrongFocus);
1747    QCOMPARE(static_cast<Qt::Key>(evalJS("MyQObject.Key_Left").toInt()),
1748             Qt::Key_Left);
1749
1750    QCOMPARE(evalJS("MyQObject.className()"), QLatin1String("MyQObject"));
1751
1752    qRegisterMetaType<MyQObject::Policy>("Policy");
1753
1754    m_myObject->resetQtFunctionInvoked();
1755    QCOMPARE(evalJS("myObject.myInvokableWithEnumArg(MyQObject.BazPolicy)"), sUndefined);
1756    QCOMPARE(m_myObject->qtFunctionInvoked(), 10);
1757    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1758    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), int(MyQObject::BazPolicy));
1759
1760    m_myObject->resetQtFunctionInvoked();
1761    QCOMPARE(evalJS("myObject.myInvokableWithQualifiedEnumArg(MyQObject.BazPolicy)"), sUndefined);
1762    QCOMPARE(m_myObject->qtFunctionInvoked(), 36);
1763    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
1764    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toInt(), int(MyQObject::BazPolicy));
1765
1766    m_myObject->resetQtFunctionInvoked();
1767    {
1768        QVariant ret = evalJS("myObject.myInvokableReturningEnum()");
1769        QCOMPARE(m_myObject->qtFunctionInvoked(), 37);
1770        QCOMPARE(m_myObject->qtFunctionActuals().size(), 0);
1771        QCOMPARE(ret.isVariant());
1772    }
1773    m_myObject->resetQtFunctionInvoked();
1774    {
1775        QVariant ret = evalJS("myObject.myInvokableReturningQualifiedEnum()");
1776        QCOMPARE(m_myObject->qtFunctionInvoked(), 38);
1777        QCOMPARE(m_myObject->qtFunctionActuals().size(), 0);
1778        QCOMPARE(ret.isNumber());
1779    }
1780    */
1781}
1782
1783void tst_QWebFrame::classConstructor()
1784{
1785    /*
1786    QString myClass = qScriptValueFromQMetaObject<MyQObject>(m_engine);
1787    m_engine->globalObject().setProperty("MyQObject", myClass);
1788
1789    QString myObj = evalJS("myObj = MyQObject()");
1790    QObject* qobj = myObj.toQObject();
1791    QVERIFY(qobj != 0);
1792    QCOMPARE(qobj->metaObject()->className(), "MyQObject");
1793    QCOMPARE(qobj->parent(), (QObject*)0);
1794
1795    QString qobjectClass = qScriptValueFromQMetaObject<QObject>(m_engine);
1796    m_engine->globalObject().setProperty("QObject", qobjectClass);
1797
1798    QString otherObj = evalJS("otherObj = QObject(myObj)");
1799    QObject* qqobj = otherObj.toQObject();
1800    QVERIFY(qqobj != 0);
1801    QCOMPARE(qqobj->metaObject()->className(), "QObject");
1802    QCOMPARE(qqobj->parent(), qobj);
1803
1804    delete qobj;
1805    */
1806}
1807
1808void tst_QWebFrame::overrideInvokable()
1809{
1810    m_myObject->resetQtFunctionInvoked();
1811    QCOMPARE(evalJS("myObject.myInvokable()"), sUndefined);
1812    QCOMPARE(m_myObject->qtFunctionInvoked(), 0);
1813
1814    /* XFAIL - can't write to functions with RuntimeObject
1815    m_myObject->resetQtFunctionInvoked();
1816    evalJS("myObject.myInvokable = function() { window.a = 123; }");
1817    evalJS("myObject.myInvokable()");
1818    QCOMPARE(m_myObject->qtFunctionInvoked(), -1);
1819    QCOMPARE(evalJS("window.a").toDouble(), 123.0);
1820
1821    evalJS("myObject.myInvokable = function() { window.a = 456; }");
1822    evalJS("myObject.myInvokable()");
1823    QCOMPARE(m_myObject->qtFunctionInvoked(), -1);
1824    QCOMPARE(evalJS("window.a").toDouble(), 456.0);
1825    */
1826
1827    evalJS("delete myObject.myInvokable");
1828    evalJS("myObject.myInvokable()");
1829    QCOMPARE(m_myObject->qtFunctionInvoked(), 0);
1830
1831    /* XFAIL - ditto
1832    m_myObject->resetQtFunctionInvoked();
1833    evalJS("myObject.myInvokable = myObject.myInvokableWithIntArg");
1834    evalJS("myObject.myInvokable(123)");
1835    QCOMPARE(m_myObject->qtFunctionInvoked(), 1);
1836    */
1837
1838    evalJS("delete myObject.myInvokable");
1839    m_myObject->resetQtFunctionInvoked();
1840    // this form (with the '()') is read-only
1841    evalJS("myObject['myInvokable()'] = function() { window.a = 123; }");
1842    evalJS("myObject.myInvokable()");
1843    QCOMPARE(m_myObject->qtFunctionInvoked(), 0);
1844}
1845
1846void tst_QWebFrame::transferInvokable()
1847{
1848    /* XFAIL - can't put to functions with RuntimeObject
1849    m_myObject->resetQtFunctionInvoked();
1850    evalJS("myObject.foozball = myObject.myInvokable");
1851    evalJS("myObject.foozball()");
1852    QCOMPARE(m_myObject->qtFunctionInvoked(), 0);
1853    m_myObject->resetQtFunctionInvoked();
1854    evalJS("myObject.foozball = myObject.myInvokableWithIntArg");
1855    evalJS("myObject.foozball(123)");
1856    QCOMPARE(m_myObject->qtFunctionInvoked(), 1);
1857    m_myObject->resetQtFunctionInvoked();
1858    evalJS("myObject.myInvokable = myObject.myInvokableWithIntArg");
1859    evalJS("myObject.myInvokable(123)");
1860    QCOMPARE(m_myObject->qtFunctionInvoked(), 1);
1861
1862    MyOtherQObject other;
1863    m_page->mainFrame()->addToJSWindowObject("myOtherObject", &other);
1864    evalJS("myOtherObject.foo = myObject.foozball");
1865    other.resetQtFunctionInvoked();
1866    evalJS("myOtherObject.foo(456)");
1867    QCOMPARE(other.qtFunctionInvoked(), 1);
1868    */
1869}
1870
1871void tst_QWebFrame::findChild()
1872{
1873    /*
1874    QObject* child = new QObject(m_myObject);
1875    child->setObjectName(QLatin1String("myChildObject"));
1876
1877    {
1878        QString result = evalJS("myObject.findChild('noSuchChild')");
1879        QCOMPARE(result.isNull());
1880    }
1881
1882    {
1883        QString result = evalJS("myObject.findChild('myChildObject')");
1884        QCOMPARE(result.isQObject());
1885        QCOMPARE(result.toQObject(), child);
1886    }
1887
1888    delete child;
1889    */
1890}
1891
1892void tst_QWebFrame::findChildren()
1893{
1894    /*
1895    QObject* child = new QObject(m_myObject);
1896    child->setObjectName(QLatin1String("myChildObject"));
1897
1898    {
1899        QString result = evalJS("myObject.findChildren('noSuchChild')");
1900        QCOMPARE(result.isArray());
1901        QCOMPARE(result.property(QLatin1String("length")).toDouble(), 0.0);
1902    }
1903
1904    {
1905        QString result = evalJS("myObject.findChildren('myChildObject')");
1906        QCOMPARE(result.isArray());
1907        QCOMPARE(result.property(QLatin1String("length")).toDouble(), 1.0);
1908        QCOMPARE(result.property(QLatin1String("0")).toQObject(), child);
1909    }
1910
1911    QObject* namelessChild = new QObject(m_myObject);
1912
1913    {
1914        QString result = evalJS("myObject.findChildren('myChildObject')");
1915        QCOMPARE(result.isArray());
1916        QCOMPARE(result.property(QLatin1String("length")).toDouble(), 1.0);
1917        QCOMPARE(result.property(QLatin1String("0")).toQObject(), child);
1918    }
1919
1920    QObject* anotherChild = new QObject(m_myObject);
1921    anotherChild->setObjectName(QLatin1String("anotherChildObject"));
1922
1923    {
1924        QString result = evalJS("myObject.findChildren('anotherChildObject')");
1925        QCOMPARE(result.isArray());
1926        QCOMPARE(result.property(QLatin1String("length")).toDouble(), 1.0);
1927        QCOMPARE(result.property(QLatin1String("0")).toQObject(), anotherChild);
1928    }
1929
1930    anotherChild->setObjectName(QLatin1String("myChildObject"));
1931    {
1932        QString result = evalJS("myObject.findChildren('myChildObject')");
1933        QCOMPARE(result.isArray());
1934        QCOMPARE(result.property(QLatin1String("length")).toDouble(), 2.0);
1935        QObject* o1 = result.property(QLatin1String("0")).toQObject();
1936        QObject* o2 = result.property(QLatin1String("1")).toQObject();
1937        if (o1 != child) {
1938            QCOMPARE(o1, anotherChild);
1939            QCOMPARE(o2, child);
1940        } else {
1941            QCOMPARE(o1, child);
1942            QCOMPARE(o2, anotherChild);
1943        }
1944    }
1945
1946    // find all
1947    {
1948        QString result = evalJS("myObject.findChildren()");
1949        QVERIFY(result.isArray());
1950        int count = 3;
1951        QCOMPARE(result.property("length"), QLatin1String(count);
1952        for (int i = 0; i < 3; ++i) {
1953            QObject* o = result.property(i).toQObject();
1954            if (o == namelessChild || o == child || o == anotherChild)
1955                --count;
1956        }
1957        QVERIFY(count == 0);
1958    }
1959
1960    delete anotherChild;
1961    delete namelessChild;
1962    delete child;
1963    */
1964}
1965
1966void tst_QWebFrame::overloadedSlots()
1967{
1968    // should pick myOverloadedSlot(double)
1969    m_myObject->resetQtFunctionInvoked();
1970    evalJS("myObject.myOverloadedSlot(10)");
1971    QCOMPARE(m_myObject->qtFunctionInvoked(), 26);
1972
1973    // should pick myOverloadedSlot(double)
1974    m_myObject->resetQtFunctionInvoked();
1975    evalJS("myObject.myOverloadedSlot(10.0)");
1976    QCOMPARE(m_myObject->qtFunctionInvoked(), 26);
1977
1978    // should pick myOverloadedSlot(QString)
1979    m_myObject->resetQtFunctionInvoked();
1980    evalJS("myObject.myOverloadedSlot('10')");
1981    QCOMPARE(m_myObject->qtFunctionInvoked(), 29);
1982
1983    // should pick myOverloadedSlot(bool)
1984    m_myObject->resetQtFunctionInvoked();
1985    evalJS("myObject.myOverloadedSlot(true)");
1986    QCOMPARE(m_myObject->qtFunctionInvoked(), 25);
1987
1988    // should pick myOverloadedSlot(QDateTime)
1989    m_myObject->resetQtFunctionInvoked();
1990    evalJS("myObject.myOverloadedSlot(new Date())");
1991    QCOMPARE(m_myObject->qtFunctionInvoked(), 32);
1992
1993    // should pick myOverloadedSlot(QRegExp)
1994    m_myObject->resetQtFunctionInvoked();
1995    evalJS("myObject.myOverloadedSlot(new RegExp())");
1996    QCOMPARE(m_myObject->qtFunctionInvoked(), 34);
1997
1998    // should pick myOverloadedSlot(QVariant)
1999    /* XFAIL
2000    m_myObject->resetQtFunctionInvoked();
2001    QString f = evalJS("myObject.myOverloadedSlot");
2002    f.call(QString(), QStringList() << m_engine->newVariant(QVariant("ciao")));
2003    QCOMPARE(m_myObject->qtFunctionInvoked(), 35);
2004    */
2005
2006    // should pick myOverloadedSlot(QRegExp)
2007    m_myObject->resetQtFunctionInvoked();
2008    evalJS("myObject.myOverloadedSlot(document.body)");
2009    QEXPECT_FAIL("", "https://bugs.webkit.org/show_bug.cgi?id=37319", Continue);
2010    QCOMPARE(m_myObject->qtFunctionInvoked(), 36);
2011
2012    // should pick myOverloadedSlot(QObject*)
2013    m_myObject->resetQtFunctionInvoked();
2014    evalJS("myObject.myOverloadedSlot(myObject)");
2015    QCOMPARE(m_myObject->qtFunctionInvoked(), 41);
2016
2017    // should pick myOverloadedSlot(QObject*)
2018    m_myObject->resetQtFunctionInvoked();
2019    evalJS("myObject.myOverloadedSlot(null)");
2020    QCOMPARE(m_myObject->qtFunctionInvoked(), 41);
2021
2022    // should pick myOverloadedSlot(QStringList)
2023    m_myObject->resetQtFunctionInvoked();
2024    evalJS("myObject.myOverloadedSlot(['hello'])");
2025    QCOMPARE(m_myObject->qtFunctionInvoked(), 42);
2026}
2027
2028void tst_QWebFrame::enumerate_data()
2029{
2030    QTest::addColumn<QStringList>("expectedNames");
2031
2032    QTest::newRow("enumerate all")
2033    << (QStringList()
2034        // meta-object-defined properties:
2035        //   inherited
2036        << "objectName"
2037        //   non-inherited
2038        << "p1" << "p2" << "p4" << "p6"
2039        // dynamic properties
2040        << "dp1" << "dp2" << "dp3"
2041        // inherited slots
2042        << "destroyed(QObject*)" << "destroyed()"
2043        << "deleteLater()"
2044        // not included because it's private:
2045        // << "_q_reregisterTimers(void*)"
2046        // signals
2047        << "mySignal()"
2048        // slots
2049        << "mySlot()" << "myOtherSlot()");
2050}
2051
2052void tst_QWebFrame::enumerate()
2053{
2054    QFETCH(QStringList, expectedNames);
2055
2056    MyEnumTestQObject enumQObject;
2057    // give it some dynamic properties
2058    enumQObject.setProperty("dp1", "dp1");
2059    enumQObject.setProperty("dp2", "dp2");
2060    enumQObject.setProperty("dp3", "dp3");
2061    m_page->mainFrame()->addToJavaScriptWindowObject("myEnumObject", &enumQObject);
2062
2063    // enumerate in script
2064    {
2065        evalJS("var enumeratedProperties = []");
2066        evalJS("for (var p in myEnumObject) { enumeratedProperties.push(p); }");
2067        QStringList result = evalJSV("enumeratedProperties").toStringList();
2068        QCOMPARE(result.size(), expectedNames.size());
2069        for (int i = 0; i < expectedNames.size(); ++i)
2070            QCOMPARE(result.at(i), expectedNames.at(i));
2071    }
2072}
2073
2074void tst_QWebFrame::objectDeleted()
2075{
2076    MyQObject* qobj = new MyQObject();
2077    m_page->mainFrame()->addToJavaScriptWindowObject("bar", qobj);
2078    evalJS("bar.objectName = 'foo';");
2079    QCOMPARE(qobj->objectName(), QLatin1String("foo"));
2080    evalJS("bar.intProperty = 123;");
2081    QCOMPARE(qobj->intProperty(), 123);
2082    qobj->resetQtFunctionInvoked();
2083    evalJS("bar.myInvokable.call(bar);");
2084    QCOMPARE(qobj->qtFunctionInvoked(), 0);
2085
2086    // do this, to ensure that we cache that it implements call
2087    evalJS("bar()");
2088
2089    // now delete the object
2090    delete qobj;
2091
2092    QCOMPARE(evalJS("typeof bar"), sObject);
2093
2094    // any attempt to access properties of the object should result in an exception
2095    {
2096        QString type;
2097        QString ret = evalJS("bar.objectName", type);
2098        QCOMPARE(type, sError);
2099        QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject"));
2100    }
2101    {
2102        QString type;
2103        QString ret = evalJS("bar.objectName = 'foo'", type);
2104        QCOMPARE(type, sError);
2105        QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject"));
2106    }
2107
2108    // myInvokable is stored in member table (since we've accessed it before deletion)
2109    {
2110        QString type;
2111        evalJS("bar.myInvokable", type);
2112        QCOMPARE(type, sFunction);
2113    }
2114
2115    {
2116        QString type;
2117        QString ret = evalJS("bar.myInvokable.call(bar);", type);
2118        ret = evalJS("bar.myInvokable(bar)", type);
2119        QCOMPARE(type, sError);
2120        QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject"));
2121    }
2122    // myInvokableWithIntArg is not stored in member table (since we've not accessed it)
2123    {
2124        QString type;
2125        QString ret = evalJS("bar.myInvokableWithIntArg", type);
2126        QCOMPARE(type, sError);
2127        QCOMPARE(ret, QLatin1String("Error: cannot access member `myInvokableWithIntArg' of deleted QObject"));
2128    }
2129
2130    // access from script
2131    evalJS("window.o = bar;");
2132    {
2133        QString type;
2134        QString ret = evalJS("o.objectName", type);
2135        QCOMPARE(type, sError);
2136        QCOMPARE(ret, QLatin1String("Error: cannot access member `objectName' of deleted QObject"));
2137    }
2138    {
2139        QString type;
2140        QString ret = evalJS("o.myInvokable()", type);
2141        QCOMPARE(type, sError);
2142        QCOMPARE(ret, QLatin1String("Error: cannot call function of deleted QObject"));
2143    }
2144    {
2145        QString type;
2146        QString ret = evalJS("o.myInvokableWithIntArg(10)", type);
2147        QCOMPARE(type, sError);
2148        QCOMPARE(ret, QLatin1String("Error: cannot access member `myInvokableWithIntArg' of deleted QObject"));
2149    }
2150}
2151
2152void tst_QWebFrame::typeConversion()
2153{
2154    m_myObject->resetQtFunctionInvoked();
2155
2156    QDateTime localdt(QDate(2008,1,18), QTime(12,31,0));
2157    QDateTime utclocaldt = localdt.toUTC();
2158    QDateTime utcdt(QDate(2008,1,18), QTime(12,31,0), Qt::UTC);
2159
2160    // Dates in JS (default to local)
2161    evalJS("myObject.myOverloadedSlot(new Date(2008,0,18,12,31,0))");
2162    QCOMPARE(m_myObject->qtFunctionInvoked(), 32);
2163    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
2164    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDateTime().toUTC(), utclocaldt);
2165
2166    m_myObject->resetQtFunctionInvoked();
2167    evalJS("myObject.myOverloadedSlot(new Date(Date.UTC(2008,0,18,12,31,0)))");
2168    QCOMPARE(m_myObject->qtFunctionInvoked(), 32);
2169    QCOMPARE(m_myObject->qtFunctionActuals().size(), 1);
2170    QCOMPARE(m_myObject->qtFunctionActuals().at(0).toDateTime().toUTC(), utcdt);
2171
2172    // Pushing QDateTimes into JS
2173    // Local
2174    evalJS("function checkDate(d) {window.__date_equals = (d.toString() == new Date(2008,0,18,12,31,0))?true:false;}");
2175    evalJS("myObject.mySignalWithDateTimeArg.connect(checkDate)");
2176    m_myObject->emitMySignalWithDateTimeArg(localdt);
2177    QCOMPARE(evalJS("window.__date_equals"), sTrue);
2178    evalJS("delete window.__date_equals");
2179    m_myObject->emitMySignalWithDateTimeArg(utclocaldt);
2180    QCOMPARE(evalJS("window.__date_equals"), sTrue);
2181    evalJS("delete window.__date_equals");
2182    evalJS("myObject.mySignalWithDateTimeArg.disconnect(checkDate); delete checkDate;");
2183
2184    // UTC
2185    evalJS("function checkDate(d) {window.__date_equals = (d.toString() == new Date(Date.UTC(2008,0,18,12,31,0)))?true:false; }");
2186    evalJS("myObject.mySignalWithDateTimeArg.connect(checkDate)");
2187    m_myObject->emitMySignalWithDateTimeArg(utcdt);
2188    QCOMPARE(evalJS("window.__date_equals"), sTrue);
2189    evalJS("delete window.__date_equals");
2190    evalJS("myObject.mySignalWithDateTimeArg.disconnect(checkDate); delete checkDate;");
2191
2192    // ### RegExps
2193}
2194
2195class StringListTestObject : public QObject {
2196    Q_OBJECT
2197public Q_SLOTS:
2198    QVariant stringList()
2199    {
2200        return QStringList() << "Q" << "t";
2201    };
2202};
2203
2204void tst_QWebFrame::arrayObjectEnumerable()
2205{
2206    QWebPage page;
2207    QWebFrame* frame = page.mainFrame();
2208    QObject* qobject = new StringListTestObject();
2209    frame->addToJavaScriptWindowObject("test", qobject, QScriptEngine::ScriptOwnership);
2210
2211    const QString script("var stringArray = test.stringList();"
2212                         "var result = '';"
2213                         "for (var i in stringArray) {"
2214                         "    result += stringArray[i];"
2215                         "}"
2216                         "result;");
2217    QCOMPARE(frame->evaluateJavaScript(script).toString(), QString::fromLatin1("Qt"));
2218}
2219
2220void tst_QWebFrame::symmetricUrl()
2221{
2222    QVERIFY(m_view->url().isEmpty());
2223
2224    QCOMPARE(m_view->history()->count(), 0);
2225
2226    QUrl dataUrl("data:text/html,<h1>Test");
2227
2228    m_view->setUrl(dataUrl);
2229    QCOMPARE(m_view->url(), dataUrl);
2230    QCOMPARE(m_view->history()->count(), 0);
2231
2232    // loading is _not_ immediate, so the text isn't set just yet.
2233    QVERIFY(m_view->page()->mainFrame()->toPlainText().isEmpty());
2234
2235    ::waitForSignal(m_view, SIGNAL(loadFinished(bool)));
2236
2237    QCOMPARE(m_view->history()->count(), 1);
2238    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test"));
2239
2240    QUrl dataUrl2("data:text/html,<h1>Test2");
2241    QUrl dataUrl3("data:text/html,<h1>Test3");
2242
2243    m_view->setUrl(dataUrl2);
2244    m_view->setUrl(dataUrl3);
2245
2246    QCOMPARE(m_view->url(), dataUrl3);
2247
2248    ::waitForSignal(m_view, SIGNAL(loadFinished(bool)));
2249
2250    QCOMPARE(m_view->history()->count(), 2);
2251
2252    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test3"));
2253}
2254
2255void tst_QWebFrame::progressSignal()
2256{
2257    QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int)));
2258
2259    QUrl dataUrl("data:text/html,<h1>Test");
2260    m_view->setUrl(dataUrl);
2261
2262    ::waitForSignal(m_view, SIGNAL(loadFinished(bool)));
2263
2264    QVERIFY(progressSpy.size() >= 2);
2265
2266    // WebKit defines initialProgressValue as 10%, not 0%
2267    QCOMPARE(progressSpy.first().first().toInt(), 10);
2268
2269    // But we always end at 100%
2270    QCOMPARE(progressSpy.last().first().toInt(), 100);
2271}
2272
2273void tst_QWebFrame::urlChange()
2274{
2275    QSignalSpy urlSpy(m_page->mainFrame(), SIGNAL(urlChanged(QUrl)));
2276
2277    QUrl dataUrl("data:text/html,<h1>Test");
2278    m_view->setUrl(dataUrl);
2279
2280    ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl)));
2281
2282    QCOMPARE(urlSpy.size(), 1);
2283
2284    QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>");
2285    m_view->setUrl(dataUrl2);
2286
2287    ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl)));
2288
2289    QCOMPARE(urlSpy.size(), 2);
2290}
2291
2292
2293void tst_QWebFrame::domCycles()
2294{
2295    m_view->setHtml("<html><body>");
2296    QVariant v = m_page->mainFrame()->evaluateJavaScript("document");
2297    QVERIFY(v.type() == QVariant::Map);
2298}
2299
2300class FakeReply : public QNetworkReply {
2301    Q_OBJECT
2302
2303public:
2304    FakeReply(const QNetworkRequest& request, QObject* parent = 0)
2305        : QNetworkReply(parent)
2306    {
2307        setOperation(QNetworkAccessManager::GetOperation);
2308        setRequest(request);
2309        if (request.url() == QUrl("qrc:/test1.html")) {
2310            setHeader(QNetworkRequest::LocationHeader, QString("qrc:/test2.html"));
2311            setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html"));
2312        }
2313#ifndef QT_NO_OPENSSL
2314        else if (request.url() == QUrl("qrc:/fake-ssl-error.html"))
2315            setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error !")); // force a ssl error
2316#endif
2317        else if (request.url().host() == QLatin1String("abcdef.abcdef"))
2318            setError(QNetworkReply::HostNotFoundError, tr("Invalid URL"));
2319
2320        open(QIODevice::ReadOnly);
2321        QTimer::singleShot(0, this, SLOT(timeout()));
2322    }
2323    ~FakeReply()
2324    {
2325        close();
2326    }
2327    virtual void abort() {}
2328    virtual void close() {}
2329
2330protected:
2331    qint64 readData(char*, qint64)
2332    {
2333        return 0;
2334    }
2335
2336private slots:
2337    void timeout()
2338    {
2339        if (request().url() == QUrl("qrc://test1.html"))
2340            emit error(this->error());
2341        else if (request().url() == QUrl("http://abcdef.abcdef/"))
2342            emit metaDataChanged();
2343#ifndef QT_NO_OPENSSL
2344        else if (request().url() == QUrl("qrc:/fake-ssl-error.html"))
2345            return;
2346#endif
2347
2348        emit readyRead();
2349        emit finished();
2350    }
2351};
2352
2353class FakeNetworkManager : public QNetworkAccessManager {
2354    Q_OBJECT
2355
2356public:
2357    FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { }
2358
2359protected:
2360    virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
2361    {
2362        QString url = request.url().toString();
2363        if (op == QNetworkAccessManager::GetOperation) {
2364            if (url == "qrc:/test1.html" ||  url == "http://abcdef.abcdef/")
2365                return new FakeReply(request, this);
2366#ifndef QT_NO_OPENSSL
2367            else if (url == "qrc:/fake-ssl-error.html") {
2368                FakeReply* reply = new FakeReply(request, this);
2369                QList<QSslError> errors;
2370                emit sslErrors(reply, errors << QSslError(QSslError::UnspecifiedError));
2371                return reply;
2372            }
2373#endif
2374       }
2375
2376        return QNetworkAccessManager::createRequest(op, request, outgoingData);
2377    }
2378};
2379
2380void tst_QWebFrame::requestedUrl()
2381{
2382    QWebPage page;
2383    QWebFrame* frame = page.mainFrame();
2384
2385    // in few seconds, the image should be completely loaded
2386    QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
2387    FakeNetworkManager* networkManager = new FakeNetworkManager(&page);
2388    page.setNetworkAccessManager(networkManager);
2389
2390    frame->setUrl(QUrl("qrc:/test1.html"));
2391    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2392    QCOMPARE(spy.count(), 1);
2393    QCOMPARE(frame->requestedUrl(), QUrl("qrc:/test1.html"));
2394    QCOMPARE(frame->url(), QUrl("qrc:/test2.html"));
2395
2396    frame->setUrl(QUrl("qrc:/non-existent.html"));
2397    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2398    QCOMPARE(spy.count(), 2);
2399    QCOMPARE(frame->requestedUrl(), QUrl("qrc:/non-existent.html"));
2400    QCOMPARE(frame->url(), QUrl("qrc:/non-existent.html"));
2401
2402    frame->setUrl(QUrl("http://abcdef.abcdef"));
2403    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2404    QCOMPARE(spy.count(), 3);
2405    QCOMPARE(frame->requestedUrl(), QUrl("http://abcdef.abcdef/"));
2406    QCOMPARE(frame->url(), QUrl("http://abcdef.abcdef/"));
2407
2408#ifndef QT_NO_OPENSSL
2409    qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
2410    qRegisterMetaType<QNetworkReply* >("QNetworkReply*");
2411
2412    QSignalSpy spy2(page.networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)));
2413    frame->setUrl(QUrl("qrc:/fake-ssl-error.html"));
2414    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2415    QCOMPARE(spy2.count(), 1);
2416    QCOMPARE(frame->requestedUrl(), QUrl("qrc:/fake-ssl-error.html"));
2417    QCOMPARE(frame->url(), QUrl("qrc:/fake-ssl-error.html"));
2418#endif
2419}
2420
2421void tst_QWebFrame::requestedUrlAfterSetAndLoadFailures()
2422{
2423    QWebPage page;
2424    QWebFrame* frame = page.mainFrame();
2425
2426    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
2427
2428    const QUrl first("http://abcdef.abcdef/");
2429    frame->setUrl(first);
2430    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
2431    QCOMPARE(frame->url(), first);
2432    QCOMPARE(frame->requestedUrl(), first);
2433    QVERIFY(!spy.at(0).first().toBool());
2434
2435    const QUrl second("http://abcdef.abcdef/another_page.html");
2436    QVERIFY(first != second);
2437
2438    frame->load(second);
2439    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
2440    QCOMPARE(frame->url(), first);
2441    QCOMPARE(frame->requestedUrl(), second);
2442    QVERIFY(!spy.at(1).first().toBool());
2443}
2444
2445void tst_QWebFrame::javaScriptWindowObjectCleared_data()
2446{
2447    QTest::addColumn<QString>("html");
2448    QTest::addColumn<int>("signalCount");
2449    QTest::newRow("with <script>") << "<html><body><script>i=0</script><p>hello world</p></body></html>" << 1;
2450    // NOTE: Empty scripts no longer cause this signal to be emitted.
2451    QTest::newRow("with empty <script>") << "<html><body><script></script><p>hello world</p></body></html>" << 0;
2452    QTest::newRow("without <script>") << "<html><body><p>hello world</p></body></html>" << 0;
2453}
2454
2455void tst_QWebFrame::javaScriptWindowObjectCleared()
2456{
2457    QWebPage page;
2458    QWebFrame* frame = page.mainFrame();
2459    QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared()));
2460    QFETCH(QString, html);
2461    frame->setHtml(html);
2462
2463    QFETCH(int, signalCount);
2464    QCOMPARE(spy.count(), signalCount);
2465}
2466
2467void tst_QWebFrame::javaScriptWindowObjectClearedOnEvaluate()
2468{
2469    QWebPage page;
2470    QWebFrame* frame = page.mainFrame();
2471    QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared()));
2472    frame->setHtml("<html></html>");
2473    QCOMPARE(spy.count(), 0);
2474    frame->evaluateJavaScript("var a = 'a';");
2475    QCOMPARE(spy.count(), 1);
2476    // no new clear for a new script:
2477    frame->evaluateJavaScript("var a = 1;");
2478    QCOMPARE(spy.count(), 1);
2479}
2480
2481void tst_QWebFrame::setHtml()
2482{
2483    QString html("<html><head></head><body><p>hello world</p></body></html>");
2484    QSignalSpy spy(m_view->page(), SIGNAL(loadFinished(bool)));
2485    m_view->page()->mainFrame()->setHtml(html);
2486    QCOMPARE(m_view->page()->mainFrame()->toHtml(), html);
2487    QCOMPARE(spy.count(), 1);
2488}
2489
2490void tst_QWebFrame::setHtmlWithResource()
2491{
2492    QString html("<html><body><p>hello world</p><img src='qrc:/image.png'/></body></html>");
2493
2494    QWebPage page;
2495    QWebFrame* frame = page.mainFrame();
2496
2497    // in few seconds, the image should be completey loaded
2498    QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
2499    frame->setHtml(html);
2500    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2501    QCOMPARE(spy.count(), 1);
2502
2503    QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1);
2504    QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128);
2505    QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128);
2506
2507    QString html2 =
2508        "<html>"
2509            "<head>"
2510                "<link rel='stylesheet' href='qrc:/style.css' type='text/css' />"
2511            "</head>"
2512            "<body>"
2513                "<p id='idP'>some text</p>"
2514            "</body>"
2515        "</html>";
2516
2517    // in few seconds, the CSS should be completey loaded
2518    frame->setHtml(html2);
2519    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2520    QCOMPARE(spy.size(), 2);
2521
2522    QWebElement p = frame->documentElement().findAll("p").at(0);
2523    QCOMPARE(p.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("red"));
2524}
2525
2526void tst_QWebFrame::setHtmlWithBaseURL()
2527{
2528    if (!QDir(TESTS_SOURCE_DIR).exists())
2529        QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
2530
2531    QDir::setCurrent(TESTS_SOURCE_DIR);
2532
2533    QString html("<html><body><p>hello world</p><img src='resources/image2.png'/></body></html>");
2534
2535    QWebPage page;
2536    QWebFrame* frame = page.mainFrame();
2537
2538    // in few seconds, the image should be completey loaded
2539    QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
2540
2541    frame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR));
2542    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2543    QCOMPARE(spy.count(), 1);
2544
2545    QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1);
2546    QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128);
2547    QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128);
2548
2549    // no history item has to be added.
2550    QCOMPARE(m_view->page()->history()->count(), 0);
2551}
2552
2553class MyPage : public QWebPage
2554{
2555public:
2556    MyPage() :  QWebPage(), alerts(0) {}
2557    int alerts;
2558
2559protected:
2560    virtual void javaScriptAlert(QWebFrame*, const QString& msg)
2561    {
2562        alerts++;
2563        QCOMPARE(msg, QString("foo"));
2564        // Should not be enough to trigger deferred loading, since we've upped the HTML
2565        // tokenizer delay in the Qt frameloader. See HTMLTokenizer::continueProcessing()
2566        QTest::qWait(1000);
2567    }
2568};
2569
2570void tst_QWebFrame::setHtmlWithJSAlert()
2571{
2572    QString html("<html><head></head><body><script>alert('foo');</script><p>hello world</p></body></html>");
2573    MyPage page;
2574    m_view->setPage(&page);
2575    page.mainFrame()->setHtml(html);
2576    QCOMPARE(page.alerts, 1);
2577    QCOMPARE(m_view->page()->mainFrame()->toHtml(), html);
2578}
2579
2580class TestNetworkManager : public QNetworkAccessManager
2581{
2582public:
2583    TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {}
2584
2585    QList<QUrl> requestedUrls;
2586
2587protected:
2588    virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) {
2589        requestedUrls.append(request.url());
2590        QNetworkRequest redirectedRequest = request;
2591        redirectedRequest.setUrl(QUrl("data:text/html,<p>hello"));
2592        return QNetworkAccessManager::createRequest(op, redirectedRequest, outgoingData);
2593    }
2594};
2595
2596void tst_QWebFrame::ipv6HostEncoding()
2597{
2598    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
2599    m_page->setNetworkAccessManager(networkManager);
2600    networkManager->requestedUrls.clear();
2601
2602    QUrl baseUrl = QUrl::fromEncoded("http://[::1]/index.html");
2603    m_view->setHtml("<p>Hi", baseUrl);
2604    m_view->page()->mainFrame()->evaluateJavaScript("var r = new XMLHttpRequest();"
2605            "r.open('GET', 'http://[::1]/test.xml', false);"
2606            "r.send(null);"
2607            );
2608    QCOMPARE(networkManager->requestedUrls.count(), 1);
2609    QCOMPARE(networkManager->requestedUrls.at(0), QUrl::fromEncoded("http://[::1]/test.xml"));
2610}
2611
2612void tst_QWebFrame::metaData()
2613{
2614    m_view->setHtml("<html>"
2615                    "    <head>"
2616                    "        <meta name=\"description\" content=\"Test description\">"
2617                    "        <meta name=\"keywords\" content=\"HTML, JavaScript, Css\">"
2618                    "    </head>"
2619                    "</html>");
2620
2621    QMultiMap<QString, QString> metaData = m_view->page()->mainFrame()->metaData();
2622
2623    QCOMPARE(metaData.count(), 2);
2624
2625    QCOMPARE(metaData.value("description"), QString("Test description"));
2626    QCOMPARE(metaData.value("keywords"), QString("HTML, JavaScript, Css"));
2627    QCOMPARE(metaData.value("nonexistant"), QString());
2628
2629    m_view->setHtml("<html>"
2630                    "    <head>"
2631                    "        <meta name=\"samekey\" content=\"FirstValue\">"
2632                    "        <meta name=\"samekey\" content=\"SecondValue\">"
2633                    "    </head>"
2634                    "</html>");
2635
2636    metaData = m_view->page()->mainFrame()->metaData();
2637
2638    QCOMPARE(metaData.count(), 2);
2639
2640    QStringList values = metaData.values("samekey");
2641    QCOMPARE(values.count(), 2);
2642
2643    QVERIFY(values.contains("FirstValue"));
2644    QVERIFY(values.contains("SecondValue"));
2645
2646    QCOMPARE(metaData.value("nonexistant"), QString());
2647}
2648
2649#if !defined(Q_WS_MAEMO_5) && !defined(Q_OS_SYMBIAN) && !defined(QT_NO_COMBOBOX)
2650void tst_QWebFrame::popupFocus()
2651{
2652    QWebView view;
2653    view.setHtml("<html>"
2654                 "    <body>"
2655                 "        <select name=\"select\">"
2656                 "            <option>1</option>"
2657                 "            <option>2</option>"
2658                 "        </select>"
2659                 "        <input type=\"text\"> </input>"
2660                 "        <textarea name=\"text_area\" rows=\"3\" cols=\"40\">"
2661                 "This test checks whether showing and hiding a popup"
2662                 "takes the focus away from the webpage."
2663                 "        </textarea>"
2664                 "    </body>"
2665                 "</html>");
2666    view.resize(400, 100);
2667    // Call setFocus before show to work around http://bugreports.qt.nokia.com/browse/QTBUG-14762
2668    view.setFocus();
2669    view.show();
2670    QTest::qWaitForWindowShown(&view);
2671    view.activateWindow();
2672    QTRY_VERIFY(view.hasFocus());
2673
2674    // open the popup by clicking. check if focus is on the popup
2675    const QWebElement webCombo = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("select[name=select]"));
2676    QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center());
2677    QObject* webpopup = firstChildByClassName(&view, "QComboBox");
2678    QComboBox* combo = qobject_cast<QComboBox*>(webpopup);
2679    QVERIFY(combo != 0);
2680    QTRY_VERIFY(!view.hasFocus() && combo->view()->hasFocus()); // Focus should be on the popup
2681
2682    // hide the popup and check if focus is on the page
2683    combo->hidePopup();
2684    QTRY_VERIFY(view.hasFocus()); // Focus should be back on the WebView
2685}
2686#endif
2687
2688void tst_QWebFrame::inputFieldFocus()
2689{
2690    QWebView view;
2691    view.setHtml("<html><body><input type=\"text\"></input></body></html>");
2692    view.resize(400, 100);
2693    view.show();
2694    QTest::qWaitForWindowShown(&view);
2695    view.activateWindow();
2696    view.setFocus();
2697    QTRY_VERIFY(view.hasFocus());
2698
2699    // double the flashing time, should at least blink once already
2700    int delay = qApp->cursorFlashTime() * 2;
2701
2702    // focus the lineedit and check if it blinks
2703    bool autoSipEnabled = qApp->autoSipEnabled();
2704    qApp->setAutoSipEnabled(false);
2705    const QWebElement inputElement = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("input[type=text]"));
2706    QTest::mouseClick(&view, Qt::LeftButton, 0, inputElement.geometry().center());
2707    m_inputFieldsTestView = &view;
2708    view.installEventFilter( this );
2709    QTest::qWait(delay);
2710    QVERIFY2(m_inputFieldTestPaintCount >= 3,
2711             "The input field should have a blinking caret");
2712    qApp->setAutoSipEnabled(autoSipEnabled);
2713}
2714
2715void tst_QWebFrame::hitTestContent()
2716{
2717    QString html("<html><body><p>A paragraph</p><br/><br/><br/><a href=\"about:blank\" target=\"_foo\" id=\"link\">link text</a></body></html>");
2718
2719    QWebPage page;
2720    QWebFrame* frame = page.mainFrame();
2721    frame->setHtml(html);
2722    page.setViewportSize(QSize(200, 0)); //no height so link is not visible
2723    const QWebElement linkElement = frame->documentElement().findFirst(QLatin1String("a#link"));
2724    QWebHitTestResult result = frame->hitTestContent(linkElement.geometry().center());
2725    QCOMPARE(result.linkText(), QString("link text"));
2726    QWebElement link = result.linkElement();
2727    QCOMPARE(link.attribute("target"), QString("_foo"));
2728}
2729
2730void tst_QWebFrame::jsByteArray()
2731{
2732    QByteArray ba("hello world");
2733    m_myObject->setByteArrayProperty(ba);
2734
2735    // read-only property
2736    QCOMPARE(m_myObject->byteArrayProperty(), ba);
2737    QString type;
2738    QVariant v = evalJSV("myObject.byteArrayProperty");
2739    QCOMPARE(int(v.type()), int(QVariant::ByteArray));
2740
2741    QCOMPARE(v.toByteArray(), ba);
2742}
2743
2744void tst_QWebFrame::ownership()
2745{
2746    // test ownership
2747    {
2748        QPointer<QObject> ptr = new QObject();
2749        QVERIFY(ptr != 0);
2750        {
2751            QWebPage page;
2752            QWebFrame* frame = page.mainFrame();
2753            frame->addToJavaScriptWindowObject("test", ptr, QScriptEngine::ScriptOwnership);
2754        }
2755        QVERIFY(ptr == 0);
2756    }
2757    {
2758        QPointer<QObject> ptr = new QObject();
2759        QVERIFY(ptr != 0);
2760        QObject* before = ptr;
2761        {
2762            QWebPage page;
2763            QWebFrame* frame = page.mainFrame();
2764            frame->addToJavaScriptWindowObject("test", ptr, QScriptEngine::QtOwnership);
2765        }
2766        QVERIFY(ptr == before);
2767        delete ptr;
2768    }
2769    {
2770        QObject* parent = new QObject();
2771        QObject* child = new QObject(parent);
2772        QWebPage page;
2773        QWebFrame* frame = page.mainFrame();
2774        frame->addToJavaScriptWindowObject("test", child, QScriptEngine::QtOwnership);
2775        QVariant v = frame->evaluateJavaScript("test");
2776        QCOMPARE(qvariant_cast<QObject*>(v), child);
2777        delete parent;
2778        v = frame->evaluateJavaScript("test");
2779        QCOMPARE(qvariant_cast<QObject*>(v), (QObject *)0);
2780    }
2781    {
2782        QPointer<QObject> ptr = new QObject();
2783        QVERIFY(ptr != 0);
2784        {
2785            QWebPage page;
2786            QWebFrame* frame = page.mainFrame();
2787            frame->addToJavaScriptWindowObject("test", ptr, QScriptEngine::AutoOwnership);
2788        }
2789        // no parent, so it should be like ScriptOwnership
2790        QVERIFY(ptr == 0);
2791    }
2792    {
2793        QObject* parent = new QObject();
2794        QPointer<QObject> child = new QObject(parent);
2795        QVERIFY(child != 0);
2796        {
2797            QWebPage page;
2798            QWebFrame* frame = page.mainFrame();
2799            frame->addToJavaScriptWindowObject("test", child, QScriptEngine::AutoOwnership);
2800        }
2801        // has parent, so it should be like QtOwnership
2802        QVERIFY(child != 0);
2803        delete parent;
2804    }
2805}
2806
2807void tst_QWebFrame::nullValue()
2808{
2809    QVariant v = m_view->page()->mainFrame()->evaluateJavaScript("null");
2810    QVERIFY(v.isNull());
2811}
2812
2813void tst_QWebFrame::baseUrl_data()
2814{
2815    QTest::addColumn<QString>("html");
2816    QTest::addColumn<QUrl>("loadUrl");
2817    QTest::addColumn<QUrl>("url");
2818    QTest::addColumn<QUrl>("baseUrl");
2819
2820    QTest::newRow("null") << QString() << QUrl()
2821                          << QUrl("about:blank") << QUrl("about:blank");
2822
2823    QTest::newRow("foo") << QString() << QUrl("http://foobar.baz/")
2824                         << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/");
2825
2826    QString html = "<html>"
2827        "<head>"
2828            "<base href=\"http://foobaz.bar/\" />"
2829        "</head>"
2830    "</html>";
2831    QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/")
2832                                   << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/");
2833}
2834
2835void tst_QWebFrame::baseUrl()
2836{
2837    QFETCH(QString, html);
2838    QFETCH(QUrl, loadUrl);
2839    QFETCH(QUrl, url);
2840    QFETCH(QUrl, baseUrl);
2841
2842    m_page->mainFrame()->setHtml(html, loadUrl);
2843    QCOMPARE(m_page->mainFrame()->url(), url);
2844    QCOMPARE(m_page->mainFrame()->baseUrl(), baseUrl);
2845}
2846
2847void tst_QWebFrame::hasSetFocus()
2848{
2849    QString html("<html><body><p>top</p>" \
2850                    "<iframe width='80%' height='30%'/>" \
2851                 "</body></html>");
2852
2853    QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool)));
2854    m_page->mainFrame()->setHtml(html);
2855
2856    waitForSignal(m_page->mainFrame(), SIGNAL(loadFinished(bool)), 200);
2857    QCOMPARE(loadSpy.size(), 1);
2858
2859    QList<QWebFrame*> children = m_page->mainFrame()->childFrames();
2860    QWebFrame* frame = children.at(0);
2861    QString innerHtml("<html><body><p>another iframe</p>" \
2862                        "<iframe width='80%' height='30%'/>" \
2863                      "</body></html>");
2864    frame->setHtml(innerHtml);
2865
2866    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
2867    QCOMPARE(loadSpy.size(), 2);
2868
2869    m_page->mainFrame()->setFocus();
2870    QTRY_VERIFY(m_page->mainFrame()->hasFocus());
2871
2872    for (int i = 0; i < children.size(); ++i) {
2873        children.at(i)->setFocus();
2874        QTRY_VERIFY(children.at(i)->hasFocus());
2875        QVERIFY(!m_page->mainFrame()->hasFocus());
2876    }
2877
2878    m_page->mainFrame()->setFocus();
2879    QTRY_VERIFY(m_page->mainFrame()->hasFocus());
2880}
2881
2882void tst_QWebFrame::render()
2883{
2884    QString html("<html>" \
2885                    "<head><style>" \
2886                       "body, iframe { margin: 0px; border: none; }" \
2887                    "</style></head>" \
2888                    "<body><iframe width='100px' height='100px'/></body>" \
2889                 "</html>");
2890
2891    QWebPage page;
2892    page.mainFrame()->setHtml(html);
2893
2894    QList<QWebFrame*> frames = page.mainFrame()->childFrames();
2895    QWebFrame *frame = frames.at(0);
2896    QString innerHtml("<body style='margin: 0px;'><img src='qrc:/image.png'/></body>");
2897    frame->setHtml(innerHtml);
2898
2899    QPicture picture;
2900
2901    QSize size = page.mainFrame()->contentsSize();
2902    page.setViewportSize(size);
2903
2904    // render contents layer only (the iframe is smaller than the image, so it will have scrollbars)
2905    QPainter painter1(&picture);
2906    frame->render(&painter1, QWebFrame::ContentsLayer);
2907    painter1.end();
2908
2909    QCOMPARE(size.width(), picture.boundingRect().width() + frame->scrollBarGeometry(Qt::Vertical).width());
2910    QCOMPARE(size.height(), picture.boundingRect().height() + frame->scrollBarGeometry(Qt::Horizontal).height());
2911
2912    // render everything, should be the size of the iframe
2913    QPainter painter2(&picture);
2914    frame->render(&painter2, QWebFrame::AllLayers);
2915    painter2.end();
2916
2917    QCOMPARE(size.width(), picture.boundingRect().width());   // width: 100px
2918    QCOMPARE(size.height(), picture.boundingRect().height()); // height: 100px
2919}
2920
2921
2922class DummyPaintEngine: public QPaintEngine {
2923public:
2924
2925    DummyPaintEngine()
2926        : QPaintEngine(QPaintEngine::AllFeatures)
2927        , renderHints(0)
2928    {
2929    }
2930
2931    bool begin(QPaintDevice*)
2932    {
2933        setActive(true);
2934        return true;
2935    }
2936
2937    bool end()
2938    {
2939        setActive(false);
2940        return false;
2941    }
2942
2943    void updateState(const QPaintEngineState& state)
2944    {
2945        renderHints = state.renderHints();
2946    }
2947
2948    void drawPath(const QPainterPath&) { }
2949    void drawPixmap(const QRectF&, const QPixmap&, const QRectF&) { }
2950
2951    QPaintEngine::Type type() const
2952    {
2953        return static_cast<QPaintEngine::Type>(QPaintEngine::User + 2);
2954    }
2955
2956    QPainter::RenderHints renderHints;
2957};
2958
2959class DummyPaintDevice: public QPaintDevice {
2960public:
2961    DummyPaintDevice()
2962        : QPaintDevice()
2963        , m_engine(new DummyPaintEngine)
2964    {
2965    }
2966
2967    ~DummyPaintDevice()
2968    {
2969        delete m_engine;
2970    }
2971
2972    QPaintEngine* paintEngine() const
2973    {
2974        return m_engine;
2975    }
2976
2977    QPainter::RenderHints renderHints() const
2978    {
2979        return m_engine->renderHints;
2980    }
2981
2982protected:
2983    int metric(PaintDeviceMetric metric) const;
2984
2985private:
2986    DummyPaintEngine* m_engine;
2987    friend class DummyPaintEngine;
2988};
2989
2990
2991int DummyPaintDevice::metric(PaintDeviceMetric metric) const
2992{
2993    switch (metric) {
2994    case PdmWidth:
2995        return 400;
2996        break;
2997
2998    case PdmHeight:
2999        return 200;
3000        break;
3001
3002    case PdmNumColors:
3003        return INT_MAX;
3004        break;
3005
3006    case PdmDepth:
3007        return 32;
3008        break;
3009
3010    default:
3011        break;
3012    }
3013    return 0;
3014}
3015
3016void tst_QWebFrame::renderHints()
3017{
3018    QString html("<html><body><p>Hello, world!</p></body></html>");
3019
3020    QWebPage page;
3021    page.mainFrame()->setHtml(html);
3022    page.setViewportSize(page.mainFrame()->contentsSize());
3023
3024    // We will call frame->render and trap the paint engine state changes
3025    // to ensure that GraphicsContext does not clobber the render hints.
3026    DummyPaintDevice buffer;
3027    QPainter painter(&buffer);
3028
3029    painter.setRenderHint(QPainter::TextAntialiasing, false);
3030    page.mainFrame()->render(&painter);
3031    QVERIFY(!(buffer.renderHints() & QPainter::TextAntialiasing));
3032    QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform));
3033    QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing));
3034
3035    painter.setRenderHint(QPainter::TextAntialiasing, true);
3036    page.mainFrame()->render(&painter);
3037    QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing);
3038    QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform));
3039    QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing));
3040
3041    painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
3042    page.mainFrame()->render(&painter);
3043    QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing);
3044    QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform);
3045    QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing));
3046
3047    painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
3048    page.mainFrame()->render(&painter);
3049    QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing);
3050    QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform);
3051    QVERIFY(buffer.renderHints() & QPainter::HighQualityAntialiasing);
3052}
3053
3054void tst_QWebFrame::scrollPosition()
3055{
3056    // enlarged image in a small viewport, to provoke the scrollbars to appear
3057    QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>");
3058
3059    QWebPage page;
3060    page.setViewportSize(QSize(200, 200));
3061
3062    QWebFrame* frame = page.mainFrame();
3063    frame->setHtml(html);
3064    frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
3065    frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
3066
3067    // try to set the scroll offset programmatically
3068    frame->setScrollPosition(QPoint(23, 29));
3069    QCOMPARE(frame->scrollPosition().x(), 23);
3070    QCOMPARE(frame->scrollPosition().y(), 29);
3071
3072    int x = frame->evaluateJavaScript("window.scrollX").toInt();
3073    int y = frame->evaluateJavaScript("window.scrollY").toInt();
3074    QCOMPARE(x, 23);
3075    QCOMPARE(y, 29);
3076}
3077
3078void tst_QWebFrame::scrollToAnchor()
3079{
3080    QWebPage page;
3081    page.setViewportSize(QSize(480, 800));
3082    QWebFrame* frame = page.mainFrame();
3083
3084    QString html("<html><body><p style=\"margin-bottom: 1500px;\">Hello.</p>"
3085                 "<p><a id=\"foo\">This</a> is an anchor</p>"
3086                 "<p style=\"margin-bottom: 1500px;\"><a id=\"bar\">This</a> is another anchor</p>"
3087                 "</body></html>");
3088    frame->setHtml(html);
3089    frame->setScrollPosition(QPoint(0, 0));
3090    QCOMPARE(frame->scrollPosition().x(), 0);
3091    QCOMPARE(frame->scrollPosition().y(), 0);
3092
3093    QWebElement fooAnchor = frame->findFirstElement("a[id=foo]");
3094
3095    frame->scrollToAnchor("foo");
3096    QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top());
3097
3098    frame->scrollToAnchor("bar");
3099    frame->scrollToAnchor("foo");
3100    QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top());
3101
3102    frame->scrollToAnchor("top");
3103    QCOMPARE(frame->scrollPosition().y(), 0);
3104
3105    frame->scrollToAnchor("bar");
3106    frame->scrollToAnchor("notexist");
3107    QVERIFY(frame->scrollPosition().y() != 0);
3108}
3109
3110
3111void tst_QWebFrame::scrollbarsOff()
3112{
3113    QWebView view;
3114    QWebFrame* mainFrame = view.page()->mainFrame();
3115
3116    mainFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
3117    mainFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
3118
3119    QString html("<script>" \
3120                 "   function checkScrollbar() {" \
3121                 "       if (innerWidth === document.documentElement.offsetWidth)" \
3122                 "           document.getElementById('span1').innerText = 'SUCCESS';" \
3123                 "       else" \
3124                 "           document.getElementById('span1').innerText = 'FAIL';" \
3125                 "   }" \
3126                 "</script>" \
3127                 "<body>" \
3128                 "   <div style='margin-top:1000px ; margin-left:1000px'>" \
3129                 "       <a id='offscreen' href='a'>End</a>" \
3130                 "   </div>" \
3131                 "<span id='span1'></span>" \
3132                 "</body>");
3133
3134
3135    view.setHtml(html);
3136    ::waitForSignal(&view, SIGNAL(loadFinished(bool)));
3137
3138    mainFrame->evaluateJavaScript("checkScrollbar();");
3139    QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS"));
3140}
3141
3142void tst_QWebFrame::horizontalScrollAfterBack()
3143{
3144    QWebView view;
3145    QWebFrame* frame = view.page()->mainFrame();
3146    QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool)));
3147
3148    view.page()->settings()->setMaximumPagesInCache(2);
3149    frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded);
3150    frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded);
3151
3152    view.load(QUrl("qrc:/testiframe2.html"));
3153    view.resize(200, 200);
3154    QTRY_COMPARE(loadSpy.count(), 1);
3155    QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height());
3156
3157    view.load(QUrl("qrc:/testiframe.html"));
3158    QTRY_COMPARE(loadSpy.count(), 2);
3159
3160    view.page()->triggerAction(QWebPage::Back);
3161    QTRY_COMPARE(loadSpy.count(), 3);
3162    QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height());
3163}
3164
3165void tst_QWebFrame::evaluateWillCauseRepaint()
3166{
3167    QWebView view;
3168    QString html("<html><body>top<div id=\"junk\" style=\"display: block;\">"
3169                    "junk</div>bottom</body></html>");
3170    view.setHtml(html);
3171    view.show();
3172
3173    QTest::qWaitForWindowShown(&view);
3174    view.page()->mainFrame()->evaluateJavaScript(
3175        "document.getElementById('junk').style.display = 'none';");
3176
3177    ::waitForSignal(view.page(), SIGNAL(repaintRequested(QRect)));
3178}
3179
3180class TestFactory : public QObject
3181{
3182    Q_OBJECT
3183public:
3184    TestFactory()
3185        : obj(0), counter(0)
3186    {}
3187
3188    Q_INVOKABLE QObject* getNewObject()
3189    {
3190        delete obj;
3191        obj = new QObject(this);
3192        obj->setObjectName(QLatin1String("test") + QString::number(++counter));
3193        return obj;
3194
3195    }
3196
3197    QObject* obj;
3198    int counter;
3199};
3200
3201void tst_QWebFrame::qObjectWrapperWithSameIdentity()
3202{
3203    m_view->setHtml("<script>function triggerBug() { document.getElementById('span1').innerText = test.getNewObject().objectName; }</script>"
3204                    "<body><span id='span1'>test</span></body>");
3205
3206    QWebFrame* mainFrame = m_view->page()->mainFrame();
3207    QCOMPARE(mainFrame->toPlainText(), QString("test"));
3208
3209    mainFrame->addToJavaScriptWindowObject("test", new TestFactory, QScriptEngine::ScriptOwnership);
3210
3211    mainFrame->evaluateJavaScript("triggerBug();");
3212    QCOMPARE(mainFrame->toPlainText(), QString("test1"));
3213
3214    mainFrame->evaluateJavaScript("triggerBug();");
3215    QCOMPARE(mainFrame->toPlainText(), QString("test2"));
3216}
3217
3218void tst_QWebFrame::introspectQtMethods_data()
3219{
3220    QTest::addColumn<QString>("objectExpression");
3221    QTest::addColumn<QString>("methodName");
3222    QTest::addColumn<QStringList>("expectedPropertyNames");
3223
3224    QTest::newRow("myObject.mySignal")
3225        << "myObject" << "mySignal" << (QStringList() << "connect" << "disconnect" << "length" << "name");
3226    QTest::newRow("myObject.mySlot")
3227        << "myObject" << "mySlot" << (QStringList() << "connect" << "disconnect" << "length" << "name");
3228    QTest::newRow("myObject.myInvokable")
3229        << "myObject" << "myInvokable" << (QStringList() << "connect" << "disconnect" << "length" << "name");
3230    QTest::newRow("myObject.mySignal.connect")
3231        << "myObject.mySignal" << "connect" << (QStringList() << "length" << "name");
3232    QTest::newRow("myObject.mySignal.disconnect")
3233        << "myObject.mySignal" << "disconnect" << (QStringList() << "length" << "name");
3234}
3235
3236void tst_QWebFrame::introspectQtMethods()
3237{
3238    QFETCH(QString, objectExpression);
3239    QFETCH(QString, methodName);
3240    QFETCH(QStringList, expectedPropertyNames);
3241
3242    QString methodLookup = QString::fromLatin1("%0['%1']").arg(objectExpression).arg(methodName);
3243    QCOMPARE(evalJSV(QString::fromLatin1("Object.getOwnPropertyNames(%0).sort()").arg(methodLookup)).toStringList(), expectedPropertyNames);
3244
3245    for (int i = 0; i < expectedPropertyNames.size(); ++i) {
3246        QString name = expectedPropertyNames.at(i);
3247        QCOMPARE(evalJS(QString::fromLatin1("%0.hasOwnProperty('%1')").arg(methodLookup).arg(name)), sTrue);
3248        evalJS(QString::fromLatin1("var descriptor = Object.getOwnPropertyDescriptor(%0, '%1')").arg(methodLookup).arg(name));
3249        QCOMPARE(evalJS("typeof descriptor"), QString::fromLatin1("object"));
3250        QCOMPARE(evalJS("descriptor.get"), sUndefined);
3251        QCOMPARE(evalJS("descriptor.set"), sUndefined);
3252        QCOMPARE(evalJS(QString::fromLatin1("descriptor.value === %0['%1']").arg(methodLookup).arg(name)), sTrue);
3253        QCOMPARE(evalJS(QString::fromLatin1("descriptor.enumerable")), sFalse);
3254        QCOMPARE(evalJS(QString::fromLatin1("descriptor.configurable")), sFalse);
3255    }
3256
3257    QVERIFY(evalJSV("var props=[]; for (var p in myObject.deleteLater) {props.push(p);}; props.sort()").toStringList().isEmpty());
3258}
3259
3260void tst_QWebFrame::setContent_data()
3261{
3262    QTest::addColumn<QString>("mimeType");
3263    QTest::addColumn<QByteArray>("testContents");
3264    QTest::addColumn<QString>("expected");
3265
3266    QString str = QString::fromUtf8("á½Î±Î»Î¿Î½ Ïαγεá¿Î½ δύναμαιΠÏοῦÏο οὠμε βλάÏÏει");
3267    QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str;
3268
3269    QTextCodec *utf16 = QTextCodec::codecForName("UTF-16");
3270    if (utf16)
3271        QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str;
3272
3273    str = QString::fromUtf8("Une chaîne de caractères à sa façon.");
3274    QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str;
3275
3276
3277}
3278
3279void tst_QWebFrame::setContent()
3280{
3281    QFETCH(QString, mimeType);
3282    QFETCH(QByteArray, testContents);
3283    QFETCH(QString, expected);
3284    m_view->setContent(testContents, mimeType);
3285    QWebFrame* mainFrame = m_view->page()->mainFrame();
3286    QCOMPARE(expected , mainFrame->toPlainText());
3287}
3288
3289class CacheNetworkAccessManager : public QNetworkAccessManager {
3290public:
3291    CacheNetworkAccessManager(QObject* parent = 0)
3292        : QNetworkAccessManager(parent)
3293        , m_lastCacheLoad(QNetworkRequest::PreferNetwork)
3294    {
3295    }
3296
3297    virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*)
3298    {
3299        QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute);
3300        if (cacheLoad.isValid())
3301            m_lastCacheLoad = static_cast<QNetworkRequest::CacheLoadControl>(cacheLoad.toUInt());
3302        else
3303            m_lastCacheLoad = QNetworkRequest::PreferNetwork; // default value
3304        return new FakeReply(request, this);
3305    }
3306
3307    QNetworkRequest::CacheLoadControl lastCacheLoad() const
3308    {
3309        return m_lastCacheLoad;
3310    }
3311
3312private:
3313    QNetworkRequest::CacheLoadControl m_lastCacheLoad;
3314};
3315
3316void tst_QWebFrame::setCacheLoadControlAttribute()
3317{
3318    QWebPage page;
3319    CacheNetworkAccessManager* manager = new CacheNetworkAccessManager(&page);
3320    page.setNetworkAccessManager(manager);
3321    QWebFrame* frame = page.mainFrame();
3322
3323    QNetworkRequest request(QUrl("http://abcdef.abcdef/"));
3324
3325    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
3326    frame->load(request);
3327    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysCache);
3328
3329    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
3330    frame->load(request);
3331    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferCache);
3332
3333    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
3334    frame->load(request);
3335    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysNetwork);
3336
3337    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork);
3338    frame->load(request);
3339    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferNetwork);
3340}
3341
3342void tst_QWebFrame::webElementSlotOnly()
3343{
3344    MyWebElementSlotOnlyObject object;
3345    m_page->mainFrame()->setHtml("<html><head><body></body></html>");
3346    m_page->mainFrame()->addToJavaScriptWindowObject("myWebElementSlotObject", &object);
3347    evalJS("myWebElementSlotObject.doSomethingWithWebElement(document.body)");
3348    QCOMPARE(evalJS("myWebElementSlotObject.tagName"), QString("BODY"));
3349}
3350
3351void tst_QWebFrame::setUrlWithPendingLoads()
3352{
3353    QWebPage page;
3354    page.mainFrame()->setHtml("<img src='dummy:'/>");
3355    page.mainFrame()->setUrl(QUrl("about:blank"));
3356}
3357
3358void tst_QWebFrame::setUrlWithFragment_data()
3359{
3360    QTest::addColumn<QUrl>("previousUrl");
3361    QTest::newRow("empty") << QUrl();
3362    QTest::newRow("same URL no fragment") << QUrl("qrc:/test1.html");
3363    // See comments in setUrlSameUrl about using setUrl() with the same url().
3364    QTest::newRow("same URL with same fragment") << QUrl("qrc:/test1.html#");
3365    QTest::newRow("same URL with different fragment") << QUrl("qrc:/test1.html#anotherFragment");
3366    QTest::newRow("another URL") << QUrl("qrc:/test2.html");
3367}
3368
3369// Based on bug report https://bugs.webkit.org/show_bug.cgi?id=32723
3370void tst_QWebFrame::setUrlWithFragment()
3371{
3372    QFETCH(QUrl, previousUrl);
3373
3374    QWebPage page;
3375    QWebFrame* frame = page.mainFrame();
3376
3377    if (!previousUrl.isEmpty()) {
3378        frame->load(previousUrl);
3379        ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
3380        QCOMPARE(frame->url(), previousUrl);
3381    }
3382
3383    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
3384    const QUrl url("qrc:/test1.html#");
3385    QVERIFY(!url.fragment().isNull());
3386
3387    frame->setUrl(url);
3388    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
3389
3390    QCOMPARE(spy.count(), 1);
3391    QVERIFY(!frame->toPlainText().isEmpty());
3392    QCOMPARE(frame->requestedUrl(), url);
3393    QCOMPARE(frame->url(), url);
3394}
3395
3396void tst_QWebFrame::setUrlToEmpty()
3397{
3398    int expectedLoadFinishedCount = 0;
3399    const QUrl aboutBlank("about:blank");
3400    const QUrl url("qrc:/test2.html");
3401
3402    QWebPage page;
3403    QWebFrame* frame = page.mainFrame();
3404    QCOMPARE(frame->url(), QUrl());
3405    QCOMPARE(frame->requestedUrl(), QUrl());
3406    QCOMPARE(frame->baseUrl(), QUrl());
3407
3408    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
3409
3410    // Set existing url
3411    frame->setUrl(url);
3412    expectedLoadFinishedCount++;
3413    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
3414
3415    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3416    QCOMPARE(frame->url(), url);
3417    QCOMPARE(frame->requestedUrl(), url);
3418    QCOMPARE(frame->baseUrl(), url);
3419
3420    // Set empty url
3421    frame->setUrl(QUrl());
3422    expectedLoadFinishedCount++;
3423
3424    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3425    QCOMPARE(frame->url(), aboutBlank);
3426    QCOMPARE(frame->requestedUrl(), QUrl());
3427    QCOMPARE(frame->baseUrl(), aboutBlank);
3428
3429    // Set existing url
3430    frame->setUrl(url);
3431    expectedLoadFinishedCount++;
3432    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
3433
3434    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3435    QCOMPARE(frame->url(), url);
3436    QCOMPARE(frame->requestedUrl(), url);
3437    QCOMPARE(frame->baseUrl(), url);
3438
3439    // Load empty url
3440    frame->load(QUrl());
3441    expectedLoadFinishedCount++;
3442
3443    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3444    QCOMPARE(frame->url(), aboutBlank);
3445    QCOMPARE(frame->requestedUrl(), QUrl());
3446    QCOMPARE(frame->baseUrl(), aboutBlank);
3447}
3448
3449void tst_QWebFrame::setUrlToInvalid()
3450{
3451    QWebPage page;
3452    QWebFrame* frame = page.mainFrame();
3453
3454    const QUrl invalidUrl("http://strange;hostname/here");
3455    QVERIFY(!invalidUrl.isEmpty());
3456    QVERIFY(!invalidUrl.isValid());
3457    QVERIFY(invalidUrl != QUrl());
3458
3459    frame->setUrl(invalidUrl);
3460    QCOMPARE(frame->url(), invalidUrl);
3461    QCOMPARE(frame->requestedUrl(), invalidUrl);
3462    QCOMPARE(frame->baseUrl(), invalidUrl);
3463
3464    // QUrls equivalent to QUrl() will be treated as such.
3465    const QUrl aboutBlank("about:blank");
3466    const QUrl anotherInvalidUrl("1http://bugs.webkit.org");
3467    QVERIFY(!anotherInvalidUrl.isEmpty()); // and they are not necessarily empty.
3468    QVERIFY(!anotherInvalidUrl.isValid());
3469    QCOMPARE(anotherInvalidUrl, QUrl());
3470
3471    frame->setUrl(anotherInvalidUrl);
3472    QCOMPARE(frame->url(), aboutBlank);
3473    QCOMPARE(frame->requestedUrl(), anotherInvalidUrl);
3474    QCOMPARE(frame->baseUrl(), aboutBlank);
3475}
3476
3477void tst_QWebFrame::setUrlHistory()
3478{
3479    const QUrl aboutBlank("about:blank");
3480    QUrl url;
3481    int expectedLoadFinishedCount = 0;
3482    QWebFrame* frame = m_page->mainFrame();
3483    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
3484
3485    QCOMPARE(m_page->history()->count(), 0);
3486
3487    frame->setUrl(QUrl());
3488    expectedLoadFinishedCount++;
3489    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3490    QCOMPARE(frame->url(), aboutBlank);
3491    QCOMPARE(frame->requestedUrl(), QUrl());
3492    QCOMPARE(m_page->history()->count(), 0);
3493
3494    url = QUrl("http://non.existant/");
3495    frame->setUrl(url);
3496    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
3497    expectedLoadFinishedCount++;
3498    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3499    QCOMPARE(frame->url(), url);
3500    QCOMPARE(frame->requestedUrl(), url);
3501    QCOMPARE(m_page->history()->count(), 0);
3502
3503    url = QUrl("qrc:/test1.html");
3504    frame->setUrl(url);
3505    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
3506    expectedLoadFinishedCount++;
3507    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3508    QCOMPARE(frame->url(), url);
3509    QCOMPARE(frame->requestedUrl(), url);
3510    QCOMPARE(m_page->history()->count(), 1);
3511
3512    frame->setUrl(QUrl());
3513    expectedLoadFinishedCount++;
3514    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3515    QCOMPARE(frame->url(), aboutBlank);
3516    QCOMPARE(frame->requestedUrl(), QUrl());
3517    QCOMPARE(m_page->history()->count(), 1);
3518
3519    // Loading same page as current in history, so history count doesn't change.
3520    url = QUrl("qrc:/test1.html");
3521    frame->setUrl(url);
3522    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
3523    expectedLoadFinishedCount++;
3524    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3525    QCOMPARE(frame->url(), url);
3526    QCOMPARE(frame->requestedUrl(), url);
3527    QCOMPARE(m_page->history()->count(), 1);
3528
3529    url = QUrl("qrc:/test2.html");
3530    frame->setUrl(url);
3531    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
3532    expectedLoadFinishedCount++;
3533    QCOMPARE(spy.count(), expectedLoadFinishedCount);
3534    QCOMPARE(frame->url(), url);
3535    QCOMPARE(frame->requestedUrl(), url);
3536    QCOMPARE(m_page->history()->count(), 2);
3537}
3538
3539void tst_QWebFrame::setUrlSameUrl()
3540{
3541    const QUrl url1("qrc:/test1.html");
3542    const QUrl url2("qrc:/test2.html");
3543
3544    QWebPage page;
3545    QWebFrame* frame = page.mainFrame();
3546    FakeNetworkManager* networkManager = new FakeNetworkManager(&page);
3547    page.setNetworkAccessManager(networkManager);
3548
3549    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
3550
3551    frame->setUrl(url1);
3552    waitForSignal(frame, SIGNAL(loadFinished(bool)));
3553    QVERIFY(frame->url() != url1); // Nota bene: our QNAM redirects url1 to url2
3554    QCOMPARE(frame->url(), url2);
3555    QCOMPARE(spy.count(), 1);
3556
3557    frame->setUrl(url1);
3558    waitForSignal(frame, SIGNAL(loadFinished(bool)));
3559    QVERIFY(frame->url() != url1);
3560    QCOMPARE(frame->url(), url2);
3561    QCOMPARE(spy.count(), 2);
3562
3563    // Now a case without redirect. The existing behavior we have for setUrl()
3564    // is more like a "clear(); load()", so the page will be loaded again, even
3565    // if urlToBeLoaded == url(). This test should be changed if we want to
3566    // make setUrl() early return in this case.
3567    frame->setUrl(url2);
3568    waitForSignal(frame, SIGNAL(loadFinished(bool)));
3569    QCOMPARE(frame->url(), url2);
3570    QCOMPARE(spy.count(), 3);
3571
3572    frame->setUrl(url1);
3573    waitForSignal(frame, SIGNAL(loadFinished(bool)));
3574    QCOMPARE(frame->url(), url2);
3575    QCOMPARE(spy.count(), 4);
3576}
3577
3578static inline QUrl extractBaseUrl(const QUrl& url)
3579{
3580    return url.resolved(QUrl());
3581}
3582
3583void tst_QWebFrame::setUrlThenLoads_data()
3584{
3585    QTest::addColumn<QUrl>("url");
3586    QTest::addColumn<QUrl>("baseUrl");
3587
3588    QTest::newRow("resource file") << QUrl("qrc:/test1.html") << extractBaseUrl(QUrl("qrc:/test1.html"));
3589    QTest::newRow("base specified in HTML") << QUrl("data:text/html,<head><base href=\"http://different.base/\"></head>") << QUrl("http://different.base/");
3590}
3591
3592void tst_QWebFrame::setUrlThenLoads()
3593{
3594    QFETCH(QUrl, url);
3595    QFETCH(QUrl, baseUrl);
3596    QWebFrame* frame = m_page->mainFrame();
3597    QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl)));
3598    QSignalSpy startedSpy(frame, SIGNAL(loadStarted()));
3599    QSignalSpy finishedSpy(frame, SIGNAL(loadFinished(bool)));
3600
3601    frame->setUrl(url);
3602    QCOMPARE(startedSpy.count(), 1);
3603    ::waitForSignal(frame, SIGNAL(urlChanged(QUrl)));
3604    QCOMPARE(urlChangedSpy.count(), 1);
3605    QVERIFY(finishedSpy.at(0).first().toBool());
3606    QCOMPARE(frame->url(), url);
3607    QCOMPARE(frame->requestedUrl(), url);
3608    QCOMPARE(frame->baseUrl(), baseUrl);
3609
3610    const QUrl urlToLoad1("qrc:/test2.html");
3611    const QUrl urlToLoad2("qrc:/test1.html");
3612
3613    // Just after first load. URL didn't changed yet.
3614    frame->load(urlToLoad1);
3615    QCOMPARE(startedSpy.count(), 2);
3616    QCOMPARE(frame->url(), url);
3617    QCOMPARE(frame->requestedUrl(), urlToLoad1);
3618    QCOMPARE(frame->baseUrl(), baseUrl);
3619
3620    // After first URL changed.
3621    ::waitForSignal(frame, SIGNAL(urlChanged(QUrl)));
3622    QCOMPARE(urlChangedSpy.count(), 2);
3623    QVERIFY(finishedSpy.at(1).first().toBool());
3624    QCOMPARE(frame->url(), urlToLoad1);
3625    QCOMPARE(frame->requestedUrl(), urlToLoad1);
3626    QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1));
3627
3628    // Just after second load. URL didn't changed yet.
3629    frame->load(urlToLoad2);
3630    QCOMPARE(startedSpy.count(), 3);
3631    QCOMPARE(frame->url(), urlToLoad1);
3632    QCOMPARE(frame->requestedUrl(), urlToLoad2);
3633    QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1));
3634
3635    // After second URL changed.
3636    ::waitForSignal(frame, SIGNAL(urlChanged(QUrl)));
3637    QCOMPARE(urlChangedSpy.count(), 3);
3638    QVERIFY(finishedSpy.at(2).first().toBool());
3639    QCOMPARE(frame->url(), urlToLoad2);
3640    QCOMPARE(frame->requestedUrl(), urlToLoad2);
3641    QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad2));
3642}
3643
3644QTEST_MAIN(tst_QWebFrame)
3645#include "tst_qwebframe.moc"
3646