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