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