Home | History | Annotate | Download | only in qt
      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