1/*
2    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
3    Copyright (C) 2009 Torch Mobile Inc.
4    Copyright (C) 2009 Girish Ramakrishnan <girish@forwardbias.in>
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19    Boston, MA 02110-1301, USA.
20*/
21
22#include <qtest.h>
23#include "../util.h"
24
25#include <qpainter.h>
26#include <qwebview.h>
27#include <qwebpage.h>
28#include <qnetworkrequest.h>
29#include <qdiriterator.h>
30#include <qwebelement.h>
31#include <qwebframe.h>
32
33#define VERIFY_INPUTMETHOD_HINTS(actual, expect) \
34    QVERIFY(actual == expect);
35
36class tst_QWebView : public QObject
37{
38    Q_OBJECT
39
40public Q_SLOTS:
41    void initTestCase();
42    void cleanupTestCase();
43    void init();
44    void cleanup();
45
46private Q_SLOTS:
47    void renderingAfterMaxAndBack();
48    void renderHints();
49    void getWebKitVersion();
50
51    void reusePage_data();
52    void reusePage();
53    void microFocusCoordinates();
54    void focusInputTypes();
55    void horizontalScrollbarTest();
56
57    void crashTests();
58#if !(defined(WTF_USE_QT_MOBILE_THEME) && WTF_USE_QT_MOBILE_THEME)
59    void setPalette_data();
60    void setPalette();
61#endif
62};
63
64// This will be called before the first test function is executed.
65// It is only called once.
66void tst_QWebView::initTestCase()
67{
68}
69
70// This will be called after the last test function is executed.
71// It is only called once.
72void tst_QWebView::cleanupTestCase()
73{
74}
75
76// This will be called before each test function is executed.
77void tst_QWebView::init()
78{
79}
80
81// This will be called after every test function.
82void tst_QWebView::cleanup()
83{
84}
85
86void tst_QWebView::renderHints()
87{
88    QWebView webView;
89
90    // default is only text antialiasing + smooth pixmap transform
91    QVERIFY(!(webView.renderHints() & QPainter::Antialiasing));
92    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
93    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
94    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
95
96    webView.setRenderHint(QPainter::Antialiasing, true);
97    QVERIFY(webView.renderHints() & QPainter::Antialiasing);
98    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
99    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
100    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
101
102    webView.setRenderHint(QPainter::Antialiasing, false);
103    QVERIFY(!(webView.renderHints() & QPainter::Antialiasing));
104    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
105    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
106    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
107
108    webView.setRenderHint(QPainter::SmoothPixmapTransform, true);
109    QVERIFY(!(webView.renderHints() & QPainter::Antialiasing));
110    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
111    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
112    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
113
114    webView.setRenderHint(QPainter::SmoothPixmapTransform, false);
115    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
116    QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform));
117    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
118}
119
120void tst_QWebView::getWebKitVersion()
121{
122    QVERIFY(qWebKitVersion().toDouble() > 0);
123}
124
125void tst_QWebView::reusePage_data()
126{
127    QTest::addColumn<QString>("html");
128    QTest::newRow("WithoutPlugin") << "<html><body id='b'>text</body></html>";
129    QTest::newRow("WindowedPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf'></embed></body></html>");
130    QTest::newRow("WindowlessPlugin") << QString("<html><body id='b'>text<embed src='resources/test.swf' wmode=\"transparent\"></embed></body></html>");
131}
132
133void tst_QWebView::reusePage()
134{
135    if (!QDir(TESTS_SOURCE_DIR).exists())
136        W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll);
137
138    QDir::setCurrent(TESTS_SOURCE_DIR);
139
140    QFETCH(QString, html);
141    QWebView* view1 = new QWebView;
142    QPointer<QWebPage> page = new QWebPage;
143    view1->setPage(page.data());
144    page.data()->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
145    QWebFrame* mainFrame = page.data()->mainFrame();
146    mainFrame->setHtml(html, QUrl::fromLocalFile(TESTS_SOURCE_DIR));
147    if (html.contains("</embed>")) {
148        // some reasonable time for the PluginStream to feed test.swf to flash and start painting
149        waitForSignal(view1, SIGNAL(loadFinished(bool)), 2000);
150    }
151
152    view1->show();
153    QTest::qWaitForWindowExposed(view1);
154    delete view1;
155    QVERIFY(page != 0); // deleting view must not have deleted the page, since it's not a child of view
156
157    QWebView *view2 = new QWebView;
158    view2->setPage(page.data());
159    view2->show(); // in Windowless mode, you should still be able to see the plugin here
160    QTest::qWaitForWindowExposed(view2);
161    delete view2;
162
163    delete page.data(); // must not crash
164
165    QDir::setCurrent(QApplication::applicationDirPath());
166}
167
168// Class used in crashTests
169class WebViewCrashTest : public QObject {
170    Q_OBJECT
171    QWebView* m_view;
172public:
173    bool m_executed;
174
175
176    WebViewCrashTest(QWebView* view)
177      : m_view(view)
178      , m_executed(false)
179    {
180        view->connect(view, SIGNAL(loadProgress(int)), this, SLOT(loading(int)));
181    }
182
183private Q_SLOTS:
184    void loading(int progress)
185    {
186        if (progress >= 20 && progress < 90) {
187            QVERIFY(!m_executed);
188            m_view->stop();
189            m_executed = true;
190        }
191    }
192};
193
194
195// Should not crash.
196void tst_QWebView::crashTests()
197{
198    // Test if loading can be stopped in loadProgress handler without crash.
199    // Test page should have frames.
200    QWebView view;
201    WebViewCrashTest tester(&view);
202    QUrl url("qrc:///resources/index.html");
203    view.load(url);
204    QTRY_VERIFY(tester.m_executed); // If fail it means that the test wasn't executed.
205}
206
207void tst_QWebView::microFocusCoordinates()
208{
209    QWebPage* page = new QWebPage;
210    QWebView* webView = new QWebView;
211    webView->setPage( page );
212
213    page->mainFrame()->setHtml("<html><body>" \
214        "<input type='text' id='input1' style='font--family: serif' value='' maxlength='20'/><br>" \
215        "<canvas id='canvas1' width='500' height='500'></canvas>" \
216        "<input type='password'/><br>" \
217        "<canvas id='canvas2' width='500' height='500'></canvas>" \
218        "</body></html>");
219
220    page->mainFrame()->setFocus();
221
222    QVariant initialMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus);
223    QVERIFY(initialMicroFocus.isValid());
224
225    page->mainFrame()->scroll(0,50);
226
227    QVariant currentMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus);
228    QVERIFY(currentMicroFocus.isValid());
229
230    QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-50)), currentMicroFocus.toRect());
231}
232
233void tst_QWebView::focusInputTypes()
234{
235    QWebView webView;
236    webView.show();
237    QTest::qWaitForWindowExposed(&webView);
238
239    QUrl url("qrc:///resources/input_types.html");
240    QWebFrame* const mainFrame = webView.page()->mainFrame();
241    mainFrame->load(url);
242    mainFrame->setFocus();
243
244    QVERIFY(waitForSignal(&webView, SIGNAL(loadFinished(bool))));
245
246    // 'text' type
247    QWebElement inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=text]"));
248    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
249    QVERIFY(webView.inputMethodHints() == Qt::ImhNone);
250    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
251
252    // 'password' field
253    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]"));
254    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
255    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText);
256    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
257
258    // 'tel' field
259    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=tel]"));
260    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
261    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhDialableCharactersOnly);
262    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
263
264    // 'number' field
265    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=number]"));
266    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
267    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhDigitsOnly);
268    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
269
270    // 'email' field
271    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=email]"));
272    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
273    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhEmailCharactersOnly);
274    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
275
276    // 'url' field
277    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=url]"));
278    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
279    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhUrlCharactersOnly);
280    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
281
282    // 'password' field
283    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]"));
284    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
285    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText);
286    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
287
288    // 'text' type
289    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=text]"));
290    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
291    QVERIFY(webView.inputMethodHints() == Qt::ImhNone);
292    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
293
294    // 'password' field
295    inputElement = mainFrame->documentElement().findFirst(QLatin1String("input[type=password]"));
296    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
297    VERIFY_INPUTMETHOD_HINTS(webView.inputMethodHints(), Qt::ImhHiddenText);
298    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
299
300    // 'text area' field
301    inputElement = mainFrame->documentElement().findFirst(QLatin1String("textarea"));
302    QTest::mouseClick(&webView, Qt::LeftButton, 0, inputElement.geometry().center());
303    QVERIFY(webView.inputMethodHints() == Qt::ImhNone);
304    QVERIFY(webView.testAttribute(Qt::WA_InputMethodEnabled));
305}
306
307void tst_QWebView::horizontalScrollbarTest()
308{
309    QWebView webView;
310    webView.resize(600, 600);
311    webView.show();
312    QTest::qWaitForWindowExposed(&webView);
313
314    QUrl url("qrc:///resources/scrolltest_page.html");
315    QWebFrame* const mainFrame = webView.page()->mainFrame();
316    mainFrame->load(url);
317    mainFrame->setFocus();
318
319    QVERIFY(waitForSignal(&webView, SIGNAL(loadFinished(bool))));
320
321    QVERIFY(webView.page()->mainFrame()->scrollPosition() == QPoint(0, 0));
322
323    // Note: The test below assumes that the layout direction is Qt::LeftToRight.
324    QTest::mouseClick(&webView, Qt::LeftButton, 0, QPoint(550, 595));
325    QVERIFY(webView.page()->mainFrame()->scrollPosition().x() > 0);
326
327    // Note: The test below assumes that the layout direction is Qt::LeftToRight.
328    QTest::mouseClick(&webView, Qt::LeftButton, 0, QPoint(20, 595));
329    QVERIFY(webView.page()->mainFrame()->scrollPosition() == QPoint(0, 0));
330}
331
332
333#if !(defined(WTF_USE_QT_MOBILE_THEME) && WTF_USE_QT_MOBILE_THEME)
334void tst_QWebView::setPalette_data()
335{
336    QTest::addColumn<bool>("active");
337    QTest::addColumn<bool>("background");
338    QTest::newRow("activeBG") << true << true;
339    QTest::newRow("activeFG") << true << false;
340    QTest::newRow("inactiveBG") << false << true;
341    QTest::newRow("inactiveFG") << false << false;
342}
343
344// Render a QWebView to a QImage twice, each time with a different palette set,
345// verify that images rendered are not the same, confirming WebCore usage of
346// custom palette on selections.
347void tst_QWebView::setPalette()
348{
349    QString html = "<html><head></head>"
350                   "<body>"
351                   "Some text here"
352                   "</body>"
353                   "</html>";
354
355    QFETCH(bool, active);
356    QFETCH(bool, background);
357
358    QWidget* activeView = 0;
359
360    // Use controlView to manage active/inactive state of test views by raising
361    // or lowering their position in the window stack.
362    QWebView controlView;
363    controlView.setHtml(html);
364
365    QWebView view1;
366
367    QPalette palette1;
368    QBrush brush1(Qt::red);
369    brush1.setStyle(Qt::SolidPattern);
370    if (active && background) {
371        // Rendered image must have red background on an active QWebView.
372        palette1.setBrush(QPalette::Active, QPalette::Highlight, brush1);
373    } else if (active && !background) {
374        // Rendered image must have red foreground on an active QWebView.
375        palette1.setBrush(QPalette::Active, QPalette::HighlightedText, brush1);
376    } else if (!active && background) {
377        // Rendered image must have red background on an inactive QWebView.
378        palette1.setBrush(QPalette::Inactive, QPalette::Highlight, brush1);
379    } else if (!active && !background) {
380        // Rendered image must have red foreground on an inactive QWebView.
381        palette1.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush1);
382    }
383
384    view1.setPalette(palette1);
385    view1.setHtml(html);
386    view1.page()->setViewportSize(view1.page()->currentFrame()->contentsSize());
387    view1.show();
388
389    QTest::qWaitForWindowExposed(&view1);
390
391    if (!active) {
392        controlView.show();
393        QTest::qWaitForWindowExposed(&controlView);
394        activeView = &controlView;
395        controlView.activateWindow();
396    } else {
397        view1.activateWindow();
398        activeView = &view1;
399    }
400
401    QTRY_COMPARE(QApplication::activeWindow(), activeView);
402
403    view1.page()->triggerAction(QWebPage::SelectAll);
404
405    QImage img1(view1.page()->viewportSize(), QImage::Format_ARGB32);
406    QPainter painter1(&img1);
407    view1.page()->currentFrame()->render(&painter1);
408    painter1.end();
409    view1.close();
410    controlView.close();
411
412    QWebView view2;
413
414    QPalette palette2;
415    QBrush brush2(Qt::blue);
416    brush2.setStyle(Qt::SolidPattern);
417    if (active && background) {
418        // Rendered image must have blue background on an active QWebView.
419        palette2.setBrush(QPalette::Active, QPalette::Highlight, brush2);
420    } else if (active && !background) {
421        // Rendered image must have blue foreground on an active QWebView.
422        palette2.setBrush(QPalette::Active, QPalette::HighlightedText, brush2);
423    } else if (!active && background) {
424        // Rendered image must have blue background on an inactive QWebView.
425        palette2.setBrush(QPalette::Inactive, QPalette::Highlight, brush2);
426    } else if (!active && !background) {
427        // Rendered image must have blue foreground on an inactive QWebView.
428        palette2.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush2);
429    }
430
431    view2.setPalette(palette2);
432    view2.setHtml(html);
433    view2.page()->setViewportSize(view2.page()->currentFrame()->contentsSize());
434    view2.show();
435
436    QTest::qWaitForWindowExposed(&view2);
437
438    if (!active) {
439        controlView.show();
440        QTest::qWaitForWindowExposed(&controlView);
441        activeView = &controlView;
442        controlView.activateWindow();
443    } else {
444        view2.activateWindow();
445        activeView = &view2;
446    }
447
448    QTRY_COMPARE(QApplication::activeWindow(), activeView);
449
450    view2.page()->triggerAction(QWebPage::SelectAll);
451
452    QImage img2(view2.page()->viewportSize(), QImage::Format_ARGB32);
453    QPainter painter2(&img2);
454    view2.page()->currentFrame()->render(&painter2);
455    painter2.end();
456
457    view2.close();
458    controlView.close();
459
460    QVERIFY(img1 != img2);
461}
462#endif
463
464void tst_QWebView::renderingAfterMaxAndBack()
465{
466    QUrl url = QUrl("data:text/html,<html><head></head>"
467                   "<body width=1024 height=768 bgcolor=red>"
468                   "</body>"
469                   "</html>");
470
471    QWebView view;
472    view.page()->mainFrame()->load(url);
473    QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool))));
474    view.show();
475
476    view.page()->settings()->setMaximumPagesInCache(3);
477
478    QTest::qWaitForWindowExposed(&view);
479
480    QPixmap reference(view.page()->viewportSize());
481    reference.fill(Qt::red);
482
483    QPixmap image(view.page()->viewportSize());
484    QPainter painter(&image);
485    view.page()->currentFrame()->render(&painter);
486
487    QCOMPARE(image, reference);
488
489    QUrl url2 = QUrl("data:text/html,<html><head></head>"
490                     "<body width=1024 height=768 bgcolor=blue>"
491                     "</body>"
492                     "</html>");
493    view.page()->mainFrame()->load(url2);
494
495    QVERIFY(waitForSignal(&view, SIGNAL(loadFinished(bool))));
496
497    view.showMaximized();
498
499    QTest::qWaitForWindowExposed(&view);
500
501    QPixmap reference2(view.page()->viewportSize());
502    reference2.fill(Qt::blue);
503
504    QPixmap image2(view.page()->viewportSize());
505    QPainter painter2(&image2);
506    view.page()->currentFrame()->render(&painter2);
507
508    QCOMPARE(image2, reference2);
509
510    view.back();
511
512    QPixmap reference3(view.page()->viewportSize());
513    reference3.fill(Qt::red);
514    QPixmap image3(view.page()->viewportSize());
515    QPainter painter3(&image3);
516    view.page()->currentFrame()->render(&painter3);
517
518    QCOMPARE(image3, reference3);
519}
520
521QTEST_MAIN(tst_QWebView)
522#include "tst_qwebview.moc"
523
524