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