1/*
2    Copyright (C) 2009 Jakub Wieczorek <faw217@gmail.com>
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#include "../util.h"
21#include <QtTest/QtTest>
22#include <QGraphicsSceneMouseEvent>
23#include <QGraphicsView>
24#include <QStyleOptionGraphicsItem>
25#include <qgraphicswebview.h>
26#include <qwebpage.h>
27#include <qwebframe.h>
28
29#if defined(ENABLE_WEBGL) && ENABLE_WEBGL
30#include <QGLWidget>
31#endif
32
33class tst_QGraphicsWebView : public QObject
34{
35    Q_OBJECT
36
37private slots:
38    void qgraphicswebview();
39    void crashOnViewlessWebPages();
40    void microFocusCoordinates();
41    void focusInputTypes();
42    void crashOnSetScaleBeforeSetUrl();
43    void widgetsRenderingThroughCache();
44    void setPalette_data();
45    void setPalette();
46    void renderHints();
47#if defined(ENABLE_TILED_BACKING_STORE) && ENABLE_TILED_BACKING_STORE
48    void bug56929();
49#endif
50#if defined(ENABLE_WEBGL) && ENABLE_WEBGL
51    void webglSoftwareFallbackVerticalOrientation();
52    void webglSoftwareFallbackHorizontalOrientation();
53
54private:
55    void compareCanvasToImage(const QUrl&, const QImage&);
56#endif
57};
58
59void tst_QGraphicsWebView::qgraphicswebview()
60{
61    QGraphicsWebView item;
62    item.url();
63    item.title();
64    item.icon();
65    item.zoomFactor();
66    item.history();
67    item.settings();
68    item.page();
69    item.setPage(0);
70    item.page();
71    item.setUrl(QUrl());
72    item.setZoomFactor(0);
73    item.load(QUrl());
74    item.setHtml(QString());
75    item.setContent(QByteArray());
76    item.isModified();
77}
78
79class WebPage : public QWebPage
80{
81    Q_OBJECT
82
83public:
84    WebPage(QObject* parent = 0): QWebPage(parent)
85    {
86    }
87
88    QGraphicsWebView* webView;
89
90private slots:
91    // Force a webview deletion during the load.
92    // It should not cause WebPage to crash due to
93    // it accessing invalid pageClient pointer.
94    void aborting()
95    {
96        delete webView;
97    }
98};
99
100class GraphicsWebView : public QGraphicsWebView
101{
102    Q_OBJECT
103
104public:
105    GraphicsWebView(QGraphicsItem* parent = 0): QGraphicsWebView(parent)
106    {
107    }
108
109    void fireMouseClick(QPointF point) {
110        QGraphicsSceneMouseEvent presEv(QEvent::GraphicsSceneMousePress);
111        presEv.setPos(point);
112        presEv.setButton(Qt::LeftButton);
113        presEv.setButtons(Qt::LeftButton);
114        QGraphicsSceneMouseEvent relEv(QEvent::GraphicsSceneMouseRelease);
115        relEv.setPos(point);
116        relEv.setButton(Qt::LeftButton);
117        relEv.setButtons(Qt::LeftButton);
118        QGraphicsWebView::sceneEvent(&presEv);
119        QGraphicsWebView::sceneEvent(&relEv);
120    }
121};
122
123void tst_QGraphicsWebView::crashOnViewlessWebPages()
124{
125    QGraphicsScene scene;
126    QGraphicsView view(&scene);
127
128    QGraphicsWebView* webView = new QGraphicsWebView;
129    WebPage* page = new WebPage;
130    webView->setPage(page);
131    page->webView = webView;
132    scene.addItem(webView);
133
134    view.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
135    view.resize(600, 480);
136    webView->resize(view.geometry().size());
137
138    QCoreApplication::processEvents();
139    view.show();
140
141    // Resizing the page will resize and layout the empty "about:blank"
142    // page, so we first connect the signal afterward.
143    connect(page->mainFrame(), SIGNAL(initialLayoutCompleted()), page, SLOT(aborting()));
144
145    page->mainFrame()->load(QUrl("data:text/html,"
146                                 "<frameset cols=\"25%,75%\">"
147                                     "<frame src=\"data:text/html,foo \">"
148                                     "<frame src=\"data:text/html,bar\">"
149                                 "</frameset>"));
150
151    QVERIFY(waitForSignal(page, SIGNAL(loadFinished(bool))));
152    delete page;
153}
154
155void tst_QGraphicsWebView::crashOnSetScaleBeforeSetUrl()
156{
157    QGraphicsWebView* webView = new QGraphicsWebView;
158    webView->setScale(2.0);
159    delete webView;
160}
161
162void tst_QGraphicsWebView::widgetsRenderingThroughCache()
163{
164    // Widgets should be rendered the same way with and without
165    // intermediate cache (tiling for example).
166    // See bug https://bugs.webkit.org/show_bug.cgi?id=47767 where
167    // widget are rendered as disabled when caching is using.
168
169    QGraphicsWebView* webView = new QGraphicsWebView;
170    webView->setHtml(QLatin1String("<body style=\"background-color: white\"><input type=range></input><input type=checkbox></input><input type=radio></input><input type=file></input></body>"));
171
172    QGraphicsView view;
173    view.show();
174    QGraphicsScene* scene = new QGraphicsScene(&view);
175    view.setScene(scene);
176    scene->addItem(webView);
177    view.setGeometry(QRect(0, 0, 500, 500));
178    QWidget *const widget = &view;
179    QTest::qWaitForWindowShown(widget);
180
181    // 1. Reference without tiling.
182    webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, false);
183    QPixmap referencePixmap(view.size());
184    widget->render(&referencePixmap);
185
186    // 2. With tiling.
187    webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, true);
188    QPixmap viewWithTiling(view.size());
189    widget->render(&viewWithTiling);
190    QApplication::processEvents();
191    viewWithTiling.fill();
192    widget->render(&viewWithTiling);
193
194    QCOMPARE(referencePixmap.toImage(), viewWithTiling.toImage());
195}
196
197#if defined(ENABLE_TILED_BACKING_STORE) && ENABLE_TILED_BACKING_STORE
198void tst_QGraphicsWebView::bug56929()
199{
200    // When rendering from tiles sychronous layout should not be triggered
201    // and scrollbars should be in sync with the size of the document in the displayed state.
202
203    QGraphicsWebView* webView = new QGraphicsWebView();
204    webView->setGeometry(QRectF(0.0, 0.0, 100.0, 100.0));
205    QGraphicsView view(new QGraphicsScene());
206    view.scene()->setParent(&view);
207    view.scene()->addItem(webView);
208    webView->settings()->setAttribute(QWebSettings::TiledBackingStoreEnabled, true);
209    QUrl url("qrc:///resources/56929.html");
210    webView->load(url);
211    QVERIFY(waitForSignal(webView, SIGNAL(loadFinished(bool))));
212    QStyleOptionGraphicsItem option;
213    option.exposedRect = webView->geometry();
214    QImage img(option.exposedRect.width(), option.exposedRect.height(), QImage::Format_ARGB32_Premultiplied);
215    QPainter painter(&img);
216    // This will not paint anything as the tiles are not ready, yet.
217    webView->paint(&painter, &option);
218    QApplication::processEvents();
219    webView->paint(&painter, &option);
220    QCOMPARE(img.pixel(option.exposedRect.width() - 2, option.exposedRect.height() / 2), qRgba(255, 255, 255, 255));
221    painter.fillRect(option.exposedRect, Qt::black);
222    QCOMPARE(img.pixel(option.exposedRect.width() - 2, option.exposedRect.height() / 2), qRgba(0, 0, 0, 255));
223    webView->page()->mainFrame()->evaluateJavaScript(QString("resizeDiv();"));
224    webView->paint(&painter, &option);
225    QCOMPARE(img.pixel(option.exposedRect.width() - 2, option.exposedRect.height() / 2), qRgba(255, 255, 255, 255));
226}
227#endif
228
229void tst_QGraphicsWebView::microFocusCoordinates()
230{
231    QWebPage* page = new QWebPage;
232    QGraphicsWebView* webView = new QGraphicsWebView;
233    webView->setPage( page );
234    QGraphicsView* view = new QGraphicsView;
235    QGraphicsScene* scene = new QGraphicsScene(view);
236    view->setScene(scene);
237    scene->addItem(webView);
238    view->setGeometry(QRect(0,0,500,500));
239
240    page->mainFrame()->setHtml("<html><body>" \
241        "<input type='text' id='input1' style='font--family: serif' value='' maxlength='20'/><br>" \
242        "<canvas id='canvas1' width='500' height='500'></canvas>" \
243        "<input type='password'/><br>" \
244        "<canvas id='canvas2' width='500' height='500'></canvas>" \
245        "</body></html>");
246
247    page->mainFrame()->setFocus();
248
249    QVariant initialMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus);
250    QVERIFY(initialMicroFocus.isValid());
251
252    page->mainFrame()->scroll(0,300);
253
254    QVariant currentMicroFocus = page->inputMethodQuery(Qt::ImMicroFocus);
255    QVERIFY(currentMicroFocus.isValid());
256
257    QCOMPARE(initialMicroFocus.toRect().translated(QPoint(0,-300)), currentMicroFocus.toRect());
258
259    delete view;
260}
261
262void tst_QGraphicsWebView::focusInputTypes()
263{
264    QWebPage* page = new QWebPage;
265    GraphicsWebView* webView = new GraphicsWebView;
266    webView->setPage( page );
267    QGraphicsView* view = new QGraphicsView;
268    QGraphicsScene* scene = new QGraphicsScene(view);
269    view->setScene(scene);
270    scene->addItem(webView);
271    view->setGeometry(QRect(0,0,500,500));
272    QCoreApplication::processEvents();
273    QUrl url("qrc:///resources/input_types.html");
274    page->mainFrame()->load(url);
275    page->mainFrame()->setFocus();
276
277    QVERIFY(waitForSignal(page, SIGNAL(loadFinished(bool))));
278
279    // 'text' type
280    webView->fireMouseClick(QPointF(20.0, 10.0));
281#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) || defined(Q_OS_SYMBIAN)
282    QVERIFY(webView->inputMethodHints() & Qt::ImhNoAutoUppercase);
283    QVERIFY(webView->inputMethodHints() & Qt::ImhNoPredictiveText);
284#else
285    QVERIFY(webView->inputMethodHints() == Qt::ImhNone);
286#endif
287
288    // 'password' field
289    webView->fireMouseClick(QPointF(20.0, 60.0));
290    QVERIFY(webView->inputMethodHints() & Qt::ImhHiddenText);
291
292    // 'tel' field
293    webView->fireMouseClick(QPointF(20.0, 110.0));
294    QVERIFY(webView->inputMethodHints() & Qt::ImhDialableCharactersOnly);
295
296    // 'number' field
297    webView->fireMouseClick(QPointF(20.0, 160.0));
298    QVERIFY(webView->inputMethodHints() & Qt::ImhDigitsOnly);
299
300    // 'email' field
301    webView->fireMouseClick(QPointF(20.0, 210.0));
302    QVERIFY(webView->inputMethodHints() & Qt::ImhEmailCharactersOnly);
303
304    // 'url' field
305    webView->fireMouseClick(QPointF(20.0, 260.0));
306    QVERIFY(webView->inputMethodHints() & Qt::ImhUrlCharactersOnly);
307
308    delete webView;
309    delete view;
310}
311
312void tst_QGraphicsWebView::setPalette_data()
313{
314    QTest::addColumn<bool>("active");
315    QTest::addColumn<bool>("background");
316    QTest::newRow("activeBG") << true << true;
317    QTest::newRow("activeFG") << true << false;
318    QTest::newRow("inactiveBG") << false << true;
319    QTest::newRow("inactiveFG") << false << false;
320}
321
322// Render a QGraphicsWebView to a QImage twice, each time with a different palette set,
323// verify that images rendered are not the same, confirming WebCore usage of
324// custom palette on selections.
325void tst_QGraphicsWebView::setPalette()
326{
327    QString html = "<html><head></head>"
328                   "<body>"
329                   "Some text here"
330                   "</body>"
331                   "</html>";
332
333    QFETCH(bool, active);
334    QFETCH(bool, background);
335
336    QWidget* activeView = 0;
337
338    // Use controlView to manage active/inactive state of test views by raising
339    // or lowering their position in the window stack.
340    QGraphicsScene controlScene;
341    QGraphicsView controlView(&controlScene);
342    QGraphicsWebView controlWebView;
343    controlScene.addItem(&controlWebView);
344    controlWebView.setHtml(html);
345    controlWebView.setGeometry(QRectF(0, 0, 200, 200));
346
347    QGraphicsScene scene1;
348    QGraphicsView view1(&scene1);
349    view1.setSceneRect(0, 0, 300, 300);
350    QGraphicsWebView webView1;
351    webView1.setResizesToContents(true);
352    scene1.addItem(&webView1);
353    webView1.setFocus();
354
355    QPalette palette1;
356    QBrush brush1(Qt::red);
357    brush1.setStyle(Qt::SolidPattern);
358    if (active && background) {
359        // Rendered image must have red background on an active QGraphicsWebView.
360        palette1.setBrush(QPalette::Active, QPalette::Highlight, brush1);
361    } else if (active && !background) {
362        // Rendered image must have red foreground on an active QGraphicsWebView.
363        palette1.setBrush(QPalette::Active, QPalette::HighlightedText, brush1);
364    } else if (!active && background) {
365        // Rendered image must have red background on an inactive QGraphicsWebView.
366        palette1.setBrush(QPalette::Inactive, QPalette::Highlight, brush1);
367    } else if (!active && !background) {
368        // Rendered image must have red foreground on an inactive QGraphicsWebView.
369        palette1.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush1);
370    }
371
372    webView1.setHtml(html);
373    view1.resize(webView1.page()->viewportSize());
374    webView1.setPalette(palette1);
375    view1.show();
376
377    QVERIFY(webView1.palette() == palette1);
378    QVERIFY(webView1.page()->palette() == palette1);
379
380    QTest::qWaitForWindowShown(&view1);
381
382    if (!active) {
383        controlView.show();
384        QTest::qWaitForWindowShown(&controlView);
385        activeView = &controlView;
386        controlView.activateWindow();
387    } else {
388        view1.activateWindow();
389        activeView = &view1;
390    }
391
392    QTRY_COMPARE(QApplication::activeWindow(), activeView);
393
394    webView1.page()->triggerAction(QWebPage::SelectAll);
395
396    QImage img1(webView1.page()->viewportSize(), QImage::Format_ARGB32);
397    QPainter painter1(&img1);
398    webView1.page()->currentFrame()->render(&painter1);
399    painter1.end();
400    view1.close();
401    controlView.close();
402
403    QGraphicsScene scene2;
404    QGraphicsView view2(&scene2);
405    view2.setSceneRect(0, 0, 300, 300);
406    QGraphicsWebView webView2;
407    webView2.setResizesToContents(true);
408    scene2.addItem(&webView2);
409    webView2.setFocus();
410
411    QPalette palette2;
412    QBrush brush2(Qt::blue);
413    brush2.setStyle(Qt::SolidPattern);
414    if (active && background) {
415        // Rendered image must have blue background on an active QGraphicsWebView.
416        palette2.setBrush(QPalette::Active, QPalette::Highlight, brush2);
417    } else if (active && !background) {
418        // Rendered image must have blue foreground on an active QGraphicsWebView.
419        palette2.setBrush(QPalette::Active, QPalette::HighlightedText, brush2);
420    } else if (!active && background) {
421        // Rendered image must have blue background on an inactive QGraphicsWebView.
422        palette2.setBrush(QPalette::Inactive, QPalette::Highlight, brush2);
423    } else if (!active && !background) {
424        // Rendered image must have blue foreground on an inactive QGraphicsWebView.
425        palette2.setBrush(QPalette::Inactive, QPalette::HighlightedText, brush2);
426    }
427
428    webView2.setHtml(html);
429    view2.resize(webView2.page()->viewportSize());
430    webView2.setPalette(palette2);
431    view2.show();
432
433    QTest::qWaitForWindowShown(&view2);
434
435    if (!active) {
436        controlView.show();
437        QTest::qWaitForWindowShown(&controlView);
438        activeView = &controlView;
439        controlView.activateWindow();
440    } else {
441        view2.activateWindow();
442        activeView = &view2;
443    }
444
445    QTRY_COMPARE(QApplication::activeWindow(), activeView);
446
447    webView2.page()->triggerAction(QWebPage::SelectAll);
448
449    QImage img2(webView2.page()->viewportSize(), QImage::Format_ARGB32);
450    QPainter painter2(&img2);
451    webView2.page()->currentFrame()->render(&painter2);
452    painter2.end();
453
454    view2.close();
455    controlView.close();
456
457    QVERIFY(img1 != img2);
458}
459
460void tst_QGraphicsWebView::renderHints()
461{
462    QGraphicsWebView webView;
463
464    // default is only text antialiasing + smooth pixmap transform
465    QVERIFY(!(webView.renderHints() & QPainter::Antialiasing));
466    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
467    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
468    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
469
470    webView.setRenderHint(QPainter::Antialiasing, true);
471    QVERIFY(webView.renderHints() & QPainter::Antialiasing);
472    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
473    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
474    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
475
476    webView.setRenderHint(QPainter::Antialiasing, false);
477    QVERIFY(!(webView.renderHints() & QPainter::Antialiasing));
478    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
479    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
480    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
481
482    webView.setRenderHint(QPainter::SmoothPixmapTransform, true);
483    QVERIFY(!(webView.renderHints() & QPainter::Antialiasing));
484    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
485    QVERIFY(webView.renderHints() & QPainter::SmoothPixmapTransform);
486    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
487
488    webView.setRenderHint(QPainter::SmoothPixmapTransform, false);
489    QVERIFY(webView.renderHints() & QPainter::TextAntialiasing);
490    QVERIFY(!(webView.renderHints() & QPainter::SmoothPixmapTransform));
491    QVERIFY(!(webView.renderHints() & QPainter::HighQualityAntialiasing));
492}
493
494class GraphicsView : public QGraphicsView {
495public:
496    GraphicsView();
497    QGraphicsWebView* m_webView;
498};
499
500#if defined(ENABLE_WEBGL) && ENABLE_WEBGL
501bool compareImagesFuzzyPixelCount(const QImage& image1, const QImage& image2, qreal tolerance = 0.05)
502{
503    if (image1.size() != image2.size())
504        return false;
505
506    unsigned diffPixelCount = 0;
507    for (int row = 0; row < image1.size().width(); ++row) {
508        for (int column = 0; column < image1.size().height(); ++column)
509            if (image1.pixel(row, column) != image2.pixel(row, column))
510                ++diffPixelCount;
511    }
512
513    if (diffPixelCount > (image1.size().width() * image1.size().height()) * tolerance)
514        return false;
515
516    return true;
517}
518
519GraphicsView::GraphicsView()
520{
521    QGraphicsScene* const scene = new QGraphicsScene(this);
522    setScene(scene);
523
524    m_webView = new QGraphicsWebView;
525    scene->addItem(m_webView);
526
527    m_webView->page()->settings()->setAttribute(QWebSettings::WebGLEnabled, true);
528    m_webView->setResizesToContents(true);
529
530    setFrameShape(QFrame::NoFrame);
531    setViewport(new QGLWidget);
532}
533
534void tst_QGraphicsWebView::webglSoftwareFallbackVerticalOrientation()
535{
536    QSize canvasSize(100, 100);
537    QImage reference(canvasSize, QImage::Format_ARGB32);
538    reference.fill(0xFF00FF00);
539    { // Reference.
540        QPainter painter(&reference);
541        QPolygonF triangleUp;
542        triangleUp << QPointF(0, canvasSize.height())
543                   << QPointF(canvasSize.width(), canvasSize.height())
544                   << QPointF(canvasSize.width() / 2.0, canvasSize.height() / 2.0);
545        painter.setPen(Qt::NoPen);
546        painter.setBrush(Qt::red);
547        painter.drawPolygon(triangleUp);
548    }
549
550    compareCanvasToImage(QUrl(QLatin1String("qrc:///resources/pointing_up.html")), reference);
551}
552
553void tst_QGraphicsWebView::webglSoftwareFallbackHorizontalOrientation()
554{
555    QSize canvasSize(100, 100);
556    QImage reference(canvasSize, QImage::Format_ARGB32);
557    reference.fill(0xFF00FF00);
558    { // Reference.
559        QPainter painter(&reference);
560        QPolygonF triangleUp;
561        triangleUp << QPointF(0, 0)
562                   << QPointF(0, canvasSize.height())
563                   << QPointF(canvasSize.width() / 2.0, canvasSize.height() / 2.0);
564        painter.setPen(Qt::NoPen);
565        painter.setBrush(Qt::red);
566        painter.drawPolygon(triangleUp);
567    }
568
569    compareCanvasToImage(QUrl(QLatin1String("qrc:///resources/pointing_right.html")), reference);
570}
571
572void tst_QGraphicsWebView::compareCanvasToImage(const QUrl& url, const QImage& reference)
573{
574    GraphicsView view;
575    view.show();
576    QTest::qWaitForWindowShown(&view);
577
578    QGraphicsWebView* const graphicsWebView = view.m_webView;
579    graphicsWebView->load(url);
580    QVERIFY(waitForSignal(graphicsWebView, SIGNAL(loadFinished(bool))));
581    { // Force a render, to create the accelerated compositing tree.
582        QPixmap pixmap(view.size());
583        QPainter painter(&pixmap);
584        view.render(&painter);
585    }
586    QApplication::syncX();
587
588    const QSize imageSize = reference.size();
589
590    QImage target(imageSize, QImage::Format_ARGB32);
591    { // Web page content.
592        QPainter painter(&target);
593        QRectF renderRect(0, 0, imageSize.width(), imageSize.height());
594        view.scene()->render(&painter, renderRect, renderRect);
595    }
596    QVERIFY(compareImagesFuzzyPixelCount(target, reference, 0.01));
597}
598#endif
599
600QTEST_MAIN(tst_QGraphicsWebView)
601
602#include "tst_qgraphicswebview.moc"
603