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 <QDir>
24#include <QGraphicsWidget>
25#include <QLineEdit>
26#include <QMenu>
27#include <QPushButton>
28#include <QtTest/QtTest>
29#include <qgraphicsscene.h>
30#include <qgraphicsview.h>
31#include <qgraphicswebview.h>
32#include <qnetworkrequest.h>
33#include <qwebdatabase.h>
34#include <qwebelement.h>
35#include <qwebframe.h>
36#include <qwebhistory.h>
37#include <qwebpage.h>
38#include <qwebsecurityorigin.h>
39#include <qwebview.h>
40
41class EventSpy : public QObject, public QList<QEvent::Type>
42{
43    Q_OBJECT
44public:
45    EventSpy(QObject* objectToSpy)
46    {
47        objectToSpy->installEventFilter(this);
48    }
49
50    virtual bool eventFilter(QObject* receiver, QEvent* event)
51    {
52        append(event->type());
53        return false;
54    }
55};
56
57class tst_QWebPage : public QObject
58{
59    Q_OBJECT
60
61public:
62    tst_QWebPage();
63    virtual ~tst_QWebPage();
64
65public slots:
66    void init();
67    void cleanup();
68    void cleanupFiles();
69
70private slots:
71    void initTestCase();
72    void cleanupTestCase();
73
74    void acceptNavigationRequest();
75    void infiniteLoopJS();
76    void loadFinished();
77    void acceptNavigationRequestWithNewWindow();
78    void userStyleSheet();
79    void modified();
80    void contextMenuCrash();
81    void database();
82    void createPlugin();
83    void destroyPlugin_data();
84    void destroyPlugin();
85    void createViewlessPlugin_data();
86    void createViewlessPlugin();
87    void multiplePageGroupsAndLocalStorage();
88    void cursorMovements();
89    void textSelection();
90    void textEditing();
91    void backActionUpdate();
92    void frameAt();
93    void requestCache();
94    void protectBindingsRuntimeObjectsFromCollector();
95    void localURLSchemes();
96    void testOptionalJSObjects();
97    void testEnablePersistentStorage();
98    void consoleOutput();
99    void inputMethods_data();
100    void inputMethods();
101    void defaultTextEncoding();
102    void errorPageExtension();
103    void errorPageExtensionInIFrames();
104    void errorPageExtensionInFrameset();
105
106    void crashTests_LazyInitializationOfMainFrame();
107
108    void screenshot_data();
109    void screenshot();
110
111    void originatingObjectInNetworkRequests();
112    void testJSPrompt();
113    void showModalDialog();
114
115private:
116    QWebView* m_view;
117    QWebPage* m_page;
118};
119
120tst_QWebPage::tst_QWebPage()
121{
122}
123
124tst_QWebPage::~tst_QWebPage()
125{
126}
127
128void tst_QWebPage::init()
129{
130    m_view = new QWebView();
131    m_page = m_view->page();
132}
133
134void tst_QWebPage::cleanup()
135{
136    delete m_view;
137}
138
139void tst_QWebPage::cleanupFiles()
140{
141    QFile::remove("Databases.db");
142    QDir::current().rmdir("http_www.myexample.com_0");
143    QFile::remove("http_www.myexample.com_0.localstorage");
144}
145
146void tst_QWebPage::initTestCase()
147{
148    cleanupFiles(); // In case there are old files from previous runs
149}
150
151void tst_QWebPage::cleanupTestCase()
152{
153    cleanupFiles(); // Be nice
154}
155
156class NavigationRequestOverride : public QWebPage
157{
158public:
159    NavigationRequestOverride(QWebView* parent, bool initialValue) : QWebPage(parent), m_acceptNavigationRequest(initialValue) {}
160
161    bool m_acceptNavigationRequest;
162protected:
163    virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, QWebPage::NavigationType type) {
164        Q_UNUSED(frame);
165        Q_UNUSED(request);
166        Q_UNUSED(type);
167
168        return m_acceptNavigationRequest;
169    }
170};
171
172void tst_QWebPage::acceptNavigationRequest()
173{
174    QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool)));
175
176    NavigationRequestOverride* newPage = new NavigationRequestOverride(m_view, false);
177    m_view->setPage(newPage);
178
179    m_view->setHtml(QString("<html><body><form name='tstform' action='data:text/html,foo'method='get'>"
180                            "<input type='text'><input type='submit'></form></body></html>"), QUrl());
181    QTRY_COMPARE(loadSpy.count(), 1);
182
183    m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();");
184
185    newPage->m_acceptNavigationRequest = true;
186    m_view->page()->mainFrame()->evaluateJavaScript("tstform.submit();");
187    QTRY_COMPARE(loadSpy.count(), 2);
188
189    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("foo?"));
190
191    // Restore default page
192    m_view->setPage(0);
193}
194
195class JSTestPage : public QWebPage
196{
197Q_OBJECT
198public:
199    JSTestPage(QObject* parent = 0)
200    : QWebPage(parent) {}
201
202public slots:
203    bool shouldInterruptJavaScript() {
204        return true;
205    }
206};
207
208void tst_QWebPage::infiniteLoopJS()
209{
210    JSTestPage* newPage = new JSTestPage(m_view);
211    m_view->setPage(newPage);
212    m_view->setHtml(QString("<html><bodytest</body></html>"), QUrl());
213    m_view->page()->mainFrame()->evaluateJavaScript("var run = true;var a = 1;while(run){a++;}");
214}
215
216void tst_QWebPage::loadFinished()
217{
218    qRegisterMetaType<QWebFrame*>("QWebFrame*");
219    qRegisterMetaType<QNetworkRequest*>("QNetworkRequest*");
220    QSignalSpy spyLoadStarted(m_view, SIGNAL(loadStarted()));
221    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
222
223    m_view->setHtml(QString("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
224                            "<head><meta http-equiv='refresh' content='1'></head>foo \">"
225                            "<frame src=\"data:text/html,bar\"></frameset>"), QUrl());
226    QTRY_COMPARE(spyLoadFinished.count(), 1);
227
228    QTRY_VERIFY(spyLoadStarted.count() > 1);
229    QTRY_VERIFY(spyLoadFinished.count() > 1);
230
231    spyLoadFinished.clear();
232
233    m_view->setHtml(QString("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
234                            "foo \"><frame src=\"data:text/html,bar\"></frameset>"), QUrl());
235    QTRY_COMPARE(spyLoadFinished.count(), 1);
236    QCOMPARE(spyLoadFinished.count(), 1);
237}
238
239class ConsolePage : public QWebPage
240{
241public:
242    ConsolePage(QObject* parent = 0) : QWebPage(parent) {}
243
244    virtual void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID)
245    {
246        messages.append(message);
247        lineNumbers.append(lineNumber);
248        sourceIDs.append(sourceID);
249    }
250
251    QStringList messages;
252    QList<int> lineNumbers;
253    QStringList sourceIDs;
254};
255
256void tst_QWebPage::consoleOutput()
257{
258    ConsolePage page;
259    page.mainFrame()->evaluateJavaScript("this is not valid JavaScript");
260    QCOMPARE(page.messages.count(), 1);
261    QCOMPARE(page.lineNumbers.at(0), 1);
262}
263
264class TestPage : public QWebPage
265{
266public:
267    TestPage(QObject* parent = 0) : QWebPage(parent) {}
268
269    struct Navigation {
270        QPointer<QWebFrame> frame;
271        QNetworkRequest request;
272        NavigationType type;
273    };
274
275    QList<Navigation> navigations;
276    QList<QWebPage*> createdWindows;
277
278    virtual bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest &request, NavigationType type) {
279        Navigation n;
280        n.frame = frame;
281        n.request = request;
282        n.type = type;
283        navigations.append(n);
284        return true;
285    }
286
287    virtual QWebPage* createWindow(WebWindowType) {
288        QWebPage* page = new TestPage(this);
289        createdWindows.append(page);
290        return page;
291    }
292};
293
294void tst_QWebPage::acceptNavigationRequestWithNewWindow()
295{
296    TestPage* page = new TestPage(m_view);
297    page->settings()->setAttribute(QWebSettings::LinksIncludedInFocusChain, true);
298    m_page = page;
299    m_view->setPage(m_page);
300
301    m_view->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>"));
302    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
303
304    QFocusEvent fe(QEvent::FocusIn);
305    m_page->event(&fe);
306
307    QVERIFY(m_page->focusNextPrevChild(/*next*/ true));
308
309    QKeyEvent keyEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
310    m_page->event(&keyEnter);
311
312    QCOMPARE(page->navigations.count(), 2);
313
314    TestPage::Navigation n = page->navigations.at(1);
315    QVERIFY(n.frame.isNull());
316    QCOMPARE(n.request.url().toString(), QString("data:text/html,Reached"));
317    QVERIFY(n.type == QWebPage::NavigationTypeLinkClicked);
318
319    QCOMPARE(page->createdWindows.count(), 1);
320}
321
322class TestNetworkManager : public QNetworkAccessManager
323{
324public:
325    TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {}
326
327    QList<QUrl> requestedUrls;
328    QList<QNetworkRequest> requests;
329
330protected:
331    virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) {
332        requests.append(request);
333        requestedUrls.append(request.url());
334        return QNetworkAccessManager::createRequest(op, request, outgoingData);
335    }
336};
337
338void tst_QWebPage::userStyleSheet()
339{
340    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
341    m_page->setNetworkAccessManager(networkManager);
342    networkManager->requestedUrls.clear();
343
344    m_page->settings()->setUserStyleSheetUrl(QUrl("data:text/css;charset=utf-8;base64,"
345            + QByteArray("p { background-image: url('http://does.not/exist.png');}").toBase64()));
346    m_view->setHtml("<p>hello world</p>");
347    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
348
349    QVERIFY(networkManager->requestedUrls.count() >= 1);
350    QCOMPARE(networkManager->requestedUrls.at(0), QUrl("http://does.not/exist.png"));
351}
352
353void tst_QWebPage::modified()
354{
355    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>blub"));
356    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
357
358    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body id=foo contenteditable>blah"));
359    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
360
361    QVERIFY(!m_page->isModified());
362
363//    m_page->mainFrame()->evaluateJavaScript("alert(document.getElementById('foo'))");
364    m_page->mainFrame()->evaluateJavaScript("document.getElementById('foo').focus()");
365    m_page->mainFrame()->evaluateJavaScript("document.execCommand('InsertText', true, 'Test');");
366
367    QVERIFY(m_page->isModified());
368
369    m_page->mainFrame()->evaluateJavaScript("document.execCommand('Undo', true);");
370
371    QVERIFY(!m_page->isModified());
372
373    m_page->mainFrame()->evaluateJavaScript("document.execCommand('Redo', true);");
374
375    QVERIFY(m_page->isModified());
376
377    QVERIFY(m_page->history()->canGoBack());
378    QVERIFY(!m_page->history()->canGoForward());
379    QCOMPARE(m_page->history()->count(), 2);
380    QVERIFY(m_page->history()->backItem().isValid());
381    QVERIFY(!m_page->history()->forwardItem().isValid());
382
383    m_page->history()->back();
384    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
385
386    QVERIFY(!m_page->history()->canGoBack());
387    QVERIFY(m_page->history()->canGoForward());
388
389    QVERIFY(!m_page->isModified());
390
391    QVERIFY(m_page->history()->currentItemIndex() == 0);
392
393    m_page->history()->setMaximumItemCount(3);
394    QVERIFY(m_page->history()->maximumItemCount() == 3);
395
396    QVariant variant("string test");
397    m_page->history()->currentItem().setUserData(variant);
398    QVERIFY(m_page->history()->currentItem().userData().toString() == "string test");
399
400    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is second page"));
401    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is third page"));
402    QVERIFY(m_page->history()->count() == 2);
403    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fourth page"));
404    QVERIFY(m_page->history()->count() == 2);
405    m_page->mainFrame()->setUrl(QUrl("data:text/html,<body>This is fifth page"));
406    QVERIFY(::waitForSignal(m_page, SIGNAL(saveFrameStateRequested(QWebFrame*,QWebHistoryItem*))));
407}
408
409void tst_QWebPage::contextMenuCrash()
410{
411    QWebView view;
412    view.setHtml("<p>test");
413    view.page()->updatePositionDependentActions(QPoint(0, 0));
414    QMenu* contextMenu = 0;
415    foreach (QObject* child, view.children()) {
416        contextMenu = qobject_cast<QMenu*>(child);
417        if (contextMenu)
418            break;
419    }
420    QVERIFY(contextMenu);
421    delete contextMenu;
422}
423
424void tst_QWebPage::database()
425{
426    QString path = QDir::currentPath();
427    m_page->settings()->setOfflineStoragePath(path);
428    QVERIFY(m_page->settings()->offlineStoragePath() == path);
429
430    QWebSettings::setOfflineStorageDefaultQuota(1024 * 1024);
431    QVERIFY(QWebSettings::offlineStorageDefaultQuota() == 1024 * 1024);
432
433    m_page->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
434    m_page->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true);
435
436    QString dbFileName = path + "Databases.db";
437
438    if (QFile::exists(dbFileName))
439        QFile::remove(dbFileName);
440
441    qRegisterMetaType<QWebFrame*>("QWebFrame*");
442    QSignalSpy spy(m_page, SIGNAL(databaseQuotaExceeded(QWebFrame*,QString)));
443    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"));
444    QTRY_COMPARE(spy.count(), 1);
445    m_page->mainFrame()->evaluateJavaScript("var db2; db2=openDatabase('testdb', '1.0', 'test database API', 50000);");
446    QTRY_COMPARE(spy.count(),1);
447
448    m_page->mainFrame()->evaluateJavaScript("localStorage.test='This is a test for local storage';");
449    m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com"));
450
451    QVariant s1 = m_page->mainFrame()->evaluateJavaScript("localStorage.test");
452    QCOMPARE(s1.toString(), QString("This is a test for local storage"));
453
454    m_page->mainFrame()->evaluateJavaScript("sessionStorage.test='This is a test for session storage';");
455    m_view->setHtml(QString("<html><body id='b'>text</body></html>"), QUrl("http://www.myexample.com"));
456    QVariant s2 = m_page->mainFrame()->evaluateJavaScript("sessionStorage.test");
457    QCOMPARE(s2.toString(), QString("This is a test for session storage"));
458
459    m_view->setHtml(QString("<html><head></head><body><div></div></body></html>"), QUrl("http://www.myexample.com"));
460    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) { });");
461    QTest::qWait(200);
462
463    // Remove all databases.
464    QWebSecurityOrigin origin = m_page->mainFrame()->securityOrigin();
465    QList<QWebDatabase> dbs = origin.databases();
466    for (int i = 0; i < dbs.count(); i++) {
467        QString fileName = dbs[i].fileName();
468        QVERIFY(QFile::exists(fileName));
469        QWebDatabase::removeDatabase(dbs[i]);
470        QVERIFY(!QFile::exists(fileName));
471    }
472    QVERIFY(!origin.databases().size());
473    // Remove removed test :-)
474    QWebDatabase::removeAllDatabases();
475    QVERIFY(!origin.databases().size());
476}
477
478class PluginPage : public QWebPage
479{
480public:
481    PluginPage(QObject *parent = 0)
482        : QWebPage(parent) {}
483
484    struct CallInfo
485    {
486        CallInfo(const QString &c, const QUrl &u,
487                 const QStringList &pn, const QStringList &pv,
488                 QObject *r)
489            : classid(c), url(u), paramNames(pn),
490              paramValues(pv), returnValue(r)
491            {}
492        QString classid;
493        QUrl url;
494        QStringList paramNames;
495        QStringList paramValues;
496        QObject *returnValue;
497    };
498
499    QList<CallInfo> calls;
500
501protected:
502    virtual QObject *createPlugin(const QString &classid, const QUrl &url,
503                                  const QStringList &paramNames,
504                                  const QStringList &paramValues)
505    {
506        QObject *result = 0;
507        if (classid == "pushbutton")
508            result = new QPushButton();
509        else if (classid == "lineedit")
510            result = new QLineEdit();
511        if (result)
512            result->setObjectName(classid);
513        calls.append(CallInfo(classid, url, paramNames, paramValues, result));
514        return result;
515    }
516};
517
518void tst_QWebPage::createPlugin()
519{
520    QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool)));
521
522    PluginPage* newPage = new PluginPage(m_view);
523    m_view->setPage(newPage);
524
525    // plugins not enabled by default, so the plugin shouldn't be loaded
526    m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></body></html>"));
527    QTRY_COMPARE(loadSpy.count(), 1);
528    QCOMPARE(newPage->calls.count(), 0);
529
530    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
531
532    // type has to be application/x-qt-plugin
533    m_view->setHtml(QString("<html><body><object type='application/x-foobarbaz' classid='pushbutton' id='mybutton'/></body></html>"));
534    QTRY_COMPARE(loadSpy.count(), 2);
535    QCOMPARE(newPage->calls.count(), 0);
536
537    m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></body></html>"));
538    QTRY_COMPARE(loadSpy.count(), 3);
539    QCOMPARE(newPage->calls.count(), 1);
540    {
541        PluginPage::CallInfo ci = newPage->calls.takeFirst();
542        QCOMPARE(ci.classid, QString::fromLatin1("pushbutton"));
543        QCOMPARE(ci.url, QUrl());
544        QCOMPARE(ci.paramNames.count(), 3);
545        QCOMPARE(ci.paramValues.count(), 3);
546        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
547        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
548        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
549        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton"));
550        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
551        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton"));
552        QVERIFY(ci.returnValue != 0);
553        QVERIFY(ci.returnValue->inherits("QPushButton"));
554    }
555    // test JS bindings
556    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("document.getElementById('mybutton').toString()").toString(),
557             QString::fromLatin1("[object HTMLObjectElement]"));
558    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.toString()").toString(),
559             QString::fromLatin1("[object HTMLObjectElement]"));
560    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.objectName").toString(),
561             QString::fromLatin1("string"));
562    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.objectName").toString(),
563             QString::fromLatin1("pushbutton"));
564    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("typeof mybutton.clicked").toString(),
565             QString::fromLatin1("function"));
566    QCOMPARE(newPage->mainFrame()->evaluateJavaScript("mybutton.clicked.toString()").toString(),
567             QString::fromLatin1("function clicked() {\n    [native code]\n}"));
568
569    m_view->setHtml(QString("<html><body><table>"
570                            "<tr><object type='application/x-qt-plugin' classid='lineedit' id='myedit'/></tr>"
571                            "<tr><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></tr>"
572                            "</table></body></html>"), QUrl("http://foo.bar.baz"));
573    QTRY_COMPARE(loadSpy.count(), 4);
574    QCOMPARE(newPage->calls.count(), 2);
575    {
576        PluginPage::CallInfo ci = newPage->calls.takeFirst();
577        QCOMPARE(ci.classid, QString::fromLatin1("lineedit"));
578        QCOMPARE(ci.url, QUrl());
579        QCOMPARE(ci.paramNames.count(), 3);
580        QCOMPARE(ci.paramValues.count(), 3);
581        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
582        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
583        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
584        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("lineedit"));
585        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
586        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("myedit"));
587        QVERIFY(ci.returnValue != 0);
588        QVERIFY(ci.returnValue->inherits("QLineEdit"));
589    }
590    {
591        PluginPage::CallInfo ci = newPage->calls.takeFirst();
592        QCOMPARE(ci.classid, QString::fromLatin1("pushbutton"));
593        QCOMPARE(ci.url, QUrl());
594        QCOMPARE(ci.paramNames.count(), 3);
595        QCOMPARE(ci.paramValues.count(), 3);
596        QCOMPARE(ci.paramNames.at(0), QString::fromLatin1("type"));
597        QCOMPARE(ci.paramValues.at(0), QString::fromLatin1("application/x-qt-plugin"));
598        QCOMPARE(ci.paramNames.at(1), QString::fromLatin1("classid"));
599        QCOMPARE(ci.paramValues.at(1), QString::fromLatin1("pushbutton"));
600        QCOMPARE(ci.paramNames.at(2), QString::fromLatin1("id"));
601        QCOMPARE(ci.paramValues.at(2), QString::fromLatin1("mybutton"));
602        QVERIFY(ci.returnValue != 0);
603        QVERIFY(ci.returnValue->inherits("QPushButton"));
604    }
605
606    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, false);
607
608    m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='pushbutton' id='mybutton'/></body></html>"));
609    QTRY_COMPARE(loadSpy.count(), 5);
610    QCOMPARE(newPage->calls.count(), 0);
611}
612
613
614// Standard base class for template PluginTracerPage. In tests it is used as interface.
615class PluginCounterPage : public QWebPage {
616public:
617    int m_count;
618    QPointer<QObject> m_widget;
619    QObject* m_pluginParent;
620    PluginCounterPage(QObject* parent = 0)
621        : QWebPage(parent)
622        , m_count(0)
623        , m_widget(0)
624        , m_pluginParent(0)
625    {
626       settings()->setAttribute(QWebSettings::PluginsEnabled, true);
627    }
628    ~PluginCounterPage()
629    {
630        if (m_pluginParent)
631            m_pluginParent->deleteLater();
632    }
633};
634
635template<class T>
636class PluginTracerPage : public PluginCounterPage {
637public:
638    PluginTracerPage(QObject* parent = 0)
639        : PluginCounterPage(parent)
640    {
641        // this is a dummy parent object for the created plugin
642        m_pluginParent = new T;
643    }
644    virtual QObject* createPlugin(const QString&, const QUrl&, const QStringList&, const QStringList&)
645    {
646        m_count++;
647        m_widget = new T;
648        // need a cast to the specific type, as QObject::setParent cannot be called,
649        // because it is not virtual. Instead it is necesary to call QWidget::setParent,
650        // which also takes a QWidget* instead of a QObject*. Therefore we need to
651        // upcast to T*, which is a QWidget.
652        static_cast<T*>(m_widget.data())->setParent(static_cast<T*>(m_pluginParent));
653        return m_widget;
654    }
655};
656
657class PluginFactory {
658public:
659    enum FactoredType {QWidgetType, QGraphicsWidgetType};
660    static PluginCounterPage* create(FactoredType type, QObject* parent = 0)
661    {
662        PluginCounterPage* result = 0;
663        switch (type) {
664        case QWidgetType:
665            result = new PluginTracerPage<QWidget>(parent);
666            break;
667        case QGraphicsWidgetType:
668            result = new PluginTracerPage<QGraphicsWidget>(parent);
669            break;
670        default: {/*Oops*/};
671        }
672        return result;
673    }
674
675    static void prepareTestData()
676    {
677        QTest::addColumn<int>("type");
678        QTest::newRow("QWidget") << (int)PluginFactory::QWidgetType;
679        QTest::newRow("QGraphicsWidget") << (int)PluginFactory::QGraphicsWidgetType;
680    }
681};
682
683void tst_QWebPage::destroyPlugin_data()
684{
685    PluginFactory::prepareTestData();
686}
687
688void tst_QWebPage::destroyPlugin()
689{
690    QFETCH(int, type);
691    PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type, m_view);
692    m_view->setPage(page);
693
694    // we create the plugin, so the widget should be constructed
695    QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>");
696    m_view->setHtml(content);
697    QVERIFY(page->m_widget);
698    QCOMPARE(page->m_count, 1);
699
700    // navigate away, the plugin widget should be destructed
701    m_view->setHtml("<html><body>Hi</body></html>");
702    QTestEventLoop::instance().enterLoop(1);
703    QVERIFY(!page->m_widget);
704}
705
706void tst_QWebPage::createViewlessPlugin_data()
707{
708    PluginFactory::prepareTestData();
709}
710
711void tst_QWebPage::createViewlessPlugin()
712{
713    QFETCH(int, type);
714    PluginCounterPage* page = PluginFactory::create((PluginFactory::FactoredType)type);
715    QString content("<html><body><object type=\"application/x-qt-plugin\" classid=\"QProgressBar\"></object></body></html>");
716    page->mainFrame()->setHtml(content);
717    QCOMPARE(page->m_count, 1);
718    QVERIFY(page->m_widget);
719    QVERIFY(page->m_pluginParent);
720    QVERIFY(page->m_widget->parent() == page->m_pluginParent);
721    delete page;
722
723}
724
725// import private API
726void QWEBKIT_EXPORT qt_webpage_setGroupName(QWebPage* page, const QString& groupName);
727QString QWEBKIT_EXPORT qt_webpage_groupName(QWebPage* page);
728
729void tst_QWebPage::multiplePageGroupsAndLocalStorage()
730{
731    QDir dir(QDir::currentPath());
732    dir.mkdir("path1");
733    dir.mkdir("path2");
734
735    QWebView view1;
736    QWebView view2;
737
738    view1.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
739    view1.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(QDir::currentPath() + "/path1"));
740    qt_webpage_setGroupName(view1.page(), "group1");
741    view2.page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
742    view2.page()->settings()->setLocalStoragePath(QDir::toNativeSeparators(QDir::currentPath() + "/path2"));
743    qt_webpage_setGroupName(view2.page(), "group2");
744    QCOMPARE(qt_webpage_groupName(view1.page()), QString("group1"));
745    QCOMPARE(qt_webpage_groupName(view2.page()), QString("group2"));
746
747
748    view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
749    view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
750
751    view1.page()->mainFrame()->evaluateJavaScript("localStorage.test='value1';");
752    view2.page()->mainFrame()->evaluateJavaScript("localStorage.test='value2';");
753
754    view1.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
755    view2.setHtml(QString("<html><body> </body></html>"), QUrl("http://www.myexample.com"));
756
757    QVariant s1 = view1.page()->mainFrame()->evaluateJavaScript("localStorage.test");
758    QCOMPARE(s1.toString(), QString("value1"));
759
760    QVariant s2 = view2.page()->mainFrame()->evaluateJavaScript("localStorage.test");
761    QCOMPARE(s2.toString(), QString("value2"));
762
763    QTest::qWait(1000);
764
765    QFile::remove(QDir::toNativeSeparators(QDir::currentPath() + "/path1/http_www.myexample.com_0.localstorage"));
766    QFile::remove(QDir::toNativeSeparators(QDir::currentPath() + "/path2/http_www.myexample.com_0.localstorage"));
767    dir.rmdir(QDir::toNativeSeparators("./path1"));
768    dir.rmdir(QDir::toNativeSeparators("./path2"));
769}
770
771class CursorTrackedPage : public QWebPage
772{
773public:
774
775    CursorTrackedPage(QWidget *parent = 0): QWebPage(parent) {
776        setViewportSize(QSize(1024, 768)); // big space
777    }
778
779    QString selectedText() {
780        return mainFrame()->evaluateJavaScript("window.getSelection().toString()").toString();
781    }
782
783    int selectionStartOffset() {
784        return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).startOffset").toInt();
785    }
786
787    int selectionEndOffset() {
788        return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).endOffset").toInt();
789    }
790
791    // true if start offset == end offset, i.e. no selected text
792    int isSelectionCollapsed() {
793        return mainFrame()->evaluateJavaScript("window.getSelection().getRangeAt(0).collapsed").toBool();
794    }
795};
796
797void tst_QWebPage::cursorMovements()
798{
799    CursorTrackedPage* page = new CursorTrackedPage;
800    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>");
801    page->mainFrame()->setHtml(content);
802
803    // this will select the first paragraph
804    QString script = "var range = document.createRange(); " \
805        "var node = document.getElementById(\"one\"); " \
806        "range.selectNode(node); " \
807        "getSelection().addRange(range);";
808    page->mainFrame()->evaluateJavaScript(script);
809    QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox"));
810
811    // these actions must exist
812    QVERIFY(page->action(QWebPage::MoveToNextChar) != 0);
813    QVERIFY(page->action(QWebPage::MoveToPreviousChar) != 0);
814    QVERIFY(page->action(QWebPage::MoveToNextWord) != 0);
815    QVERIFY(page->action(QWebPage::MoveToPreviousWord) != 0);
816    QVERIFY(page->action(QWebPage::MoveToNextLine) != 0);
817    QVERIFY(page->action(QWebPage::MoveToPreviousLine) != 0);
818    QVERIFY(page->action(QWebPage::MoveToStartOfLine) != 0);
819    QVERIFY(page->action(QWebPage::MoveToEndOfLine) != 0);
820    QVERIFY(page->action(QWebPage::MoveToStartOfBlock) != 0);
821    QVERIFY(page->action(QWebPage::MoveToEndOfBlock) != 0);
822    QVERIFY(page->action(QWebPage::MoveToStartOfDocument) != 0);
823    QVERIFY(page->action(QWebPage::MoveToEndOfDocument) != 0);
824
825    // right now they are disabled because contentEditable is false
826    QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), false);
827    QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), false);
828    QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), false);
829    QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), false);
830    QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), false);
831    QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), false);
832    QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), false);
833    QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), false);
834    QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), false);
835    QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), false);
836    QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), false);
837    QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), false);
838
839    // make it editable before navigating the cursor
840    page->setContentEditable(true);
841
842    // here the actions are enabled after contentEditable is true
843    QCOMPARE(page->action(QWebPage::MoveToNextChar)->isEnabled(), true);
844    QCOMPARE(page->action(QWebPage::MoveToPreviousChar)->isEnabled(), true);
845    QCOMPARE(page->action(QWebPage::MoveToNextWord)->isEnabled(), true);
846    QCOMPARE(page->action(QWebPage::MoveToPreviousWord)->isEnabled(), true);
847    QCOMPARE(page->action(QWebPage::MoveToNextLine)->isEnabled(), true);
848    QCOMPARE(page->action(QWebPage::MoveToPreviousLine)->isEnabled(), true);
849    QCOMPARE(page->action(QWebPage::MoveToStartOfLine)->isEnabled(), true);
850    QCOMPARE(page->action(QWebPage::MoveToEndOfLine)->isEnabled(), true);
851    QCOMPARE(page->action(QWebPage::MoveToStartOfBlock)->isEnabled(), true);
852    QCOMPARE(page->action(QWebPage::MoveToEndOfBlock)->isEnabled(), true);
853    QCOMPARE(page->action(QWebPage::MoveToStartOfDocument)->isEnabled(), true);
854    QCOMPARE(page->action(QWebPage::MoveToEndOfDocument)->isEnabled(), true);
855
856    // cursor will be before the word "jump"
857    page->triggerAction(QWebPage::MoveToNextChar);
858    QVERIFY(page->isSelectionCollapsed());
859    QCOMPARE(page->selectionStartOffset(), 0);
860
861    // cursor will be between 'j' and 'u' in the word "jump"
862    page->triggerAction(QWebPage::MoveToNextChar);
863    QVERIFY(page->isSelectionCollapsed());
864    QCOMPARE(page->selectionStartOffset(), 1);
865
866    // cursor will be between 'u' and 'm' in the word "jump"
867    page->triggerAction(QWebPage::MoveToNextChar);
868    QVERIFY(page->isSelectionCollapsed());
869    QCOMPARE(page->selectionStartOffset(), 2);
870
871    // cursor will be after the word "jump"
872    page->triggerAction(QWebPage::MoveToNextWord);
873    QVERIFY(page->isSelectionCollapsed());
874    QCOMPARE(page->selectionStartOffset(), 5);
875
876    // cursor will be after the word "lazy"
877    page->triggerAction(QWebPage::MoveToNextWord);
878    page->triggerAction(QWebPage::MoveToNextWord);
879    page->triggerAction(QWebPage::MoveToNextWord);
880    QVERIFY(page->isSelectionCollapsed());
881    QCOMPARE(page->selectionStartOffset(), 19);
882
883    // cursor will be between 'z' and 'y' in "lazy"
884    page->triggerAction(QWebPage::MoveToPreviousChar);
885    QVERIFY(page->isSelectionCollapsed());
886    QCOMPARE(page->selectionStartOffset(), 18);
887
888    // cursor will be between 'a' and 'z' in "lazy"
889    page->triggerAction(QWebPage::MoveToPreviousChar);
890    QVERIFY(page->isSelectionCollapsed());
891    QCOMPARE(page->selectionStartOffset(), 17);
892
893    // cursor will be before the word "lazy"
894    page->triggerAction(QWebPage::MoveToPreviousWord);
895    QVERIFY(page->isSelectionCollapsed());
896    QCOMPARE(page->selectionStartOffset(), 15);
897
898    // cursor will be before the word "quick"
899    page->triggerAction(QWebPage::MoveToPreviousWord);
900    page->triggerAction(QWebPage::MoveToPreviousWord);
901    page->triggerAction(QWebPage::MoveToPreviousWord);
902    page->triggerAction(QWebPage::MoveToPreviousWord);
903    page->triggerAction(QWebPage::MoveToPreviousWord);
904    page->triggerAction(QWebPage::MoveToPreviousWord);
905    QVERIFY(page->isSelectionCollapsed());
906    QCOMPARE(page->selectionStartOffset(), 4);
907
908    // cursor will be between 'p' and 's' in the word "jumps"
909    page->triggerAction(QWebPage::MoveToNextWord);
910    page->triggerAction(QWebPage::MoveToNextWord);
911    page->triggerAction(QWebPage::MoveToNextWord);
912    page->triggerAction(QWebPage::MoveToNextChar);
913    page->triggerAction(QWebPage::MoveToNextChar);
914    page->triggerAction(QWebPage::MoveToNextChar);
915    page->triggerAction(QWebPage::MoveToNextChar);
916    page->triggerAction(QWebPage::MoveToNextChar);
917    QVERIFY(page->isSelectionCollapsed());
918    QCOMPARE(page->selectionStartOffset(), 4);
919
920    // cursor will be before the word "jumps"
921    page->triggerAction(QWebPage::MoveToStartOfLine);
922    QVERIFY(page->isSelectionCollapsed());
923    QCOMPARE(page->selectionStartOffset(), 0);
924
925    // cursor will be after the word "dog"
926    page->triggerAction(QWebPage::MoveToEndOfLine);
927    QVERIFY(page->isSelectionCollapsed());
928    QCOMPARE(page->selectionStartOffset(), 23);
929
930    // cursor will be between 'w' and 'n' in "brown"
931    page->triggerAction(QWebPage::MoveToStartOfLine);
932    page->triggerAction(QWebPage::MoveToPreviousWord);
933    page->triggerAction(QWebPage::MoveToPreviousWord);
934    page->triggerAction(QWebPage::MoveToNextChar);
935    page->triggerAction(QWebPage::MoveToNextChar);
936    page->triggerAction(QWebPage::MoveToNextChar);
937    page->triggerAction(QWebPage::MoveToNextChar);
938    QVERIFY(page->isSelectionCollapsed());
939    QCOMPARE(page->selectionStartOffset(), 14);
940
941    // cursor will be after the word "fox"
942    page->triggerAction(QWebPage::MoveToEndOfLine);
943    QVERIFY(page->isSelectionCollapsed());
944    QCOMPARE(page->selectionStartOffset(), 19);
945
946    // cursor will be before the word "The"
947    page->triggerAction(QWebPage::MoveToStartOfDocument);
948    QVERIFY(page->isSelectionCollapsed());
949    QCOMPARE(page->selectionStartOffset(), 0);
950
951    // cursor will be after the word "you!"
952    page->triggerAction(QWebPage::MoveToEndOfDocument);
953    QVERIFY(page->isSelectionCollapsed());
954    QCOMPARE(page->selectionStartOffset(), 12);
955
956    // cursor will be before the word "be"
957    page->triggerAction(QWebPage::MoveToStartOfBlock);
958    QVERIFY(page->isSelectionCollapsed());
959    QCOMPARE(page->selectionStartOffset(), 0);
960
961    // cursor will be after the word "you!"
962    page->triggerAction(QWebPage::MoveToEndOfBlock);
963    QVERIFY(page->isSelectionCollapsed());
964    QCOMPARE(page->selectionStartOffset(), 12);
965
966    // try to move before the document start
967    page->triggerAction(QWebPage::MoveToStartOfDocument);
968    page->triggerAction(QWebPage::MoveToPreviousChar);
969    QVERIFY(page->isSelectionCollapsed());
970    QCOMPARE(page->selectionStartOffset(), 0);
971    page->triggerAction(QWebPage::MoveToStartOfDocument);
972    page->triggerAction(QWebPage::MoveToPreviousWord);
973    QVERIFY(page->isSelectionCollapsed());
974    QCOMPARE(page->selectionStartOffset(), 0);
975
976    // try to move past the document end
977    page->triggerAction(QWebPage::MoveToEndOfDocument);
978    page->triggerAction(QWebPage::MoveToNextChar);
979    QVERIFY(page->isSelectionCollapsed());
980    QCOMPARE(page->selectionStartOffset(), 12);
981    page->triggerAction(QWebPage::MoveToEndOfDocument);
982    page->triggerAction(QWebPage::MoveToNextWord);
983    QVERIFY(page->isSelectionCollapsed());
984    QCOMPARE(page->selectionStartOffset(), 12);
985
986    delete page;
987}
988
989void tst_QWebPage::textSelection()
990{
991    CursorTrackedPage* page = new CursorTrackedPage;
992    QString content("<html><body<p id=one>The quick brown fox</p>" \
993        "<p id=two>jumps over the lazy dog</p>" \
994        "<p>May the source<br/>be with you!</p></body></html>");
995    page->mainFrame()->setHtml(content);
996
997    // these actions must exist
998    QVERIFY(page->action(QWebPage::SelectAll) != 0);
999    QVERIFY(page->action(QWebPage::SelectNextChar) != 0);
1000    QVERIFY(page->action(QWebPage::SelectPreviousChar) != 0);
1001    QVERIFY(page->action(QWebPage::SelectNextWord) != 0);
1002    QVERIFY(page->action(QWebPage::SelectPreviousWord) != 0);
1003    QVERIFY(page->action(QWebPage::SelectNextLine) != 0);
1004    QVERIFY(page->action(QWebPage::SelectPreviousLine) != 0);
1005    QVERIFY(page->action(QWebPage::SelectStartOfLine) != 0);
1006    QVERIFY(page->action(QWebPage::SelectEndOfLine) != 0);
1007    QVERIFY(page->action(QWebPage::SelectStartOfBlock) != 0);
1008    QVERIFY(page->action(QWebPage::SelectEndOfBlock) != 0);
1009    QVERIFY(page->action(QWebPage::SelectStartOfDocument) != 0);
1010    QVERIFY(page->action(QWebPage::SelectEndOfDocument) != 0);
1011
1012    // right now they are disabled because contentEditable is false and
1013    // there isn't an existing selection to modify
1014    QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), false);
1015    QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), false);
1016    QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), false);
1017    QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), false);
1018    QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), false);
1019    QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), false);
1020    QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), false);
1021    QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), false);
1022    QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), false);
1023    QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), false);
1024    QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), false);
1025    QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), false);
1026
1027    // ..but SelectAll is awalys enabled
1028    QCOMPARE(page->action(QWebPage::SelectAll)->isEnabled(), true);
1029
1030    // this will select the first paragraph
1031    QString selectScript = "var range = document.createRange(); " \
1032        "var node = document.getElementById(\"one\"); " \
1033        "range.selectNode(node); " \
1034        "getSelection().addRange(range);";
1035    page->mainFrame()->evaluateJavaScript(selectScript);
1036    QCOMPARE(page->selectedText().trimmed(), QString::fromLatin1("The quick brown fox"));
1037
1038    // here the actions are enabled after a selection has been created
1039    QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true);
1040    QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true);
1041    QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true);
1042    QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true);
1043    QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true);
1044    QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true);
1045    QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true);
1046    QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true);
1047    QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true);
1048    QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true);
1049    QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true);
1050    QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true);
1051
1052    // make it editable before navigating the cursor
1053    page->setContentEditable(true);
1054
1055    // cursor will be before the word "The", this makes sure there is a charet
1056    page->triggerAction(QWebPage::MoveToStartOfDocument);
1057    QVERIFY(page->isSelectionCollapsed());
1058    QCOMPARE(page->selectionStartOffset(), 0);
1059
1060    // here the actions are enabled after contentEditable is true
1061    QCOMPARE(page->action(QWebPage::SelectNextChar)->isEnabled(), true);
1062    QCOMPARE(page->action(QWebPage::SelectPreviousChar)->isEnabled(), true);
1063    QCOMPARE(page->action(QWebPage::SelectNextWord)->isEnabled(), true);
1064    QCOMPARE(page->action(QWebPage::SelectPreviousWord)->isEnabled(), true);
1065    QCOMPARE(page->action(QWebPage::SelectNextLine)->isEnabled(), true);
1066    QCOMPARE(page->action(QWebPage::SelectPreviousLine)->isEnabled(), true);
1067    QCOMPARE(page->action(QWebPage::SelectStartOfLine)->isEnabled(), true);
1068    QCOMPARE(page->action(QWebPage::SelectEndOfLine)->isEnabled(), true);
1069    QCOMPARE(page->action(QWebPage::SelectStartOfBlock)->isEnabled(), true);
1070    QCOMPARE(page->action(QWebPage::SelectEndOfBlock)->isEnabled(), true);
1071    QCOMPARE(page->action(QWebPage::SelectStartOfDocument)->isEnabled(), true);
1072    QCOMPARE(page->action(QWebPage::SelectEndOfDocument)->isEnabled(), true);
1073
1074    delete page;
1075}
1076
1077void tst_QWebPage::textEditing()
1078{
1079    CursorTrackedPage* page = new CursorTrackedPage;
1080    QString content("<html><body<p id=one>The quick brown fox</p>" \
1081        "<p id=two>jumps over the lazy dog</p>" \
1082        "<p>May the source<br/>be with you!</p></body></html>");
1083    page->mainFrame()->setHtml(content);
1084
1085    // these actions must exist
1086    QVERIFY(page->action(QWebPage::Cut) != 0);
1087    QVERIFY(page->action(QWebPage::Copy) != 0);
1088    QVERIFY(page->action(QWebPage::Paste) != 0);
1089    QVERIFY(page->action(QWebPage::DeleteStartOfWord) != 0);
1090    QVERIFY(page->action(QWebPage::DeleteEndOfWord) != 0);
1091    QVERIFY(page->action(QWebPage::SetTextDirectionDefault) != 0);
1092    QVERIFY(page->action(QWebPage::SetTextDirectionLeftToRight) != 0);
1093    QVERIFY(page->action(QWebPage::SetTextDirectionRightToLeft) != 0);
1094    QVERIFY(page->action(QWebPage::ToggleBold) != 0);
1095    QVERIFY(page->action(QWebPage::ToggleItalic) != 0);
1096    QVERIFY(page->action(QWebPage::ToggleUnderline) != 0);
1097    QVERIFY(page->action(QWebPage::InsertParagraphSeparator) != 0);
1098    QVERIFY(page->action(QWebPage::InsertLineSeparator) != 0);
1099    QVERIFY(page->action(QWebPage::PasteAndMatchStyle) != 0);
1100    QVERIFY(page->action(QWebPage::RemoveFormat) != 0);
1101    QVERIFY(page->action(QWebPage::ToggleStrikethrough) != 0);
1102    QVERIFY(page->action(QWebPage::ToggleSubscript) != 0);
1103    QVERIFY(page->action(QWebPage::ToggleSuperscript) != 0);
1104    QVERIFY(page->action(QWebPage::InsertUnorderedList) != 0);
1105    QVERIFY(page->action(QWebPage::InsertOrderedList) != 0);
1106    QVERIFY(page->action(QWebPage::Indent) != 0);
1107    QVERIFY(page->action(QWebPage::Outdent) != 0);
1108    QVERIFY(page->action(QWebPage::AlignCenter) != 0);
1109    QVERIFY(page->action(QWebPage::AlignJustified) != 0);
1110    QVERIFY(page->action(QWebPage::AlignLeft) != 0);
1111    QVERIFY(page->action(QWebPage::AlignRight) != 0);
1112
1113    // right now they are disabled because contentEditable is false
1114    QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false);
1115    QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), false);
1116    QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), false);
1117    QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), false);
1118    QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), false);
1119    QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), false);
1120    QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), false);
1121    QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), false);
1122    QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), false);
1123    QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), false);
1124    QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), false);
1125    QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), false);
1126    QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), false);
1127    QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false);
1128    QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), false);
1129    QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), false);
1130    QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), false);
1131    QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), false);
1132    QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), false);
1133    QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), false);
1134    QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), false);
1135    QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), false);
1136    QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), false);
1137    QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), false);
1138    QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), false);
1139
1140    // Select everything
1141    page->triggerAction(QWebPage::SelectAll);
1142
1143    // make sure it is enabled since there is a selection
1144    QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), true);
1145
1146    // make it editable before navigating the cursor
1147    page->setContentEditable(true);
1148
1149    // clear the selection
1150    page->triggerAction(QWebPage::MoveToStartOfDocument);
1151    QVERIFY(page->isSelectionCollapsed());
1152    QCOMPARE(page->selectionStartOffset(), 0);
1153
1154    // make sure it is disabled since there isn't a selection
1155    QCOMPARE(page->action(QWebPage::Copy)->isEnabled(), false);
1156
1157    // here the actions are enabled after contentEditable is true
1158    QCOMPARE(page->action(QWebPage::Paste)->isEnabled(), true);
1159    QCOMPARE(page->action(QWebPage::DeleteStartOfWord)->isEnabled(), true);
1160    QCOMPARE(page->action(QWebPage::DeleteEndOfWord)->isEnabled(), true);
1161    QCOMPARE(page->action(QWebPage::SetTextDirectionDefault)->isEnabled(), true);
1162    QCOMPARE(page->action(QWebPage::SetTextDirectionLeftToRight)->isEnabled(), true);
1163    QCOMPARE(page->action(QWebPage::SetTextDirectionRightToLeft)->isEnabled(), true);
1164    QCOMPARE(page->action(QWebPage::ToggleBold)->isEnabled(), true);
1165    QCOMPARE(page->action(QWebPage::ToggleItalic)->isEnabled(), true);
1166    QCOMPARE(page->action(QWebPage::ToggleUnderline)->isEnabled(), true);
1167    QCOMPARE(page->action(QWebPage::InsertParagraphSeparator)->isEnabled(), true);
1168    QCOMPARE(page->action(QWebPage::InsertLineSeparator)->isEnabled(), true);
1169    QCOMPARE(page->action(QWebPage::PasteAndMatchStyle)->isEnabled(), true);
1170    QCOMPARE(page->action(QWebPage::ToggleStrikethrough)->isEnabled(), true);
1171    QCOMPARE(page->action(QWebPage::ToggleSubscript)->isEnabled(), true);
1172    QCOMPARE(page->action(QWebPage::ToggleSuperscript)->isEnabled(), true);
1173    QCOMPARE(page->action(QWebPage::InsertUnorderedList)->isEnabled(), true);
1174    QCOMPARE(page->action(QWebPage::InsertOrderedList)->isEnabled(), true);
1175    QCOMPARE(page->action(QWebPage::Indent)->isEnabled(), true);
1176    QCOMPARE(page->action(QWebPage::Outdent)->isEnabled(), true);
1177    QCOMPARE(page->action(QWebPage::AlignCenter)->isEnabled(), true);
1178    QCOMPARE(page->action(QWebPage::AlignJustified)->isEnabled(), true);
1179    QCOMPARE(page->action(QWebPage::AlignLeft)->isEnabled(), true);
1180    QCOMPARE(page->action(QWebPage::AlignRight)->isEnabled(), true);
1181
1182    // make sure these are disabled since there isn't a selection
1183    QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), false);
1184    QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), false);
1185
1186    // make sure everything is selected
1187    page->triggerAction(QWebPage::SelectAll);
1188
1189    // this is only true if there is an editable selection
1190    QCOMPARE(page->action(QWebPage::Cut)->isEnabled(), true);
1191    QCOMPARE(page->action(QWebPage::RemoveFormat)->isEnabled(), true);
1192
1193    delete page;
1194}
1195
1196void tst_QWebPage::requestCache()
1197{
1198    TestPage page;
1199    QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
1200
1201    page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me</a>"));
1202    QTRY_COMPARE(loadSpy.count(), 1);
1203    QTRY_COMPARE(page.navigations.count(), 1);
1204
1205    page.mainFrame()->setUrl(QString("data:text/html,<a href=\"data:text/html,Reached\" target=\"_blank\">Click me2</a>"));
1206    QTRY_COMPARE(loadSpy.count(), 2);
1207    QTRY_COMPARE(page.navigations.count(), 2);
1208
1209    page.triggerAction(QWebPage::Stop);
1210    QVERIFY(page.history()->canGoBack());
1211    page.triggerAction(QWebPage::Back);
1212
1213    QTRY_COMPARE(loadSpy.count(), 3);
1214    QTRY_COMPARE(page.navigations.count(), 3);
1215    QCOMPARE(page.navigations.at(0).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(),
1216             (int)QNetworkRequest::PreferNetwork);
1217    QCOMPARE(page.navigations.at(1).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(),
1218             (int)QNetworkRequest::PreferNetwork);
1219    QCOMPARE(page.navigations.at(2).request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(),
1220             (int)QNetworkRequest::PreferCache);
1221}
1222
1223void tst_QWebPage::backActionUpdate()
1224{
1225    QWebView view;
1226    QWebPage *page = view.page();
1227    QAction *action = page->action(QWebPage::Back);
1228    QVERIFY(!action->isEnabled());
1229    QSignalSpy loadSpy(page, SIGNAL(loadFinished(bool)));
1230    QUrl url = QUrl("qrc:///resources/index.html");
1231    page->mainFrame()->load(url);
1232    QTRY_COMPARE(loadSpy.count(), 1);
1233    QVERIFY(!action->isEnabled());
1234    QTest::mouseClick(&view, Qt::LeftButton, 0, QPoint(10, 10));
1235    QTRY_COMPARE(loadSpy.count(), 2);
1236
1237    QVERIFY(action->isEnabled());
1238}
1239
1240void frameAtHelper(QWebPage* webPage, QWebFrame* webFrame, QPoint framePosition)
1241{
1242    if (!webFrame)
1243        return;
1244
1245    framePosition += QPoint(webFrame->pos());
1246    QList<QWebFrame*> children = webFrame->childFrames();
1247    for (int i = 0; i < children.size(); ++i) {
1248        if (children.at(i)->childFrames().size() > 0)
1249            frameAtHelper(webPage, children.at(i), framePosition);
1250
1251        QRect frameRect(children.at(i)->pos() + framePosition, children.at(i)->geometry().size());
1252        QVERIFY(children.at(i) == webPage->frameAt(frameRect.topLeft()));
1253    }
1254}
1255
1256void tst_QWebPage::frameAt()
1257{
1258    QWebView webView;
1259    QWebPage* webPage = webView.page();
1260    QSignalSpy loadSpy(webPage, SIGNAL(loadFinished(bool)));
1261    QUrl url = QUrl("qrc:///resources/iframe.html");
1262    webPage->mainFrame()->load(url);
1263    QTRY_COMPARE(loadSpy.count(), 1);
1264    frameAtHelper(webPage, webPage->mainFrame(), webPage->mainFrame()->pos());
1265}
1266
1267void tst_QWebPage::inputMethods_data()
1268{
1269    QTest::addColumn<QString>("viewType");
1270    QTest::newRow("QWebView") << "QWebView";
1271#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1272    QTest::newRow("QGraphicsWebView") << "QGraphicsWebView";
1273#endif
1274}
1275
1276#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1277static Qt::InputMethodHints inputMethodHints(QObject* object)
1278{
1279    if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object))
1280        return o->inputMethodHints();
1281    if (QWidget* w = qobject_cast<QWidget*>(object))
1282        return w->inputMethodHints();
1283    return Qt::InputMethodHints();
1284}
1285#endif
1286
1287static bool inputMethodEnabled(QObject* object)
1288{
1289#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1290    if (QGraphicsObject* o = qobject_cast<QGraphicsObject*>(object))
1291        return o->flags() & QGraphicsItem::ItemAcceptsInputMethod;
1292#endif
1293    if (QWidget* w = qobject_cast<QWidget*>(object))
1294        return w->testAttribute(Qt::WA_InputMethodEnabled);
1295    return false;
1296}
1297
1298void tst_QWebPage::inputMethods()
1299{
1300    QFETCH(QString, viewType);
1301    QWebPage* page = new QWebPage;
1302    QObject* view = 0;
1303    QObject* container = 0;
1304    if (viewType == "QWebView") {
1305        QWebView* wv = new QWebView;
1306        wv->setPage(page);
1307        view = wv;
1308        container = view;
1309    }
1310#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1311    else if (viewType == "QGraphicsWebView") {
1312        QGraphicsWebView* wv = new QGraphicsWebView;
1313        wv->setPage(page);
1314        view = wv;
1315
1316        QGraphicsView* gv = new QGraphicsView;
1317        QGraphicsScene* scene = new QGraphicsScene(gv);
1318        gv->setScene(scene);
1319        scene->addItem(wv);
1320        wv->setGeometry(QRect(0, 0, 500, 500));
1321
1322        container = gv;
1323    }
1324#endif
1325    else
1326        QVERIFY2(false, "Unknown view type");
1327
1328    page->settings()->setFontFamily(QWebSettings::SerifFont, "FooSerifFont");
1329    page->mainFrame()->setHtml("<html><body>" \
1330                                            "<input type='text' id='input1' style='font-family: serif' value='' maxlength='20'/><br>" \
1331                                            "<input type='password'/>" \
1332                                            "</body></html>");
1333    page->mainFrame()->setFocus();
1334
1335    EventSpy viewEventSpy(container);
1336
1337    QWebElementCollection inputs = page->mainFrame()->documentElement().findAll("input");
1338
1339    QMouseEvent evpres(QEvent::MouseButtonPress, inputs.at(0).geometry().center(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1340    page->event(&evpres);
1341    QMouseEvent evrel(QEvent::MouseButtonRelease, inputs.at(0).geometry().center(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1342    page->event(&evrel);
1343
1344#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1345    QVERIFY(!viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1346#endif
1347    viewEventSpy.clear();
1348
1349    page->event(&evpres);
1350    page->event(&evrel);
1351
1352#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1353    QVERIFY(viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1354#endif
1355
1356    //ImMicroFocus
1357    QVariant variant = page->inputMethodQuery(Qt::ImMicroFocus);
1358    QRect focusRect = variant.toRect();
1359    QVERIFY(inputs.at(0).geometry().contains(variant.toRect().topLeft()));
1360
1361    //ImFont
1362    variant = page->inputMethodQuery(Qt::ImFont);
1363    QFont font = variant.value<QFont>();
1364    QCOMPARE(page->settings()->fontFamily(QWebSettings::SerifFont), font.family());
1365
1366    QList<QInputMethodEvent::Attribute> inputAttributes;
1367
1368    //Insert text.
1369    {
1370        QInputMethodEvent eventText("QtWebKit", inputAttributes);
1371        QSignalSpy signalSpy(page, SIGNAL(microFocusChanged()));
1372        page->event(&eventText);
1373        QCOMPARE(signalSpy.count(), 0);
1374    }
1375
1376    {
1377        QInputMethodEvent eventText("", inputAttributes);
1378        eventText.setCommitString(QString("QtWebKit"), 0, 0);
1379        page->event(&eventText);
1380    }
1381
1382#if QT_VERSION >= 0x040600
1383    //ImMaximumTextLength
1384    variant = page->inputMethodQuery(Qt::ImMaximumTextLength);
1385    QCOMPARE(20, variant.toInt());
1386
1387    //Set selection
1388    inputAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 3, 2, QVariant());
1389    QInputMethodEvent eventSelection("",inputAttributes);
1390    page->event(&eventSelection);
1391
1392    //ImAnchorPosition
1393    variant = page->inputMethodQuery(Qt::ImAnchorPosition);
1394    int anchorPosition =  variant.toInt();
1395    QCOMPARE(anchorPosition, 3);
1396
1397    //ImCursorPosition
1398    variant = page->inputMethodQuery(Qt::ImCursorPosition);
1399    int cursorPosition =  variant.toInt();
1400    QCOMPARE(cursorPosition, 5);
1401
1402    //ImCurrentSelection
1403    variant = page->inputMethodQuery(Qt::ImCurrentSelection);
1404    QString selectionValue = variant.value<QString>();
1405    QCOMPARE(selectionValue, QString("eb"));
1406#endif
1407
1408    //ImSurroundingText
1409    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1410    QString value = variant.value<QString>();
1411    QCOMPARE(value, QString("QtWebKit"));
1412
1413#if QT_VERSION >= 0x040600
1414    {
1415        QList<QInputMethodEvent::Attribute> attributes;
1416        // Clear the selection, so the next test does not clear any contents.
1417        QInputMethodEvent::Attribute newSelection(QInputMethodEvent::Selection, 0, 0, QVariant());
1418        attributes.append(newSelection);
1419        QInputMethodEvent event("composition", attributes);
1420        page->event(&event);
1421    }
1422
1423    // A ongoing composition should not change the surrounding text before it is committed.
1424    variant = page->inputMethodQuery(Qt::ImSurroundingText);
1425    value = variant.value<QString>();
1426    QCOMPARE(value, QString("QtWebKit"));
1427#endif
1428
1429    //ImhHiddenText
1430    QMouseEvent evpresPassword(QEvent::MouseButtonPress, inputs.at(1).geometry().center(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1431    page->event(&evpresPassword);
1432    QMouseEvent evrelPassword(QEvent::MouseButtonRelease, inputs.at(1).geometry().center(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1433    page->event(&evrelPassword);
1434
1435    QVERIFY(inputMethodEnabled(view));
1436#if QT_VERSION >= 0x040600
1437    QVERIFY(inputMethodHints(view) & Qt::ImhHiddenText);
1438
1439    page->event(&evpres);
1440    page->event(&evrel);
1441    QVERIFY(!(inputMethodHints(view) & Qt::ImhHiddenText));
1442#endif
1443
1444    page->mainFrame()->setHtml("<html><body><p>nothing to input here");
1445    viewEventSpy.clear();
1446
1447    QWebElement para = page->mainFrame()->findFirstElement("p");
1448    {
1449        QMouseEvent evpres(QEvent::MouseButtonPress, para.geometry().center(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1450        page->event(&evpres);
1451        QMouseEvent evrel(QEvent::MouseButtonRelease, para.geometry().center(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1452        page->event(&evrel);
1453    }
1454
1455#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1456    QVERIFY(!viewEventSpy.contains(QEvent::RequestSoftwareInputPanel));
1457#endif
1458
1459    delete container;
1460}
1461
1462// import a little DRT helper function to trigger the garbage collector
1463void QWEBKIT_EXPORT qt_drt_garbageCollector_collect();
1464
1465void tst_QWebPage::protectBindingsRuntimeObjectsFromCollector()
1466{
1467    QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool)));
1468
1469    PluginPage* newPage = new PluginPage(m_view);
1470    m_view->setPage(newPage);
1471
1472    m_view->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
1473
1474    m_view->setHtml(QString("<html><body><object type='application/x-qt-plugin' classid='lineedit' id='mylineedit'/></body></html>"));
1475    QTRY_COMPARE(loadSpy.count(), 1);
1476
1477    newPage->mainFrame()->evaluateJavaScript("function testme(text) { var lineedit = document.getElementById('mylineedit'); lineedit.setText(text); lineedit.selectAll(); }");
1478
1479    newPage->mainFrame()->evaluateJavaScript("testme('foo')");
1480
1481    qt_drt_garbageCollector_collect();
1482
1483    // don't crash!
1484    newPage->mainFrame()->evaluateJavaScript("testme('bar')");
1485}
1486
1487void tst_QWebPage::localURLSchemes()
1488{
1489    int i = QWebSecurityOrigin::localSchemes().size();
1490
1491    QWebSecurityOrigin::removeLocalScheme("file");
1492    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
1493    QWebSecurityOrigin::addLocalScheme("file");
1494    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
1495
1496    QWebSecurityOrigin::removeLocalScheme("qrc");
1497    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i - 1);
1498    QWebSecurityOrigin::addLocalScheme("qrc");
1499    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
1500
1501    QString myscheme = "myscheme";
1502    QWebSecurityOrigin::addLocalScheme(myscheme);
1503    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i + 1);
1504    QVERIFY(QWebSecurityOrigin::localSchemes().contains(myscheme));
1505    QWebSecurityOrigin::removeLocalScheme(myscheme);
1506    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
1507    QWebSecurityOrigin::removeLocalScheme(myscheme);
1508    QTRY_COMPARE(QWebSecurityOrigin::localSchemes().size(), i);
1509}
1510
1511static inline bool testFlag(QWebPage& webPage, QWebSettings::WebAttribute settingAttribute, const QString& jsObjectName, bool settingValue)
1512{
1513    webPage.settings()->setAttribute(settingAttribute, settingValue);
1514    return webPage.mainFrame()->evaluateJavaScript(QString("(window.%1 != undefined)").arg(jsObjectName)).toBool();
1515}
1516
1517void tst_QWebPage::testOptionalJSObjects()
1518{
1519    // Once a feature is enabled and the JS object is accessed turning off the setting will not turn off
1520    // the visibility of the JS object any more. For this reason this test uses two QWebPage instances.
1521    // Part of the test is to make sure that the QWebPage instances do not interfere with each other so turning on
1522    // a feature for one instance will not turn it on for another.
1523
1524    QWebPage webPage1;
1525    QWebPage webPage2;
1526
1527    webPage1.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl());
1528    webPage2.currentFrame()->setHtml(QString("<html><body>test</body></html>"), QUrl());
1529
1530    QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue);
1531    QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false);
1532    QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", true),  true);
1533    QEXPECT_FAIL("","Feature enabled/disabled checking problem. Look at bugs.webkit.org/show_bug.cgi?id=29867", Continue);
1534    QCOMPARE(testFlag(webPage1, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), false);
1535    QCOMPARE(testFlag(webPage2, QWebSettings::OfflineWebApplicationCacheEnabled, "applicationCache", false), true);
1536
1537    QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false);
1538    QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", true),  true);
1539    QCOMPARE(testFlag(webPage1, QWebSettings::LocalStorageEnabled, "localStorage", false), false);
1540    QCOMPARE(testFlag(webPage2, QWebSettings::LocalStorageEnabled, "localStorage", false), true);
1541}
1542
1543void tst_QWebPage::testEnablePersistentStorage()
1544{
1545    QWebPage webPage;
1546
1547    // By default all persistent options should be disabled
1548    QCOMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), false);
1549    QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), false);
1550    QCOMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), false);
1551    QVERIFY(webPage.settings()->iconDatabasePath().isEmpty());
1552
1553    QWebSettings::enablePersistentStorage();
1554
1555
1556    QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::LocalStorageEnabled), true);
1557    QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled), true);
1558    QTRY_COMPARE(webPage.settings()->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled), true);
1559
1560    QTRY_VERIFY(!webPage.settings()->offlineStoragePath().isEmpty());
1561    QTRY_VERIFY(!webPage.settings()->offlineWebApplicationCachePath().isEmpty());
1562    QTRY_VERIFY(!webPage.settings()->iconDatabasePath().isEmpty());
1563}
1564
1565void tst_QWebPage::defaultTextEncoding()
1566{
1567    QWebFrame* mainFrame = m_page->mainFrame();
1568
1569    QString defaultCharset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
1570    QVERIFY(!defaultCharset.isEmpty());
1571    QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), defaultCharset);
1572
1573    m_page->settings()->setDefaultTextEncoding(QString("utf-8"));
1574    QString charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
1575    QCOMPARE(charset, QString("utf-8"));
1576    QCOMPARE(m_page->settings()->defaultTextEncoding(), charset);
1577
1578    m_page->settings()->setDefaultTextEncoding(QString());
1579    charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
1580    QVERIFY(!charset.isEmpty());
1581    QCOMPARE(charset, defaultCharset);
1582
1583    QWebSettings::globalSettings()->setDefaultTextEncoding(QString("utf-8"));
1584    charset = mainFrame->evaluateJavaScript("document.defaultCharset").toString();
1585    QCOMPARE(charset, QString("utf-8"));
1586    QCOMPARE(QWebSettings::globalSettings()->defaultTextEncoding(), charset);
1587}
1588
1589class ErrorPage : public QWebPage
1590{
1591public:
1592
1593    ErrorPage(QWidget* parent = 0): QWebPage(parent)
1594    {
1595    }
1596
1597    virtual bool supportsExtension(Extension extension) const
1598    {
1599        return extension == ErrorPageExtension;
1600    }
1601
1602    virtual bool extension(Extension, const ExtensionOption* option, ExtensionReturn* output)
1603    {
1604        ErrorPageExtensionReturn* errorPage = static_cast<ErrorPageExtensionReturn*>(output);
1605
1606        errorPage->content = "data:text/html,error";
1607        return true;
1608    }
1609};
1610
1611void tst_QWebPage::errorPageExtension()
1612{
1613    ErrorPage* page = new ErrorPage;
1614    m_view->setPage(page);
1615
1616    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
1617
1618    m_view->setUrl(QUrl("data:text/html,foo"));
1619    QTRY_COMPARE(spyLoadFinished.count(), 1);
1620
1621    page->mainFrame()->setUrl(QUrl("http://non.existent/url"));
1622    QTRY_COMPARE(spyLoadFinished.count(), 2);
1623    QCOMPARE(page->mainFrame()->toPlainText(), QString("data:text/html,error"));
1624    QCOMPARE(page->history()->count(), 2);
1625    QCOMPARE(page->history()->currentItem().url(), QUrl("http://non.existent/url"));
1626    QCOMPARE(page->history()->canGoBack(), true);
1627    QCOMPARE(page->history()->canGoForward(), false);
1628
1629    page->triggerAction(QWebPage::Back);
1630    QTRY_COMPARE(page->history()->canGoBack(), false);
1631    QTRY_COMPARE(page->history()->canGoForward(), true);
1632
1633    page->triggerAction(QWebPage::Forward);
1634    QTRY_COMPARE(page->history()->canGoBack(), true);
1635    QTRY_COMPARE(page->history()->canGoForward(), false);
1636
1637    page->triggerAction(QWebPage::Back);
1638    QTRY_COMPARE(page->history()->canGoBack(), false);
1639    QTRY_COMPARE(page->history()->canGoForward(), true);
1640    QTRY_COMPARE(page->history()->currentItem().url(), QUrl("data:text/html,foo"));
1641
1642    m_view->setPage(0);
1643}
1644
1645void tst_QWebPage::errorPageExtensionInIFrames()
1646{
1647    ErrorPage* page = new ErrorPage;
1648    m_view->setPage(page);
1649
1650    m_view->setHtml(QString("data:text/html,"
1651                            "<h1>h1</h1>"
1652                            "<iframe src='data:text/html,<p/>p'></iframe>"
1653                            "<iframe src='non-existent.html'></iframe>"));
1654    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
1655    QTRY_COMPARE(spyLoadFinished.count(), 1);
1656
1657    QCOMPARE(page->mainFrame()->childFrames()[1]->toPlainText(), QString("data:text/html,error"));
1658
1659    m_view->setPage(0);
1660}
1661
1662void tst_QWebPage::errorPageExtensionInFrameset()
1663{
1664    ErrorPage* page = new ErrorPage;
1665    m_view->setPage(page);
1666
1667    m_view->load(QUrl("qrc:///resources/index.html"));
1668
1669    QSignalSpy spyLoadFinished(m_view, SIGNAL(loadFinished(bool)));
1670    QTRY_COMPARE(spyLoadFinished.count(), 1);
1671    QCOMPARE(page->mainFrame()->childFrames()[1]->toPlainText(), QString("data:text/html,error"));
1672
1673    m_view->setPage(0);
1674}
1675
1676void tst_QWebPage::crashTests_LazyInitializationOfMainFrame()
1677{
1678    {
1679        QWebPage webPage;
1680    }
1681    {
1682        QWebPage webPage;
1683        webPage.selectedText();
1684    }
1685    {
1686        QWebPage webPage;
1687        webPage.triggerAction(QWebPage::Back, true);
1688    }
1689    {
1690        QWebPage webPage;
1691        QPoint pos(10,10);
1692        webPage.updatePositionDependentActions(pos);
1693    }
1694}
1695
1696static void takeScreenshot(QWebPage* page)
1697{
1698    QWebFrame* mainFrame = page->mainFrame();
1699    page->setViewportSize(mainFrame->contentsSize());
1700    QImage image(page->viewportSize(), QImage::Format_ARGB32);
1701    QPainter painter(&image);
1702    mainFrame->render(&painter);
1703    painter.end();
1704}
1705
1706void tst_QWebPage::screenshot_data()
1707{
1708    QTest::addColumn<QString>("html");
1709    QTest::newRow("WithoutPlugin") << "<html><body id='b'>text</body></html>";
1710    QTest::newRow("WindowedPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf'></embed></body></html>");
1711    QTest::newRow("WindowlessPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf' wmode='transparent'></embed></body></html>");
1712}
1713
1714void tst_QWebPage::screenshot()
1715{
1716    if (!QDir(TESTS_SOURCE_DIR).exists())
1717        QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
1718
1719    QDir::setCurrent(TESTS_SOURCE_DIR);
1720
1721    QFETCH(QString, html);
1722    QWebPage* page = new QWebPage;
1723    page->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
1724    QWebFrame* mainFrame = page->mainFrame();
1725    mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR));
1726    ::waitForSignal(mainFrame, SIGNAL(loadFinished(bool)), 2000);
1727
1728    // take screenshot without a view
1729    takeScreenshot(page);
1730
1731    QWebView* view = new QWebView;
1732    view->setPage(page);
1733
1734    // take screenshot when attached to a view
1735    takeScreenshot(page);
1736
1737    delete page;
1738    delete view;
1739
1740    QDir::setCurrent(QApplication::applicationDirPath());
1741}
1742
1743void tst_QWebPage::originatingObjectInNetworkRequests()
1744{
1745    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
1746    m_page->setNetworkAccessManager(networkManager);
1747    networkManager->requests.clear();
1748
1749    m_view->setHtml(QString("data:text/html,<frameset cols=\"25%,75%\"><frame src=\"data:text/html,"
1750                            "<head><meta http-equiv='refresh' content='1'></head>foo \">"
1751                            "<frame src=\"data:text/html,bar\"></frameset>"), QUrl());
1752    QVERIFY(::waitForSignal(m_view, SIGNAL(loadFinished(bool))));
1753
1754    QCOMPARE(networkManager->requests.count(), 2);
1755
1756    QList<QWebFrame*> childFrames = m_page->mainFrame()->childFrames();
1757    QCOMPARE(childFrames.count(), 2);
1758
1759#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
1760    for (int i = 0; i < 2; ++i)
1761        QVERIFY(qobject_cast<QWebFrame*>(networkManager->requests.at(i).originatingObject()) == childFrames.at(i));
1762#endif
1763}
1764
1765/**
1766 * Test fixups for https://bugs.webkit.org/show_bug.cgi?id=30914
1767 *
1768 * From JS we test the following conditions.
1769 *
1770 *   OK     + QString() => SUCCESS, empty string (but not null)
1771 *   OK     + "text"    => SUCCESS, "text"
1772 *   CANCEL + QString() => CANCEL, null string
1773 *   CANCEL + "text"    => CANCEL, null string
1774 */
1775class JSPromptPage : public QWebPage {
1776    Q_OBJECT
1777public:
1778    JSPromptPage()
1779    {}
1780
1781    bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result)
1782    {
1783        if (msg == QLatin1String("test1")) {
1784            *result = QString();
1785            return true;
1786        } else if (msg == QLatin1String("test2")) {
1787            *result = QLatin1String("text");
1788            return true;
1789        } else if (msg == QLatin1String("test3")) {
1790            *result = QString();
1791            return false;
1792        } else if (msg == QLatin1String("test4")) {
1793            *result = QLatin1String("text");
1794            return false;
1795        }
1796
1797        qFatal("Unknown msg.");
1798        return QWebPage::javaScriptPrompt(frame, msg, defaultValue, result);
1799    }
1800};
1801
1802void tst_QWebPage::testJSPrompt()
1803{
1804    JSPromptPage page;
1805    bool res;
1806
1807    // OK + QString()
1808    res = page.mainFrame()->evaluateJavaScript(
1809            "var retval = prompt('test1');"
1810            "retval=='' && retval.length == 0;").toBool();
1811    QVERIFY(res);
1812
1813    // OK + "text"
1814    res = page.mainFrame()->evaluateJavaScript(
1815            "var retval = prompt('test2');"
1816            "retval=='text' && retval.length == 4;").toBool();
1817    QVERIFY(res);
1818
1819    // Cancel + QString()
1820    res = page.mainFrame()->evaluateJavaScript(
1821            "var retval = prompt('test3');"
1822            "retval===null;").toBool();
1823    QVERIFY(res);
1824
1825    // Cancel + "text"
1826    res = page.mainFrame()->evaluateJavaScript(
1827            "var retval = prompt('test4');"
1828            "retval===null;").toBool();
1829    QVERIFY(res);
1830}
1831
1832class TestModalPage : public QWebPage
1833{
1834    Q_OBJECT
1835public:
1836    TestModalPage(QObject* parent = 0) : QWebPage(parent) {
1837    }
1838    virtual QWebPage* createWindow(WebWindowType) {
1839        QWebPage* page = new TestModalPage();
1840        connect(page, SIGNAL(windowCloseRequested()), page, SLOT(deleteLater()));
1841        return page;
1842    }
1843};
1844
1845void tst_QWebPage::showModalDialog()
1846{
1847    TestModalPage page;
1848    page.mainFrame()->setHtml(QString("<html></html>"));
1849    QString res = page.mainFrame()->evaluateJavaScript("window.showModalDialog('javascript:window.returnValue=dialogArguments; window.close();', 'This is a test');").toString();
1850    QCOMPARE(res, QString("This is a test"));
1851}
1852
1853QTEST_MAIN(tst_QWebPage)
1854#include "tst_qwebpage.moc"
1855