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