1/*
2    Copyright (C) 2008,2009 Nokia Corporation and/or its subsidiary(-ies)
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20
21#include <QtTest/QtTest>
22
23#include <qwebpage.h>
24#include <qwebelement.h>
25#include <qwebview.h>
26#include <qwebframe.h>
27#include <qwebhistory.h>
28#include <QAbstractItemView>
29#include <QApplication>
30#include <QComboBox>
31#include <QPaintEngine>
32#include <QPicture>
33#include <QRegExp>
34#include <QNetworkRequest>
35#include <QNetworkReply>
36#include <QTextCodec>
37#ifndef QT_NO_OPENSSL
38#include <qsslerror.h>
39#endif
40#include "../util.h"
41
42class tst_QWebFrame : public QObject
43{
44    Q_OBJECT
45
46public:
47    bool eventFilter(QObject* watched, QEvent* event);
48
49public Q_SLOTS:
50    void init();
51    void cleanup();
52
53private Q_SLOTS:
54    void horizontalScrollAfterBack();
55    void symmetricUrl();
56    void progressSignal();
57    void urlChange();
58    void requestedUrl();
59    void requestedUrlAfterSetAndLoadFailures();
60    void javaScriptWindowObjectCleared_data();
61    void javaScriptWindowObjectCleared();
62    void javaScriptWindowObjectClearedOnEvaluate();
63    void setHtml();
64    void setHtmlWithImageResource();
65    void setHtmlWithStylesheetResource();
66    void setHtmlWithBaseURL();
67    void setHtmlWithJSAlert();
68    void ipv6HostEncoding();
69    void metaData();
70#if !defined(QT_NO_COMBOBOX)
71    void popupFocus();
72#endif
73    void inputFieldFocus();
74    void hitTestContent();
75    void baseUrl_data();
76    void baseUrl();
77    void hasSetFocus();
78    void renderGeometry();
79    void renderHints();
80    void scrollPosition();
81    void scrollToAnchor();
82    void scrollbarsOff();
83    void evaluateWillCauseRepaint();
84    void setContent_data();
85    void setContent();
86    void setCacheLoadControlAttribute();
87    void setUrlWithPendingLoads();
88    void setUrlWithFragment_data();
89    void setUrlWithFragment();
90    void setUrlToEmpty();
91    void setUrlToInvalid();
92    void setUrlHistory();
93    void setUrlUsingStateObject();
94    void setUrlSameUrl();
95    void setUrlThenLoads_data();
96    void setUrlThenLoads();
97    void loadFinishedAfterNotFoundError();
98    void loadInSignalHandlers_data();
99    void loadInSignalHandlers();
100
101private:
102    QWebView* m_view;
103    QWebPage* m_page;
104    QWebView* m_inputFieldsTestView;
105    int m_inputFieldTestPaintCount;
106};
107
108bool tst_QWebFrame::eventFilter(QObject* watched, QEvent* event)
109{
110    // used on the inputFieldFocus test
111    if (watched == m_inputFieldsTestView) {
112        if (event->type() == QEvent::Paint)
113            m_inputFieldTestPaintCount++;
114    }
115    return QObject::eventFilter(watched, event);
116}
117
118void tst_QWebFrame::init()
119{
120    m_view = new QWebView();
121    m_page = m_view->page();
122}
123
124void tst_QWebFrame::cleanup()
125{
126    delete m_view;
127}
128
129void tst_QWebFrame::symmetricUrl()
130{
131    QVERIFY(m_view->url().isEmpty());
132
133    QCOMPARE(m_view->history()->count(), 0);
134
135    QUrl dataUrl("data:text/html,<h1>Test");
136
137    m_view->setUrl(dataUrl);
138    QCOMPARE(m_view->url(), dataUrl);
139    QCOMPARE(m_view->history()->count(), 0);
140
141    // loading is _not_ immediate, so the text isn't set just yet.
142    QVERIFY(m_view->page()->mainFrame()->toPlainText().isEmpty());
143
144    ::waitForSignal(m_view, SIGNAL(loadFinished(bool)));
145
146    QCOMPARE(m_view->history()->count(), 1);
147    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test"));
148
149    QUrl dataUrl2("data:text/html,<h1>Test2");
150    QUrl dataUrl3("data:text/html,<h1>Test3");
151
152    m_view->setUrl(dataUrl2);
153    m_view->setUrl(dataUrl3);
154
155    QCOMPARE(m_view->url(), dataUrl3);
156
157    ::waitForSignal(m_view, SIGNAL(loadFinished(bool)));
158
159    QCOMPARE(m_view->history()->count(), 2);
160
161    QCOMPARE(m_view->page()->mainFrame()->toPlainText(), QString("Test3"));
162}
163
164void tst_QWebFrame::progressSignal()
165{
166    QSignalSpy progressSpy(m_view, SIGNAL(loadProgress(int)));
167
168    QUrl dataUrl("data:text/html,<h1>Test");
169    m_view->setUrl(dataUrl);
170
171    ::waitForSignal(m_view, SIGNAL(loadFinished(bool)));
172
173    QVERIFY(progressSpy.size() >= 2);
174
175    // WebKit defines initialProgressValue as 10%, not 0%
176    QCOMPARE(progressSpy.first().first().toInt(), 10);
177
178    // But we always end at 100%
179    QCOMPARE(progressSpy.last().first().toInt(), 100);
180}
181
182void tst_QWebFrame::urlChange()
183{
184    QSignalSpy urlSpy(m_page->mainFrame(), SIGNAL(urlChanged(QUrl)));
185
186    QUrl dataUrl("data:text/html,<h1>Test");
187    m_view->setUrl(dataUrl);
188
189    ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl)));
190
191    QCOMPARE(urlSpy.size(), 1);
192
193    QUrl dataUrl2("data:text/html,<html><head><title>title</title></head><body><h1>Test</body></html>");
194    m_view->setUrl(dataUrl2);
195
196    ::waitForSignal(m_page->mainFrame(), SIGNAL(urlChanged(QUrl)));
197
198    QCOMPARE(urlSpy.size(), 2);
199}
200
201class FakeReply : public QNetworkReply {
202    Q_OBJECT
203
204public:
205    static const QUrl urlFor404ErrorWithoutContents;
206
207    FakeReply(const QNetworkRequest& request, QObject* parent = 0)
208        : QNetworkReply(parent)
209    {
210        setOperation(QNetworkAccessManager::GetOperation);
211        setRequest(request);
212        setUrl(request.url());
213        if (request.url() == QUrl("qrc:/test1.html")) {
214            setHeader(QNetworkRequest::LocationHeader, QString("qrc:/test2.html"));
215            setAttribute(QNetworkRequest::RedirectionTargetAttribute, QUrl("qrc:/test2.html"));
216            QTimer::singleShot(0, this, SLOT(continueRedirect()));
217        }
218#ifndef QT_NO_OPENSSL
219        else if (request.url() == QUrl("qrc:/fake-ssl-error.html")) {
220            setError(QNetworkReply::SslHandshakeFailedError, tr("Fake error!"));
221            QTimer::singleShot(0, this, SLOT(continueError()));
222        }
223#endif
224        else if (request.url().host() == QLatin1String("abcdef.abcdef")) {
225            setError(QNetworkReply::HostNotFoundError, tr("Invalid URL"));
226            QTimer::singleShot(0, this, SLOT(continueError()));
227        } else if (request.url() == FakeReply::urlFor404ErrorWithoutContents) {
228            setError(QNetworkReply::ContentNotFoundError, "Not found");
229            setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404);
230            QTimer::singleShot(0, this, SLOT(continueError()));
231        }
232
233        open(QIODevice::ReadOnly);
234    }
235    ~FakeReply()
236    {
237        close();
238    }
239    virtual void abort() {}
240    virtual void close() {}
241
242protected:
243    qint64 readData(char*, qint64)
244    {
245        return 0;
246    }
247
248private Q_SLOTS:
249    void continueRedirect()
250    {
251        emit metaDataChanged();
252        emit finished();
253    }
254
255    void continueError()
256    {
257        emit error(this->error());
258        emit finished();
259    }
260};
261
262const QUrl FakeReply::urlFor404ErrorWithoutContents = QUrl("http://this.will/return-http-404-error-without-contents.html");
263
264class FakeNetworkManager : public QNetworkAccessManager {
265    Q_OBJECT
266
267public:
268    FakeNetworkManager(QObject* parent) : QNetworkAccessManager(parent) { }
269
270protected:
271    virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
272    {
273        QString url = request.url().toString();
274        if (op == QNetworkAccessManager::GetOperation) {
275#ifndef QT_NO_OPENSSL
276            if (url == "qrc:/fake-ssl-error.html") {
277                FakeReply* reply = new FakeReply(request, this);
278                QList<QSslError> errors;
279                emit sslErrors(reply, errors << QSslError(QSslError::UnspecifiedError));
280                return reply;
281            }
282#endif
283            if (url == "qrc:/test1.html" || url == "http://abcdef.abcdef/" || request.url() == FakeReply::urlFor404ErrorWithoutContents)
284                return new FakeReply(request, this);
285        }
286
287        return QNetworkAccessManager::createRequest(op, request, outgoingData);
288    }
289};
290
291void tst_QWebFrame::requestedUrl()
292{
293    QWebPage page;
294    QWebFrame* frame = page.mainFrame();
295
296    // in few seconds, the image should be completely loaded
297    QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
298    FakeNetworkManager* networkManager = new FakeNetworkManager(&page);
299    page.setNetworkAccessManager(networkManager);
300
301    frame->setUrl(QUrl("qrc:/test1.html"));
302    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
303    QCOMPARE(spy.count(), 1);
304    QCOMPARE(frame->requestedUrl(), QUrl("qrc:/test1.html"));
305    QCOMPARE(frame->url(), QUrl("qrc:/test2.html"));
306
307    frame->setUrl(QUrl("qrc:/non-existent.html"));
308    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
309    QCOMPARE(spy.count(), 2);
310    QCOMPARE(frame->requestedUrl(), QUrl("qrc:/non-existent.html"));
311    QCOMPARE(frame->url(), QUrl("qrc:/non-existent.html"));
312
313    frame->setUrl(QUrl("http://abcdef.abcdef"));
314    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
315    QCOMPARE(spy.count(), 3);
316    QCOMPARE(frame->requestedUrl(), QUrl("http://abcdef.abcdef/"));
317    QCOMPARE(frame->url(), QUrl("http://abcdef.abcdef/"));
318
319#ifndef QT_NO_OPENSSL
320    qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
321    qRegisterMetaType<QNetworkReply* >("QNetworkReply*");
322
323    QSignalSpy spy2(page.networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)));
324    frame->setUrl(QUrl("qrc:/fake-ssl-error.html"));
325    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
326    QCOMPARE(spy2.count(), 1);
327    QCOMPARE(frame->requestedUrl(), QUrl("qrc:/fake-ssl-error.html"));
328    QCOMPARE(frame->url(), QUrl("qrc:/fake-ssl-error.html"));
329#endif
330}
331
332void tst_QWebFrame::requestedUrlAfterSetAndLoadFailures()
333{
334    QWebPage page;
335    QWebFrame* frame = page.mainFrame();
336
337    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
338
339    const QUrl first("http://abcdef.abcdef/");
340    frame->setUrl(first);
341    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
342    QCOMPARE(frame->url(), first);
343    QCOMPARE(frame->requestedUrl(), first);
344    QVERIFY(!spy.at(0).first().toBool());
345
346    const QUrl second("http://abcdef.abcdef/another_page.html");
347    QVERIFY(first != second);
348
349    frame->load(second);
350    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
351    QCOMPARE(frame->url(), first);
352    QCOMPARE(frame->requestedUrl(), second);
353    QVERIFY(!spy.at(1).first().toBool());
354}
355
356void tst_QWebFrame::javaScriptWindowObjectCleared_data()
357{
358    QTest::addColumn<QString>("html");
359    QTest::addColumn<int>("signalCount");
360    QTest::newRow("with <script>") << "<html><body><script>i=0</script><p>hello world</p></body></html>" << 1;
361    // NOTE: Empty scripts no longer cause this signal to be emitted.
362    QTest::newRow("with empty <script>") << "<html><body><script></script><p>hello world</p></body></html>" << 0;
363    QTest::newRow("without <script>") << "<html><body><p>hello world</p></body></html>" << 0;
364}
365
366void tst_QWebFrame::javaScriptWindowObjectCleared()
367{
368    QWebPage page;
369    QWebFrame* frame = page.mainFrame();
370    QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared()));
371    QFETCH(QString, html);
372    frame->setHtml(html);
373
374    QFETCH(int, signalCount);
375    QCOMPARE(spy.count(), signalCount);
376}
377
378void tst_QWebFrame::javaScriptWindowObjectClearedOnEvaluate()
379{
380    QWebPage page;
381    QWebFrame* frame = page.mainFrame();
382    QSignalSpy spy(frame, SIGNAL(javaScriptWindowObjectCleared()));
383    frame->setHtml("<html></html>");
384    QCOMPARE(spy.count(), 0);
385    frame->evaluateJavaScript("var a = 'a';");
386    QCOMPARE(spy.count(), 1);
387    // no new clear for a new script:
388    frame->evaluateJavaScript("var a = 1;");
389    QCOMPARE(spy.count(), 1);
390}
391
392void tst_QWebFrame::setHtml()
393{
394    QString html("<html><head></head><body><p>hello world</p></body></html>");
395    QSignalSpy spy(m_view->page(), SIGNAL(loadFinished(bool)));
396    m_view->page()->mainFrame()->setHtml(html);
397    QCOMPARE(m_view->page()->mainFrame()->toHtml(), html);
398    QCOMPARE(spy.count(), 1);
399}
400
401void tst_QWebFrame::setHtmlWithImageResource()
402{
403    // By default, only security origins of local files can load local resources.
404    // So we should specify baseUrl to be a local file in order to get a proper origin and load the local image.
405
406    QLatin1String html("<html><body><p>hello world</p><img src='qrc:/image.png'/></body></html>");
407    QWebPage page;
408    QWebFrame* frame = page.mainFrame();
409
410    frame->setHtml(html, QUrl(QLatin1String("file:///path/to/file")));
411    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
412
413    QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1);
414    QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128);
415    QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128);
416
417    // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources.
418
419    frame->setHtml(html);
420    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
421    QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1);
422    QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 0);
423    QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 0);
424}
425
426void tst_QWebFrame::setHtmlWithStylesheetResource()
427{
428    // By default, only security origins of local files can load local resources.
429    // So we should specify baseUrl to be a local file in order to be able to download the local stylesheet.
430
431    const char* htmlData =
432        "<html>"
433            "<head>"
434                "<link rel='stylesheet' href='qrc:/style.css' type='text/css' />"
435            "</head>"
436            "<body>"
437                "<p id='idP'>some text</p>"
438            "</body>"
439        "</html>";
440    QLatin1String html(htmlData);
441    QWebPage page;
442    QWebFrame* frame = page.mainFrame();
443    QWebElement webElement;
444
445    frame->setHtml(html, QUrl(QLatin1String("qrc:///file")));
446    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
447    webElement = frame->documentElement().findFirst("p");
448    QCOMPARE(webElement.styleProperty("color", QWebElement::CascadedStyle), QLatin1String("red"));
449
450    // Now we test the opposite: without a baseUrl as a local file, we cannot request local resources.
451
452    frame->setHtml(html, QUrl(QLatin1String("http://www.example.com/")));
453    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
454    webElement = frame->documentElement().findFirst("p");
455    QCOMPARE(webElement.styleProperty("color", QWebElement::CascadedStyle), QString());
456}
457
458void tst_QWebFrame::setHtmlWithBaseURL()
459{
460    // This tests if baseUrl is indeed affecting the relative paths from resources.
461    // As we are using a local file as baseUrl, its security origin should be able to load local resources.
462
463    if (!QDir(TESTS_SOURCE_DIR).exists())
464        W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
465
466    QDir::setCurrent(TESTS_SOURCE_DIR);
467
468    QString html("<html><body><p>hello world</p><img src='resources/image2.png'/></body></html>");
469
470    QWebPage page;
471    QWebFrame* frame = page.mainFrame();
472
473    // in few seconds, the image should be completey loaded
474    QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
475
476    frame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR));
477    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
478    QCOMPARE(spy.count(), 1);
479
480    QCOMPARE(frame->evaluateJavaScript("document.images.length").toInt(), 1);
481    QCOMPARE(frame->evaluateJavaScript("document.images[0].width").toInt(), 128);
482    QCOMPARE(frame->evaluateJavaScript("document.images[0].height").toInt(), 128);
483
484    // no history item has to be added.
485    QCOMPARE(m_view->page()->history()->count(), 0);
486}
487
488class MyPage : public QWebPage
489{
490public:
491    MyPage() :  QWebPage(), alerts(0) {}
492    int alerts;
493
494protected:
495    virtual void javaScriptAlert(QWebFrame*, const QString& msg)
496    {
497        alerts++;
498        QCOMPARE(msg, QString("foo"));
499        // Should not be enough to trigger deferred loading, since we've upped the HTML
500        // tokenizer delay in the Qt frameloader. See HTMLTokenizer::continueProcessing()
501        QTest::qWait(1000);
502    }
503};
504
505void tst_QWebFrame::setHtmlWithJSAlert()
506{
507    QString html("<html><head></head><body><script>alert('foo');</script><p>hello world</p></body></html>");
508    MyPage page;
509    m_view->setPage(&page);
510    page.mainFrame()->setHtml(html);
511    QCOMPARE(page.alerts, 1);
512    QCOMPARE(m_view->page()->mainFrame()->toHtml(), html);
513}
514
515class TestNetworkManager : public QNetworkAccessManager
516{
517public:
518    TestNetworkManager(QObject* parent) : QNetworkAccessManager(parent) {}
519
520    QList<QUrl> requestedUrls;
521
522protected:
523    virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest &request, QIODevice* outgoingData) {
524        requestedUrls.append(request.url());
525        QNetworkRequest redirectedRequest = request;
526        redirectedRequest.setUrl(QUrl("data:text/html,<p>hello"));
527        return QNetworkAccessManager::createRequest(op, redirectedRequest, outgoingData);
528    }
529};
530
531void tst_QWebFrame::ipv6HostEncoding()
532{
533    TestNetworkManager* networkManager = new TestNetworkManager(m_page);
534    m_page->setNetworkAccessManager(networkManager);
535    networkManager->requestedUrls.clear();
536
537    QUrl baseUrl = QUrl::fromEncoded("http://[::1]/index.html");
538    m_view->setHtml("<p>Hi", baseUrl);
539    m_view->page()->mainFrame()->evaluateJavaScript("var r = new XMLHttpRequest();"
540            "r.open('GET', 'http://[::1]/test.xml', false);"
541            "r.send(null);"
542            );
543    QCOMPARE(networkManager->requestedUrls.count(), 1);
544    QCOMPARE(networkManager->requestedUrls.at(0), QUrl::fromEncoded("http://[::1]/test.xml"));
545}
546
547void tst_QWebFrame::metaData()
548{
549    m_view->setHtml("<html>"
550                    "    <head>"
551                    "        <meta name=\"description\" content=\"Test description\">"
552                    "        <meta name=\"keywords\" content=\"HTML, JavaScript, Css\">"
553                    "    </head>"
554                    "</html>");
555
556    QMultiMap<QString, QString> metaData = m_view->page()->mainFrame()->metaData();
557
558    QCOMPARE(metaData.count(), 2);
559
560    QCOMPARE(metaData.value("description"), QString("Test description"));
561    QCOMPARE(metaData.value("keywords"), QString("HTML, JavaScript, Css"));
562    QCOMPARE(metaData.value("nonexistant"), QString());
563
564    m_view->setHtml("<html>"
565                    "    <head>"
566                    "        <meta name=\"samekey\" content=\"FirstValue\">"
567                    "        <meta name=\"samekey\" content=\"SecondValue\">"
568                    "    </head>"
569                    "</html>");
570
571    metaData = m_view->page()->mainFrame()->metaData();
572
573    QCOMPARE(metaData.count(), 2);
574
575    QStringList values = metaData.values("samekey");
576    QCOMPARE(values.count(), 2);
577
578    QVERIFY(values.contains("FirstValue"));
579    QVERIFY(values.contains("SecondValue"));
580
581    QCOMPARE(metaData.value("nonexistant"), QString());
582}
583
584#if !defined(QT_NO_COMBOBOX)
585void tst_QWebFrame::popupFocus()
586{
587    QWebView view;
588    view.setHtml("<html>"
589                 "    <body>"
590                 "        <select name=\"select\">"
591                 "            <option>1</option>"
592                 "            <option>2</option>"
593                 "        </select>"
594                 "        <input type=\"text\"> </input>"
595                 "        <textarea name=\"text_area\" rows=\"3\" cols=\"40\">"
596                 "This test checks whether showing and hiding a popup"
597                 "takes the focus away from the webpage."
598                 "        </textarea>"
599                 "    </body>"
600                 "</html>");
601    view.resize(400, 100);
602    // Call setFocus before show to work around http://bugreports.qt.nokia.com/browse/QTBUG-14762
603    view.setFocus();
604    view.show();
605    QTest::qWaitForWindowExposed(&view);
606    view.activateWindow();
607    QTRY_VERIFY(view.hasFocus());
608
609    // open the popup by clicking. check if focus is on the popup
610    const QWebElement webCombo = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("select[name=select]"));
611    QTest::mouseClick(&view, Qt::LeftButton, 0, webCombo.geometry().center());
612
613    QComboBox* combo = view.findChild<QComboBox*>();
614    QVERIFY(combo != 0);
615    QTRY_VERIFY(!view.hasFocus() && combo->view()->hasFocus()); // Focus should be on the popup
616
617    // hide the popup and check if focus is on the page
618    combo->hidePopup();
619    QTRY_VERIFY(view.hasFocus()); // Focus should be back on the WebView
620}
621#endif
622
623void tst_QWebFrame::inputFieldFocus()
624{
625    QWebView view;
626    view.setHtml("<html><body><input type=\"text\"></input></body></html>");
627    view.resize(400, 100);
628    view.show();
629    QTest::qWaitForWindowExposed(&view);
630    view.activateWindow();
631    view.setFocus();
632    QTRY_VERIFY(view.hasFocus());
633
634    // double the flashing time, should at least blink once already
635    int delay = qApp->cursorFlashTime() * 2;
636
637    // focus the lineedit and check if it blinks
638    bool autoSipEnabled = qApp->autoSipEnabled();
639    qApp->setAutoSipEnabled(false);
640    const QWebElement inputElement = view.page()->mainFrame()->documentElement().findFirst(QLatin1String("input[type=text]"));
641    QTest::mouseClick(&view, Qt::LeftButton, 0, inputElement.geometry().center());
642    m_inputFieldsTestView = &view;
643    view.installEventFilter( this );
644    QTest::qWait(delay);
645    QVERIFY2(m_inputFieldTestPaintCount >= 3,
646             "The input field should have a blinking caret");
647    qApp->setAutoSipEnabled(autoSipEnabled);
648}
649
650void tst_QWebFrame::hitTestContent()
651{
652    QString html("<html><body><p>A paragraph</p><br/><br/><br/><a href=\"about:blank\" target=\"_foo\" id=\"link\">link text</a></body></html>");
653
654    QWebPage page;
655    QWebFrame* frame = page.mainFrame();
656    frame->setHtml(html);
657    page.setViewportSize(QSize(200, 0)); //no height so link is not visible
658    const QWebElement linkElement = frame->documentElement().findFirst(QLatin1String("a#link"));
659    QWebHitTestResult result = frame->hitTestContent(linkElement.geometry().center());
660    QCOMPARE(result.linkText(), QString("link text"));
661    QWebElement link = result.linkElement();
662    QCOMPARE(link.attribute("target"), QString("_foo"));
663}
664
665void tst_QWebFrame::baseUrl_data()
666{
667    QTest::addColumn<QString>("html");
668    QTest::addColumn<QUrl>("loadUrl");
669    QTest::addColumn<QUrl>("url");
670    QTest::addColumn<QUrl>("baseUrl");
671
672    QTest::newRow("null") << QString() << QUrl()
673                          << QUrl("about:blank") << QUrl("about:blank");
674
675    QTest::newRow("foo") << QString() << QUrl("http://foobar.baz/")
676                         << QUrl("http://foobar.baz/") << QUrl("http://foobar.baz/");
677
678    QString html = "<html>"
679        "<head>"
680            "<base href=\"http://foobaz.bar/\" />"
681        "</head>"
682    "</html>";
683    QTest::newRow("customBaseUrl") << html << QUrl("http://foobar.baz/")
684                                   << QUrl("http://foobar.baz/") << QUrl("http://foobaz.bar/");
685}
686
687void tst_QWebFrame::baseUrl()
688{
689    QFETCH(QString, html);
690    QFETCH(QUrl, loadUrl);
691    QFETCH(QUrl, url);
692    QFETCH(QUrl, baseUrl);
693
694    m_page->mainFrame()->setHtml(html, loadUrl);
695    QCOMPARE(m_page->mainFrame()->url(), url);
696    QCOMPARE(m_page->mainFrame()->baseUrl(), baseUrl);
697}
698
699void tst_QWebFrame::hasSetFocus()
700{
701    QString html("<html><body><p>top</p>" \
702                    "<iframe width='80%' height='30%'/>" \
703                 "</body></html>");
704
705    QSignalSpy loadSpy(m_page, SIGNAL(loadFinished(bool)));
706    m_page->mainFrame()->setHtml(html);
707
708    waitForSignal(m_page->mainFrame(), SIGNAL(loadFinished(bool)), 200);
709    QCOMPARE(loadSpy.size(), 1);
710
711    QList<QWebFrame*> children = m_page->mainFrame()->childFrames();
712    QWebFrame* frame = children.at(0);
713    QString innerHtml("<html><body><p>another iframe</p>" \
714                        "<iframe width='80%' height='30%'/>" \
715                      "</body></html>");
716    frame->setHtml(innerHtml);
717
718    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
719    QCOMPARE(loadSpy.size(), 2);
720
721    m_page->mainFrame()->setFocus();
722    QTRY_VERIFY(m_page->mainFrame()->hasFocus());
723
724    for (int i = 0; i < children.size(); ++i) {
725        children.at(i)->setFocus();
726        QTRY_VERIFY(children.at(i)->hasFocus());
727        QVERIFY(!m_page->mainFrame()->hasFocus());
728    }
729
730    m_page->mainFrame()->setFocus();
731    QTRY_VERIFY(m_page->mainFrame()->hasFocus());
732}
733
734void tst_QWebFrame::renderGeometry()
735{
736    QString html("<html>" \
737                    "<head><style>" \
738                       "body, iframe { margin: 0px; border: none; }" \
739                    "</style></head>" \
740                    "<body><iframe width='100px' height='100px'/></body>" \
741                 "</html>");
742
743    QWebPage page;
744    page.mainFrame()->setHtml(html);
745
746    QList<QWebFrame*> frames = page.mainFrame()->childFrames();
747    QWebFrame *frame = frames.at(0);
748    QString innerHtml("<body style='margin: 0px;'><img src='qrc:/image.png'/></body>");
749
750    // By default, only security origins of local files can load local resources.
751    // So we should specify baseUrl to be a local file in order to get a proper origin.
752    frame->setHtml(innerHtml, QUrl("file:///path/to/file"));
753    waitForSignal(frame, SIGNAL(loadFinished(bool)), 200);
754
755    QPicture picture;
756
757    QSize size = page.mainFrame()->contentsSize();
758    page.setViewportSize(size);
759
760    // render contents layer only (the iframe is smaller than the image, so it will have scrollbars)
761    QPainter painter1(&picture);
762    frame->render(&painter1, QWebFrame::ContentsLayer);
763    painter1.end();
764
765    QCOMPARE(size.width(), picture.boundingRect().width() + frame->scrollBarGeometry(Qt::Vertical).width());
766    QCOMPARE(size.height(), picture.boundingRect().height() + frame->scrollBarGeometry(Qt::Horizontal).height());
767
768    // render everything, should be the size of the iframe
769    QPainter painter2(&picture);
770    frame->render(&painter2, QWebFrame::AllLayers);
771    painter2.end();
772
773    QCOMPARE(size.width(), picture.boundingRect().width());   // width: 100px
774    QCOMPARE(size.height(), picture.boundingRect().height()); // height: 100px
775}
776
777
778class DummyPaintEngine: public QPaintEngine {
779public:
780
781    DummyPaintEngine()
782        : QPaintEngine(QPaintEngine::AllFeatures)
783        , renderHints(0)
784    {
785    }
786
787    bool begin(QPaintDevice*)
788    {
789        setActive(true);
790        return true;
791    }
792
793    bool end()
794    {
795        setActive(false);
796        return false;
797    }
798
799    void updateState(const QPaintEngineState& state)
800    {
801        renderHints = state.renderHints();
802    }
803
804    void drawPath(const QPainterPath&) { }
805    void drawPixmap(const QRectF&, const QPixmap&, const QRectF&) { }
806
807    QPaintEngine::Type type() const
808    {
809        return static_cast<QPaintEngine::Type>(QPaintEngine::User + 2);
810    }
811
812    QPainter::RenderHints renderHints;
813};
814
815class DummyPaintDevice: public QPaintDevice {
816public:
817    DummyPaintDevice()
818        : QPaintDevice()
819        , m_engine(new DummyPaintEngine)
820    {
821    }
822
823    ~DummyPaintDevice()
824    {
825        delete m_engine;
826    }
827
828    QPaintEngine* paintEngine() const
829    {
830        return m_engine;
831    }
832
833    QPainter::RenderHints renderHints() const
834    {
835        return m_engine->renderHints;
836    }
837
838protected:
839    int metric(PaintDeviceMetric metric) const;
840
841private:
842    DummyPaintEngine* m_engine;
843    friend class DummyPaintEngine;
844};
845
846
847int DummyPaintDevice::metric(PaintDeviceMetric metric) const
848{
849    switch (metric) {
850    case PdmWidth:
851        return 400;
852        break;
853
854    case PdmHeight:
855        return 200;
856        break;
857
858    case PdmNumColors:
859        return INT_MAX;
860        break;
861
862    case PdmDepth:
863        return 32;
864        break;
865
866    default:
867        break;
868    }
869    return 0;
870}
871
872void tst_QWebFrame::renderHints()
873{
874    QString html("<html><body><p>Hello, world!</p></body></html>");
875
876    QWebPage page;
877    page.mainFrame()->setHtml(html);
878    page.setViewportSize(page.mainFrame()->contentsSize());
879
880    // We will call frame->render and trap the paint engine state changes
881    // to ensure that GraphicsContext does not clobber the render hints.
882    DummyPaintDevice buffer;
883    QPainter painter(&buffer);
884
885    painter.setRenderHint(QPainter::TextAntialiasing, false);
886    page.mainFrame()->render(&painter);
887    QVERIFY(!(buffer.renderHints() & QPainter::TextAntialiasing));
888    QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform));
889    QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing));
890
891    painter.setRenderHint(QPainter::TextAntialiasing, true);
892    page.mainFrame()->render(&painter);
893    QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing);
894    QVERIFY(!(buffer.renderHints() & QPainter::SmoothPixmapTransform));
895    QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing));
896
897    painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
898    page.mainFrame()->render(&painter);
899    QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing);
900    QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform);
901    QVERIFY(!(buffer.renderHints() & QPainter::HighQualityAntialiasing));
902
903    painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
904    page.mainFrame()->render(&painter);
905    QVERIFY(buffer.renderHints() & QPainter::TextAntialiasing);
906    QVERIFY(buffer.renderHints() & QPainter::SmoothPixmapTransform);
907    QVERIFY(buffer.renderHints() & QPainter::HighQualityAntialiasing);
908}
909
910void tst_QWebFrame::scrollPosition()
911{
912    // enlarged image in a small viewport, to provoke the scrollbars to appear
913    QString html("<html><body><img src='qrc:/image.png' height=500 width=500/></body></html>");
914
915    QWebPage page;
916    page.setViewportSize(QSize(200, 200));
917
918    QWebFrame* frame = page.mainFrame();
919    frame->setHtml(html);
920    frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
921    frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
922
923    // try to set the scroll offset programmatically
924    frame->setScrollPosition(QPoint(23, 29));
925    QCOMPARE(frame->scrollPosition().x(), 23);
926    QCOMPARE(frame->scrollPosition().y(), 29);
927
928    int x = frame->evaluateJavaScript("window.scrollX").toInt();
929    int y = frame->evaluateJavaScript("window.scrollY").toInt();
930    QCOMPARE(x, 23);
931    QCOMPARE(y, 29);
932}
933
934void tst_QWebFrame::scrollToAnchor()
935{
936    QWebPage page;
937    page.setViewportSize(QSize(480, 800));
938    QWebFrame* frame = page.mainFrame();
939
940    QString html("<html><body><p style=\"margin-bottom: 1500px;\">Hello.</p>"
941                 "<p><a id=\"foo\">This</a> is an anchor</p>"
942                 "<p style=\"margin-bottom: 1500px;\"><a id=\"bar\">This</a> is another anchor</p>"
943                 "</body></html>");
944    frame->setHtml(html);
945    frame->setScrollPosition(QPoint(0, 0));
946    QCOMPARE(frame->scrollPosition().x(), 0);
947    QCOMPARE(frame->scrollPosition().y(), 0);
948
949    QWebElement fooAnchor = frame->findFirstElement("a[id=foo]");
950
951    frame->scrollToAnchor("foo");
952    QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top());
953
954    frame->scrollToAnchor("bar");
955    frame->scrollToAnchor("foo");
956    QCOMPARE(frame->scrollPosition().y(), fooAnchor.geometry().top());
957
958    frame->scrollToAnchor("top");
959    QCOMPARE(frame->scrollPosition().y(), 0);
960
961    frame->scrollToAnchor("bar");
962    frame->scrollToAnchor("notexist");
963    QVERIFY(frame->scrollPosition().y() != 0);
964}
965
966
967void tst_QWebFrame::scrollbarsOff()
968{
969    QWebView view;
970    QWebFrame* mainFrame = view.page()->mainFrame();
971
972    mainFrame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
973    mainFrame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
974
975    QString html("<script>" \
976                 "   function checkScrollbar() {" \
977                 "       if (innerWidth === document.documentElement.offsetWidth)" \
978                 "           document.getElementById('span1').innerText = 'SUCCESS';" \
979                 "       else" \
980                 "           document.getElementById('span1').innerText = 'FAIL';" \
981                 "   }" \
982                 "</script>" \
983                 "<body>" \
984                 "   <div style='margin-top:1000px ; margin-left:1000px'>" \
985                 "       <a id='offscreen' href='a'>End</a>" \
986                 "   </div>" \
987                 "<span id='span1'></span>" \
988                 "</body>");
989
990
991    QSignalSpy loadSpy(&view, SIGNAL(loadFinished(bool)));
992    view.setHtml(html);
993    ::waitForSignal(&view, SIGNAL(loadFinished(bool)), 200);
994    QCOMPARE(loadSpy.count(), 1);
995
996    mainFrame->evaluateJavaScript("checkScrollbar();");
997    QCOMPARE(mainFrame->documentElement().findAll("span").at(0).toPlainText(), QString("SUCCESS"));
998}
999
1000void tst_QWebFrame::horizontalScrollAfterBack()
1001{
1002    QWebView view;
1003    QWebFrame* frame = view.page()->mainFrame();
1004    QSignalSpy loadSpy(view.page(), SIGNAL(loadFinished(bool)));
1005
1006    view.page()->settings()->setMaximumPagesInCache(2);
1007    frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAsNeeded);
1008    frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAsNeeded);
1009
1010    view.load(QUrl("qrc:/testiframe2.html"));
1011    view.resize(200, 200);
1012    QTRY_COMPARE(loadSpy.count(), 1);
1013    QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height());
1014
1015    view.load(QUrl("qrc:/testiframe.html"));
1016    QTRY_COMPARE(loadSpy.count(), 2);
1017
1018    view.page()->triggerAction(QWebPage::Back);
1019    QTRY_COMPARE(loadSpy.count(), 3);
1020    QTRY_VERIFY((frame->scrollBarGeometry(Qt::Horizontal)).height());
1021}
1022
1023void tst_QWebFrame::evaluateWillCauseRepaint()
1024{
1025    QWebView view;
1026    QString html("<html><body>top<div id=\"junk\" style=\"display: block;\">"
1027                    "junk</div>bottom</body></html>");
1028    view.setHtml(html);
1029    view.show();
1030
1031    QTest::qWaitForWindowExposed(&view);
1032    view.page()->mainFrame()->evaluateJavaScript(
1033        "document.getElementById('junk').style.display = 'none';");
1034
1035    ::waitForSignal(view.page(), SIGNAL(repaintRequested(QRect)));
1036}
1037
1038void tst_QWebFrame::setContent_data()
1039{
1040    QTest::addColumn<QString>("mimeType");
1041    QTest::addColumn<QByteArray>("testContents");
1042    QTest::addColumn<QString>("expected");
1043
1044    QString str = QString::fromUtf8("ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει");
1045    QTest::newRow("UTF-8 plain text") << "text/plain; charset=utf-8" << str.toUtf8() << str;
1046
1047    QTextCodec *utf16 = QTextCodec::codecForName("UTF-16");
1048    if (utf16)
1049        QTest::newRow("UTF-16 plain text") << "text/plain; charset=utf-16" << utf16->fromUnicode(str) << str;
1050
1051    str = QString::fromUtf8("Une chaîne de caractères à sa façon.");
1052    QTest::newRow("latin-1 plain text") << "text/plain; charset=iso-8859-1" << str.toLatin1() << str;
1053
1054
1055}
1056
1057void tst_QWebFrame::setContent()
1058{
1059    QFETCH(QString, mimeType);
1060    QFETCH(QByteArray, testContents);
1061    QFETCH(QString, expected);
1062    m_view->setContent(testContents, mimeType);
1063    QWebFrame* mainFrame = m_view->page()->mainFrame();
1064    QCOMPARE(expected , mainFrame->toPlainText());
1065}
1066
1067class CacheNetworkAccessManager : public QNetworkAccessManager {
1068public:
1069    CacheNetworkAccessManager(QObject* parent = 0)
1070        : QNetworkAccessManager(parent)
1071        , m_lastCacheLoad(QNetworkRequest::PreferNetwork)
1072    {
1073    }
1074
1075    virtual QNetworkReply* createRequest(Operation, const QNetworkRequest& request, QIODevice*)
1076    {
1077        QVariant cacheLoad = request.attribute(QNetworkRequest::CacheLoadControlAttribute);
1078        if (cacheLoad.isValid())
1079            m_lastCacheLoad = static_cast<QNetworkRequest::CacheLoadControl>(cacheLoad.toUInt());
1080        else
1081            m_lastCacheLoad = QNetworkRequest::PreferNetwork; // default value
1082        return new FakeReply(request, this);
1083    }
1084
1085    QNetworkRequest::CacheLoadControl lastCacheLoad() const
1086    {
1087        return m_lastCacheLoad;
1088    }
1089
1090private:
1091    QNetworkRequest::CacheLoadControl m_lastCacheLoad;
1092};
1093
1094void tst_QWebFrame::setCacheLoadControlAttribute()
1095{
1096    QWebPage page;
1097    CacheNetworkAccessManager* manager = new CacheNetworkAccessManager(&page);
1098    page.setNetworkAccessManager(manager);
1099    QWebFrame* frame = page.mainFrame();
1100
1101    QNetworkRequest request(QUrl("http://abcdef.abcdef/"));
1102
1103    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
1104    frame->load(request);
1105    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysCache);
1106
1107    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
1108    frame->load(request);
1109    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferCache);
1110
1111    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
1112    frame->load(request);
1113    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::AlwaysNetwork);
1114
1115    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork);
1116    frame->load(request);
1117    QCOMPARE(manager->lastCacheLoad(), QNetworkRequest::PreferNetwork);
1118}
1119
1120void tst_QWebFrame::setUrlWithPendingLoads()
1121{
1122    QWebPage page;
1123    page.mainFrame()->setHtml("<img src='dummy:'/>");
1124    page.mainFrame()->setUrl(QUrl("about:blank"));
1125}
1126
1127void tst_QWebFrame::setUrlWithFragment_data()
1128{
1129    QTest::addColumn<QUrl>("previousUrl");
1130    QTest::newRow("empty") << QUrl();
1131    QTest::newRow("same URL no fragment") << QUrl("qrc:/test1.html");
1132    // See comments in setUrlSameUrl about using setUrl() with the same url().
1133    QTest::newRow("same URL with same fragment") << QUrl("qrc:/test1.html#");
1134    QTest::newRow("same URL with different fragment") << QUrl("qrc:/test1.html#anotherFragment");
1135    QTest::newRow("another URL") << QUrl("qrc:/test2.html");
1136}
1137
1138// Based on bug report https://bugs.webkit.org/show_bug.cgi?id=32723
1139void tst_QWebFrame::setUrlWithFragment()
1140{
1141    QFETCH(QUrl, previousUrl);
1142
1143    QWebPage page;
1144    QWebFrame* frame = page.mainFrame();
1145
1146    if (!previousUrl.isEmpty()) {
1147        frame->load(previousUrl);
1148        ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
1149        QCOMPARE(frame->url(), previousUrl);
1150    }
1151
1152    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
1153    const QUrl url("qrc:/test1.html#");
1154    QVERIFY(!url.fragment().isNull());
1155
1156    frame->setUrl(url);
1157    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
1158
1159    QCOMPARE(spy.count(), 1);
1160    QVERIFY(!frame->toPlainText().isEmpty());
1161    QCOMPARE(frame->requestedUrl(), url);
1162    QCOMPARE(frame->url(), url);
1163}
1164
1165void tst_QWebFrame::setUrlToEmpty()
1166{
1167    int expectedLoadFinishedCount = 0;
1168    const QUrl aboutBlank("about:blank");
1169    const QUrl url("qrc:/test2.html");
1170
1171    QWebPage page;
1172    QWebFrame* frame = page.mainFrame();
1173    QCOMPARE(frame->url(), QUrl());
1174    QCOMPARE(frame->requestedUrl(), QUrl());
1175    QCOMPARE(frame->baseUrl(), QUrl());
1176
1177    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
1178
1179    // Set existing url
1180    frame->setUrl(url);
1181    expectedLoadFinishedCount++;
1182    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
1183
1184    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1185    QCOMPARE(frame->url(), url);
1186    QCOMPARE(frame->requestedUrl(), url);
1187    QCOMPARE(frame->baseUrl(), url);
1188
1189    // Set empty url
1190    frame->setUrl(QUrl());
1191    expectedLoadFinishedCount++;
1192
1193    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1194    QCOMPARE(frame->url(), aboutBlank);
1195    QCOMPARE(frame->requestedUrl(), QUrl());
1196    QCOMPARE(frame->baseUrl(), aboutBlank);
1197
1198    // Set existing url
1199    frame->setUrl(url);
1200    expectedLoadFinishedCount++;
1201    ::waitForSignal(frame, SIGNAL(loadFinished(bool)));
1202
1203    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1204    QCOMPARE(frame->url(), url);
1205    QCOMPARE(frame->requestedUrl(), url);
1206    QCOMPARE(frame->baseUrl(), url);
1207
1208    // Load empty url
1209    frame->load(QUrl());
1210    expectedLoadFinishedCount++;
1211
1212    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1213    QCOMPARE(frame->url(), aboutBlank);
1214    QCOMPARE(frame->requestedUrl(), QUrl());
1215    QCOMPARE(frame->baseUrl(), aboutBlank);
1216}
1217
1218void tst_QWebFrame::setUrlToInvalid()
1219{
1220    QWebPage page;
1221    QWebFrame* frame = page.mainFrame();
1222
1223    const QUrl invalidUrl("http:/example.com");
1224    QVERIFY(!invalidUrl.isEmpty());
1225    QVERIFY(invalidUrl != QUrl());
1226
1227    // QWebFrame will do its best to accept the URL, possible converting it to a valid equivalent URL.
1228    const QUrl validUrl("http://example.com/");
1229    frame->setUrl(invalidUrl);
1230    QCOMPARE(frame->url(), validUrl);
1231    QCOMPARE(frame->requestedUrl(), validUrl);
1232    QCOMPARE(frame->baseUrl(), validUrl);
1233
1234    // QUrls equivalent to QUrl() will be treated as such.
1235    const QUrl aboutBlank("about:blank");
1236    const QUrl anotherInvalidUrl("1http://bugs.webkit.org");
1237    QVERIFY(!anotherInvalidUrl.isEmpty()); // and they are not necessarily empty.
1238    QVERIFY(!anotherInvalidUrl.isValid());
1239    QCOMPARE(anotherInvalidUrl.toEncoded(), QUrl().toEncoded());
1240
1241    frame->setUrl(anotherInvalidUrl);
1242    QCOMPARE(frame->url(), aboutBlank);
1243    QCOMPARE(frame->requestedUrl().toEncoded(), anotherInvalidUrl.toEncoded());
1244    QCOMPARE(frame->baseUrl(), aboutBlank);
1245}
1246
1247void tst_QWebFrame::setUrlHistory()
1248{
1249    const QUrl aboutBlank("about:blank");
1250    QUrl url;
1251    int expectedLoadFinishedCount = 0;
1252    QWebFrame* frame = m_page->mainFrame();
1253    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
1254
1255    QCOMPARE(m_page->history()->count(), 0);
1256
1257    frame->setUrl(QUrl());
1258    expectedLoadFinishedCount++;
1259    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1260    QCOMPARE(frame->url(), aboutBlank);
1261    QCOMPARE(frame->requestedUrl(), QUrl());
1262    QCOMPARE(m_page->history()->count(), 0);
1263
1264    url = QUrl("http://non.existant/");
1265    frame->setUrl(url);
1266    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
1267    expectedLoadFinishedCount++;
1268    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1269    QCOMPARE(frame->url(), url);
1270    QCOMPARE(frame->requestedUrl(), url);
1271    QCOMPARE(m_page->history()->count(), 0);
1272
1273    url = QUrl("qrc:/test1.html");
1274    frame->setUrl(url);
1275    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
1276    expectedLoadFinishedCount++;
1277    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1278    QCOMPARE(frame->url(), url);
1279    QCOMPARE(frame->requestedUrl(), url);
1280    QCOMPARE(m_page->history()->count(), 1);
1281
1282    frame->setUrl(QUrl());
1283    expectedLoadFinishedCount++;
1284    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1285    QCOMPARE(frame->url(), aboutBlank);
1286    QCOMPARE(frame->requestedUrl(), QUrl());
1287    QCOMPARE(m_page->history()->count(), 1);
1288
1289    // Loading same page as current in history, so history count doesn't change.
1290    url = QUrl("qrc:/test1.html");
1291    frame->setUrl(url);
1292    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
1293    expectedLoadFinishedCount++;
1294    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1295    QCOMPARE(frame->url(), url);
1296    QCOMPARE(frame->requestedUrl(), url);
1297    QCOMPARE(m_page->history()->count(), 1);
1298
1299    url = QUrl("qrc:/test2.html");
1300    frame->setUrl(url);
1301    ::waitForSignal(m_page, SIGNAL(loadFinished(bool)));
1302    expectedLoadFinishedCount++;
1303    QCOMPARE(spy.count(), expectedLoadFinishedCount);
1304    QCOMPARE(frame->url(), url);
1305    QCOMPARE(frame->requestedUrl(), url);
1306    QCOMPARE(m_page->history()->count(), 2);
1307}
1308
1309void tst_QWebFrame::setUrlUsingStateObject()
1310{
1311    const QUrl aboutBlank("about:blank");
1312    QUrl url;
1313    QWebFrame* frame = m_page->mainFrame();
1314    QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl)));
1315    int expectedUrlChangeCount = 0;
1316
1317    QCOMPARE(m_page->history()->count(), 0);
1318
1319    url = QUrl("qrc:/test1.html");
1320    frame->setUrl(url);
1321    waitForSignal(m_page, SIGNAL(loadFinished(bool)));
1322    expectedUrlChangeCount++;
1323    QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount);
1324    QCOMPARE(frame->url(), url);
1325    QCOMPARE(m_page->history()->count(), 1);
1326
1327    frame->evaluateJavaScript("window.history.pushState(null,'push', 'navigate/to/here')");
1328    expectedUrlChangeCount++;
1329    QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount);
1330    QCOMPARE(frame->url(), QUrl("qrc:/navigate/to/here"));
1331    QCOMPARE(m_page->history()->count(), 2);
1332    QVERIFY(m_page->history()->canGoBack());
1333
1334    frame->evaluateJavaScript("window.history.replaceState(null,'replace', 'another/location')");
1335    expectedUrlChangeCount++;
1336    QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount);
1337    QCOMPARE(frame->url(), QUrl("qrc:/navigate/to/another/location"));
1338    QCOMPARE(m_page->history()->count(), 2);
1339    QVERIFY(!m_page->history()->canGoForward());
1340    QVERIFY(m_page->history()->canGoBack());
1341
1342    frame->evaluateJavaScript("window.history.back()");
1343    QTest::qWait(100);
1344    expectedUrlChangeCount++;
1345    QCOMPARE(urlChangedSpy.count(), expectedUrlChangeCount);
1346    QCOMPARE(frame->url(), QUrl("qrc:/test1.html"));
1347    QVERIFY(m_page->history()->canGoForward());
1348    QVERIFY(!m_page->history()->canGoBack());
1349}
1350
1351void tst_QWebFrame::setUrlSameUrl()
1352{
1353    const QUrl url1("qrc:/test1.html");
1354    const QUrl url2("qrc:/test2.html");
1355
1356    QWebPage page;
1357    QWebFrame* frame = page.mainFrame();
1358    FakeNetworkManager* networkManager = new FakeNetworkManager(&page);
1359    page.setNetworkAccessManager(networkManager);
1360
1361    QSignalSpy spy(frame, SIGNAL(loadFinished(bool)));
1362
1363    frame->setUrl(url1);
1364    waitForSignal(frame, SIGNAL(loadFinished(bool)));
1365    QVERIFY(frame->url() != url1); // Nota bene: our QNAM redirects url1 to url2
1366    QCOMPARE(frame->url(), url2);
1367    QCOMPARE(spy.count(), 1);
1368
1369    frame->setUrl(url1);
1370    waitForSignal(frame, SIGNAL(loadFinished(bool)));
1371    QVERIFY(frame->url() != url1);
1372    QCOMPARE(frame->url(), url2);
1373    QCOMPARE(spy.count(), 2);
1374
1375    // Now a case without redirect. The existing behavior we have for setUrl()
1376    // is more like a "clear(); load()", so the page will be loaded again, even
1377    // if urlToBeLoaded == url(). This test should be changed if we want to
1378    // make setUrl() early return in this case.
1379    frame->setUrl(url2);
1380    waitForSignal(frame, SIGNAL(loadFinished(bool)));
1381    QCOMPARE(frame->url(), url2);
1382    QCOMPARE(spy.count(), 3);
1383
1384    frame->setUrl(url1);
1385    waitForSignal(frame, SIGNAL(loadFinished(bool)));
1386    QCOMPARE(frame->url(), url2);
1387    QCOMPARE(spy.count(), 4);
1388}
1389
1390static inline QUrl extractBaseUrl(const QUrl& url)
1391{
1392    return url.resolved(QUrl());
1393}
1394
1395void tst_QWebFrame::setUrlThenLoads_data()
1396{
1397    QTest::addColumn<QUrl>("url");
1398    QTest::addColumn<QUrl>("baseUrl");
1399
1400    QTest::newRow("resource file") << QUrl("qrc:/test1.html") << extractBaseUrl(QUrl("qrc:/test1.html"));
1401    QTest::newRow("base specified in HTML") << QUrl("data:text/html,<head><base href=\"http://different.base/\"></head>") << QUrl("http://different.base/");
1402}
1403
1404void tst_QWebFrame::setUrlThenLoads()
1405{
1406    QFETCH(QUrl, url);
1407    QFETCH(QUrl, baseUrl);
1408    QWebFrame* frame = m_page->mainFrame();
1409    QSignalSpy urlChangedSpy(frame, SIGNAL(urlChanged(QUrl)));
1410    QSignalSpy startedSpy(frame, SIGNAL(loadStarted()));
1411    QSignalSpy finishedSpy(frame, SIGNAL(loadFinished(bool)));
1412
1413    frame->setUrl(url);
1414    QCOMPARE(startedSpy.count(), 1);
1415    ::waitForSignal(frame, SIGNAL(urlChanged(QUrl)));
1416    QCOMPARE(urlChangedSpy.count(), 1);
1417    QVERIFY(finishedSpy.at(0).first().toBool());
1418    QCOMPARE(frame->url(), url);
1419    QCOMPARE(frame->requestedUrl(), url);
1420    QCOMPARE(frame->baseUrl(), baseUrl);
1421
1422    const QUrl urlToLoad1("qrc:/test2.html");
1423    const QUrl urlToLoad2("qrc:/test1.html");
1424
1425    // Just after first load. URL didn't changed yet.
1426    frame->load(urlToLoad1);
1427    QCOMPARE(startedSpy.count(), 2);
1428    QCOMPARE(frame->url(), url);
1429    QCOMPARE(frame->requestedUrl(), urlToLoad1);
1430    QCOMPARE(frame->baseUrl(), baseUrl);
1431
1432    // After first URL changed.
1433    ::waitForSignal(frame, SIGNAL(urlChanged(QUrl)));
1434    QCOMPARE(urlChangedSpy.count(), 2);
1435    QVERIFY(finishedSpy.at(1).first().toBool());
1436    QCOMPARE(frame->url(), urlToLoad1);
1437    QCOMPARE(frame->requestedUrl(), urlToLoad1);
1438    QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1));
1439
1440    // Just after second load. URL didn't changed yet.
1441    frame->load(urlToLoad2);
1442    QCOMPARE(startedSpy.count(), 3);
1443    QCOMPARE(frame->url(), urlToLoad1);
1444    QCOMPARE(frame->requestedUrl(), urlToLoad2);
1445    QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad1));
1446
1447    // After second URL changed.
1448    ::waitForSignal(frame, SIGNAL(urlChanged(QUrl)));
1449    QCOMPARE(urlChangedSpy.count(), 3);
1450    QVERIFY(finishedSpy.at(2).first().toBool());
1451    QCOMPARE(frame->url(), urlToLoad2);
1452    QCOMPARE(frame->requestedUrl(), urlToLoad2);
1453    QCOMPARE(frame->baseUrl(), extractBaseUrl(urlToLoad2));
1454}
1455
1456void tst_QWebFrame::loadFinishedAfterNotFoundError()
1457{
1458    QWebPage page;
1459    QWebFrame* frame = page.mainFrame();
1460
1461    QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
1462    FakeNetworkManager* networkManager = new FakeNetworkManager(&page);
1463    page.setNetworkAccessManager(networkManager);
1464
1465    frame->setUrl(FakeReply::urlFor404ErrorWithoutContents);
1466    QTRY_COMPARE(spy.count(), 1);
1467    const bool wasLoadOk = spy.at(0).at(0).toBool();
1468    QVERIFY(!wasLoadOk);
1469}
1470
1471class URLSetter : public QObject {
1472    Q_OBJECT
1473
1474public:
1475    enum Signal {
1476        LoadStarted,
1477        LoadFinished,
1478        ProvisionalLoad
1479    };
1480
1481    enum Type {
1482        UseLoad,
1483        UseSetUrl
1484    };
1485
1486    URLSetter(QWebFrame*, Signal, Type, const QUrl&);
1487
1488public Q_SLOTS:
1489    void execute();
1490
1491Q_SIGNALS:
1492    void finished();
1493
1494private:
1495    QWebFrame* m_frame;
1496    QUrl m_url;
1497    Type m_type;
1498};
1499
1500Q_DECLARE_METATYPE(URLSetter::Signal)
1501Q_DECLARE_METATYPE(URLSetter::Type)
1502
1503URLSetter::URLSetter(QWebFrame* frame, Signal signal, URLSetter::Type type, const QUrl& url)
1504    : m_frame(frame), m_url(url), m_type(type)
1505{
1506    if (signal == LoadStarted)
1507        connect(m_frame, SIGNAL(loadStarted()), SLOT(execute()));
1508    else if (signal == LoadFinished)
1509        connect(m_frame, SIGNAL(loadFinished(bool)), SLOT(execute()));
1510    else
1511        connect(m_frame, SIGNAL(provisionalLoad()), SLOT(execute()));
1512}
1513
1514void URLSetter::execute()
1515{
1516    // We track only the first emission.
1517    m_frame->disconnect(this);
1518    if (m_type == URLSetter::UseLoad)
1519        m_frame->load(m_url);
1520    else
1521        m_frame->setUrl(m_url);
1522    connect(m_frame, SIGNAL(loadFinished(bool)), SIGNAL(finished()));
1523}
1524
1525void tst_QWebFrame::loadInSignalHandlers_data()
1526{
1527    QTest::addColumn<URLSetter::Type>("type");
1528    QTest::addColumn<URLSetter::Signal>("signal");
1529    QTest::addColumn<QUrl>("url");
1530
1531    const QUrl validUrl("qrc:/test2.html");
1532    const QUrl invalidUrl("qrc:/invalid");
1533
1534    QTest::newRow("call load() in loadStarted() after valid url") << URLSetter::UseLoad << URLSetter::LoadStarted << validUrl;
1535    QTest::newRow("call load() in loadStarted() after invalid url") << URLSetter::UseLoad << URLSetter::LoadStarted << invalidUrl;
1536    QTest::newRow("call load() in loadFinished() after valid url") << URLSetter::UseLoad << URLSetter::LoadFinished << validUrl;
1537    QTest::newRow("call load() in loadFinished() after invalid url") << URLSetter::UseLoad << URLSetter::LoadFinished << invalidUrl;
1538    QTest::newRow("call load() in provisionalLoad() after valid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << validUrl;
1539    QTest::newRow("call load() in provisionalLoad() after invalid url") << URLSetter::UseLoad << URLSetter::ProvisionalLoad << invalidUrl;
1540
1541    QTest::newRow("call setUrl() in loadStarted() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << validUrl;
1542    QTest::newRow("call setUrl() in loadStarted() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadStarted << invalidUrl;
1543    QTest::newRow("call setUrl() in loadFinished() after valid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << validUrl;
1544    QTest::newRow("call setUrl() in loadFinished() after invalid url") << URLSetter::UseSetUrl << URLSetter::LoadFinished << invalidUrl;
1545    QTest::newRow("call setUrl() in provisionalLoad() after valid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << validUrl;
1546    QTest::newRow("call setUrl() in provisionalLoad() after invalid url") << URLSetter::UseSetUrl << URLSetter::ProvisionalLoad << invalidUrl;
1547}
1548
1549void tst_QWebFrame::loadInSignalHandlers()
1550{
1551    QFETCH(URLSetter::Type, type);
1552    QFETCH(URLSetter::Signal, signal);
1553    QFETCH(QUrl, url);
1554
1555    QWebFrame* frame = m_page->mainFrame();
1556    const QUrl urlForSetter("qrc:/test1.html");
1557    URLSetter setter(frame, signal, type, urlForSetter);
1558
1559    frame->load(url);
1560    waitForSignal(&setter, SIGNAL(finished()), 200);
1561    QCOMPARE(frame->url(), urlForSetter);
1562}
1563
1564QTEST_MAIN(tst_QWebFrame)
1565#include "tst_qwebframe.moc"
1566