tst_qwebpage.cpp revision 81bc750723a18f21cd17d1b173cd2a4dda9cea6e
1/*
2    Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
3    Copyright (C) 2009 Girish Ramakrishnan <girish@forwardbias.in>
4    Copyright (C) 2010 Holger Hans Peter Freyther
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19    Boston, MA 02110-1301, USA.
20*/
21
22#include "../util.h"
23#include "../WebCoreSupport/DumpRenderTreeSupportQt.h"
24#include <QClipboard>
25#include <QDir>
26#include <QGraphicsWidget>
27#include <QLineEdit>
28#include <QMainWindow>
29#include <QMenu>
30#include <QPushButton>
31#include <QStyle>
32#include <QtTest/QtTest>
33#include <QTextCharFormat>
34#include <qgraphicsscene.h>
35#include <qgraphicsview.h>
36#include <qgraphicswebview.h>
37#include <qnetworkcookiejar.h>
38#include <qnetworkrequest.h>
39#include <qwebdatabase.h>
40#include <qwebelement.h>
41#include <qwebframe.h>
42#include <qwebhistory.h>
43#include <qwebpage.h>
44#include <qwebsecurityorigin.h>
45#include <qwebview.h>
46#include <qimagewriter.h>
47
48class EventSpy : public QObject, public QList<QEvent::Type>
49{
50    Q_OBJECT
51public:
52    EventSpy(QObject* objectToSpy)
53    {
54        objectToSpy->installEventFilter(this);
55    }
56
57    virtual bool eventFilter(QObject* receiver, QEvent* event)
58    {
59        append(event->type());
60        return false;
61    }
62};
63
64class tst_QWebPage : public QObject
65{
66    Q_OBJECT
67
68public:
69    tst_QWebPage();
70    virtual ~tst_QWebPage();
71
72public slots:
73    void init();
74    void cleanup();
75    void cleanupFiles();
76
77private slots:
78    void initTestCase();
79    void cleanupTestCase();
80
81    void contextMenuCopy();
82    void acceptNavigationRequest();
83    void geolocationRequestJS();
84    void loadFinished();
85    void acceptNavigationRequestWithNewWindow();
86    void userStyleSheet();
87    void loadHtml5Video();
88    void modified();
89    void contextMenuCrash();
90    void updatePositionDependentActionsCrash();
91    void database();
92    void createPluginWithPluginsEnabled();
93    void createPluginWithPluginsDisabled();
94    void destroyPlugin_data();
95    void destroyPlugin();
96    void createViewlessPlugin_data();
97    void createViewlessPlugin();
98    void graphicsWidgetPlugin();
99    void multiplePageGroupsAndLocalStorage();
100    void cursorMovements();
101    void textSelection();
102    void textEditing();
103    void backActionUpdate();
104    void frameAt();
105    void requestCache();
106    void loadCachedPage();
107    void protectBindingsRuntimeObjectsFromCollector();
108    void localURLSchemes();
109    void testOptionalJSObjects();
110    void testEnablePersistentStorage();
111    void consoleOutput();
112    void inputMethods_data();
113    void inputMethods();
114    void inputMethodsTextFormat_data();
115    void inputMethodsTextFormat();
116    void defaultTextEncoding();
117    void errorPageExtension();
118    void errorPageExtensionInIFrames();
119    void errorPageExtensionInFrameset();
120    void userAgentApplicationName();
121
122    void viewModes();
123
124    void crashTests_LazyInitializationOfMainFrame();
125
126    void screenshot_data();
127    void screenshot();
128
129    void originatingObjectInNetworkRequests();
130    void testJSPrompt();
131    void showModalDialog();
132    void testStopScheduledPageRefresh();
133    void findText();
134    void supportedContentType();
135    void infiniteLoopJS();
136    void navigatorCookieEnabled();
137    void deleteQWebViewTwice();
138
139#ifdef Q_OS_MAC
140    void macCopyUnicodeToClipboard();
141#endif
142
143private:
144    QWebView* m_view;
145    QWebPage* m_page;
146};
147
148tst_QWebPage::tst_QWebPage()
149{
150}
151
152tst_QWebPage::~tst_QWebPage()
153{
154}
155
156void tst_QWebPage::init()
157{
158    m_view = new QWebView();
159    m_page = m_view->page();
160}
161
162void tst_QWebPage::cleanup()
163{
164    delete m_view;
165}
166
167void tst_QWebPage::cleanupFiles()
168{
169    QFile::remove("Databases.db");
170    QDir::current().rmdir("http_www.myexample.com_0");
171    QFile::remove("http_www.myexample.com_0.localstorage");
172}
173
174void tst_QWebPage::initTestCase()
175{
176    cleanupFiles(); // In case there are old files from previous runs
177}
178
179void tst_QWebPage::cleanupTestCase()
180{
181    cleanupFiles(); // Be nice
182}
183
184class NavigationRequestOverride : public QWebPage
185{
186public:
187    NavigationRequestOverride(QWebView* parent, bool initialValue) : QWebPage(parent), m_acceptNavigationRequest(initialValue) {}
188
189    bool m_acceptNavigationRequest;
190protected:
191    virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, QWebPage::NavigationType type) {
192        Q_UNUSED(frame);
193        Q_UNUSED(request);
194        Q_UNUSED(type);
195
196        return m_acceptNavigationRequest;
197    }
198};
199
200void tst_QWebPage::acceptNavigationRequest()
201{
202    QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool)));
203
204    NavigationRequestOverride* newPage = new NavigationRequestOverride(m_view, false);
205    m_view->setPage(newPage);
206
207    m_view->setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>"
208                            "<input type='text'><input type='submit'></form></body></html>"), QUrl());
209    QTRY_COMPARE(loadSpy.count(), 1);
210
211    m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();");
212
213    newPage->m_acceptNavigationRequest = true;
214    m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();");
215    QTRY_COMPARE(loadSpy.count(), 2);
216
217    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("foo?"));
218
219    // Restore default page
220    m_view->setPage(0);
221}
222
223class JSTestPage : public QWebPage
224{
225Q_OBJECT
226public:
227    JSTestPage(QObject* parent = 0)
228    : QWebPage(parent) {}
229
230public slots:
231    bool shouldInterruptJavaScript() {
232        return true;
233    }
234    void requestPermission(QWebFrame* frame, QWebPage::Feature feature)
235    {
236        if (m_allowGeolocation)
237            setFeaturePermission(frame, feature, PermissionGrantedByUser);
238        else
239            setFeaturePermission(frame, feature, PermissionDeniedByUser);
240    }
241
242public:
243    void setGeolocationPermission(bool allow)
244    {
245        m_allowGeolocation = allow;
246    }
247
248private:
249    bool m_allowGeolocation;
250};
251
252void tst_QWebPage::infiniteLoopJS()
253{
254    JSTestPage* newPage = new JSTestPage(m_view);
255    m_view->setPage(newPage);
256    m_view->setHtml(QString("<html><body>test</body></html>"), QUrl());
257    m_view->page()->mainFrame()->evaluateJavaScript("var run = true;var a = 1;while(run){a++;}");
258    delete newPage;
259}
260
261void tst_QWebPage::geolocationRequestJS()
262{
263    JSTestPage* newPage = new JSTestPage(m_view);
264
265    if (newPage->mainFrame()->evaluateJavaScript(QLatin1String("!navigator.geolocation")).toBool()) {
266        delete newPage;
267        QSKIP("Geolocation is not supported.", SkipSingle);
268    }
269
270    connect(newPage, SIGNAL(featurePermissionRequested(QWebFrame*, QWebPage::Feature)),
271            newPage, SLOT(requestPermission(QWebFrame*, QWebPage::Feature)));
272
273    newPage->setGeolocationPermission(false);
274    m_view->setPage(newPage);
275    m_view->setHtml(QString("<html><body>test</body></html>"), QUrl());
276    m_view->page()->mainFrame()->evaluateJavaScript("var errorCode = 0; function error(err) { errorCode = err.code; } function success(pos) { } navigator.geolocation.getCurrentPosition(success, error)");
277    QTest::qWait(2000);
278    QVariant empty = m_view->page()->mainFrame()->evaluateJavaScript("errorCode");
279
280    QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 0);
281
282    newPage->setGeolocationPermission(true);
283    m_view->page()->mainFrame()->evaluateJavaScript("errorCode = 0; navigator.geolocation.getCurrentPosition(success, error);");
284    empty = m_view->page()->mainFrame()->evaluateJavaScript("errorCode");
285
286    //http://dev.w3.org/geo/api/spec-source.html#position
287    //PositionError: const unsigned short PERMISSION_DENIED = 1;
288    QVERIFY(empty.type() == QVariant::Double && empty.toInt() != 1);
289    delete newPage;
290}
291
292void tst_QWebPage::loadFinished()
293{
294    qRegisterMetaType<QWebFrame*>("QWebFrame*");
295    qRegisterMetaType<QNetworkRequest*>("QNetworkRequest*");
296    QSignalSpy spyLoadStarted(m_view, SIGNAL(loadStarted()));
297    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
298
299    m_view->page()->mainFrame()->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
300                                           "<head><meta http-equiv='refresh' content='1'></head>foo \">"
301                                           "<frame src=\"data:text/html,bar\"></frameset>"));
302    QTRY_COMPARE(spyLoadFinished.count(), 1);
303
304    QTRY_VERIFY(spyLoadStarted.count() > 1);
305    QTRY_VERIFY(spyLoadFinished.count() > 1);
306
307    spyLoadFinished.clear();
308
309    m_view->page()->mainFrame()->load(QUrl("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
310                                           "foo \"><frame src=\"data:text/html,bar\"></frameset>"));
311    QTRY_COMPARE(spyLoadFinished.count(), 1);
312    QCOMPARE(spyLoadFinished.count(), 1);
313}
314
315class ConsolePage : public QWebPage
316{
317public:
318    ConsolePage(QObject* parent = 0) : QWebPage(parent) {}
319
320    virtual void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID)
321    {
322        messages.append(message);
323        lineNumbers.append(lineNumber);
324        sourceIDs.append(sourceID);
325    }
326
327    QStringList messages;
328    QList<int> lineNumbers;
329    QStringList sourceIDs;
330};
331
332void tst_QWebPage::consoleOutput()
333{
334    ConsolePage page;
335    page.mainFrame()->evaluateJavaScript("this is not valid JavaScript");
336    QCOMPARE(page.messages.count(), 1);
337    QCOMPARE(page.lineNumbers.at(0), 1);
338}
339
340class TestPage : public QWebPage
341{
342public:
343    TestPage(QObject* parent = 0) : QWebPage(parent) {}
344
345    struct Navigation {
346        QPointer<QWebFrame> frame;
347        QNetworkRequest request;
348        NavigationType type;
349    };
350
351    QList<Navigation> navigations;
352    QList<QWebPage*> createdWindows;
353
354    virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, NavigationType type) {
355        Navigation n;
356        n.frame = frame;
357        n.request = request;
358        n.type = type;
359        navigations.append(n);
360        return true;
361    }
362
363    virtual QWebPage* createWindow(WebWindowType) {
364        QWebPage* page = new TestPage(this);
365        createdWindows.append(page);
366        return page;
367    }
368};
369
370void tst_QWebPage::acceptNavigationRequestWithNewWindow()
371{
372    TestPage* page = new TestPage(m_view);
373    page->settings()->setAttribute(QWebSettings::LinksIncludedInFocusChain, true);
374    m_page = page;
375    m_view->setPage(m_page);
376
377    m_view->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>"));
378    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
379
380    QFocusEvent fe(QEvent::FocusIn);
381    m_page->event(&fe);
382
383    QVERIFY(m_page->focusNextPrevChild(/*next*/ true));
384
385    QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
386    m_page->event(&keyEnter);
387
388    QCOMPARE(page->navigations.count(), 2);
389
390    TestPage::Navigation n = page->navigations.at(1);
391    QVERIFY(n.frame.isNull());
392    QCOMPARE(n.request.url().toString(), QString("data:text/html,Reached"));
393    QVERIFY(n.type == QWebPage::NavigationTypeLinkClicked);
394
395    QCOMPARE(page->createdWindows.count(), 1);
396}
397
398class TestNetworkManager : public QNetworkAccessManager
399{
400public:
401    TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {}
402
403    QList<QUrl> requestedUrls;
404    QList<QNetworkRequest> requests;
405
406protected:
407    virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) {
408        requests.append(request);
409        requestedUrls.append(request.url());
410        return QNetworkAccessManager::createRequest(op, request, outgoingData);
411    }
412};
413
414void tst_QWebPage::userStyleSheet()
415{
416    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
417    m_page->setNetworkAccessManager(networkManager);
418    networkManager->requestedUrls.clear();
419
420    m_page->settings()->setUserStyleSheetUrl(QUrl("data:text/css;charset=utf-8;base64,"
421            + QByteArray("p { background-image: url('http://does.not/exist.png');}").toBase64()));
422    m_view->setHtml("<p>hello world</p>");
423    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
424
425    QVERIFY(networkManager->requestedUrls.count() >= 1);
426    QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png"));
427}
428
429void tst_QWebPage::loadHtml5Video()
430{
431#if defined(ENABLE_QT_MULTIMEDIA) && ENABLE_QT_MULTIMEDIA
432    QByteArray url("http://does.not/exist?a=1%2Cb=2");
433    m_view->setHtml("<p><video id ='video' src='" + url + "' autoplay/></p>");
434    QTest::qWait(2000);
435    QUrl mUrl = DumpRenderTreeSupportQt::mediaContentUrlByElementId(m_page->mainFrame(), "video");
436    QCOMPARE(mUrl.toEncoded(), url);
437#else
438    QSKIP("This test requires Qt Multimedia", SkipAll);
439#endif
440}
441
442void tst_QWebPage::viewModes()
443{
444    m_view->setHtml("<body></body>");
445    m_page->setProperty("_q_viewMode", "minimized");
446
447    QVariant empty = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode)\")");
448    QVERIFY(empty.type() == QVariant::Bool && empty.toBool());
449
450    QVariant minimized = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode: minimized)\")");
451    QVERIFY(minimized.type() == QVariant::Bool && minimized.toBool());
452
453    QVariant maximized = m_page->mainFrame()->evaluateJavaScript("window.styleMedia.matchMedium(\"(-webkit-view-mode: maximized)\")");
454    QVERIFY(maximized.type() == QVariant::Bool && !maximized.toBool());
455}
456
457void tst_QWebPage::modified()
458{
459    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>blub"));
460    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
461
462    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body id=foo contenteditable>blah"));
463    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
464
465    QVERIFY(!m_page->isModified());
466
467//    m_page->mainFrame()->evaluateJavaScript("alert(document.getElementById('foo'))");
468    m_page->mainFrame()->evaluateJavaScript("document.getElementById('foo').focus()");
469    m_page->mainFrame()->evaluateJavaScript("document.execCommand('InsertText', true, 'Test');");
470
471    QVERIFY(m_page->isModified());
472
473    m_page->mainFrame()->evaluateJavaScript("document.execCommand('Undo', true);");
474
475    QVERIFY(!m_page->isModified());
476
477    m_page->mainFrame()->evaluateJavaScript("document.execCommand('Redo', true);");
478
479    QVERIFY(m_page->isModified());
480
481    QVERIFY(m_page->history()->canGoBack());
482    QVERIFY(!m_page->history()->canGoForward());
483    QCOMPARE(m_page->history()->count(), 2);
484    QVERIFY(m_page->history()->backItem().isValid());
485    QVERIFY(!m_page->history()->forwardItem().isValid());
486
487    m_page->history()->back();
488    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
489
490    QVERIFY(!m_page->history()->canGoBack());
491    QVERIFY(m_page->history()->canGoForward());
492
493    QVERIFY(!m_page->isModified());
494
495    QVERIFY(m_page->history()->currentItemIndex() == 0);
496
497    m_page->history()->setMaximumItemCount(3);
498    QVERIFY(m_page->history()->maximumItemCount() == 3);
499
500    QVariant variant("string test");
501    m_page->history()->currentItem().setUserData(variant);
502    QVERIFY(m_page->history()->currentItem().userData().toString() == "string test");
503
504    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is second page"));
505    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is third page"));
506    QVERIFY(m_page->history()->count() == 2);
507    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fourth page"));
508    QVERIFY(m_page->history()->count() == 2);
509    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fifth page"));
510    QVERIFY(::waitForSignal(m_page, SIGNAL(saveFrameStateRequested(QWebFrame*,QWebHistoryItem*))));
511}
512
513// https://bugs.webkit.org/show_bug.cgi?id=51331
514void tst_QWebPage::updatePositionDependentActionsCrash()
515{
516    QWebView view;
517    view.setHtml("<p>test");
518    QPoint pos(0, 0);
519    view.page()->updatePositionDependentActions(pos);
520    QMenu* contextMenu = 0;
521    foreach (QObject* child, view.children()) {
522        contextMenu = qobject_cast<QMenu*>(child);
523        if (contextMenu)
524            break;
525    }
526    QVERIFY(!contextMenu);
527}
528
529// https://bugs.webkit.org/show_bug.cgi?id=20357
530void tst_QWebPage::contextMenuCrash()
531{
532    QWebView view;
533    view.setHtml("<p>test");
534    QPoint pos(0, 0);
535    QContextMenuEvent event(QContextMenuEvent::Mouse, pos);
536    view.page()->swallowContextMenuEvent(&event);
537    view.page()->updatePositionDependentActions(pos);
538    QMenu* contextMenu = 0;
539    foreach (QObject* child, view.children()) {
540        contextMenu = qobject_cast<QMenu*>(child);
541        if (contextMenu)
542            break;
543    }
544    QVERIFY(contextMenu);
545    delete contextMenu;
546}
547
548void tst_QWebPage::database()
549{
550    QString path = QDir::currentPath();
551    m_page->settings()->setOfflineStoragePath(path);
552    QVERIFY(m_page->settings()->offlineStoragePath() == path);
553
554    QWebSettings::setOfflineStorageDefaultQuota(1024 * 1024);
555    QVERIFY(QWebSettings::offlineStorageDefaultQuota() == 1024 * 1024);
556
557    m_page->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
558    m_page->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true);
559
560    QString dbFileName = path + "Databases.db";
561
562    if (QFile::exists(dbFileName))
563        QFile::remove(dbFileName);
564
565    qRegisterMetaType<QWebFrame*>("QWebFrame*");
566    QSignalSpy spy(m_page, SIGNAL(databaseQuotaExceeded(QWebFrame*,QString)));
567    m_view->setHtml(QString("<html><head><script>var db; db=openDatabase('testdb', '1.0', 'test database API', 50000); </script></head><body><div></div></body></html>"), QUrl("http://www.myexample.com"));
568    QTRY_COMPARE(spy.count(), 1);
569    m_page->mainFrame()->evaluateJavaScript("var db2; db2=openDatabase('testdb', '1.0', 'test database API', 50000);");
570    QTRY_COMPARE(spy.count(),1);
571
572    m_page->mainFrame()->evaluateJavaScript("localStorage.test='This is a test for local storage';");
573    m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com"));
574
575    QVariant s1 = m_page->mainFrame()->evaluateJavaScript("localStorage.test");
576    QCOMPARE(s1.toString(), QString("This is a test for local storage"));
577
578    m_page->mainFrame()->evaluateJavaScript("sessionStorage.test='This is a test for session storage';");
579    m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com"));
580    QVariant s2 = m_page->mainFrame()->evaluateJavaScript("sessionStorage.test");
581    QCOMPARE(s2.toString(), QString("This is a test for session storage"));
582
583    m_view->setHtml(QString("<html><head></head><body><div></div></body></html>"), QUrl("http://www.myexample.com"));
584    m_page->mainFrame()->evaluateJavaScript("var db3; db3=openDatabase('testdb', '1.0', 'test database API', 50000);db3.transaction(function(tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS Test (text TEXT)', []); }, function(tx, result) { }, function(tx, error) { });");
585    QTest::qWait(200);
586
587    // Remove all databases.
588    QWebSecurityOrigin origin = m_page->mainFrame()->securityOrigin();
589    QList<QWebDatabase> dbs = origin.databases();
590    for (int i = 0; i < dbs.count(); i++) {
591        QString fileName = dbs[i].fileName();
592        QVERIFY(QFile::exists(fileName));
593        QWebDatabase::removeDatabase(dbs[i]);
594        QVERIFY(!QFile::exists(fileName));
595    }
596    QVERIFY(!origin.databases().size());
597    // Remove removed test :-)
598    QWebDatabase::removeAllDatabases();
599    QVERIFY(!origin.databases().size());
600}
601
602class PluginPage : public QWebPage
603{
604public:
605    PluginPage(QObject *parent = 0)
606        : QWebPage(parent) {}
607
608    struct CallInfo
609    {
610        CallInfo(const QString &c, const QUrl &u,
611                 const QStringList &pn, const QStringList &pv,
612                 QObject *r)
613            : classid(c), url(u), paramNames(pn),
614              paramValues(pv), returnValue(r)
615            {}
616        QString classid;
617        QUrl url;
618        QStringList paramNames;
619        QStringList paramValues;
620        QObject *returnValue;
621    };
622
623    QList<CallInfo> calls;
624
625protected:
626    virtual QObject *createPlugin(const QString &classid, const QUrl &url,
627                                  const QStringList &paramNames,
628                                  const QStringList &paramValues)
629    {
630        QObject *result = 0;
631        if (classid == "pushbutton")
632            result = new QPushButton();
633#ifndef QT_NO_INPUTDIALOG
634        else if (classid == "lineedit")
635            result = new QLineEdit();
636#endif
637        else if (classid == "graphicswidget")
638            result = new QGraphicsWidget();
639        if (result)
640            result->setObjectName(classid);
641        calls.append(CallInfo(classid, url, paramNames, paramValues, result));
642        return result;
643    }
644};
645
646static void createPlugin(QWebView *view)
647{
648    QSignalSpy loadSpy(view, SIGNAL(loadFinished(bool)));
649
650    PluginPage* newPage = new PluginPage(view);
651    view->setPage(newPage);
652
653    // type has to be application/x-qt-plugin
654    view->setHtml(QString("<html><body><object type='application/x-foobarbaz' classid='pushbutton' id='mybutton'/></body></html>"));
655    QTRY_COMPARE(loadSpy.count(), 1);
656    QCOMPARE(newPage->calls.count(), 0);
657
658    view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></body></html>"));
659    QTRY_COMPARE(loadSpy.count(), 2);
660    QCOMPARE(newPage->calls.count(), 1);
661    {
662        PluginPage::CallInfo ci = newPage->calls.takeFirst();
663        QCOMPARE(ci.classid, QString::fromLatin1("pushbutton"));
664        QCOMPARE(ci.url, QUrl());
665        QCOMPARE(ci.paramNames.count(), 3);
666        QCOMPARE(ci.paramValues.count(), 3);
667        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
668        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
669        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
670        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton"));
671        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
672        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton"));
673        QVERIFY(ci.returnValue != 0);
674        QVERIFY(ci.returnValue->inherits("QPushButton"));
675    }
676    // test JS bindings
677    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mybutton').toString()").toString(),
678             QString::fromLatin1("[object HTMLObjectElement]"));
679    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.toString()").toString(),
680             QString::fromLatin1("[object HTMLObjectElement]"));
681    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.objectName").toString(),
682             QString::fromLatin1("string"));
683    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.objectName").toString(),
684             QString::fromLatin1("pushbutton"));
685    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.clicked").toString(),
686             QString::fromLatin1("function"));
687    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.clicked.toString()").toString(),
688             QString::fromLatin1("function clicked() {\n    [native code]\n}"));
689
690    view->setHtml(QString("<html><body><table>"
691                            "<tr><object type='application/x-qt-plugin' classid='lineedit' id='myedit'/></tr>"
692                            "<tr><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></tr>"
693                            "</table></body></html>"), QUrl("http://foo.bar.baz"));
694    QTRY_COMPARE(loadSpy.count(), 3);
695    QCOMPARE(newPage->calls.count(), 2);
696    {
697        PluginPage::CallInfo ci = newPage->calls.takeFirst();
698        QCOMPARE(ci.classid, QString::fromLatin1("lineedit"));
699        QCOMPARE(ci.url, QUrl());
700        QCOMPARE(ci.paramNames.count(), 3);
701        QCOMPARE(ci.paramValues.count(), 3);
702        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
703        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
704        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
705        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("lineedit"));
706        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
707        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("myedit"));
708        QVERIFY(ci.returnValue != 0);
709        QVERIFY(ci.returnValue->inherits("QLineEdit"));
710    }
711    {
712        PluginPage::CallInfo ci = newPage->calls.takeFirst();
713        QCOMPARE(ci.classid, QString::fromLatin1("pushbutton"));
714        QCOMPARE(ci.url, QUrl());
715        QCOMPARE(ci.paramNames.count(), 3);
716        QCOMPARE(ci.paramValues.count(), 3);
717        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
718        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
719        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
720        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton"));
721        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
722        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton"));
723        QVERIFY(ci.returnValue != 0);
724        QVERIFY(ci.returnValue->inherits("QPushButton"));
725    }
726}
727
728void tst_QWebPage::graphicsWidgetPlugin()
729{
730    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
731    QGraphicsWebView webView;
732
733    QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool)));
734
735    PluginPage* newPage = new PluginPage(&webView);
736    webView.setPage(newPage);
737
738    // type has to be application/x-qt-plugin
739    webView.setHtml(QString("<html><body><object type='application/x-foobarbaz' classid='graphicswidget' id='mygraphicswidget'/></body></html>"));
740    QTRY_COMPARE(loadSpy.count(), 1);
741    QCOMPARE(newPage->calls.count(), 0);
742
743    webView.setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='graphicswidget' id='mygraphicswidget'/></body></html>"));
744    QTRY_COMPARE(loadSpy.count(), 2);
745    QCOMPARE(newPage->calls.count(), 1);
746    {
747        PluginPage::CallInfo ci = newPage->calls.takeFirst();
748        QCOMPARE(ci.classid, QString::fromLatin1("graphicswidget"));
749        QCOMPARE(ci.url, QUrl());
750        QCOMPARE(ci.paramNames.count(), 3);
751        QCOMPARE(ci.paramValues.count(), 3);
752        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
753        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
754        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
755        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("graphicswidget"));
756        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
757        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mygraphicswidget"));
758        QVERIFY(ci.returnValue);
759        QVERIFY(ci.returnValue->inherits("QGraphicsWidget"));
760    }
761    // test JS bindings
762    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mygraphicswidget').toString()").toString(),
763             QString::fromLatin1("[object HTMLObjectElement]"));
764    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.toString()").toString(),
765             QString::fromLatin1("[object HTMLObjectElement]"));
766    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mygraphicswidget.objectName").toString(),
767             QString::fromLatin1("string"));
768    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.objectName").toString(),
769             QString::fromLatin1("graphicswidget"));
770    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mygraphicswidget.geometryChanged").toString(),
771             QString::fromLatin1("function"));
772    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mygraphicswidget.geometryChanged.toString()").toString(),
773             QString::fromLatin1("function geometryChanged() {\n    [native code]\n}"));
774}
775
776void tst_QWebPage::createPluginWithPluginsEnabled()
777{
778    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
779    createPlugin(m_view);
780}
781
782void tst_QWebPage::createPluginWithPluginsDisabled()
783{
784    // Qt Plugins should be loaded by QtWebKit even when PluginsEnabled is
785    // false. The client decides whether a Qt plugin is enabled or not when
786    // it decides whether or not to instantiate it.
787    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, false);
788    createPlugin(m_view);
789}
790
791// Standard base class for template PluginTracerPage. In tests it is used as interface.
792class PluginCounterPage : public QWebPage {
793public:
794    int m_count;
795    QPointer<QObject> m_widget;
796    QObject* m_pluginParent;
797    PluginCounterPage(QObject* parent = 0)
798        : QWebPage(parent)
799        , m_count(0)
800        , m_widget(0)
801        , m_pluginParent(0)
802    {
803       settings()->setAttribute(QWebSettings::PluginsEnabled, true);
804    }
805    ~PluginCounterPage()
806    {
807        if (m_pluginParent)
808            m_pluginParent->deleteLater();
809    }
810};
811
812template<class T>
813class PluginTracerPage : public PluginCounterPage {
814public:
815    PluginTracerPage(QObject* parent = 0)
816        : PluginCounterPage(parent)
817    {
818        // this is a dummy parent object for the created plugin
819        m_pluginParent = new T;
820    }
821    virtual QObject* createPlugin(const QString&, const QUrl&, const QStringList&, const QStringList&)
822    {
823        m_count++;
824        m_widget = new T;
825        // need a cast to the specific type, as QObject::setParent cannot be called,
826        // because it is not virtual. Instead it is necesary to call QWidget::setParent,
827        // which also takes a QWidget* instead of a QObject*. Therefore we need to
828        // upcast to T*, which is a QWidget.
829        static_cast<T*>(m_widget.data())->setParent(static_cast<T*>(m_pluginParent));
830        return m_widget;
831    }
832};
833
834class PluginFactory {
835public:
836    enum FactoredType {QWidgetType, QGraphicsWidgetType};
837    static PluginCounterPage* create(FactoredType type, QObject* parent = 0)
838    {
839        PluginCounterPage* result = 0;
840        switch (type) {
841        case QWidgetType:
842            result = new PluginTracerPage<QWidget>(parent);
843            break;
844        case QGraphicsWidgetType:
845            result = new PluginTracerPage<QGraphicsWidget>(parent);
846            break;
847        default: {/*Oops*/};
848        }
849        return result;
850    }
851
852    static void prepareTestData()
853    {
854        QTest::addColumn<int>("type");
855        QTest::newRow("QWidget") << (int)PluginFactory::QWidgetType;
856        QTest::newRow("QGraphicsWidget") << (int)PluginFactory::QGraphicsWidgetType;
857    }
858};
859
860void tst_QWebPage::destroyPlugin_data()
861{
862    PluginFactory::prepareTestData();
863}
864
865void tst_QWebPage::destroyPlugin()
866{
867    QFETCH(int, type);
868    PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type, m_view);
869    m_view->setPage(page);
870
871    // we create the plugin, so the widget should be constructed
872    QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>");
873    m_view->setHtml(content);
874    QVERIFY(page->m_widget);
875    QCOMPARE(page->m_count, 1);
876
877    // navigate away, the plugin widget should be destructed
878    m_view->setHtml("<html><body>Hi</body></html>");
879    QTestEventLoop::instance().enterLoop(1);
880    QVERIFY(!page->m_widget);
881}
882
883void tst_QWebPage::createViewlessPlugin_data()
884{
885    PluginFactory::prepareTestData();
886}
887
888void tst_QWebPage::createViewlessPlugin()
889{
890    QFETCH(int, type);
891    PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type);
892    QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>");
893    page->mainFrame()->setHtml(content);
894    QCOMPARE(page->m_count, 1);
895    QVERIFY(page->m_widget);
896    QVERIFY(page->m_pluginParent);
897    QVERIFY(page->m_widget->parent() == page->m_pluginParent);
898    delete page;
899
900}
901
902void tst_QWebPage::multiplePageGroupsAndLocalStorage()
903{
904    QDir dir(QDir::currentPath());
905    dir.mkdir("path1");
906    dir.mkdir("path2");
907
908    QWebView view1;
909    QWebView view2;
910
911    view1.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
912    view1.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(QDir::currentPath() + "/path1"));
913    DumpRenderTreeSupportQt::webPageSetGroupName(view1.page(), "group1");
914    view2.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
915    view2.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(QDir::currentPath() + "/path2"));
916    DumpRenderTreeSupportQt::webPageSetGroupName(view2.page(), "group2");
917    QCOMPARE(DumpRenderTreeSupportQt::webPageGroupName(view1.page()), QString("group1"));
918    QCOMPARE(DumpRenderTreeSupportQt::webPageGroupName(view2.page()), QString("group2"));
919
920
921    view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
922    view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
923
924    view1.page()->mainFrame()->evaluateJavaScript("localStorage.test='value1';");
925    view2.page()->mainFrame()->evaluateJavaScript("localStorage.test='value2';");
926
927    view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
928    view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
929
930    QVariant s1 = view1.page()->mainFrame()->evaluateJavaScript("localStorage.test");
931    QCOMPARE(s1.toString(), QString("value1"));
932
933    QVariant s2 = view2.page()->mainFrame()->evaluateJavaScript("localStorage.test");
934    QCOMPARE(s2.toString(), QString("value2"));
935
936    QTest::qWait(1000);
937
938    QFile::remove(QDir::toNativeSeparators(QDir::currentPath() + "/path1/http_www.myexample.com_0.localstorage"));
939    QFile::remove(QDir::toNativeSeparators(QDir::currentPath() + "/path2/http_www.myexample.com_0.localstorage"));
940    dir.rmdir(QDir::toNativeSeparators("./path1"));
941    dir.rmdir(QDir::toNativeSeparators("./path2"));
942}
943
944class CursorTrackedPage : public QWebPage
945{
946public:
947
948    CursorTrackedPage(QWidget *parent = 0): QWebPage(parent) {
949        setViewportSize(QSize(1024, 768)); // big space
950    }
951
952    QString selectedText() {
953        return mainFrame()->evaluateJavaScript("window.getSelection().toString()").toString();
954    }
955
956    int selectionStartOffset() {
957        return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).startOffset").toInt();
958    }
959
960    int selectionEndOffset() {
961        return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).endOffset").toInt();
962    }
963
964    // true if start offset == end offset, i.e. no selected text
965    int isSelectionCollapsed() {
966        return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).collapsed").toBool();
967    }
968};
969
970void tst_QWebPage::cursorMovements()
971{
972    CursorTrackedPage* page = new CursorTrackedPage;
973    QString content("<html><body><p id=one>The quick brown fox</p><p id=two>jumps over the lazy dog</p><p>May the source<br/>be with you!</p></body></html>");
974    page->mainFrame()->setHtml(content);
975
976    // this will select the first paragraph
977    QString script = "var range = document.createRange(); " \
978        "var node = document.getElementById(\"one\"); " \
979        "range.selectNode(node); " \
980        "getSelection().addRange(range);";
981    page->mainFrame()->evaluateJavaScript(script);
982    QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox"));
983    QCOMPARE(page->selectedHtml().trimmed(), QString::fromLatin1("<span class=\"Apple-style-span\" style=\"border-collapse: separate; color: rgb(0, 0, 0); font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-size: medium; \"><p id=\"one\">The quick brown fox</p></span>"));
984
985    // these actions must exist
986    QVERIFY(page->action(QWebPage::MoveToNextChar) != 0);
987    QVERIFY(page->action(QWebPage::MoveToPreviousChar) != 0);
988    QVERIFY(page->action(QWebPage::MoveToNextWord) != 0);
989    QVERIFY(page->action(QWebPage::MoveToPreviousWord) != 0);
990    QVERIFY(page->action(QWebPage::MoveToNextLine) != 0);
991    QVERIFY(page->action(QWebPage::MoveToPreviousLine) != 0);
992    QVERIFY(page->action(QWebPage::MoveToStartOfLine) != 0);
993    QVERIFY(page->action(QWebPage::MoveToEndOfLine) != 0);
994    QVERIFY(page->action(QWebPage::MoveToStartOfBlock) != 0);
995    QVERIFY(page->action(QWebPage::MoveToEndOfBlock) != 0);
996    QVERIFY(page->action(QWebPage::MoveToStartOfDocument) != 0);
997    QVERIFY(page->action(QWebPage::MoveToEndOfDocument) != 0);
998
999    // right now they are disabled because contentEditable is false
1000    QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), false);
1001    QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), false);
1002    QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), false);
1003    QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), false);
1004    QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), false);
1005    QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), false);
1006    QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), false);
1007    QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), false);
1008    QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), false);
1009    QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), false);
1010    QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), false);
1011    QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), false);
1012
1013    // make it editable before navigating the cursor
1014    page->setContentEditable(true);
1015
1016    // here the actions are enabled after contentEditable is true
1017    QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), true);
1018    QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), true);
1019    QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), true);
1020    QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), true);
1021    QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), true);
1022    QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), true);
1023    QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), true);
1024    QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), true);
1025    QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), true);
1026    QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), true);
1027    QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), true);
1028    QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), true);
1029
1030    // cursor will be before the word "jump"
1031    page->triggerAction(QWebPage::MoveToNextChar);
1032    QVERIFY(page->isSelectionCollapsed());
1033    QCOMPARE(page->selectionStartOffset(), 0);
1034
1035    // cursor will be between 'j' and 'u' in the word "jump"
1036    page->triggerAction(QWebPage::MoveToNextChar);
1037    QVERIFY(page->isSelectionCollapsed());
1038    QCOMPARE(page->selectionStartOffset(), 1);
1039
1040    // cursor will be between 'u' and 'm' in the word "jump"
1041    page->triggerAction(QWebPage::MoveToNextChar);
1042    QVERIFY(page->isSelectionCollapsed());
1043    QCOMPARE(page->selectionStartOffset(), 2);
1044
1045    // cursor will be after the word "jump"
1046    page->triggerAction(QWebPage::MoveToNextWord);
1047    QVERIFY(page->isSelectionCollapsed());
1048    QCOMPARE(page->selectionStartOffset(), 5);
1049
1050    // cursor will be after the word "lazy"
1051    page->triggerAction(QWebPage::MoveToNextWord);
1052    page->triggerAction(QWebPage::MoveToNextWord);
1053    page->triggerAction(QWebPage::MoveToNextWord);
1054    QVERIFY(page->isSelectionCollapsed());
1055    QCOMPARE(page->selectionStartOffset(), 19);
1056
1057    // cursor will be between 'z' and 'y' in "lazy"
1058    page->triggerAction(QWebPage::MoveToPreviousChar);
1059    QVERIFY(page->isSelectionCollapsed());
1060    QCOMPARE(page->selectionStartOffset(), 18);
1061
1062    // cursor will be between 'a' and 'z' in "lazy"
1063    page->triggerAction(QWebPage::MoveToPreviousChar);
1064    QVERIFY(page->isSelectionCollapsed());
1065    QCOMPARE(page->selectionStartOffset(), 17);
1066
1067    // cursor will be before the word "lazy"
1068    page->triggerAction(QWebPage::MoveToPreviousWord);
1069    QVERIFY(page->isSelectionCollapsed());
1070    QCOMPARE(page->selectionStartOffset(), 15);
1071
1072    // cursor will be before the word "quick"
1073    page->triggerAction(QWebPage::MoveToPreviousWord);
1074    page->triggerAction(QWebPage::MoveToPreviousWord);
1075    page->triggerAction(QWebPage::MoveToPreviousWord);
1076    page->triggerAction(QWebPage::MoveToPreviousWord);
1077    page->triggerAction(QWebPage::MoveToPreviousWord);
1078    page->triggerAction(QWebPage::MoveToPreviousWord);
1079    QVERIFY(page->isSelectionCollapsed());
1080    QCOMPARE(page->selectionStartOffset(), 4);
1081
1082    // cursor will be between 'p' and 's' in the word "jumps"
1083    page->triggerAction(QWebPage::MoveToNextWord);
1084    page->triggerAction(QWebPage::MoveToNextWord);
1085    page->triggerAction(QWebPage::MoveToNextWord);
1086    page->triggerAction(QWebPage::MoveToNextChar);
1087    page->triggerAction(QWebPage::MoveToNextChar);
1088    page->triggerAction(QWebPage::MoveToNextChar);
1089    page->triggerAction(QWebPage::MoveToNextChar);
1090    page->triggerAction(QWebPage::MoveToNextChar);
1091    QVERIFY(page->isSelectionCollapsed());
1092    QCOMPARE(page->selectionStartOffset(), 4);
1093
1094    // cursor will be before the word "jumps"
1095    page->triggerAction(QWebPage::MoveToStartOfLine);
1096    QVERIFY(page->isSelectionCollapsed());
1097    QCOMPARE(page->selectionStartOffset(), 0);
1098
1099    // cursor will be after the word "dog"
1100    page->triggerAction(QWebPage::MoveToEndOfLine);
1101    QVERIFY(page->isSelectionCollapsed());
1102    QCOMPARE(page->selectionStartOffset(), 23);
1103
1104    // cursor will be between 'w' and 'n' in "brown"
1105    page->triggerAction(QWebPage::MoveToStartOfLine);
1106    page->triggerAction(QWebPage::MoveToPreviousWord);
1107    page->triggerAction(QWebPage::MoveToPreviousWord);
1108    page->triggerAction(QWebPage::MoveToNextChar);
1109    page->triggerAction(QWebPage::MoveToNextChar);
1110    page->triggerAction(QWebPage::MoveToNextChar);
1111    page->triggerAction(QWebPage::MoveToNextChar);
1112    QVERIFY(page->isSelectionCollapsed());
1113    QCOMPARE(page->selectionStartOffset(), 14);
1114
1115    // cursor will be after the word "fox"
1116    page->triggerAction(QWebPage::MoveToEndOfLine);
1117    QVERIFY(page->isSelectionCollapsed());
1118    QCOMPARE(page->selectionStartOffset(), 19);
1119
1120    // cursor will be before the word "The"
1121    page->triggerAction(QWebPage::MoveToStartOfDocument);
1122    QVERIFY(page->isSelectionCollapsed());
1123    QCOMPARE(page->selectionStartOffset(), 0);
1124
1125    // cursor will be after the word "you!"
1126    page->triggerAction(QWebPage::MoveToEndOfDocument);
1127    QVERIFY(page->isSelectionCollapsed());
1128    QCOMPARE(page->selectionStartOffset(), 12);
1129
1130    // cursor will be before the word "be"
1131    page->triggerAction(QWebPage::MoveToStartOfBlock);
1132    QVERIFY(page->isSelectionCollapsed());
1133    QCOMPARE(page->selectionStartOffset(), 0);
1134
1135    // cursor will be after the word "you!"
1136    page->triggerAction(QWebPage::MoveToEndOfBlock);
1137    QVERIFY(page->isSelectionCollapsed());
1138    QCOMPARE(page->selectionStartOffset(), 12);
1139
1140    // try to move before the document start
1141    page->triggerAction(QWebPage::MoveToStartOfDocument);
1142    page->triggerAction(QWebPage::MoveToPreviousChar);
1143    QVERIFY(page->isSelectionCollapsed());
1144    QCOMPARE(page->selectionStartOffset(), 0);
1145    page->triggerAction(QWebPage::MoveToStartOfDocument);
1146    page->triggerAction(QWebPage::MoveToPreviousWord);
1147    QVERIFY(page->isSelectionCollapsed());
1148    QCOMPARE(page->selectionStartOffset(), 0);
1149
1150    // try to move past the document end
1151    page->triggerAction(QWebPage::MoveToEndOfDocument);
1152    page->triggerAction(QWebPage::MoveToNextChar);
1153    QVERIFY(page->isSelectionCollapsed());
1154    QCOMPARE(page->selectionStartOffset(), 12);
1155    page->triggerAction(QWebPage::MoveToEndOfDocument);
1156    page->triggerAction(QWebPage::MoveToNextWord);
1157    QVERIFY(page->isSelectionCollapsed());
1158    QCOMPARE(page->selectionStartOffset(), 12);
1159
1160    delete page;
1161}
1162
1163void tst_QWebPage::textSelection()
1164{
1165    CursorTrackedPage* page = new CursorTrackedPage;
1166    QString content("<html><body><p id=one>The quick brown fox</p>" \
1167        "<p id=two>jumps over the lazy dog</p>" \
1168        "<p>May the source<br/>be with you!</p></body></html>");
1169    page->mainFrame()->setHtml(content);
1170
1171    // these actions must exist
1172    QVERIFY(page->action(QWebPage::SelectAll) != 0);
1173    QVERIFY(page->action(QWebPage::SelectNextChar) != 0);
1174    QVERIFY(page->action(QWebPage::SelectPreviousChar) != 0);
1175    QVERIFY(page->action(QWebPage::SelectNextWord) != 0);
1176    QVERIFY(page->action(QWebPage::SelectPreviousWord) != 0);
1177    QVERIFY(page->action(QWebPage::SelectNextLine) != 0);
1178    QVERIFY(page->action(QWebPage::SelectPreviousLine) != 0);
1179    QVERIFY(page->action(QWebPage::SelectStartOfLine) != 0);
1180    QVERIFY(page->action(QWebPage::SelectEndOfLine) != 0);
1181    QVERIFY(page->action(QWebPage::SelectStartOfBlock) != 0);
1182    QVERIFY(page->action(QWebPage::SelectEndOfBlock) != 0);
1183    QVERIFY(page->action(QWebPage::SelectStartOfDocument) != 0);
1184    QVERIFY(page->action(QWebPage::SelectEndOfDocument) != 0);
1185
1186    // right now they are disabled because contentEditable is false and
1187    // there isn't an existing selection to modify
1188    QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), false);
1189    QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), false);
1190    QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), false);
1191    QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), false);
1192    QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), false);
1193    QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), false);
1194    QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), false);
1195    QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), false);
1196    QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), false);
1197    QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), false);
1198    QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), false);
1199    QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), false);
1200
1201    // ..but SelectAll is awalys enabled
1202    QCOMPARE(page->action(QWebPage::SelectAll)->isEnabled(), true);
1203
1204    // Verify hasSelection returns false since there is no selection yet...
1205    QCOMPARE(page->hasSelection(), false);
1206
1207    // this will select the first paragraph
1208    QString selectScript = "var range = document.createRange(); " \
1209        "var node = document.getElementById(\"one\"); " \
1210        "range.selectNode(node); " \
1211        "getSelection().addRange(range);";
1212    page->mainFrame()->evaluateJavaScript(selectScript);
1213    QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox"));
1214    QCOMPARE(page->selectedHtml().trimmed(), QString::fromLatin1("<span class=\"Apple-style-span\" style=\"border-collapse: separate; color: rgb(0, 0, 0); font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-size: medium; \"><p id=\"one\">The quick brown fox</p></span>"));
1215
1216    // Make sure hasSelection returns true, since there is selected text now...
1217    QCOMPARE(page->hasSelection(), true);
1218
1219    // here the actions are enabled after a selection has been created
1220    QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true);
1221    QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true);
1222    QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true);
1223    QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true);
1224    QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true);
1225    QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true);
1226    QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true);
1227    QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true);
1228    QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true);
1229    QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true);
1230    QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true);
1231    QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true);
1232
1233    // make it editable before navigating the cursor
1234    page->setContentEditable(true);
1235
1236    // cursor will be before the word "The", this makes sure there is a charet
1237    page->triggerAction(QWebPage::MoveToStartOfDocument);
1238    QVERIFY(page->isSelectionCollapsed());
1239    QCOMPARE(page->selectionStartOffset(), 0);
1240
1241    // here the actions are enabled after contentEditable is true
1242    QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true);
1243    QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true);
1244    QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true);
1245    QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true);
1246    QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true);
1247    QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true);
1248    QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true);
1249    QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true);
1250    QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true);
1251    QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true);
1252    QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true);
1253    QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true);
1254
1255    delete page;
1256}
1257
1258void tst_QWebPage::textEditing()
1259{
1260    CursorTrackedPage* page = new CursorTrackedPage;
1261    QString content("<html><body><p id=one>The quick brown fox</p>" \
1262        "<p id=two>jumps over the lazy dog</p>" \
1263        "<p>May the source<br/>be with you!</p></body></html>");
1264    page->mainFrame()->setHtml(content);
1265
1266    // these actions must exist
1267    QVERIFY(page->action(QWebPage::Cut) != 0);
1268    QVERIFY(page->action(QWebPage::Copy) != 0);
1269    QVERIFY(page->action(QWebPage::Paste) != 0);
1270    QVERIFY(page->action(QWebPage::DeleteStartOfWord) != 0);
1271    QVERIFY(page->action(QWebPage::DeleteEndOfWord) != 0);
1272    QVERIFY(page->action(QWebPage::SetTextDirectionDefault) != 0);
1273    QVERIFY(page->action(QWebPage::SetTextDirectionLeftToRight) != 0);
1274    QVERIFY(page->action(QWebPage::SetTextDirectionRightToLeft) != 0);
1275    QVERIFY(page->action(QWebPage::ToggleBold) != 0);
1276    QVERIFY(page->action(QWebPage::ToggleItalic) != 0);
1277    QVERIFY(page->action(QWebPage::ToggleUnderline) != 0);
1278    QVERIFY(page->action(QWebPage::InsertParagraphSeparator) != 0);
1279    QVERIFY(page->action(QWebPage::InsertLineSeparator) != 0);
1280    QVERIFY(page->action(QWebPage::PasteAndMatchStyle) != 0);
1281    QVERIFY(page->action(QWebPage::RemoveFormat) != 0);
1282    QVERIFY(page->action(QWebPage::ToggleStrikethrough) != 0);
1283    QVERIFY(page->action(QWebPage::ToggleSubscript) != 0);
1284    QVERIFY(page->action(QWebPage::ToggleSuperscript) != 0);
1285    QVERIFY(page->action(QWebPage::InsertUnorderedList) != 0);
1286    QVERIFY(page->action(QWebPage::InsertOrderedList) != 0);
1287    QVERIFY(page->action(QWebPage::Indent) != 0);
1288    QVERIFY(page->action(QWebPage::Outdent) != 0);
1289    QVERIFY(page->action(QWebPage::AlignCenter) != 0);
1290    QVERIFY(page->action(QWebPage::AlignJustified) != 0);
1291    QVERIFY(page->action(QWebPage::AlignLeft) != 0);
1292    QVERIFY(page->action(QWebPage::AlignRight) != 0);
1293
1294    // right now they are disabled because contentEditable is false
1295    QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false);
1296    QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), false);
1297    QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), false);
1298    QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), false);
1299    QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), false);
1300    QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), false);
1301    QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), false);
1302    QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), false);
1303    QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), false);
1304    QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), false);
1305    QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), false);
1306    QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), false);
1307    QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), false);
1308    QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false);
1309    QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), false);
1310    QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), false);
1311    QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), false);
1312    QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), false);
1313    QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), false);
1314    QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), false);
1315    QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), false);
1316    QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), false);
1317    QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), false);
1318    QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), false);
1319    QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), false);
1320
1321    // Select everything
1322    page->triggerAction(QWebPage::SelectAll);
1323
1324    // make sure it is enabled since there is a selection
1325    QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), true);
1326
1327    // make it editable before navigating the cursor
1328    page->setContentEditable(true);
1329
1330    // clear the selection
1331    page->triggerAction(QWebPage::MoveToStartOfDocument);
1332    QVERIFY(page->isSelectionCollapsed());
1333    QCOMPARE(page->selectionStartOffset(), 0);
1334
1335    // make sure it is disabled since there isn't a selection
1336    QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), false);
1337
1338    // here the actions are enabled after contentEditable is true
1339    QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), true);
1340    QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), true);
1341    QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), true);
1342    QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), true);
1343    QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), true);
1344    QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), true);
1345    QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), true);
1346    QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), true);
1347    QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), true);
1348    QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), true);
1349    QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), true);
1350    QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), true);
1351    QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), true);
1352    QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), true);
1353    QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), true);
1354    QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), true);
1355    QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), true);
1356    QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), true);
1357    QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), true);
1358    QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), true);
1359    QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), true);
1360    QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), true);
1361    QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), true);
1362
1363    // make sure these are disabled since there isn't a selection
1364    QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false);
1365    QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false);
1366
1367    // make sure everything is selected
1368    page->triggerAction(QWebPage::SelectAll);
1369
1370    // this is only true if there is an editable selection
1371    QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), true);
1372    QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), true);
1373
1374    delete page;
1375}
1376
1377void tst_QWebPage::requestCache()
1378{
1379    TestPage page;
1380    QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
1381
1382    page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>"));
1383    QTRY_COMPARE(loadSpy.count(), 1);
1384    QTRY_COMPARE(page.navigations.count(), 1);
1385
1386    page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me2</a>"));
1387    QTRY_COMPARE(loadSpy.count(), 2);
1388    QTRY_COMPARE(page.navigations.count(), 2);
1389
1390    page.triggerAction(QWebPage::Stop);
1391    QVERIFY(page.history()->canGoBack());
1392    page.triggerAction(QWebPage::Back);
1393
1394    QTRY_COMPARE(loadSpy.count(), 3);
1395    QTRY_COMPARE(page.navigations.count(), 3);
1396    QCOMPARE(page.navigations.at(0).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(),
1397             (int)QNetworkRequest::PreferNetwork);
1398    QCOMPARE(page.navigations.at(1).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(),
1399             (int)QNetworkRequest::PreferNetwork);
1400    QCOMPARE(page.navigations.at(2).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(),
1401             (int)QNetworkRequest::PreferCache);
1402}
1403
1404void tst_QWebPage::loadCachedPage()
1405{
1406    TestPage page;
1407    QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
1408    page.settings()->setMaximumPagesInCache(3);
1409
1410    page.mainFrame()->load(QUrl("data:text/html,This is first page"));
1411
1412    QTRY_COMPARE(loadSpy.count(), 1);
1413    QTRY_COMPARE(page.navigations.count(), 1);
1414
1415    QUrl firstPageUrl = page.mainFrame()->url();
1416    page.mainFrame()->load(QUrl("data:text/html,This is second page"));
1417
1418    QTRY_COMPARE(loadSpy.count(), 2);
1419    QTRY_COMPARE(page.navigations.count(), 2);
1420
1421    page.triggerAction(QWebPage::Stop);
1422    QVERIFY(page.history()->canGoBack());
1423
1424    QSignalSpy urlSpy(page.mainFrame(), SIGNAL(urlChanged(QUrl)));
1425    QVERIFY(urlSpy.isValid());
1426
1427    page.triggerAction(QWebPage::Back);
1428    ::waitForSignal(page.mainFrame(), SIGNAL(urlChanged(QUrl)));
1429    QCOMPARE(urlSpy.size(), 1);
1430
1431    QList<QVariant> arguments1 = urlSpy.takeFirst();
1432    QCOMPARE(arguments1.at(0).toUrl(), firstPageUrl);
1433
1434}
1435void tst_QWebPage::backActionUpdate()
1436{
1437    QWebView view;
1438    QWebPage *page = view.page();
1439    QAction *action = page->action(QWebPage::Back);
1440    QVERIFY(!action->isEnabled());
1441    QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool)));
1442    QUrl url = QUrl("qrc:///resources/framedindex.html");
1443    page->mainFrame()->load(url);
1444    QTRY_COMPARE(loadSpy.count(), 1);
1445    QVERIFY(!action->isEnabled());
1446    QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10));
1447    QTRY_COMPARE(loadSpy.count(), 2);
1448
1449    QVERIFY(action->isEnabled());
1450}
1451
1452void frameAtHelper(QWebPage* webPage, QWebFrame* webFrame, QPoint framePosition)
1453{
1454    if (!webFrame)
1455        return;
1456
1457    framePosition += QPoint(webFrame->pos());
1458    QList<QWebFrame*> children = webFrame->childFrames();
1459    for (int i = 0; i < children.size(); ++i) {
1460        if (children.at(i)->childFrames().size() > 0)
1461            frameAtHelper(webPage, children.at(i), framePosition);
1462
1463        QRect frameRect(children.at(i)->pos() + framePosition, children.at(i)->geometry().size());
1464        QVERIFY(children.at(i) == webPage->frameAt(frameRect.topLeft()));
1465    }
1466}
1467
1468void tst_QWebPage::frameAt()
1469{
1470    QWebView webView;
1471    QWebPage* webPage = webView.page();
1472    QSignalSpy loadSpy(webPage, SIGNAL(loadFinished(bool)));
1473    QUrl url = QUrl("qrc:///resources/iframe.html");
1474    webPage->mainFrame()->load(url);
1475    QTRY_COMPARE(loadSpy.count(), 1);
1476    frameAtHelper(webPage, webPage->mainFrame(), webPage->mainFrame()->pos());
1477}
1478
1479void tst_QWebPage::inputMethods_data()
1480{
1481    QTest::addColumn<QString>("viewType");
1482    QTest::newRow("QWebView") << "QWebView";
1483    QTest::newRow("QGraphicsWebView") << "QGraphicsWebView";
1484}
1485
1486static Qt::InputMethodHints inputMethodHints(QObject* object)
1487{
1488    if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object))
1489        return o->inputMethodHints();
1490    if (QWidget* w = qobject_cast<QWidget*>(object))
1491        return w->inputMethodHints();
1492    return Qt::InputMethodHints();
1493}
1494
1495static bool inputMethodEnabled(QObject* object)
1496{
1497    if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object))
1498        return o->flags() & QGraphicsItem::ItemAcceptsInputMethod;
1499    if (QWidget* w = qobject_cast<QWidget*>(object))
1500        return w->testAttribute(Qt::WA_InputMethodEnabled);
1501    return false;
1502}
1503
1504static void clickOnPage(QWebPage* page, const QPoint& position)
1505{
1506    QMouseEvent evpres(QEvent::MouseButtonPress, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1507    page->event(&evpres);
1508    QMouseEvent evrel(QEvent::MouseButtonRelease, position, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1509    page->event(&evrel);
1510}
1511
1512void tst_QWebPage::inputMethods()
1513{
1514    QFETCH(QString, viewType);
1515    QWebPage* page = new QWebPage;
1516    QObject* view = 0;
1517    QObject* container = 0;
1518    if (viewType == "QWebView") {
1519        QWebView* wv = new QWebView;
1520        wv->setPage(page);
1521        view = wv;
1522        container = view;
1523    } else if (viewType == "QGraphicsWebView") {
1524        QGraphicsWebView* wv = new QGraphicsWebView;
1525        wv->setPage(page);
1526        view = wv;
1527
1528        QGraphicsView* gv = new QGraphicsView;
1529        QGraphicsScene* scene = new QGraphicsScene(gv);
1530        gv->setScene(scene);
1531        scene->addItem(wv);
1532        wv->setGeometry(QRect(0, 0, 500, 500));
1533
1534        container = gv;
1535    } else
1536        QVERIFY2(false, "Unknown view type");
1537
1538    page->settings()->setFontFamily(QWebSettings::SerifFont, "FooSerifFont");
1539    page->mainFrame()->setHtml("<html><body>" \
1540                                            "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/><br>" \
1541                                            "<input type='password'/>" \
1542                                            "</body></html>");
1543    page->mainFrame()->setFocus();
1544
1545    EventSpy viewEventSpy(container);
1546
1547    QWebElementCollection inputs = page->mainFrame()->documentElement().findAll("input");
1548    QPoint textInputCenter = inputs.at(0).geometry().center();
1549
1550    clickOnPage(page, textInputCenter);
1551
1552    // This part of the test checks if the SIP (Software Input Panel) is triggered,
1553    // which normally happens on mobile platforms, when a user input form receives
1554    // a mouse click.
1555    int  inputPanel = 0;
1556    if (viewType == "QWebView") {
1557        if (QWebView* wv = qobject_cast<QWebView*>(view))
1558            inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel);
1559    } else if (viewType == "QGraphicsWebView") {
1560        if (QGraphicsWebView* wv = qobject_cast<QGraphicsWebView*>(view))
1561            inputPanel = wv->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel);
1562    }
1563
1564    // For non-mobile platforms RequestSoftwareInputPanel event is not called
1565    // because there is no SIP (Software Input Panel) triggered. In the case of a
1566    // mobile platform, an input panel, e.g. virtual keyboard, is usually invoked
1567    // and the RequestSoftwareInputPanel event is called. For these two situations
1568    // this part of the test can verified as the checks below.
1569    if (inputPanel)
1570        QVERIFY(viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1571    else
1572        QVERIFY(!viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1573    viewEventSpy.clear();
1574
1575    clickOnPage(page, textInputCenter);
1576    QVERIFY(viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1577
1578    //ImMicroFocus
1579    QVariant variant = page->inputMethodQuery(Qt::ImMicroFocus);
1580    QRect focusRect = variant.toRect();
1581    QVERIFY(inputs.at(0).geometry().contains(variant.toRect().topLeft()));
1582
1583    //ImFont
1584    variant = page->inputMethodQuery(Qt::ImFont);
1585    QFont font = variant.value<QFont>();
1586    QCOMPARE(page->settings()->fontFamily(QWebSettings::SerifFont), font.family());
1587
1588    QList<QInputMethodEvent::Attribute> inputAttributes;
1589
1590    //Insert text.
1591    {
1592        QInputMethodEvent eventText("QtWebKit", inputAttributes);
1593        QSignalSpy signalSpy(page, SIGNAL(microFocusChanged()));
1594        page->event(&eventText);
1595        QCOMPARE(signalSpy.count(), 0);
1596    }
1597
1598    {
1599        QInputMethodEvent eventText("", inputAttributes);
1600        eventText.setCommitString(QString("QtWebKit"), 0, 0);
1601        page->event(&eventText);
1602    }
1603
1604    //ImMaximumTextLength
1605    variant = page->inputMethodQuery(Qt::ImMaximumTextLength);
1606    QCOMPARE(20, variant.toInt());
1607
1608    //Set selection
1609    inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 3, 2, QVariant());
1610    QInputMethodEvent eventSelection("",inputAttributes);
1611    page->event(&eventSelection);
1612
1613    //ImAnchorPosition
1614    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1615    int anchorPosition =  variant.toInt();
1616    QCOMPARE(anchorPosition, 3);
1617
1618    //ImCursorPosition
1619    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1620    int cursorPosition =  variant.toInt();
1621    QCOMPARE(cursorPosition, 5);
1622
1623    //ImCurrentSelection
1624    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1625    QString selectionValue = variant.value<QString>();
1626    QCOMPARE(selectionValue, QString("eb"));
1627
1628    //Set selection with negative length
1629    inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 6, -5, QVariant());
1630    QInputMethodEvent eventSelection3("",inputAttributes);
1631    page->event(&eventSelection3);
1632
1633    //ImAnchorPosition
1634    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1635    anchorPosition =  variant.toInt();
1636    QCOMPARE(anchorPosition, 1);
1637
1638    //ImCursorPosition
1639    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1640    cursorPosition =  variant.toInt();
1641    QCOMPARE(cursorPosition, 6);
1642
1643    //ImCurrentSelection
1644    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1645    selectionValue = variant.value<QString>();
1646    QCOMPARE(selectionValue, QString("tWebK"));
1647
1648    //ImSurroundingText
1649    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1650    QString value = variant.value<QString>();
1651    QCOMPARE(value, QString("QtWebKit"));
1652
1653    {
1654        QList<QInputMethodEvent::Attribute> attributes;
1655        // Clear the selection, so the next test does not clear any contents.
1656        QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
1657        attributes.append(newSelection);
1658        QInputMethodEvent event("composition", attributes);
1659        page->event(&event);
1660    }
1661
1662    // A ongoing composition should not change the surrounding text before it is committed.
1663    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1664    value = variant.value<QString>();
1665    QCOMPARE(value, QString("QtWebKit"));
1666
1667    // Cancel current composition first
1668    inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 0, QVariant());
1669    QInputMethodEvent eventSelection4("", inputAttributes);
1670    page->event(&eventSelection4);
1671
1672    // START - Tests for Selection when the Editor is NOT in Composition mode
1673
1674    // LEFT to RIGHT selection
1675    // Deselect the selection by sending MouseButtonPress events
1676    // This moves the current cursor to the end of the text
1677    clickOnPage(page, textInputCenter);
1678
1679    {
1680        QList<QInputMethodEvent::Attribute> attributes;
1681        QInputMethodEvent event(QString(), attributes);
1682        event.setCommitString("XXX", 0, 0);
1683        page->event(&event);
1684        event.setCommitString(QString(), -2, 2); // Erase two characters.
1685        page->event(&event);
1686        event.setCommitString(QString(), -1, 1); // Erase one character.
1687        page->event(&event);
1688        variant = page->inputMethodQuery(Qt::ImSurroundingText);
1689        value = variant.value<QString>();
1690        QCOMPARE(value, QString("QtWebKit"));
1691    }
1692
1693    //Move to the start of the line
1694    page->triggerAction(QWebPage::MoveToStartOfLine);
1695
1696    QKeyEvent keyRightEventPress(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier);
1697    QKeyEvent keyRightEventRelease(QEvent::KeyRelease, Qt::Key_Right, Qt::NoModifier);
1698
1699    //Move 2 characters RIGHT
1700    for (int j = 0; j < 2; ++j) {
1701        page->event(&keyRightEventPress);
1702        page->event(&keyRightEventRelease);
1703    }
1704
1705    //Select to the end of the line
1706    page->triggerAction(QWebPage::SelectEndOfLine);
1707
1708    //ImAnchorPosition QtWebKit
1709    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1710    anchorPosition =  variant.toInt();
1711    QCOMPARE(anchorPosition, 2);
1712
1713    //ImCursorPosition
1714    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1715    cursorPosition =  variant.toInt();
1716    QCOMPARE(cursorPosition, 8);
1717
1718    //ImCurrentSelection
1719    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1720    selectionValue = variant.value<QString>();
1721    QCOMPARE(selectionValue, QString("WebKit"));
1722
1723    //RIGHT to LEFT selection
1724    //Deselect the selection (this moves the current cursor to the end of the text)
1725    clickOnPage(page, textInputCenter);
1726
1727    //ImAnchorPosition
1728    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1729    anchorPosition =  variant.toInt();
1730    QCOMPARE(anchorPosition, 8);
1731
1732    //ImCursorPosition
1733    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1734    cursorPosition =  variant.toInt();
1735    QCOMPARE(cursorPosition, 8);
1736
1737    //ImCurrentSelection
1738    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1739    selectionValue = variant.value<QString>();
1740    QCOMPARE(selectionValue, QString(""));
1741
1742    QKeyEvent keyLeftEventPress(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier);
1743    QKeyEvent keyLeftEventRelease(QEvent::KeyRelease, Qt::Key_Left, Qt::NoModifier);
1744
1745    //Move 2 characters LEFT
1746    for (int i = 0; i < 2; ++i) {
1747        page->event(&keyLeftEventPress);
1748        page->event(&keyLeftEventRelease);
1749    }
1750
1751    //Select to the start of the line
1752    page->triggerAction(QWebPage::SelectStartOfLine);
1753
1754    //ImAnchorPosition
1755    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1756    anchorPosition =  variant.toInt();
1757    QCOMPARE(anchorPosition, 6);
1758
1759    //ImCursorPosition
1760    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1761    cursorPosition =  variant.toInt();
1762    QCOMPARE(cursorPosition, 0);
1763
1764    //ImCurrentSelection
1765    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1766    selectionValue = variant.value<QString>();
1767    QCOMPARE(selectionValue, QString("QtWebK"));
1768
1769    //END - Tests for Selection when the Editor is not in Composition mode
1770
1771    //ImhHiddenText
1772    QPoint passwordInputCenter = inputs.at(1).geometry().center();
1773    clickOnPage(page, passwordInputCenter);
1774
1775    QVERIFY(inputMethodEnabled(view));
1776    QVERIFY(inputMethodHints(view) & Qt::ImhHiddenText);
1777
1778    clickOnPage(page, textInputCenter);
1779    QVERIFY(!(inputMethodHints(view) & Qt::ImhHiddenText));
1780
1781    page->mainFrame()->setHtml("<html><body><p>nothing to input here");
1782    viewEventSpy.clear();
1783
1784    QWebElement para = page->mainFrame()->findFirstElement("p");
1785    clickOnPage(page, para.geometry().center());
1786
1787    QVERIFY(!viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1788
1789    //START - Test for sending empty QInputMethodEvent
1790    page->mainFrame()->setHtml("<html><body>" \
1791                                            "<input type='text' id='input3' value='QtWebKit2'/>" \
1792                                            "</body></html>");
1793    page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input3'); inputEle.focus(); inputEle.select();");
1794
1795    //Send empty QInputMethodEvent
1796    QInputMethodEvent emptyEvent;
1797    page->event(&emptyEvent);
1798
1799    QString inputValue = page->mainFrame()->evaluateJavaScript("document.getElementById('input3').value").toString();
1800    QCOMPARE(inputValue, QString("QtWebKit2"));
1801    //END - Test for sending empty QInputMethodEvent
1802
1803    page->mainFrame()->setHtml("<html><body>" \
1804                                            "<input type='text' id='input4' value='QtWebKit inputMethod'/>" \
1805                                            "</body></html>");
1806    page->mainFrame()->evaluateJavaScript("var inputEle = document.getElementById('input4'); inputEle.focus(); inputEle.select();");
1807
1808    // Clear the selection, also cancel the ongoing composition if there is one.
1809    {
1810        QList<QInputMethodEvent::Attribute> attributes;
1811        QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
1812        attributes.append(newSelection);
1813        QInputMethodEvent event("", attributes);
1814        page->event(&event);
1815    }
1816
1817    // ImCurrentSelection
1818    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1819    selectionValue = variant.value<QString>();
1820    QCOMPARE(selectionValue, QString(""));
1821
1822    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1823    QString surroundingValue = variant.value<QString>();
1824    QCOMPARE(surroundingValue, QString("QtWebKit inputMethod"));
1825
1826    // ImAnchorPosition
1827    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1828    anchorPosition =  variant.toInt();
1829    QCOMPARE(anchorPosition, 0);
1830
1831    // ImCursorPosition
1832    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1833    cursorPosition =  variant.toInt();
1834    QCOMPARE(cursorPosition, 0);
1835
1836    // 1. Insert a character to the begining of the line.
1837    // Send temporary text, which makes the editor has composition 'm'.
1838    {
1839        QList<QInputMethodEvent::Attribute> attributes;
1840        QInputMethodEvent event("m", attributes);
1841        page->event(&event);
1842    }
1843
1844    // ImCurrentSelection
1845    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1846    selectionValue = variant.value<QString>();
1847    QCOMPARE(selectionValue, QString(""));
1848
1849    // ImSurroundingText
1850    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1851    surroundingValue = variant.value<QString>();
1852    QCOMPARE(surroundingValue, QString("QtWebKit inputMethod"));
1853
1854    // ImCursorPosition
1855    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1856    cursorPosition =  variant.toInt();
1857    QCOMPARE(cursorPosition, 0);
1858
1859    // ImAnchorPosition
1860    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1861    anchorPosition =  variant.toInt();
1862    QCOMPARE(anchorPosition, 0);
1863
1864    // Send temporary text, which makes the editor has composition 'n'.
1865    {
1866        QList<QInputMethodEvent::Attribute> attributes;
1867        QInputMethodEvent event("n", attributes);
1868        page->event(&event);
1869    }
1870
1871    // ImCurrentSelection
1872    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1873    selectionValue = variant.value<QString>();
1874    QCOMPARE(selectionValue, QString(""));
1875
1876    // ImSurroundingText
1877    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1878    surroundingValue = variant.value<QString>();
1879    QCOMPARE(surroundingValue, QString("QtWebKit inputMethod"));
1880
1881    // ImCursorPosition
1882    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1883    cursorPosition =  variant.toInt();
1884    QCOMPARE(cursorPosition, 0);
1885
1886    // ImAnchorPosition
1887    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1888    anchorPosition =  variant.toInt();
1889    QCOMPARE(anchorPosition, 0);
1890
1891    // Send commit text, which makes the editor conforms composition.
1892    {
1893        QList<QInputMethodEvent::Attribute> attributes;
1894        QInputMethodEvent event("", attributes);
1895        event.setCommitString("o");
1896        page->event(&event);
1897    }
1898
1899    // ImCurrentSelection
1900    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1901    selectionValue = variant.value<QString>();
1902    QCOMPARE(selectionValue, QString(""));
1903
1904    // ImSurroundingText
1905    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1906    surroundingValue = variant.value<QString>();
1907    QCOMPARE(surroundingValue, QString("oQtWebKit inputMethod"));
1908
1909    // ImCursorPosition
1910    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1911    cursorPosition =  variant.toInt();
1912    QCOMPARE(cursorPosition, 1);
1913
1914    // ImAnchorPosition
1915    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1916    anchorPosition =  variant.toInt();
1917    QCOMPARE(anchorPosition, 1);
1918
1919    // 2. insert a character to the middle of the line.
1920    // Send temporary text, which makes the editor has composition 'd'.
1921    {
1922        QList<QInputMethodEvent::Attribute> attributes;
1923        QInputMethodEvent event("d", attributes);
1924        page->event(&event);
1925    }
1926
1927    // ImCurrentSelection
1928    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1929    selectionValue = variant.value<QString>();
1930    QCOMPARE(selectionValue, QString(""));
1931
1932    // ImSurroundingText
1933    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1934    surroundingValue = variant.value<QString>();
1935    QCOMPARE(surroundingValue, QString("oQtWebKit inputMethod"));
1936
1937    // ImCursorPosition
1938    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1939    cursorPosition =  variant.toInt();
1940    QCOMPARE(cursorPosition, 1);
1941
1942    // ImAnchorPosition
1943    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1944    anchorPosition =  variant.toInt();
1945    QCOMPARE(anchorPosition, 1);
1946
1947    // Send commit text, which makes the editor conforms composition.
1948    {
1949        QList<QInputMethodEvent::Attribute> attributes;
1950        QInputMethodEvent event("", attributes);
1951        event.setCommitString("e");
1952        page->event(&event);
1953    }
1954
1955    // ImCurrentSelection
1956    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1957    selectionValue = variant.value<QString>();
1958    QCOMPARE(selectionValue, QString(""));
1959
1960    // ImSurroundingText
1961    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1962    surroundingValue = variant.value<QString>();
1963    QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethod"));
1964
1965    // ImCursorPosition
1966    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1967    cursorPosition =  variant.toInt();
1968    QCOMPARE(cursorPosition, 2);
1969
1970    // ImAnchorPosition
1971    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1972    anchorPosition =  variant.toInt();
1973    QCOMPARE(anchorPosition, 2);
1974
1975    // 3. Insert a character to the end of the line.
1976    page->triggerAction(QWebPage::MoveToEndOfLine);
1977
1978    // Send temporary text, which makes the editor has composition 't'.
1979    {
1980        QList<QInputMethodEvent::Attribute> attributes;
1981        QInputMethodEvent event("t", attributes);
1982        page->event(&event);
1983    }
1984
1985    // ImCurrentSelection
1986    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1987    selectionValue = variant.value<QString>();
1988    QCOMPARE(selectionValue, QString(""));
1989
1990    // ImSurroundingText
1991    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1992    surroundingValue = variant.value<QString>();
1993    QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethod"));
1994
1995    // ImCursorPosition
1996    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1997    cursorPosition =  variant.toInt();
1998    QCOMPARE(cursorPosition, 22);
1999
2000    // ImAnchorPosition
2001    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
2002    anchorPosition =  variant.toInt();
2003    QCOMPARE(anchorPosition, 22);
2004
2005    // Send commit text, which makes the editor conforms composition.
2006    {
2007        QList<QInputMethodEvent::Attribute> attributes;
2008        QInputMethodEvent event("", attributes);
2009        event.setCommitString("t");
2010        page->event(&event);
2011    }
2012
2013    // ImCurrentSelection
2014    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
2015    selectionValue = variant.value<QString>();
2016    QCOMPARE(selectionValue, QString(""));
2017
2018    // ImSurroundingText
2019    variant = page->inputMethodQuery(Qt::ImSurroundingText);
2020    surroundingValue = variant.value<QString>();
2021    QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethodt"));
2022
2023    // ImCursorPosition
2024    variant = page->inputMethodQuery(Qt::ImCursorPosition);
2025    cursorPosition =  variant.toInt();
2026    QCOMPARE(cursorPosition, 23);
2027
2028    // ImAnchorPosition
2029    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
2030    anchorPosition =  variant.toInt();
2031    QCOMPARE(anchorPosition, 23);
2032
2033    // 4. Replace the selection.
2034    page->triggerAction(QWebPage::SelectPreviousWord);
2035
2036    // ImCurrentSelection
2037    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
2038    selectionValue = variant.value<QString>();
2039    QCOMPARE(selectionValue, QString("inputMethodt"));
2040
2041    // ImSurroundingText
2042    variant = page->inputMethodQuery(Qt::ImSurroundingText);
2043    surroundingValue = variant.value<QString>();
2044    QCOMPARE(surroundingValue, QString("oeQtWebKit inputMethodt"));
2045
2046    // ImCursorPosition
2047    variant = page->inputMethodQuery(Qt::ImCursorPosition);
2048    cursorPosition =  variant.toInt();
2049    QCOMPARE(cursorPosition, 11);
2050
2051    // ImAnchorPosition
2052    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
2053    anchorPosition =  variant.toInt();
2054    QCOMPARE(anchorPosition, 23);
2055
2056    // Send temporary text, which makes the editor has composition 'w'.
2057    {
2058        QList<QInputMethodEvent::Attribute> attributes;
2059        QInputMethodEvent event("w", attributes);
2060        page->event(&event);
2061    }
2062
2063    // ImCurrentSelection
2064    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
2065    selectionValue = variant.value<QString>();
2066    QCOMPARE(selectionValue, QString(""));
2067
2068    // ImSurroundingText
2069    variant = page->inputMethodQuery(Qt::ImSurroundingText);
2070    surroundingValue = variant.value<QString>();
2071    QCOMPARE(surroundingValue, QString("oeQtWebKit "));
2072
2073    // ImCursorPosition
2074    variant = page->inputMethodQuery(Qt::ImCursorPosition);
2075    cursorPosition =  variant.toInt();
2076    QCOMPARE(cursorPosition, 11);
2077
2078    // ImAnchorPosition
2079    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
2080    anchorPosition =  variant.toInt();
2081    QCOMPARE(anchorPosition, 11);
2082
2083    // Send commit text, which makes the editor conforms composition.
2084    {
2085        QList<QInputMethodEvent::Attribute> attributes;
2086        QInputMethodEvent event("", attributes);
2087        event.setCommitString("2");
2088        page->event(&event);
2089    }
2090
2091    // ImCurrentSelection
2092    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
2093    selectionValue = variant.value<QString>();
2094    QCOMPARE(selectionValue, QString(""));
2095
2096    // ImSurroundingText
2097    variant = page->inputMethodQuery(Qt::ImSurroundingText);
2098    surroundingValue = variant.value<QString>();
2099    QCOMPARE(surroundingValue, QString("oeQtWebKit 2"));
2100
2101    // ImCursorPosition
2102    variant = page->inputMethodQuery(Qt::ImCursorPosition);
2103    cursorPosition =  variant.toInt();
2104    QCOMPARE(cursorPosition, 12);
2105
2106    // ImAnchorPosition
2107    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
2108    anchorPosition =  variant.toInt();
2109    QCOMPARE(anchorPosition, 12);
2110
2111    // Check sending RequestSoftwareInputPanel event
2112    page->mainFrame()->setHtml("<html><body>" \
2113                                            "<input type='text' id='input5' value='QtWebKit inputMethod'/>" \
2114                                            "<div id='btnDiv' onclick='i=document.getElementById(&quot;input5&quot;); i.focus();'>abc</div>"\
2115                                            "</body></html>");
2116    QWebElement inputElement = page->mainFrame()->findFirstElement("div");
2117    clickOnPage(page, inputElement.geometry().center());
2118
2119    QVERIFY(!viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
2120    delete container;
2121}
2122
2123void tst_QWebPage::inputMethodsTextFormat_data()
2124{
2125    QTest::addColumn<QString>("string");
2126    QTest::addColumn<int>("start");
2127    QTest::addColumn<int>("length");
2128
2129    QTest::newRow("") << QString("") << 0 << 0;
2130    QTest::newRow("Q") << QString("Q") << 0 << 1;
2131    QTest::newRow("Qt") << QString("Qt") << 0 << 1;
2132    QTest::newRow("Qt") << QString("Qt") << 0 << 2;
2133    QTest::newRow("Qt") << QString("Qt") << 1 << 1;
2134    QTest::newRow("Qt ") << QString("Qt ") << 0 << 1;
2135    QTest::newRow("Qt ") << QString("Qt ") << 1 << 1;
2136    QTest::newRow("Qt ") << QString("Qt ") << 2 << 1;
2137    QTest::newRow("Qt ") << QString("Qt ") << 2 << -1;
2138    QTest::newRow("Qt ") << QString("Qt ") << -2 << 3;
2139    QTest::newRow("Qt ") << QString("Qt ") << 0 << 3;
2140    QTest::newRow("Qt by") << QString("Qt by") << 0 << 1;
2141    QTest::newRow("Qt by Nokia") << QString("Qt by Nokia") << 0 << 1;
2142}
2143
2144
2145void tst_QWebPage::inputMethodsTextFormat()
2146{
2147    QWebPage* page = new QWebPage;
2148    QWebView* view = new QWebView;
2149    view->setPage(page);
2150    page->settings()->setFontFamily(QWebSettings::SerifFont, "FooSerifFont");
2151    page->mainFrame()->setHtml("<html><body>" \
2152                                            "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/>");
2153    page->mainFrame()->evaluateJavaScript("document.getElementById('input1').focus()");
2154    page->mainFrame()->setFocus();
2155    view->show();
2156
2157    QFETCH(QString, string);
2158    QFETCH(int, start);
2159    QFETCH(int, length);
2160
2161    QList<QInputMethodEvent::Attribute> attrs;
2162    QTextCharFormat format;
2163    format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
2164    format.setUnderlineColor(Qt::red);
2165    attrs.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, length, format));
2166    QInputMethodEvent im(string, attrs);
2167    page->event(&im);
2168
2169    QTest::qWait(1000);
2170
2171    delete view;
2172}
2173
2174void tst_QWebPage::protectBindingsRuntimeObjectsFromCollector()
2175{
2176    QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool)));
2177
2178    PluginPage* newPage = new PluginPage(m_view);
2179    m_view->setPage(newPage);
2180
2181    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
2182
2183    m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='lineedit' id='mylineedit'/></body></html>"));
2184    QTRY_COMPARE(loadSpy.count(), 1);
2185
2186    newPage->mainFrame()->evaluateJavaScript("function testme(text) { var lineedit = document.getElementById('mylineedit'); lineedit.setText(text); lineedit.selectAll(); }");
2187
2188    newPage->mainFrame()->evaluateJavaScript("testme('foo')");
2189
2190    DumpRenderTreeSupportQt::garbageCollectorCollect();
2191
2192    // don't crash!
2193    newPage->mainFrame()->evaluateJavaScript("testme('bar')");
2194}
2195
2196void tst_QWebPage::localURLSchemes()
2197{
2198    int i = QWebSecurityOrigin::localSchemes().size();
2199
2200    QWebSecurityOrigin::removeLocalScheme("file");
2201    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
2202    QWebSecurityOrigin::addLocalScheme("file");
2203    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
2204
2205    QWebSecurityOrigin::removeLocalScheme("qrc");
2206    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i - 1);
2207    QWebSecurityOrigin::addLocalScheme("qrc");
2208    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
2209
2210    QString myscheme = "myscheme";
2211    QWebSecurityOrigin::addLocalScheme(myscheme);
2212    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i + 1);
2213    QVERIFY(QWebSecurityOrigin::localSchemes().contains(myscheme));
2214    QWebSecurityOrigin::removeLocalScheme(myscheme);
2215    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
2216    QWebSecurityOrigin::removeLocalScheme(myscheme);
2217    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
2218}
2219
2220static inline bool testFlag(QWebPage& webPage, QWebSettings::WebAttribute settingAttribute, const QString& jsObjectName, bool settingValue)
2221{
2222    webPage.settings()->setAttribute(settingAttribute, settingValue);
2223    return webPage.mainFrame()->evaluateJavaScript(QString("(window.%1 != undefined)").arg(jsObjectName)).toBool();
2224}
2225
2226void tst_QWebPage::testOptionalJSObjects()
2227{
2228    // Once a feature is enabled and the JS object is accessed turning off the setting will not turn off
2229    // the visibility of the JS object any more. For this reason this test uses two QWebPage instances.
2230    // Part of the test is to make sure that the QWebPage instances do not interfere with each other so turning on
2231    // a feature for one instance will not turn it on for another.
2232
2233    QWebPage webPage1;
2234    QWebPage webPage2;
2235
2236    webPage1.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl());
2237    webPage2.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl());
2238
2239    QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue);
2240    QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false);
2241    QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", true),  true);
2242    QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue);
2243    QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false);
2244    QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), true);
2245
2246    QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false);
2247    QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", true),  true);
2248    QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false);
2249    QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", false), true);
2250}
2251
2252void tst_QWebPage::testEnablePersistentStorage()
2253{
2254    QWebPage webPage;
2255
2256    // By default all persistent options should be disabled
2257    QCOMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), false);
2258    QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), false);
2259    QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), false);
2260    QVERIFY(webPage.settings()->iconDatabasePath().isEmpty());
2261
2262    QWebSettings::enablePersistentStorage();
2263
2264
2265    QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), true);
2266    QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), true);
2267    QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), true);
2268
2269    QTRY_VERIFY(!webPage.settings()->offlineStoragePath().isEmpty());
2270    QTRY_VERIFY(!webPage.settings()->offlineWebApplicationCachePath().isEmpty());
2271    QTRY_VERIFY(!webPage.settings()->iconDatabasePath().isEmpty());
2272}
2273
2274void tst_QWebPage::defaultTextEncoding()
2275{
2276    QWebFrame* mainFrame = m_page->mainFrame();
2277
2278    QString defaultCharset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
2279    QVERIFY(!defaultCharset.isEmpty());
2280    QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), defaultCharset);
2281
2282    m_page->settings()->setDefaultTextEncoding(QString("utf-8"));
2283    QString charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
2284    QCOMPARE(charset, QString("utf-8"));
2285    QCOMPARE(m_page->settings()->defaultTextEncoding(), charset);
2286
2287    m_page->settings()->setDefaultTextEncoding(QString());
2288    charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
2289    QVERIFY(!charset.isEmpty());
2290    QCOMPARE(charset, defaultCharset);
2291
2292    QWebSettings::globalSettings()->setDefaultTextEncoding(QString("utf-8"));
2293    charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
2294    QCOMPARE(charset, QString("utf-8"));
2295    QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), charset);
2296}
2297
2298class ErrorPage : public QWebPage
2299{
2300public:
2301
2302    ErrorPage(QWidget* parent = 0): QWebPage(parent)
2303    {
2304    }
2305
2306    virtual bool supportsExtension(Extension extension) const
2307    {
2308        return extension == ErrorPageExtension;
2309    }
2310
2311    virtual bool extension(Extension, const ExtensionOption* option, ExtensionReturn* output)
2312    {
2313        ErrorPageExtensionReturn* errorPage = static_cast<ErrorPageExtensionReturn*>(output);
2314
2315        errorPage->contentType = "text/html";
2316        errorPage->content = "error";
2317        return true;
2318    }
2319};
2320
2321void tst_QWebPage::errorPageExtension()
2322{
2323    ErrorPage* page = new ErrorPage;
2324    m_view->setPage(page);
2325
2326    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
2327
2328    m_view->setUrl(QUrl("data:text/html,foo"));
2329    QTRY_COMPARE(spyLoadFinished.count(), 1);
2330
2331    page->mainFrame()->setUrl(QUrl("http://non.existent/url"));
2332    QTRY_COMPARE(spyLoadFinished.count(), 2);
2333    QCOMPARE(page->mainFrame()->toPlainText(), QString("error"));
2334    QCOMPARE(page->history()->count(), 2);
2335    QCOMPARE(page->history()->currentItem().url(), QUrl("http://non.existent/url"));
2336    QCOMPARE(page->history()->canGoBack(), true);
2337    QCOMPARE(page->history()->canGoForward(), false);
2338
2339    page->triggerAction(QWebPage::Back);
2340    QTRY_COMPARE(page->history()->canGoBack(), false);
2341    QTRY_COMPARE(page->history()->canGoForward(), true);
2342
2343    page->triggerAction(QWebPage::Forward);
2344    QTRY_COMPARE(page->history()->canGoBack(), true);
2345    QTRY_COMPARE(page->history()->canGoForward(), false);
2346
2347    page->triggerAction(QWebPage::Back);
2348    QTRY_COMPARE(page->history()->canGoBack(), false);
2349    QTRY_COMPARE(page->history()->canGoForward(), true);
2350    QTRY_COMPARE(page->history()->currentItem().url(), QUrl("data:text/html,foo"));
2351
2352    m_view->setPage(0);
2353}
2354
2355void tst_QWebPage::errorPageExtensionInIFrames()
2356{
2357    ErrorPage* page = new ErrorPage;
2358    m_view->setPage(page);
2359
2360    m_view->page()->mainFrame()->load(QUrl(
2361        "data:text/html,"
2362        "<h1>h1</h1>"
2363        "<iframe src='data:text/html,<p/>p'></iframe>"
2364        "<iframe src='http://non.existent/url'></iframe>"));
2365    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
2366    QTRY_COMPARE(spyLoadFinished.count(), 1);
2367
2368    QCOMPARE(page->mainFrame()->childFrames()[1]->toPlainText(), QString("error"));
2369
2370    m_view->setPage(0);
2371}
2372
2373void tst_QWebPage::errorPageExtensionInFrameset()
2374{
2375    ErrorPage* page = new ErrorPage;
2376    m_view->setPage(page);
2377
2378    m_view->load(QUrl("qrc:///resources/index.html"));
2379
2380    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
2381    QTRY_COMPARE(spyLoadFinished.count(), 1);
2382    QCOMPARE(page->mainFrame()->childFrames()[1]->toPlainText(), QString("error"));
2383
2384    m_view->setPage(0);
2385}
2386
2387class FriendlyWebPage : public QWebPage
2388{
2389public:
2390    friend class tst_QWebPage;
2391};
2392
2393void tst_QWebPage::userAgentApplicationName()
2394{
2395    const QString oldApplicationName = QCoreApplication::applicationName();
2396    FriendlyWebPage page;
2397
2398    const QString applicationNameMarker = QString::fromUtf8("StrangeName\342\210\236");
2399    QCoreApplication::setApplicationName(applicationNameMarker);
2400    QVERIFY(page.userAgentForUrl(QUrl()).contains(applicationNameMarker));
2401
2402    QCoreApplication::setApplicationName(oldApplicationName);
2403}
2404
2405void tst_QWebPage::crashTests_LazyInitializationOfMainFrame()
2406{
2407    {
2408        QWebPage webPage;
2409    }
2410    {
2411        QWebPage webPage;
2412        webPage.selectedText();
2413    }
2414    {
2415        QWebPage webPage;
2416        webPage.selectedHtml();
2417    }
2418    {
2419        QWebPage webPage;
2420        webPage.triggerAction(QWebPage::Back, true);
2421    }
2422    {
2423        QWebPage webPage;
2424        QPoint pos(10,10);
2425        webPage.updatePositionDependentActions(pos);
2426    }
2427}
2428
2429static void takeScreenshot(QWebPage* page)
2430{
2431    QWebFrame* mainFrame = page->mainFrame();
2432    page->setViewportSize(mainFrame->contentsSize());
2433    QImage image(page->viewportSize(), QImage::Format_ARGB32);
2434    QPainter painter(&image);
2435    mainFrame->render(&painter);
2436    painter.end();
2437}
2438
2439void tst_QWebPage::screenshot_data()
2440{
2441    QTest::addColumn<QString>("html");
2442    QTest::newRow("WithoutPlugin") << "<html><body id='b'>text</body></html>";
2443    QTest::newRow("WindowedPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf'></embed></body></html>");
2444    QTest::newRow("WindowlessPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf' wmode='transparent'></embed></body></html>");
2445}
2446
2447void tst_QWebPage::screenshot()
2448{
2449    if (!QDir(TESTS_SOURCE_DIR).exists())
2450        QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
2451
2452    QDir::setCurrent(TESTS_SOURCE_DIR);
2453
2454    QFETCH(QString, html);
2455    QWebPage* page = new QWebPage;
2456    page->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
2457    QWebFrame* mainFrame = page->mainFrame();
2458    mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR));
2459    ::waitForSignal(mainFrame, SIGNAL(loadFinished(bool)), 2000);
2460
2461    // take screenshot without a view
2462    takeScreenshot(page);
2463
2464    QWebView* view = new QWebView;
2465    view->setPage(page);
2466
2467    // take screenshot when attached to a view
2468    takeScreenshot(page);
2469
2470    delete page;
2471    delete view;
2472
2473    QDir::setCurrent(QApplication::applicationDirPath());
2474}
2475
2476void tst_QWebPage::originatingObjectInNetworkRequests()
2477{
2478    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
2479    m_page->setNetworkAccessManager(networkManager);
2480    networkManager->requests.clear();
2481
2482    m_view->setHtml(QString("<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
2483                            "<head><meta http-equiv='refresh' content='1'></head>foo \">"
2484                            "<frame src=\"data:text/html,bar\"></frameset>"), QUrl());
2485    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
2486
2487    QCOMPARE(networkManager->requests.count(), 2);
2488
2489    QList<QWebFrame*> childFrames = m_page->mainFrame()->childFrames();
2490    QCOMPARE(childFrames.count(), 2);
2491
2492    for (int i = 0; i < 2; ++i)
2493        QVERIFY(qobject_cast<QWebFrame*>(networkManager->requests.at(i).originatingObject()) == childFrames.at(i));
2494}
2495
2496/**
2497 * Test fixups for https://bugs.webkit.org/show_bug.cgi?id=30914
2498 *
2499 * From JS we test the following conditions.
2500 *
2501 *   OK     + QString() => SUCCESS, empty string (but not null)
2502 *   OK     + "text"    => SUCCESS, "text"
2503 *   CANCEL + QString() => CANCEL, null string
2504 *   CANCEL + "text"    => CANCEL, null string
2505 */
2506class JSPromptPage : public QWebPage {
2507    Q_OBJECT
2508public:
2509    JSPromptPage()
2510    {}
2511
2512    bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result)
2513    {
2514        if (msg == QLatin1String("test1")) {
2515            *result = QString();
2516            return true;
2517        } else if (msg == QLatin1String("test2")) {
2518            *result = QLatin1String("text");
2519            return true;
2520        } else if (msg == QLatin1String("test3")) {
2521            *result = QString();
2522            return false;
2523        } else if (msg == QLatin1String("test4")) {
2524            *result = QLatin1String("text");
2525            return false;
2526        }
2527
2528        qFatal("Unknown msg.");
2529        return QWebPage::javaScriptPrompt(frame, msg, defaultValue, result);
2530    }
2531};
2532
2533void tst_QWebPage::testJSPrompt()
2534{
2535    JSPromptPage page;
2536    bool res;
2537
2538    // OK + QString()
2539    res = page.mainFrame()->evaluateJavaScript(
2540            "var retval = prompt('test1');"
2541            "retval=='' && retval.length == 0;").toBool();
2542    QVERIFY(res);
2543
2544    // OK + "text"
2545    res = page.mainFrame()->evaluateJavaScript(
2546            "var retval = prompt('test2');"
2547            "retval=='text' && retval.length == 4;").toBool();
2548    QVERIFY(res);
2549
2550    // Cancel + QString()
2551    res = page.mainFrame()->evaluateJavaScript(
2552            "var retval = prompt('test3');"
2553            "retval===null;").toBool();
2554    QVERIFY(res);
2555
2556    // Cancel + "text"
2557    res = page.mainFrame()->evaluateJavaScript(
2558            "var retval = prompt('test4');"
2559            "retval===null;").toBool();
2560    QVERIFY(res);
2561}
2562
2563class TestModalPage : public QWebPage
2564{
2565    Q_OBJECT
2566public:
2567    TestModalPage(QObject* parent = 0) : QWebPage(parent) {
2568    }
2569    virtual QWebPage* createWindow(WebWindowType) {
2570        QWebPage* page = new TestModalPage();
2571        connect(page, SIGNAL(windowCloseRequested()), page, SLOT(deleteLater()));
2572        return page;
2573    }
2574};
2575
2576void tst_QWebPage::showModalDialog()
2577{
2578    TestModalPage page;
2579    page.mainFrame()->setHtml(QString("<html></html>"));
2580    QString res = page.mainFrame()->evaluateJavaScript("window.showModalDialog('javascript:window.returnValue=dialogArguments; window.close();', 'This is a test');").toString();
2581    QCOMPARE(res, QString("This is a test"));
2582}
2583
2584void tst_QWebPage::testStopScheduledPageRefresh()
2585{
2586    // Without QWebPage::StopScheduledPageRefresh
2587    QWebPage page1;
2588    page1.setNetworkAccessManager(new TestNetworkManager(&page1));
2589    page1.mainFrame()->setHtml("<html><head>"
2590                                "<meta http-equiv=\"refresh\"content=\"0;URL=qrc:///resources/index.html\">"
2591                                "</head><body><h1>Page redirects immediately...</h1>"
2592                                "</body></html>");
2593    QVERIFY(::waitForSignal(&page1, SIGNAL(loadFinished(bool))));
2594    QTest::qWait(500);
2595    QCOMPARE(page1.mainFrame()->url(), QUrl(QLatin1String("qrc:///resources/index.html")));
2596
2597    // With QWebPage::StopScheduledPageRefresh
2598    QWebPage page2;
2599    page2.setNetworkAccessManager(new TestNetworkManager(&page2));
2600    page2.mainFrame()->setHtml("<html><head>"
2601                               "<meta http-equiv=\"refresh\"content=\"1;URL=qrc:///resources/index.html\">"
2602                               "</head><body><h1>Page redirect test with 1 sec timeout...</h1>"
2603                               "</body></html>");
2604    page2.triggerAction(QWebPage::StopScheduledPageRefresh);
2605    QTest::qWait(1500);
2606    QCOMPARE(page2.mainFrame()->url().toString(), QLatin1String("about:blank"));
2607}
2608
2609void tst_QWebPage::findText()
2610{
2611    m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>"));
2612    m_page->triggerAction(QWebPage::SelectAll);
2613    QVERIFY(!m_page->selectedText().isEmpty());
2614    QVERIFY(!m_page->selectedHtml().isEmpty());
2615    m_page->findText("");
2616    QVERIFY(m_page->selectedText().isEmpty());
2617    QVERIFY(m_page->selectedHtml().isEmpty());
2618    QStringList words = (QStringList() << "foo" << "bar");
2619    foreach (QString subString, words) {
2620        m_page->findText(subString, QWebPage::FindWrapsAroundDocument);
2621        QCOMPARE(m_page->selectedText(), subString);
2622        QCOMPARE(m_page->selectedHtml(), QString("<span class=\"Apple-style-span\" style=\"border-collapse: separate; color: rgb(0, 0, 0); font-family: Times; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-size: medium; \">%1</span>").arg(subString));
2623        m_page->findText("");
2624        QVERIFY(m_page->selectedText().isEmpty());
2625        QVERIFY(m_page->selectedHtml().isEmpty());
2626    }
2627}
2628
2629struct ImageExtensionMap {
2630    const char* extension;
2631    const char* mimeType;
2632};
2633
2634static const ImageExtensionMap extensionMap[] = {
2635    { "bmp", "image/bmp" },
2636    { "css", "text/css" },
2637    { "gif", "image/gif" },
2638    { "html", "text/html" },
2639    { "htm", "text/html" },
2640    { "ico", "image/x-icon" },
2641    { "jpeg", "image/jpeg" },
2642    { "jpg", "image/jpeg" },
2643    { "js", "application/x-javascript" },
2644    { "mng", "video/x-mng" },
2645    { "pbm", "image/x-portable-bitmap" },
2646    { "pgm", "image/x-portable-graymap" },
2647    { "pdf", "application/pdf" },
2648    { "png", "image/png" },
2649    { "ppm", "image/x-portable-pixmap" },
2650    { "rss", "application/rss+xml" },
2651    { "svg", "image/svg+xml" },
2652    { "text", "text/plain" },
2653    { "tif", "image/tiff" },
2654    { "tiff", "image/tiff" },
2655    { "txt", "text/plain" },
2656    { "xbm", "image/x-xbitmap" },
2657    { "xml", "text/xml" },
2658    { "xpm", "image/x-xpm" },
2659    { "xsl", "text/xsl" },
2660    { "xhtml", "application/xhtml+xml" },
2661    { "wml", "text/vnd.wap.wml" },
2662    { "wmlc", "application/vnd.wap.wmlc" },
2663    { 0, 0 }
2664};
2665
2666static QString getMimeTypeForExtension(const QString &ext)
2667{
2668    const ImageExtensionMap *e = extensionMap;
2669    while (e->extension) {
2670        if (ext.compare(QLatin1String(e->extension), Qt::CaseInsensitive) == 0)
2671            return QLatin1String(e->mimeType);
2672        ++e;
2673    }
2674
2675    return QString();
2676}
2677
2678void tst_QWebPage::supportedContentType()
2679{
2680   QStringList contentTypes;
2681
2682   // Add supported non image types...
2683   contentTypes << "text/html" << "text/xml" << "text/xsl" << "text/plain" << "text/"
2684                << "application/xml" << "application/xhtml+xml" << "application/vnd.wap.xhtml+xml"
2685                << "application/rss+xml" << "application/atom+xml" << "application/json";
2686
2687   // Add supported image types...
2688   Q_FOREACH(const QByteArray& imageType, QImageWriter::supportedImageFormats()) {
2689      const QString mimeType = getMimeTypeForExtension(imageType);
2690      if (!mimeType.isEmpty())
2691          contentTypes << mimeType;
2692   }
2693
2694   // Get the mime types supported by webkit...
2695   const QStringList supportedContentTypes = m_page->supportedContentTypes();
2696
2697   Q_FOREACH(const QString& mimeType, contentTypes)
2698      QVERIFY2(supportedContentTypes.contains(mimeType), QString("'%1' is not a supported content type!").arg(mimeType).toLatin1());
2699
2700   Q_FOREACH(const QString& mimeType, contentTypes)
2701      QVERIFY2(m_page->supportsContentType(mimeType), QString("Cannot handle content types '%1'!").arg(mimeType).toLatin1());
2702}
2703
2704
2705void tst_QWebPage::navigatorCookieEnabled()
2706{
2707    m_page->networkAccessManager()->setCookieJar(0);
2708    QVERIFY(!m_page->networkAccessManager()->cookieJar());
2709    QVERIFY(!m_page->mainFrame()->evaluateJavaScript("navigator.cookieEnabled").toBool());
2710
2711    m_page->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
2712    QVERIFY(m_page->networkAccessManager()->cookieJar());
2713    QVERIFY(m_page->mainFrame()->evaluateJavaScript("navigator.cookieEnabled").toBool());
2714}
2715
2716#ifdef Q_OS_MAC
2717void tst_QWebPage::macCopyUnicodeToClipboard()
2718{
2719    QString unicodeText = QString::fromUtf8("αβγδεζηθικλμπ");
2720    m_page->mainFrame()->setHtml(QString("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>%1</body></html>").arg(unicodeText));
2721    m_page->triggerAction(QWebPage::SelectAll);
2722    m_page->triggerAction(QWebPage::Copy);
2723
2724    QString clipboardData = QString::fromUtf8(QApplication::clipboard()->mimeData()->data(QLatin1String("text/html")));
2725
2726    QVERIFY(clipboardData.contains(QLatin1String("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />")));
2727    QVERIFY(clipboardData.contains(unicodeText));
2728}
2729#endif
2730
2731void tst_QWebPage::contextMenuCopy()
2732{
2733    QWebView view;
2734
2735    view.setHtml("<a href=\"http://www.google.com\">You cant miss this</a>");
2736
2737    view.page()->triggerAction(QWebPage::SelectAll);
2738    QVERIFY(!view.page()->selectedText().isEmpty());
2739
2740    QWebElement link = view.page()->mainFrame()->findFirstElement("a");
2741    QPoint pos(link.geometry().center());
2742    QContextMenuEvent event(QContextMenuEvent::Mouse, pos);
2743    view.page()->swallowContextMenuEvent(&event);
2744    view.page()->updatePositionDependentActions(pos);
2745
2746    QList<QMenu*> contextMenus = view.findChildren<QMenu*>();
2747    QVERIFY(!contextMenus.isEmpty());
2748    QMenu* contextMenu = contextMenus.first();
2749    QVERIFY(contextMenu);
2750
2751    QList<QAction *> list = contextMenu->actions();
2752    int index = list.indexOf(view.page()->action(QWebPage::Copy));
2753    QVERIFY(index != -1);
2754}
2755
2756void tst_QWebPage::deleteQWebViewTwice()
2757{
2758    for (int i = 0; i < 2; ++i) {
2759        QMainWindow mainWindow;
2760        QWebView* webView = new QWebView(&mainWindow);
2761        mainWindow.setCentralWidget(webView);
2762        webView->load(QUrl("qrc:///resources/frame_a.html"));
2763        mainWindow.show();
2764        connect(webView, SIGNAL(loadFinished(bool)), &mainWindow, SLOT(close()));
2765        QApplication::instance()->exec();
2766    }
2767}
2768
2769QTEST_MAIN(tst_QWebPage)
2770#include "tst_qwebpage.moc"
2771