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