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