1 /* 2 Copyright (C) 2009 Jakub Wieczorek <faw217 (at) 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 33 class tst_QGraphicsWebView : public QObject 34 { 35 Q_OBJECT 36 37 private 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 54 private: 55 void compareCanvasToImage(const QUrl&, const QImage&); 56 #endif 57 }; 58 59 void 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 79 class WebPage : public QWebPage 80 { 81 Q_OBJECT 82 83 public: 84 WebPage(QObject* parent = 0): QWebPage(parent) 85 { 86 } 87 88 QGraphicsWebView* webView; 89 90 private 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 100 class GraphicsWebView : public QGraphicsWebView 101 { 102 Q_OBJECT 103 104 public: 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 123 void 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 155 void tst_QGraphicsWebView::crashOnSetScaleBeforeSetUrl() 156 { 157 QGraphicsWebView* webView = new QGraphicsWebView; 158 webView->setScale(2.0); 159 delete webView; 160 } 161 162 void 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 198 void 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 229 void 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 262 void 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 312 void 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. 325 void 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 460 void 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 494 class GraphicsView : public QGraphicsView { 495 public: 496 GraphicsView(); 497 QGraphicsWebView* m_webView; 498 }; 499 500 #if defined(ENABLE_WEBGL) && ENABLE_WEBGL 501 bool 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 519 GraphicsView::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 534 void 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 553 void 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 572 void 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 600 QTEST_MAIN(tst_QGraphicsWebView) 601 602 #include "tst_qgraphicswebview.moc" 603