1 /* 2 * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. 3 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann (at) kde.org> 4 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 5 * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 34 #include "DumpRenderTreeQt.h" 35 #include "EventSenderQt.h" 36 #include "GCControllerQt.h" 37 #include "LayoutTestControllerQt.h" 38 #include "TextInputControllerQt.h" 39 #include "testplugin.h" 40 #include "WorkQueue.h" 41 42 #include <QBuffer> 43 #include <QCryptographicHash> 44 #include <QDir> 45 #include <QFile> 46 #include <QApplication> 47 #include <QUrl> 48 #include <QFileInfo> 49 #include <QFocusEvent> 50 #include <QFontDatabase> 51 #include <QLocale> 52 #include <QNetworkAccessManager> 53 #include <QNetworkReply> 54 #include <QNetworkRequest> 55 #include <QUndoStack> 56 57 #include <qwebsettings.h> 58 #include <qwebsecurityorigin.h> 59 60 #ifndef QT_NO_UITOOLS 61 #include <QtUiTools/QUiLoader> 62 #endif 63 64 #ifdef Q_WS_X11 65 #include <fontconfig/fontconfig.h> 66 #endif 67 68 #include <limits.h> 69 70 #ifndef Q_OS_WIN 71 #include <unistd.h> 72 #endif 73 74 #include <qdebug.h> 75 76 extern void qt_drt_run(bool b); 77 extern void qt_dump_set_accepts_editing(bool b); 78 extern void qt_dump_frame_loader(bool b); 79 extern void qt_drt_clearFrameName(QWebFrame* qFrame); 80 extern void qt_drt_overwritePluginDirectories(); 81 extern void qt_drt_resetOriginAccessWhiteLists(); 82 extern bool qt_drt_hasDocumentElement(QWebFrame* qFrame); 83 84 namespace WebCore { 85 86 // Choose some default values. 87 const unsigned int maxViewWidth = 800; 88 const unsigned int maxViewHeight = 600; 89 90 NetworkAccessManager::NetworkAccessManager(QObject* parent) 91 : QNetworkAccessManager(parent) 92 { 93 #ifndef QT_NO_OPENSSL 94 connect(this, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), 95 this, SLOT(sslErrorsEncountered(QNetworkReply*, const QList<QSslError>&))); 96 #endif 97 } 98 99 #ifndef QT_NO_OPENSSL 100 void NetworkAccessManager::sslErrorsEncountered(QNetworkReply* reply, const QList<QSslError>& errors) 101 { 102 if (reply->url().host() == "127.0.0.1" || reply->url().host() == "localhost") { 103 bool ignore = true; 104 105 // Accept any HTTPS certificate. 106 foreach (const QSslError& error, errors) { 107 if (error.error() < QSslError::UnableToGetIssuerCertificate || error.error() > QSslError::HostNameMismatch) { 108 ignore = false; 109 break; 110 } 111 } 112 113 if (ignore) 114 reply->ignoreSslErrors(); 115 } 116 } 117 #endif 118 119 WebPage::WebPage(QObject* parent, DumpRenderTree* drt) 120 : QWebPage(parent) 121 , m_webInspector(0) 122 , m_drt(drt) 123 { 124 QWebSettings* globalSettings = QWebSettings::globalSettings(); 125 126 globalSettings->setFontSize(QWebSettings::MinimumFontSize, 5); 127 globalSettings->setFontSize(QWebSettings::MinimumLogicalFontSize, 5); 128 globalSettings->setFontSize(QWebSettings::DefaultFontSize, 16); 129 globalSettings->setFontSize(QWebSettings::DefaultFixedFontSize, 13); 130 131 globalSettings->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); 132 globalSettings->setAttribute(QWebSettings::JavascriptCanAccessClipboard, true); 133 globalSettings->setAttribute(QWebSettings::LinksIncludedInFocusChain, false); 134 globalSettings->setAttribute(QWebSettings::PluginsEnabled, true); 135 globalSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true); 136 globalSettings->setAttribute(QWebSettings::JavascriptEnabled, true); 137 globalSettings->setAttribute(QWebSettings::PrivateBrowsingEnabled, false); 138 globalSettings->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, false); 139 140 connect(this, SIGNAL(geometryChangeRequested(const QRect &)), 141 this, SLOT(setViewGeometry(const QRect & ))); 142 143 setNetworkAccessManager(new NetworkAccessManager(this)); 144 setPluginFactory(new TestPlugin(this)); 145 } 146 147 WebPage::~WebPage() 148 { 149 delete m_webInspector; 150 } 151 152 QWebInspector* WebPage::webInspector() 153 { 154 if (!m_webInspector) { 155 m_webInspector = new QWebInspector; 156 m_webInspector->setPage(this); 157 } 158 return m_webInspector; 159 } 160 161 void WebPage::resetSettings() 162 { 163 // After each layout test, reset the settings that may have been changed by 164 // layoutTestController.overridePreference() or similar. 165 settings()->resetFontSize(QWebSettings::DefaultFontSize); 166 settings()->resetAttribute(QWebSettings::JavascriptCanOpenWindows); 167 settings()->resetAttribute(QWebSettings::JavascriptEnabled); 168 settings()->resetAttribute(QWebSettings::PrivateBrowsingEnabled); 169 settings()->resetAttribute(QWebSettings::LinksIncludedInFocusChain); 170 settings()->resetAttribute(QWebSettings::OfflineWebApplicationCacheEnabled); 171 settings()->resetAttribute(QWebSettings::LocalContentCanAccessRemoteUrls); 172 173 // globalSettings must be reset explicitly. 174 m_drt->layoutTestController()->setXSSAuditorEnabled(false); 175 176 QWebSettings::setMaximumPagesInCache(0); // reset to default 177 settings()->setUserStyleSheetUrl(QUrl()); // reset to default 178 } 179 180 QWebPage *WebPage::createWindow(QWebPage::WebWindowType) 181 { 182 return m_drt->createWindow(); 183 } 184 185 void WebPage::javaScriptAlert(QWebFrame*, const QString& message) 186 { 187 if (!isTextOutputEnabled()) 188 return; 189 190 fprintf(stdout, "ALERT: %s\n", message.toUtf8().constData()); 191 } 192 193 static QString urlSuitableForTestResult(const QString& url) 194 { 195 if (url.isEmpty() || !url.startsWith(QLatin1String("file://"))) 196 return url; 197 198 return QFileInfo(url).fileName(); 199 } 200 201 void WebPage::javaScriptConsoleMessage(const QString& message, int lineNumber, const QString&) 202 { 203 if (!isTextOutputEnabled()) 204 return; 205 206 QString newMessage; 207 if (!message.isEmpty()) { 208 newMessage = message; 209 210 size_t fileProtocol = newMessage.indexOf(QLatin1String("file://")); 211 if (fileProtocol != -1) { 212 newMessage = newMessage.left(fileProtocol) + urlSuitableForTestResult(newMessage.mid(fileProtocol)); 213 } 214 } 215 216 fprintf (stdout, "CONSOLE MESSAGE: line %d: %s\n", lineNumber, newMessage.toUtf8().constData()); 217 } 218 219 bool WebPage::javaScriptConfirm(QWebFrame*, const QString& msg) 220 { 221 if (!isTextOutputEnabled()) 222 return true; 223 224 fprintf(stdout, "CONFIRM: %s\n", msg.toUtf8().constData()); 225 return true; 226 } 227 228 bool WebPage::javaScriptPrompt(QWebFrame*, const QString& msg, const QString& defaultValue, QString* result) 229 { 230 if (!isTextOutputEnabled()) 231 return true; 232 233 fprintf(stdout, "PROMPT: %s, default text: %s\n", msg.toUtf8().constData(), defaultValue.toUtf8().constData()); 234 *result = defaultValue; 235 return true; 236 } 237 238 bool WebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, NavigationType type) 239 { 240 if (m_drt->layoutTestController()->waitForPolicy()) { 241 QString url = QString::fromUtf8(request.url().toEncoded()); 242 QString typeDescription; 243 244 switch (type) { 245 case NavigationTypeLinkClicked: 246 typeDescription = "link clicked"; 247 break; 248 case NavigationTypeFormSubmitted: 249 typeDescription = "form submitted"; 250 break; 251 case NavigationTypeBackOrForward: 252 typeDescription = "back/forward"; 253 break; 254 case NavigationTypeReload: 255 typeDescription = "reload"; 256 break; 257 case NavigationTypeFormResubmitted: 258 typeDescription = "form resubmitted"; 259 break; 260 case NavigationTypeOther: 261 typeDescription = "other"; 262 break; 263 default: 264 typeDescription = "illegal value"; 265 } 266 267 if (isTextOutputEnabled()) 268 fprintf(stdout, "Policy delegate: attempt to load %s with navigation type '%s'\n", 269 url.toUtf8().constData(), typeDescription.toUtf8().constData()); 270 271 m_drt->layoutTestController()->notifyDone(); 272 } 273 return QWebPage::acceptNavigationRequest(frame, request, type); 274 } 275 276 bool WebPage::supportsExtension(QWebPage::Extension extension) const 277 { 278 if (extension == QWebPage::ErrorPageExtension) 279 return m_drt->layoutTestController()->shouldHandleErrorPages(); 280 281 return false; 282 } 283 284 bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) 285 { 286 const QWebPage::ErrorPageExtensionOption* info = static_cast<const QWebPage::ErrorPageExtensionOption*>(option); 287 288 // Lets handle error pages for the main frame for now. 289 if (info->frame != mainFrame()) 290 return false; 291 292 QWebPage::ErrorPageExtensionReturn* errorPage = static_cast<QWebPage::ErrorPageExtensionReturn*>(output); 293 294 errorPage->content = QString("data:text/html,<body/>").toUtf8(); 295 296 return true; 297 } 298 299 QObject* WebPage::createPlugin(const QString& classId, const QUrl& url, const QStringList& paramNames, const QStringList& paramValues) 300 { 301 Q_UNUSED(url); 302 Q_UNUSED(paramNames); 303 Q_UNUSED(paramValues); 304 #ifndef QT_NO_UITOOLS 305 QUiLoader loader; 306 return loader.createWidget(classId, view()); 307 #else 308 Q_UNUSED(classId); 309 return 0; 310 #endif 311 } 312 313 DumpRenderTree::DumpRenderTree() 314 : m_dumpPixels(false) 315 , m_stdin(0) 316 , m_enableTextOutput(false) 317 , m_singleFileMode(false) 318 { 319 qt_drt_overwritePluginDirectories(); 320 QWebSettings::enablePersistentStorage(); 321 322 // create our primary testing page/view. 323 m_mainView = new QWebView(0); 324 m_mainView->resize(QSize(maxViewWidth, maxViewHeight)); 325 m_page = new WebPage(m_mainView, this); 326 m_mainView->setPage(m_page); 327 328 // create our controllers. This has to be done before connectFrame, 329 // as it exports there to the JavaScript DOM window. 330 m_controller = new LayoutTestController(this); 331 connect(m_controller, SIGNAL(showPage()), this, SLOT(showPage())); 332 connect(m_controller, SIGNAL(hidePage()), this, SLOT(hidePage())); 333 334 connect(m_controller, SIGNAL(done()), this, SLOT(dump())); 335 m_eventSender = new EventSender(m_page); 336 m_textInputController = new TextInputController(m_page); 337 m_gcController = new GCController(m_page); 338 339 // now connect our different signals 340 connect(m_page, SIGNAL(frameCreated(QWebFrame *)), 341 this, SLOT(connectFrame(QWebFrame *))); 342 connectFrame(m_page->mainFrame()); 343 344 connect(m_page, SIGNAL(loadFinished(bool)), 345 m_controller, SLOT(maybeDump(bool))); 346 // We need to connect to loadStarted() because notifyDone should only 347 // dump results itself when the last page loaded in the test has finished loading. 348 connect(m_page, SIGNAL(loadStarted()), 349 m_controller, SLOT(resetLoadFinished())); 350 connect(m_page, SIGNAL(windowCloseRequested()), this, SLOT(windowCloseRequested())); 351 352 connect(m_page->mainFrame(), SIGNAL(titleChanged(const QString&)), 353 SLOT(titleChanged(const QString&))); 354 connect(m_page, SIGNAL(databaseQuotaExceeded(QWebFrame*,QString)), 355 this, SLOT(dumpDatabaseQuota(QWebFrame*,QString))); 356 connect(m_page, SIGNAL(statusBarMessage(const QString&)), 357 this, SLOT(statusBarMessage(const QString&))); 358 359 QObject::connect(this, SIGNAL(quit()), qApp, SLOT(quit()), Qt::QueuedConnection); 360 qt_drt_run(true); 361 QFocusEvent event(QEvent::FocusIn, Qt::ActiveWindowFocusReason); 362 QApplication::sendEvent(m_mainView, &event); 363 } 364 365 DumpRenderTree::~DumpRenderTree() 366 { 367 delete m_mainView; 368 delete m_stdin; 369 } 370 371 static void clearHistory(QWebPage* page) 372 { 373 // QWebHistory::clear() leaves current page, so remove it as well by setting 374 // max item count to 0, and then setting it back to it's original value. 375 376 QWebHistory* history = page->history(); 377 int itemCount = history->maximumItemCount(); 378 379 history->clear(); 380 history->setMaximumItemCount(0); 381 history->setMaximumItemCount(itemCount); 382 } 383 384 void DumpRenderTree::resetToConsistentStateBeforeTesting() 385 { 386 // reset so that any current loads are stopped 387 // NOTE: that this has to be done before the layoutTestController is 388 // reset or we get timeouts for some tests. 389 m_page->blockSignals(true); 390 m_page->triggerAction(QWebPage::Stop); 391 m_page->blockSignals(false); 392 393 // reset the layoutTestController at this point, so that we under no 394 // circumstance dump (stop the waitUntilDone timer) during the reset 395 // of the DRT. 396 m_controller->reset(); 397 398 closeRemainingWindows(); 399 400 m_page->resetSettings(); 401 m_page->undoStack()->clear(); 402 m_page->mainFrame()->setZoomFactor(1.0); 403 clearHistory(m_page); 404 qt_drt_clearFrameName(m_page->mainFrame()); 405 406 WorkQueue::shared()->clear(); 407 WorkQueue::shared()->setFrozen(false); 408 409 qt_drt_resetOriginAccessWhiteLists(); 410 411 QLocale::setDefault(QLocale::c()); 412 setlocale(LC_ALL, ""); 413 } 414 415 void DumpRenderTree::open(const QUrl& url) 416 { 417 resetToConsistentStateBeforeTesting(); 418 419 // W3C SVG tests expect to be 480x360 420 bool isW3CTest = url.toString().contains("svg/W3C-SVG-1.1"); 421 int width = isW3CTest ? 480 : maxViewWidth; 422 int height = isW3CTest ? 360 : maxViewHeight; 423 m_mainView->resize(QSize(width, height)); 424 m_page->setPreferredContentsSize(QSize()); 425 m_page->setViewportSize(QSize(width, height)); 426 427 QFocusEvent ev(QEvent::FocusIn); 428 m_page->event(&ev); 429 430 QWebSettings::clearMemoryCaches(); 431 QFontDatabase::removeAllApplicationFonts(); 432 #if defined(Q_WS_X11) 433 initializeFonts(); 434 #endif 435 436 qt_dump_frame_loader(url.toString().contains("loading/")); 437 setTextOutputEnabled(true); 438 m_page->mainFrame()->load(url); 439 } 440 441 void DumpRenderTree::readLine() 442 { 443 if (!m_stdin) { 444 m_stdin = new QFile; 445 m_stdin->open(stdin, QFile::ReadOnly); 446 447 if (!m_stdin->isReadable()) { 448 emit quit(); 449 return; 450 } 451 } 452 453 QByteArray line = m_stdin->readLine().trimmed(); 454 455 if (line.isEmpty()) { 456 emit quit(); 457 return; 458 } 459 460 processLine(QString::fromLocal8Bit(line.constData(), line.length())); 461 } 462 463 void DumpRenderTree::processLine(const QString &input) 464 { 465 QString line = input; 466 467 m_expectedHash = QString(); 468 if (m_dumpPixels) { 469 // single quote marks the pixel dump hash 470 int i = line.indexOf('\''); 471 if (i > -1) { 472 m_expectedHash = line.mid(i + 1, line.length()); 473 line.remove(i, line.length()); 474 } 475 } 476 477 if (line.startsWith(QLatin1String("http:")) 478 || line.startsWith(QLatin1String("https:")) 479 || line.startsWith(QLatin1String("file:"))) { 480 open(QUrl(line)); 481 } else { 482 QFileInfo fi(line); 483 484 if (!fi.exists()) { 485 QDir currentDir = QDir::currentPath(); 486 487 // Try to be smart about where the test is located 488 if (currentDir.dirName() == QLatin1String("LayoutTests")) 489 fi = QFileInfo(currentDir, line.replace(QRegExp(".*?LayoutTests/(.*)"), "\\1")); 490 else if (!line.contains(QLatin1String("LayoutTests"))) 491 fi = QFileInfo(currentDir, line.prepend(QLatin1String("LayoutTests/"))); 492 493 if (!fi.exists()) { 494 if (isSingleFileMode()) 495 emit quit(); 496 else 497 emit ready(); 498 499 return; 500 } 501 502 } 503 504 open(QUrl::fromLocalFile(fi.absoluteFilePath())); 505 } 506 507 fflush(stdout); 508 } 509 510 void DumpRenderTree::setDumpPixels(bool dump) 511 { 512 m_dumpPixels = dump; 513 } 514 515 void DumpRenderTree::closeRemainingWindows() 516 { 517 foreach (QObject* widget, windows) 518 delete widget; 519 windows.clear(); 520 } 521 522 void DumpRenderTree::initJSObjects() 523 { 524 QWebFrame *frame = qobject_cast<QWebFrame*>(sender()); 525 Q_ASSERT(frame); 526 frame->addToJavaScriptWindowObject(QLatin1String("layoutTestController"), m_controller); 527 frame->addToJavaScriptWindowObject(QLatin1String("eventSender"), m_eventSender); 528 frame->addToJavaScriptWindowObject(QLatin1String("textInputController"), m_textInputController); 529 frame->addToJavaScriptWindowObject(QLatin1String("GCController"), m_gcController); 530 } 531 532 void DumpRenderTree::showPage() 533 { 534 m_mainView->show(); 535 // we need a paint event but cannot process all the events 536 QPixmap pixmap(m_mainView->size()); 537 m_mainView->render(&pixmap); 538 } 539 540 void DumpRenderTree::hidePage() 541 { 542 m_mainView->hide(); 543 } 544 545 QString DumpRenderTree::dumpFramesAsText(QWebFrame* frame) 546 { 547 if (!frame || !qt_drt_hasDocumentElement(frame)) 548 return QString(); 549 550 QString result; 551 QWebFrame *parent = qobject_cast<QWebFrame *>(frame->parent()); 552 if (parent) { 553 result.append(QLatin1String("\n--------\nFrame: '")); 554 result.append(frame->frameName()); 555 result.append(QLatin1String("'\n--------\n")); 556 } 557 558 QString innerText = frame->toPlainText(); 559 result.append(innerText); 560 result.append(QLatin1String("\n")); 561 562 if (m_controller->shouldDumpChildrenAsText()) { 563 QList<QWebFrame *> children = frame->childFrames(); 564 for (int i = 0; i < children.size(); ++i) 565 result += dumpFramesAsText(children.at(i)); 566 } 567 568 return result; 569 } 570 571 static QString dumpHistoryItem(const QWebHistoryItem& item, int indent, bool current) 572 { 573 QString result; 574 575 int start = 0; 576 if (current) { 577 result.append(QLatin1String("curr->")); 578 start = 6; 579 } 580 for (int i = start; i < indent; i++) 581 result.append(' '); 582 583 QString url = item.url().toString(); 584 if (url.contains("file://")) { 585 static QString layoutTestsString("/LayoutTests/"); 586 static QString fileTestString("(file test):"); 587 588 QString res = url.mid(url.indexOf(layoutTestsString) + layoutTestsString.length()); 589 if (res.isEmpty()) 590 return result; 591 592 result.append(fileTestString); 593 result.append(res); 594 } else { 595 result.append(url); 596 } 597 598 // FIXME: Wrong, need (private?) API for determining this. 599 result.append(QLatin1String(" **nav target**")); 600 result.append(QLatin1String("\n")); 601 602 return result; 603 } 604 605 QString DumpRenderTree::dumpBackForwardList() 606 { 607 QWebHistory* history = webPage()->history(); 608 609 QString result; 610 result.append(QLatin1String("\n============== Back Forward List ==============\n")); 611 612 // FORMAT: 613 // " (file test):fast/loader/resources/click-fragment-link.html **nav target**" 614 // "curr-> (file test):fast/loader/resources/click-fragment-link.html#testfragment **nav target**" 615 616 int maxItems = history->maximumItemCount(); 617 618 foreach (const QWebHistoryItem item, history->backItems(maxItems)) { 619 if (!item.isValid()) 620 continue; 621 result.append(dumpHistoryItem(item, 8, false)); 622 } 623 624 QWebHistoryItem item = history->currentItem(); 625 if (item.isValid()) 626 result.append(dumpHistoryItem(item, 8, true)); 627 628 foreach (const QWebHistoryItem item, history->forwardItems(maxItems)) { 629 if (!item.isValid()) 630 continue; 631 result.append(dumpHistoryItem(item, 8, false)); 632 } 633 634 result.append(QLatin1String("===============================================\n")); 635 return result; 636 } 637 638 static const char *methodNameStringForFailedTest(LayoutTestController *controller) 639 { 640 const char *errorMessage; 641 if (controller->shouldDumpAsText()) 642 errorMessage = "[documentElement innerText]"; 643 // FIXME: Add when we have support 644 //else if (controller->dumpDOMAsWebArchive()) 645 // errorMessage = "[[mainFrame DOMDocument] webArchive]"; 646 //else if (controller->dumpSourceAsWebArchive()) 647 // errorMessage = "[[mainFrame dataSource] webArchive]"; 648 else 649 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]"; 650 651 return errorMessage; 652 } 653 654 void DumpRenderTree::dump() 655 { 656 // Prevent any further frame load callbacks from appearing after we dump the result. 657 qt_dump_frame_loader(false); 658 659 QWebFrame *mainFrame = m_page->mainFrame(); 660 661 if (isSingleFileMode()) { 662 QString markup = mainFrame->toHtml(); 663 fprintf(stdout, "Source:\n\n%s\n", markup.toUtf8().constData()); 664 } 665 666 // Dump render text... 667 QString resultString; 668 if (m_controller->shouldDumpAsText()) 669 resultString = dumpFramesAsText(mainFrame); 670 else 671 resultString = mainFrame->renderTreeDump(); 672 673 if (!resultString.isEmpty()) { 674 fprintf(stdout, "%s", resultString.toUtf8().constData()); 675 676 if (m_controller->shouldDumpBackForwardList()) 677 fprintf(stdout, "%s", dumpBackForwardList().toUtf8().constData()); 678 679 } else 680 printf("ERROR: nil result from %s", methodNameStringForFailedTest(m_controller)); 681 682 // signal end of text block 683 fputs("#EOF\n", stdout); 684 fputs("#EOF\n", stderr); 685 686 if (m_dumpPixels) { 687 QImage image(m_page->viewportSize(), QImage::Format_ARGB32); 688 image.fill(Qt::white); 689 QPainter painter(&image); 690 mainFrame->render(&painter); 691 painter.end(); 692 693 QCryptographicHash hash(QCryptographicHash::Md5); 694 for (int row = 0; row < image.height(); ++row) 695 hash.addData(reinterpret_cast<const char*>(image.scanLine(row)), image.width() * 4); 696 QString actualHash = hash.result().toHex(); 697 698 fprintf(stdout, "\nActualHash: %s\n", qPrintable(actualHash)); 699 700 bool dumpImage = true; 701 702 if (!m_expectedHash.isEmpty()) { 703 Q_ASSERT(m_expectedHash.length() == 32); 704 fprintf(stdout, "\nExpectedHash: %s\n", qPrintable(m_expectedHash)); 705 706 if (m_expectedHash == actualHash) 707 dumpImage = false; 708 } 709 710 if (dumpImage) { 711 QBuffer buffer; 712 buffer.open(QBuffer::WriteOnly); 713 image.save(&buffer, "PNG"); 714 buffer.close(); 715 const QByteArray &data = buffer.data(); 716 717 printf("Content-Type: %s\n", "image/png"); 718 printf("Content-Length: %lu\n", static_cast<unsigned long>(data.length())); 719 720 const quint32 bytesToWriteInOneChunk = 1 << 15; 721 quint32 dataRemainingToWrite = data.length(); 722 const char *ptr = data.data(); 723 while (dataRemainingToWrite) { 724 quint32 bytesToWriteInThisChunk = qMin(dataRemainingToWrite, bytesToWriteInOneChunk); 725 quint32 bytesWritten = fwrite(ptr, 1, bytesToWriteInThisChunk, stdout); 726 if (bytesWritten != bytesToWriteInThisChunk) 727 break; 728 dataRemainingToWrite -= bytesWritten; 729 ptr += bytesWritten; 730 } 731 } 732 733 fflush(stdout); 734 } 735 736 puts("#EOF"); // terminate the (possibly empty) pixels block 737 738 fflush(stdout); 739 fflush(stderr); 740 741 if (isSingleFileMode()) 742 emit quit(); 743 else 744 emit ready(); 745 } 746 747 void DumpRenderTree::titleChanged(const QString &s) 748 { 749 if (m_controller->shouldDumpTitleChanges()) 750 printf("TITLE CHANGED: %s\n", s.toUtf8().data()); 751 } 752 753 void DumpRenderTree::connectFrame(QWebFrame *frame) 754 { 755 connect(frame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(initJSObjects())); 756 connect(frame, SIGNAL(provisionalLoad()), 757 layoutTestController(), SLOT(provisionalLoad())); 758 } 759 760 void DumpRenderTree::dumpDatabaseQuota(QWebFrame* frame, const QString& dbName) 761 { 762 if (!m_controller->shouldDumpDatabaseCallbacks()) 763 return; 764 QWebSecurityOrigin origin = frame->securityOrigin(); 765 printf("UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{%s, %s, %i} database:%s\n", 766 origin.scheme().toUtf8().data(), 767 origin.host().toUtf8().data(), 768 origin.port(), 769 dbName.toUtf8().data()); 770 origin.setDatabaseQuota(5 * 1024 * 1024); 771 } 772 773 void DumpRenderTree::statusBarMessage(const QString& message) 774 { 775 if (!m_controller->shouldDumpStatusCallbacks()) 776 return; 777 778 printf("UI DELEGATE STATUS CALLBACK: setStatusText:%s\n", message.toUtf8().constData()); 779 } 780 781 QWebPage *DumpRenderTree::createWindow() 782 { 783 if (!m_controller->canOpenWindows()) 784 return 0; 785 786 // Create a dummy container object to track the page in DRT. 787 // QObject is used instead of QWidget to prevent DRT from 788 // showing the main view when deleting the container. 789 790 QObject* container = new QObject(m_mainView); 791 // create a QWebPage we want to return 792 QWebPage* page = static_cast<QWebPage*>(new WebPage(container, this)); 793 // gets cleaned up in closeRemainingWindows() 794 windows.append(container); 795 796 // connect the needed signals to the page 797 connect(page, SIGNAL(frameCreated(QWebFrame*)), this, SLOT(connectFrame(QWebFrame*))); 798 connectFrame(page->mainFrame()); 799 connect(page, SIGNAL(loadFinished(bool)), m_controller, SLOT(maybeDump(bool))); 800 connect(page, SIGNAL(windowCloseRequested()), this, SLOT(windowCloseRequested())); 801 return page; 802 } 803 804 void DumpRenderTree::windowCloseRequested() 805 { 806 QWebPage* page = qobject_cast<QWebPage*>(sender()); 807 QObject* container = page->parent(); 808 windows.removeAll(container); 809 container->deleteLater(); 810 } 811 812 int DumpRenderTree::windowCount() const 813 { 814 // include the main view in the count 815 return windows.count() + 1; 816 } 817 818 void DumpRenderTree::switchFocus(bool focused) 819 { 820 QFocusEvent event((focused) ? QEvent::FocusIn : QEvent::FocusOut, Qt::ActiveWindowFocusReason); 821 QApplication::sendEvent(m_mainView, &event); 822 } 823 824 #if defined(Q_WS_X11) 825 void DumpRenderTree::initializeFonts() 826 { 827 static int numFonts = -1; 828 829 // Some test cases may add or remove application fonts (via @font-face). 830 // Make sure to re-initialize the font set if necessary. 831 FcFontSet* appFontSet = FcConfigGetFonts(0, FcSetApplication); 832 if (appFontSet && numFonts >= 0 && appFontSet->nfont == numFonts) 833 return; 834 835 QByteArray fontDir = getenv("WEBKIT_TESTFONTS"); 836 if (fontDir.isEmpty() || !QDir(fontDir).exists()) { 837 fprintf(stderr, 838 "\n\n" 839 "----------------------------------------------------------------------\n" 840 "WEBKIT_TESTFONTS environment variable is not set correctly.\n" 841 "This variable has to point to the directory containing the fonts\n" 842 "you can clone from git://gitorious.org/qtwebkit/testfonts.git\n" 843 "----------------------------------------------------------------------\n" 844 ); 845 exit(1); 846 } 847 char currentPath[PATH_MAX+1]; 848 if (!getcwd(currentPath, PATH_MAX)) 849 qFatal("Couldn't get current working directory"); 850 QByteArray configFile = currentPath; 851 FcConfig *config = FcConfigCreate(); 852 configFile += "/WebKitTools/DumpRenderTree/qt/fonts.conf"; 853 if (!FcConfigParseAndLoad (config, (FcChar8*) configFile.data(), true)) 854 qFatal("Couldn't load font configuration file"); 855 if (!FcConfigAppFontAddDir (config, (FcChar8*) fontDir.data())) 856 qFatal("Couldn't add font dir!"); 857 FcConfigSetCurrent(config); 858 859 appFontSet = FcConfigGetFonts(config, FcSetApplication); 860 numFonts = appFontSet->nfont; 861 } 862 #endif 863 864 } 865