2010-12-06 Philippe Normand <pnormand@igalia.com>
[WebKit-https.git] / WebKitTools / DumpRenderTree / gtk / DumpRenderTree.cpp
1 /*
2  * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
3  * Copyright (C) 2008 Alp Toker <alp@nuanti.com>
4  * Copyright (C) 2009 Jan Alonzo <jmalonzo@gmail.com>
5  * Copyright (C) 2010 Igalia S.L.
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 #include "DumpRenderTree.h"
34
35 #include "AccessibilityController.h"
36 #include "EditingCallbacks.h"
37 #include "EventSender.h"
38 #include "GCController.h"
39 #include "GOwnPtr.h"
40 #include "LayoutTestController.h"
41 #include "PixelDumpSupport.h"
42 #include "WebCoreSupport/DumpRenderTreeSupportGtk.h"
43 #include "WorkQueue.h"
44 #include "WorkQueueItem.h"
45 #include <JavaScriptCore/JavaScript.h>
46 #include <cassert>
47 #include <cstdlib>
48 #include <cstring>
49 #include <getopt.h>
50 #include <gtk/gtk.h>
51 #include <webkit/webkit.h>
52 #include <wtf/Assertions.h>
53
54 #if PLATFORM(X11)
55 #include <fontconfig/fontconfig.h>
56 #endif
57
58
59 using namespace std;
60
61 extern "C" {
62 // This API is not yet public.
63 extern G_CONST_RETURN gchar* webkit_web_history_item_get_target(WebKitWebHistoryItem*);
64 extern gboolean webkit_web_history_item_is_target_item(WebKitWebHistoryItem*);
65 extern GList* webkit_web_history_item_get_children(WebKitWebHistoryItem*);
66 extern void webkit_web_settings_add_extra_plugin_directory(WebKitWebView* view, const gchar* directory);
67 extern gchar* webkit_web_frame_get_response_mime_type(WebKitWebFrame* frame);
68 }
69
70 volatile bool done;
71 static bool printSeparators;
72 static int dumpPixels;
73 static int dumpTree = 1;
74
75 AccessibilityController* axController = 0;
76 RefPtr<LayoutTestController> gLayoutTestController;
77 static GCController* gcController = 0;
78 static WebKitWebView* webView;
79 static GtkWidget* window;
80 static GtkWidget* container;
81 static GtkWidget* webInspectorWindow;
82 WebKitWebFrame* mainFrame = 0;
83 WebKitWebFrame* topLoadingFrame = 0;
84 guint waitToDumpWatchdog = 0;
85 bool waitForPolicy = false;
86
87 // This is a list of opened webviews
88 GSList* webViewList = 0;
89
90 // current b/f item at the end of the previous test
91 static WebKitWebHistoryItem* prevTestBFItem = NULL;
92
93 const unsigned historyItemIndent = 8;
94
95 static void runTest(const string& testPathOrURL);
96
97 static bool shouldLogFrameLoadDelegates(const string& pathOrURL)
98 {
99     return pathOrURL.find("loading/") != string::npos;
100 }
101
102 static bool shouldOpenWebInspector(const string& pathOrURL)
103 {
104     return pathOrURL.find("inspector/") != string::npos;
105 }
106
107 static bool shouldEnableDeveloperExtras(const string& pathOrURL)
108 {
109     return true;
110 }
111
112 void dumpFrameScrollPosition(WebKitWebFrame* frame)
113 {
114
115 }
116
117 void displayWebView()
118 {
119     gtk_widget_queue_draw(GTK_WIDGET(webView));
120 }
121
122 static void appendString(gchar*& target, gchar* string)
123 {
124     gchar* oldString = target;
125     target = g_strconcat(target, string, NULL);
126     g_free(oldString);
127 }
128
129 static void initializeGtkFontSettings(const char* testURL)
130 {
131     GtkSettings* settings = gtk_settings_get_default();
132     if (!settings)
133         return;
134     g_object_set(settings, "gtk-xft-antialias", 1,
135                  "gtk-xft-hinting", 0,
136                  "gtk-font-name", "Liberation Sans 16", NULL);
137
138     // One test needs subpixel anti-aliasing turned on, but generally we
139     // want all text in other tests to use to grayscale anti-aliasing.
140     if (testURL && strstr(testURL, "xsettings_antialias_settings.html"))
141         g_object_set(settings, "gtk-xft-rgba", "rgb", NULL);
142     else
143         g_object_set(settings, "gtk-xft-rgba", "none", NULL);
144 }
145
146 static void initializeFonts(const char* testURL = 0)
147 {
148 #if PLATFORM(X11)
149     initializeGtkFontSettings(testURL);
150
151     FcInit();
152
153     // If a test resulted a font being added or removed via the @font-face rule, then
154     // we want to reset the FontConfig configuration to prevent it from affecting other tests.
155     static int numFonts = 0;
156     FcFontSet* appFontSet = FcConfigGetFonts(0, FcSetApplication);
157     if (appFontSet && numFonts && appFontSet->nfont == numFonts)
158         return;
159
160     // Load our configuration file, which sets up proper aliases for family
161     // names like sans, serif and monospace.
162     FcConfig* config = FcConfigCreate();
163     GOwnPtr<gchar> fontConfigFilename(g_build_filename(FONTS_CONF_DIR, "fonts.conf", NULL));
164     if (!FcConfigParseAndLoad(config, reinterpret_cast<FcChar8*>(fontConfigFilename.get()), true))
165         g_error("Couldn't load font configuration file from: %s", fontConfigFilename.get());
166
167     static const char *const fontPaths[][2] = {
168         { "/usr/share/fonts/truetype/ttf-liberation/LiberationMono-BoldItalic.ttf",
169           "/usr/share/fonts/liberation/LiberationMono-BoldItalic.ttf", },
170         { "/usr/share/fonts/truetype/ttf-liberation/LiberationMono-Bold.ttf",
171           "/usr/share/fonts/liberation/LiberationMono-Bold.ttf", },
172         { "/usr/share/fonts/truetype/ttf-liberation/LiberationMono-Italic.ttf",
173           "/usr/share/fonts/liberation/LiberationMono-Italic.ttf", },
174         { "/usr/share/fonts/truetype/ttf-liberation/LiberationMono-Regular.ttf",
175           "/usr/share/fonts/liberation/LiberationMono-Regular.ttf", },
176         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSans-BoldItalic.ttf",
177           "/usr/share/fonts/liberation/LiberationSans-BoldItalic.ttf", },
178         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSans-Bold.ttf",
179           "/usr/share/fonts/liberation/LiberationSans-Bold.ttf", },
180         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSans-Italic.ttf",
181           "/usr/share/fonts/liberation/LiberationSans-Italic.ttf", },
182         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSans-Regular.ttf",
183           "/usr/share/fonts/liberation/LiberationSans-Regular.ttf", },
184         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-BoldItalic.ttf",
185           "/usr/share/fonts/liberation/LiberationSerif-BoldItalic.ttf", },
186         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Bold.ttf",
187           "/usr/share/fonts/liberation/LiberationSerif-Bold.ttf", },
188         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Italic.ttf",
189           "/usr/share/fonts/liberation/LiberationSerif-Italic.ttf", },
190         { "/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Regular.ttf",
191           "/usr/share/fonts/liberation/LiberationSerif-Regular.ttf", },
192         { "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf",
193           "/usr/share/fonts/dejavu/DejaVuSans.ttf", },
194         { "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf",
195           "/usr/share/fonts/dejavu/DejaVuSerif.ttf", },
196
197         // MathML tests require the STIX fonts.
198         { "/usr/share/fonts/opentype/stix/STIXGeneral.otf",
199           "/usr/share/fonts/stix/STIXGeneral.otf" },
200         { "/usr/share/fonts/opentype/stix/STIXGeneralBolIta.otf",
201           "/usr/share/fonts/stix/STIXGeneralBolIta.otf" },
202         { "/usr/share/fonts/opentype/stix/STIXGeneralBol.otf",
203           "/usr/share/fonts/stix/STIXGeneralBol.otf" },
204         { "/usr/share/fonts/opentype/stix/STIXGeneralItalic.otf",
205           "/usr/share/fonts/stix/STIXGeneralItalic.otf" }
206     };
207
208     // TODO: Some tests use Lucida. We should load these as well, once it becomes
209     // clear how to install these fonts easily on Fedora.
210     for (size_t font = 0; font < G_N_ELEMENTS(fontPaths); font++) {
211         bool found = false;
212         for (size_t path = 0; path < 2; path++) {
213
214             if (g_file_test(fontPaths[font][path], G_FILE_TEST_EXISTS)) {
215                 found = true;
216                 if (!FcConfigAppFontAddFile(config, reinterpret_cast<const FcChar8*>(fontPaths[font][path])))
217                     g_error("Could not load font at %s!", fontPaths[font][path]);
218                 else
219                     break;
220             }
221         }
222
223         if (!found)
224             g_error("Could not find font at %s. Either install this font or file a bug "
225                     "at http://bugs.webkit.org if it is installed in another location.",
226                     fontPaths[font][0]);
227     }
228
229     // Ahem is used by many layout tests.
230     GOwnPtr<gchar> ahemFontFilename(g_build_filename(FONTS_CONF_DIR, "AHEM____.TTF", NULL));
231     if (!FcConfigAppFontAddFile(config, reinterpret_cast<FcChar8*>(ahemFontFilename.get())))
232         g_error("Could not load font at %s!", ahemFontFilename.get()); 
233
234     if (!FcConfigSetCurrent(config))
235         g_error("Could not set the current font configuration!");
236
237     numFonts = FcConfigGetFonts(config, FcSetApplication)->nfont;
238 #endif
239 }
240
241 static gchar* dumpFramesAsText(WebKitWebFrame* frame)
242 {
243     gchar* result = 0;
244
245     // Add header for all but the main frame.
246     bool isMainFrame = (webkit_web_view_get_main_frame(webView) == frame);
247
248     CString innerText = DumpRenderTreeSupportGtk::getInnerText(frame);
249     if (isMainFrame)
250         result = g_strdup_printf("%s\n", innerText.data());
251     else {
252         const gchar* frameName = webkit_web_frame_get_name(frame);
253         result = g_strdup_printf("\n--------\nFrame: '%s'\n--------\n%s\n", frameName, innerText.data());
254     }
255
256     if (gLayoutTestController->dumpChildFramesAsText()) {
257         GSList* children = DumpRenderTreeSupportGtk::getFrameChildren(frame);
258         for (GSList* child = children; child; child = g_slist_next(child))
259             appendString(result, dumpFramesAsText(static_cast<WebKitWebFrame* >(child->data)));
260         g_slist_free(children);
261     }
262
263     return result;
264 }
265
266 static gint compareHistoryItems(gpointer* item1, gpointer* item2)
267 {
268     return g_ascii_strcasecmp(webkit_web_history_item_get_target(WEBKIT_WEB_HISTORY_ITEM(item1)),
269                               webkit_web_history_item_get_target(WEBKIT_WEB_HISTORY_ITEM(item2)));
270 }
271
272 static void dumpHistoryItem(WebKitWebHistoryItem* item, int indent, bool current)
273 {
274     ASSERT(item != NULL);
275     int start = 0;
276     g_object_ref(item);
277     if (current) {
278         printf("curr->");
279         start = 6;
280     }
281     for (int i = start; i < indent; i++)
282         putchar(' ');
283
284     // normalize file URLs.
285     const gchar* uri = webkit_web_history_item_get_uri(item);
286     gchar* uriScheme = g_uri_parse_scheme(uri);
287     if (g_strcmp0(uriScheme, "file") == 0) {
288         gchar* pos = g_strstr_len(uri, -1, "/LayoutTests/");
289         if (!pos)
290             return;
291
292         GString* result = g_string_sized_new(strlen(uri));
293         result = g_string_append(result, "(file test):");
294         result = g_string_append(result, pos + strlen("/LayoutTests/"));
295         printf("%s", result->str);
296         g_string_free(result, TRUE);
297     } else
298         printf("%s", uri);
299
300     g_free(uriScheme);
301
302     const gchar* target = webkit_web_history_item_get_target(item);
303     if (target && strlen(target) > 0)
304         printf(" (in frame \"%s\")", target);
305     if (webkit_web_history_item_is_target_item(item))
306         printf("  **nav target**");
307     putchar('\n');
308     GList* kids = webkit_web_history_item_get_children(item);
309     if (kids) {
310         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
311         kids = g_list_sort(kids, (GCompareFunc) compareHistoryItems);
312         for (unsigned i = 0; i < g_list_length(kids); i++)
313             dumpHistoryItem(WEBKIT_WEB_HISTORY_ITEM(g_list_nth_data(kids, i)), indent+4, FALSE);
314     }
315     g_object_unref(item);
316 }
317
318 static void dumpBackForwardListForWebView(WebKitWebView* view)
319 {
320     printf("\n============== Back Forward List ==============\n");
321     WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(view);
322
323     // Print out all items in the list after prevTestBFItem, which was from the previous test
324     // Gather items from the end of the list, the print them out from oldest to newest
325     GList* itemsToPrint = NULL;
326     gint forwardListCount = webkit_web_back_forward_list_get_forward_length(bfList);
327     for (int i = forwardListCount; i > 0; i--) {
328         WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
329         // something is wrong if the item from the last test is in the forward part of the b/f list
330         ASSERT(item != prevTestBFItem);
331         g_object_ref(item);
332         itemsToPrint = g_list_append(itemsToPrint, item);
333     }
334
335     WebKitWebHistoryItem* currentItem = webkit_web_back_forward_list_get_current_item(bfList);
336
337     g_object_ref(currentItem);
338     itemsToPrint = g_list_append(itemsToPrint, currentItem);
339
340     gint currentItemIndex = g_list_length(itemsToPrint) - 1;
341     gint backListCount = webkit_web_back_forward_list_get_back_length(bfList);
342     for (int i = -1; i >= -(backListCount); i--) {
343         WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
344         if (item == prevTestBFItem)
345             break;
346         g_object_ref(item);
347         itemsToPrint = g_list_append(itemsToPrint, item);
348     }
349
350     for (int i = g_list_length(itemsToPrint) - 1; i >= 0; i--) {
351         WebKitWebHistoryItem* item = WEBKIT_WEB_HISTORY_ITEM(g_list_nth_data(itemsToPrint, i));
352         dumpHistoryItem(item, historyItemIndent, i == currentItemIndex);
353         g_object_unref(item);
354     }
355     g_list_free(itemsToPrint);
356     printf("===============================================\n");
357 }
358
359 static void dumpBackForwardListForAllWebViews()
360 {
361     // Dump the back forward list of the main WebView first
362     dumpBackForwardListForWebView(webView);
363
364     // The view list is prepended. Reverse the list so we get the order right.
365     GSList* viewList = g_slist_reverse(webViewList);
366     for (unsigned i = 0; i < g_slist_length(viewList); ++i)
367         dumpBackForwardListForWebView(WEBKIT_WEB_VIEW(g_slist_nth_data(viewList, i)));
368 }
369
370 static void invalidateAnyPreviousWaitToDumpWatchdog()
371 {
372     if (waitToDumpWatchdog) {
373         g_source_remove(waitToDumpWatchdog);
374         waitToDumpWatchdog = 0;
375     }
376
377     waitForPolicy = false;
378 }
379
380 static void resetDefaultsToConsistentValues()
381 {
382     WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
383     g_object_set(G_OBJECT(settings),
384                  "enable-private-browsing", FALSE,
385                  "enable-developer-extras", FALSE,
386                  "enable-spell-checking", TRUE,
387                  "enable-html5-database", TRUE,
388                  "enable-html5-local-storage", TRUE,
389                  "enable-xss-auditor", FALSE,
390                  "enable-spatial-navigation", FALSE,
391                  "enable-frame-flattening", FALSE,
392                  "javascript-can-access-clipboard", TRUE,
393                  "javascript-can-open-windows-automatically", TRUE,
394                  "enable-offline-web-application-cache", TRUE,
395                  "enable-universal-access-from-file-uris", TRUE,
396                  "enable-scripts", TRUE,
397                  "enable-dom-paste", TRUE,
398                  "default-font-family", "Times",
399                  "monospace-font-family", "Courier",
400                  "serif-font-family", "Times",
401                  "sans-serif-font-family", "Helvetica",
402                  "cursive-font-family", "cursive",
403                  "fantasy-font-family", "fantasy",
404                  "default-font-size", 16,
405                  "default-monospace-font-size", 13,
406                  "minimum-font-size", 1,
407                  "enable-caret-browsing", FALSE,
408                  "enable-page-cache", FALSE,
409                  "auto-resize-window", TRUE,
410                  "enable-java-applet", FALSE,
411                  "enable-plugins", TRUE,
412                  "enable-hyperlink-auditing", FALSE,
413                  "editing-behavior", WEBKIT_EDITING_BEHAVIOR_MAC,
414                  "enable-fullscreen", TRUE,
415                  NULL);
416     webkit_web_view_set_settings(webView, settings);
417
418     DumpRenderTreeSupportGtk::clearMainFrameName(mainFrame);
419
420     WebKitWebInspector* inspector = webkit_web_view_get_inspector(webView);
421     g_object_set(G_OBJECT(inspector), "javascript-profiling-enabled", FALSE, NULL);
422
423     webkit_web_view_set_zoom_level(webView, 1.0);
424
425     DumpRenderTreeSupportGtk::resetOriginAccessWhiteLists();
426
427     WebKitWebBackForwardList* list = webkit_web_view_get_back_forward_list(webView);
428     webkit_web_back_forward_list_clear(list);
429
430 #ifdef HAVE_LIBSOUP_2_29_90
431     SoupSession* session = webkit_get_default_session();
432     SoupCookieJar* jar = reinterpret_cast<SoupCookieJar*>(soup_session_get_feature(session, SOUP_TYPE_COOKIE_JAR));
433
434     // We only create the jar when the soup backend needs to do
435     // HTTP. Should we initialize it earlier, perhaps?
436     if (jar)
437         g_object_set(G_OBJECT(jar), SOUP_COOKIE_JAR_ACCEPT_POLICY, SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, NULL);
438 #endif
439
440     setlocale(LC_ALL, "");
441
442     DumpRenderTreeSupportGtk::setLinksIncludedInFocusChain(true);
443 }
444
445 static bool useLongRunningServerMode(int argc, char *argv[])
446 {
447     // This assumes you've already called getopt_long
448     return (argc == optind+1 && !strcmp(argv[optind], "-"));
449 }
450
451 static void runTestingServerLoop()
452 {
453     // When DumpRenderTree runs in server mode, we just wait around for file names
454     // to be passed to us and read each in turn, passing the results back to the client
455     char filenameBuffer[2048];
456     while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
457         char* newLineCharacter = strchr(filenameBuffer, '\n');
458         if (newLineCharacter)
459             *newLineCharacter = '\0';
460
461         if (!strlen(filenameBuffer))
462             continue;
463
464         runTest(filenameBuffer);
465     }
466 }
467
468 static void initializeGlobalsFromCommandLineOptions(int argc, char *argv[])
469 {
470     struct option options[] = {
471         {"notree", no_argument, &dumpTree, false},
472         {"pixel-tests", no_argument, &dumpPixels, true},
473         {"tree", no_argument, &dumpTree, true},
474         {NULL, 0, NULL, 0}
475     };
476     
477     int option;
478     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) {
479         switch (option) {
480         case '?': // unknown or ambiguous option
481         case ':': // missing argument
482             exit(1);
483             break;
484         }
485     }
486 }
487
488
489 void dump()
490 {
491     invalidateAnyPreviousWaitToDumpWatchdog();
492
493     if (dumpTree) {
494         char* result = 0;
495         gchar* responseMimeType = webkit_web_frame_get_response_mime_type(mainFrame);
496
497         if (g_str_equal(responseMimeType, "text/plain")) {
498             gLayoutTestController->setDumpAsText(true);        
499             gLayoutTestController->setGeneratePixelResults(false);
500         }
501         g_free(responseMimeType);
502
503         if (gLayoutTestController->dumpAsText())
504             result = dumpFramesAsText(mainFrame);
505         else {
506             // Widget resizing is done asynchronously in GTK+. We pump the main
507             // loop here, to flush any pending resize requests. This prevents
508             // timing issues which affect the size of elements in the output.
509             // We only enable this workaround for tests that print the render tree
510             // because this seems to break some dumpAsText tests: see bug 39988
511             // After fixing that test, we should apply this approach to all dumps.
512             while (gtk_events_pending())
513                 gtk_main_iteration();
514
515             result = g_strdup(DumpRenderTreeSupportGtk::dumpRenderTree(mainFrame).data());
516         }
517
518         if (!result) {
519             const char* errorMessage;
520             if (gLayoutTestController->dumpAsText())
521                 errorMessage = "[documentElement innerText]";
522             else if (gLayoutTestController->dumpDOMAsWebArchive())
523                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
524             else if (gLayoutTestController->dumpSourceAsWebArchive())
525                 errorMessage = "[[mainFrame dataSource] webArchive]";
526             else
527                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
528             printf("ERROR: nil result from %s", errorMessage);
529         } else {
530             printf("%s", result);
531             g_free(result);
532             if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive())
533                 dumpFrameScrollPosition(mainFrame);
534
535             if (gLayoutTestController->dumpBackForwardList())
536                 dumpBackForwardListForAllWebViews();
537         }
538
539         if (printSeparators) {
540             puts("#EOF"); // terminate the content block
541             fputs("#EOF\n", stderr);
542             fflush(stdout);
543             fflush(stderr);
544         }
545     }
546
547     if (dumpPixels
548      && gLayoutTestController->generatePixelResults()
549      && !gLayoutTestController->dumpDOMAsWebArchive()
550      && !gLayoutTestController->dumpSourceAsWebArchive())
551         dumpWebViewAsPixelsAndCompareWithExpected(gLayoutTestController->expectedPixelHash());
552
553     // FIXME: call displayWebView here when we support --paint
554
555     done = true;
556     gtk_main_quit();
557 }
558
559 static void setDefaultsToConsistentStateValuesForTesting()
560 {
561     gdk_screen_set_resolution(gdk_screen_get_default(), 72.0);
562
563     resetDefaultsToConsistentValues();
564
565     /* Disable the default auth dialog for testing */
566     SoupSession* session = webkit_get_default_session();
567     soup_session_remove_feature_by_type(session, WEBKIT_TYPE_SOUP_AUTH_DIALOG);
568
569 #if PLATFORM(X11)
570     webkit_web_settings_add_extra_plugin_directory(webView, TEST_PLUGIN_DIR);
571 #endif
572
573     gchar* databaseDirectory = g_build_filename(g_get_user_data_dir(), "gtkwebkitdrt", "databases", NULL);
574     webkit_set_web_database_directory_path(databaseDirectory);
575     g_free(databaseDirectory);
576 }
577
578 static void sendPixelResultsEOF()
579 {
580     puts("#EOF");
581
582     fflush(stdout);
583     fflush(stderr);
584 }
585
586 static void runTest(const string& testPathOrURL)
587 {
588     ASSERT(!testPathOrURL.empty());
589
590     // Look for "'" as a separator between the path or URL, and the pixel dump hash that follows.
591     string testURL(testPathOrURL);
592     string expectedPixelHash;
593     size_t separatorPos = testURL.find("'");
594     if (separatorPos != string::npos) {
595         testURL = string(testPathOrURL, 0, separatorPos);
596         expectedPixelHash = string(testPathOrURL, separatorPos + 1);
597     }
598
599     // Convert the path into a full file URL if it does not look
600     // like an HTTP/S URL (doesn't start with http:// or https://).
601     if (testURL.find("http://") && testURL.find("https://")) {
602         GFile* testFile = g_file_new_for_path(testURL.c_str());
603         gchar* testURLCString = g_file_get_uri(testFile);
604         testURL = testURLCString;
605         g_free(testURLCString);
606         g_object_unref(testFile);
607     }
608
609     resetDefaultsToConsistentValues();
610
611     gLayoutTestController = LayoutTestController::create(testURL, expectedPixelHash);
612     topLoadingFrame = 0;
613     done = false;
614
615     gLayoutTestController->setIconDatabaseEnabled(false);
616
617     if (shouldLogFrameLoadDelegates(testURL))
618         gLayoutTestController->setDumpFrameLoadCallbacks(true);
619
620     if (shouldEnableDeveloperExtras(testURL)) {
621         gLayoutTestController->setDeveloperExtrasEnabled(true);
622         if (shouldOpenWebInspector(testURL))
623             gLayoutTestController->showWebInspector();
624     }
625
626     WorkQueue::shared()->clear();
627     WorkQueue::shared()->setFrozen(false);
628
629     bool isSVGW3CTest = (testURL.find("svg/W3C-SVG-1.1") != string::npos);
630     GtkAllocation size;
631     size.x = size.y = 0;
632     size.width = isSVGW3CTest ? 480 : LayoutTestController::maxViewWidth;
633     size.height = isSVGW3CTest ? 360 : LayoutTestController::maxViewHeight;
634     gtk_window_resize(GTK_WINDOW(window), size.width, size.height);
635     gtk_widget_size_allocate(container, &size);
636
637     if (prevTestBFItem)
638         g_object_unref(prevTestBFItem);
639     WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(webView);
640     prevTestBFItem = webkit_web_back_forward_list_get_current_item(bfList);
641     if (prevTestBFItem)
642         g_object_ref(prevTestBFItem);
643
644     initializeFonts(testURL.c_str());
645
646     // Focus the web view before loading the test to avoid focusing problems
647     gtk_widget_grab_focus(GTK_WIDGET(webView));
648     webkit_web_view_open(webView, testURL.c_str());
649
650     gtk_main();
651
652     // If developer extras enabled Web Inspector may have been open by the test.
653     if (shouldEnableDeveloperExtras(testURL)) {
654         gLayoutTestController->closeWebInspector();
655         gLayoutTestController->setDeveloperExtrasEnabled(false);
656     }
657
658     // Also check if we still have opened webViews and free them.
659     if (gLayoutTestController->closeRemainingWindowsWhenComplete() || webViewList) {
660         while (webViewList) {
661             g_object_unref(WEBKIT_WEB_VIEW(webViewList->data));
662             webViewList = g_slist_next(webViewList);
663         }
664         g_slist_free(webViewList);
665         webViewList = 0;
666     }
667
668     // A blank load seems to be necessary to reset state after certain tests.
669     webkit_web_view_open(webView, "about:blank");
670
671     gLayoutTestController.clear();
672
673     // terminate the (possibly empty) pixels block after all the state reset
674     sendPixelResultsEOF();
675 }
676
677 void webViewLoadStarted(WebKitWebView* view, WebKitWebFrame* frame, void*)
678 {
679     // Make sure we only set this once per test.  If it gets cleared, and then set again, we might
680     // end up doing two dumps for one test.
681     if (!topLoadingFrame && !done)
682         topLoadingFrame = frame;
683 }
684
685 static gboolean processWork(void* data)
686 {
687     // if we finish all the commands, we're ready to dump state
688     if (WorkQueue::shared()->processWork() && !gLayoutTestController->waitToDump())
689         dump();
690
691     return FALSE;
692 }
693
694 static char* getFrameNameSuitableForTestResult(WebKitWebView* view, WebKitWebFrame* frame)
695 {
696     char* frameName = g_strdup(webkit_web_frame_get_name(frame));
697
698     if (frame == webkit_web_view_get_main_frame(view)) {
699         // This is a bit strange. Shouldn't web_frame_get_name return NULL?
700         if (frameName && (frameName[0] != '\0')) {
701             char* tmp = g_strdup_printf("main frame \"%s\"", frameName);
702             g_free(frameName);
703             frameName = tmp;
704         } else {
705             g_free(frameName);
706             frameName = g_strdup("main frame");
707         }
708     } else if (!frameName || (frameName[0] == '\0')) {
709         g_free(frameName);
710         frameName = g_strdup("frame (anonymous)");
711     } else {
712         char* tmp = g_strdup_printf("frame \"%s\"", frameName);
713         g_free(frameName);
714         frameName = tmp;
715     }
716
717     return frameName;
718 }
719
720 static void webViewLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*)
721 {
722     if (frame != topLoadingFrame)
723         return;
724
725     topLoadingFrame = 0;
726     WorkQueue::shared()->setFrozen(true); // first complete load freezes the queue for the rest of this test
727     if (gLayoutTestController->waitToDump())
728         return;
729
730     if (WorkQueue::shared()->count())
731         g_timeout_add(0, processWork, 0);
732     else
733         dump();
734 }
735
736 static void webViewDocumentLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*)
737 {
738     if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) {
739         char* frameName = getFrameNameSuitableForTestResult(view, frame);
740         printf("%s - didFinishDocumentLoadForFrame\n", frameName);
741         g_free(frameName);
742     } else if (!done) {
743         guint pendingFrameUnloadEvents = DumpRenderTreeSupportGtk::getPendingUnloadEventCount(frame);
744         if (pendingFrameUnloadEvents) {
745             char* frameName = getFrameNameSuitableForTestResult(view, frame);
746             printf("%s - has %u onunload handler(s)\n", frameName, pendingFrameUnloadEvents);
747             g_free(frameName);
748         }
749     }
750 }
751
752 static void webViewOnloadEvent(WebKitWebView* view, WebKitWebFrame* frame, void*)
753 {
754     if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) {
755         char* frameName = getFrameNameSuitableForTestResult(view, frame);
756         printf("%s - didHandleOnloadEventsForFrame\n", frameName);
757         g_free(frameName);
758     }
759 }
760
761 static void webViewWindowObjectCleared(WebKitWebView* view, WebKitWebFrame* frame, JSGlobalContextRef context, JSObjectRef windowObject, gpointer data)
762 {
763     JSValueRef exception = 0;
764     ASSERT(gLayoutTestController);
765
766     gLayoutTestController->makeWindowObject(context, windowObject, &exception);
767     ASSERT(!exception);
768
769     gcController->makeWindowObject(context, windowObject, &exception);
770     ASSERT(!exception);
771
772     axController->makeWindowObject(context, windowObject, &exception);
773     ASSERT(!exception);
774
775     JSStringRef eventSenderStr = JSStringCreateWithUTF8CString("eventSender");
776     JSValueRef eventSender = makeEventSender(context, !webkit_web_frame_get_parent(frame));
777     JSObjectSetProperty(context, windowObject, eventSenderStr, eventSender, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, 0);
778     JSStringRelease(eventSenderStr);
779 }
780
781 static gboolean webViewConsoleMessage(WebKitWebView* view, const gchar* message, unsigned int line, const gchar* sourceId, gpointer data)
782 {
783     gchar* testMessage = 0;
784     const gchar* uriScheme;
785
786     // Tests expect only the filename part of local URIs
787     uriScheme = g_strstr_len(message, -1, "file://");
788     if (uriScheme) {
789         GString* tempString = g_string_sized_new(strlen(message));
790         gchar* filename = g_strrstr(uriScheme, G_DIR_SEPARATOR_S);
791
792         if (filename) {
793             filename += strlen(G_DIR_SEPARATOR_S);
794             tempString = g_string_append_len(tempString, message, (uriScheme - message));
795             tempString = g_string_append_len(tempString, filename, strlen(filename));
796             testMessage = g_string_free(tempString, FALSE);
797         }
798     }
799
800     fprintf(stdout, "CONSOLE MESSAGE: line %d: %s\n", line, testMessage ? testMessage : message);
801     g_free(testMessage);
802
803     return TRUE;
804 }
805
806
807 static gboolean webViewScriptAlert(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gpointer data)
808 {
809     fprintf(stdout, "ALERT: %s\n", message);
810     return TRUE;
811 }
812
813 static gboolean webViewScriptPrompt(WebKitWebView* webView, WebKitWebFrame* frame, const gchar* message, const gchar* defaultValue, gchar** value, gpointer data)
814 {
815     fprintf(stdout, "PROMPT: %s, default text: %s\n", message, defaultValue);
816     *value = g_strdup(defaultValue);
817     return TRUE;
818 }
819
820 static gboolean webViewScriptConfirm(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gboolean* didConfirm, gpointer data)
821 {
822     fprintf(stdout, "CONFIRM: %s\n", message);
823     *didConfirm = TRUE;
824     return TRUE;
825 }
826
827 static void webViewTitleChanged(WebKitWebView* view, WebKitWebFrame* frame, const gchar* title, gpointer data)
828 {
829     if (gLayoutTestController->dumpTitleChanges() && !done)
830         printf("TITLE CHANGED: %s\n", title ? title : "");
831 }
832
833 static bool webViewNavigationPolicyDecisionRequested(WebKitWebView* view, WebKitWebFrame* frame,
834                                                      WebKitNetworkRequest* request,
835                                                      WebKitWebNavigationAction* navAction,
836                                                      WebKitWebPolicyDecision* policyDecision)
837 {
838     // Use the default handler if we're not waiting for policy,
839     // i.e., LayoutTestController::waitForPolicyDelegate
840     if (!waitForPolicy)
841         return FALSE;
842
843     gchar* typeDescription;
844     WebKitWebNavigationReason reason;
845     g_object_get(G_OBJECT(navAction), "reason", &reason, NULL);
846
847     switch(reason) {
848         case WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED:
849             typeDescription = g_strdup("link clicked");
850             break;
851         case WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED:
852             typeDescription = g_strdup("form submitted");
853             break;
854         case WEBKIT_WEB_NAVIGATION_REASON_BACK_FORWARD:
855             typeDescription = g_strdup("back/forward");
856             break;
857         case WEBKIT_WEB_NAVIGATION_REASON_RELOAD:
858             typeDescription = g_strdup("reload");
859             break;
860         case WEBKIT_WEB_NAVIGATION_REASON_FORM_RESUBMITTED:
861             typeDescription = g_strdup("form resubmitted");
862             break;
863         case WEBKIT_WEB_NAVIGATION_REASON_OTHER:
864             typeDescription = g_strdup("other");
865             break;
866         default:
867             typeDescription = g_strdup("illegal value");
868     }
869
870     printf("Policy delegate: attempt to load %s with navigation type '%s'\n", webkit_network_request_get_uri(request), typeDescription);
871     g_free(typeDescription);
872
873     webkit_web_policy_decision_ignore(policyDecision);
874     gLayoutTestController->notifyDone();
875
876     return TRUE;
877 }
878
879 static void webViewStatusBarTextChanged(WebKitWebView* view, const gchar* message, gpointer data)
880 {
881     // Are we doing anything wrong? One test that does not call
882     // dumpStatusCallbacks gets true here
883     if (gLayoutTestController->dumpStatusCallbacks()) {
884         if (message && strcmp(message, ""))
885             printf("UI DELEGATE STATUS CALLBACK: setStatusText:%s\n", message);
886     }
887 }
888
889 static gboolean webViewClose(WebKitWebView* view)
890 {
891     ASSERT(view);
892
893     webViewList = g_slist_remove(webViewList, view);
894     g_object_unref(view);
895
896     return TRUE;
897 }
898
899 static void databaseQuotaExceeded(WebKitWebView* view, WebKitWebFrame* frame, WebKitWebDatabase *database)
900 {
901     ASSERT(view);
902     ASSERT(frame);
903     ASSERT(database);
904
905     WebKitSecurityOrigin* origin = webkit_web_database_get_security_origin(database);
906     if (gLayoutTestController->dumpDatabaseCallbacks()) {
907         printf("UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{%s, %s, %i} database:%s\n",
908             webkit_security_origin_get_protocol(origin),
909             webkit_security_origin_get_host(origin),
910             webkit_security_origin_get_port(origin),
911             webkit_web_database_get_name(database));
912     }
913     webkit_security_origin_set_web_database_quota(origin, 5 * 1024 * 1024);
914 }
915
916 static bool
917 geolocationPolicyDecisionRequested(WebKitWebView*, WebKitWebFrame*, WebKitGeolocationPolicyDecision* decision)
918 {
919     if (!gLayoutTestController->isGeolocationPermissionSet())
920         return FALSE;
921     if (gLayoutTestController->geolocationPermission())
922         webkit_geolocation_policy_allow(decision);
923     else
924         webkit_geolocation_policy_deny(decision);
925
926     return TRUE;
927 }
928
929
930 static WebKitWebView* webViewCreate(WebKitWebView*, WebKitWebFrame*);
931
932 static gboolean webInspectorShowWindow(WebKitWebInspector*, gpointer data)
933 {
934     gtk_window_set_default_size(GTK_WINDOW(webInspectorWindow), 800, 600);
935     gtk_widget_show_all(webInspectorWindow);
936     return TRUE;
937 }
938
939 static gboolean webInspectorCloseWindow(WebKitWebInspector*, gpointer data)
940 {
941     gtk_widget_destroy(webInspectorWindow);
942     webInspectorWindow = 0;
943     return TRUE;
944 }
945
946 static WebKitWebView* webInspectorInspectWebView(WebKitWebInspector*, gpointer data)
947 {
948     webInspectorWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
949
950     GtkWidget* webView = webkit_web_view_new();
951     gtk_container_add(GTK_CONTAINER(webInspectorWindow),
952                       webView);
953
954     return WEBKIT_WEB_VIEW(webView);
955 }
956
957 static void webFrameLoadStatusNotified(WebKitWebFrame* frame, gpointer user_data)
958 {
959     WebKitLoadStatus loadStatus = webkit_web_frame_get_load_status(frame);
960
961     if (gLayoutTestController->dumpFrameLoadCallbacks()) {
962         GOwnPtr<char> frameName(getFrameNameSuitableForTestResult(webkit_web_frame_get_web_view(frame), frame));
963
964         switch (loadStatus) {
965         case WEBKIT_LOAD_PROVISIONAL:
966             if (!done)
967                 printf("%s - didStartProvisionalLoadForFrame\n", frameName.get());
968             break;
969         case WEBKIT_LOAD_COMMITTED:
970             if (!done)
971                 printf("%s - didCommitLoadForFrame\n", frameName.get());
972             break;
973         case WEBKIT_LOAD_FINISHED:
974             if (frame != topLoadingFrame || !done)
975                 printf("%s - didFinishLoadForFrame\n", frameName.get());
976             break;
977         default:
978             break;
979         }
980     }
981 }
982
983 static void frameCreatedCallback(WebKitWebView* webView, WebKitWebFrame* webFrame, gpointer user_data)
984 {
985     g_signal_connect(webFrame, "notify::load-status", G_CALLBACK(webFrameLoadStatusNotified), NULL);
986 }
987
988 static WebKitWebView* createWebView()
989 {
990     WebKitWebView* view = WEBKIT_WEB_VIEW(webkit_web_view_new());
991
992     DumpRenderTreeSupportGtk::setDumpRenderTreeModeEnabled(true);
993
994     g_object_connect(G_OBJECT(view),
995                      "signal::load-started", webViewLoadStarted, 0,
996                      "signal::load-finished", webViewLoadFinished, 0,
997                      "signal::window-object-cleared", webViewWindowObjectCleared, 0,
998                      "signal::console-message", webViewConsoleMessage, 0,
999                      "signal::script-alert", webViewScriptAlert, 0,
1000                      "signal::script-prompt", webViewScriptPrompt, 0,
1001                      "signal::script-confirm", webViewScriptConfirm, 0,
1002                      "signal::title-changed", webViewTitleChanged, 0,
1003                      "signal::navigation-policy-decision-requested", webViewNavigationPolicyDecisionRequested, 0,
1004                      "signal::status-bar-text-changed", webViewStatusBarTextChanged, 0,
1005                      "signal::create-web-view", webViewCreate, 0,
1006                      "signal::close-web-view", webViewClose, 0,
1007                      "signal::database-quota-exceeded", databaseQuotaExceeded, 0,
1008                      "signal::document-load-finished", webViewDocumentLoadFinished, 0,
1009                      "signal::geolocation-policy-decision-requested", geolocationPolicyDecisionRequested, 0,
1010                      "signal::onload-event", webViewOnloadEvent, 0,
1011                      "signal::drag-begin", dragBeginCallback, 0,
1012                      "signal::drag-end", dragEndCallback, 0,
1013                      "signal::drag-failed", dragFailedCallback, 0,
1014                      "signal::frame-created", frameCreatedCallback, 0,
1015
1016                      NULL);
1017     connectEditingCallbacks(view);
1018
1019     WebKitWebInspector* inspector = webkit_web_view_get_inspector(view);
1020     g_object_connect(G_OBJECT(inspector),
1021                      "signal::inspect-web-view", webInspectorInspectWebView, 0,
1022                      "signal::show-window", webInspectorShowWindow, 0,
1023                      "signal::close-window", webInspectorCloseWindow, 0,
1024                      NULL);
1025
1026     if (webView) {
1027         WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
1028         webkit_web_view_set_settings(view, settings);
1029     }
1030
1031     // frame-created is not issued for main frame. That's why we must do this here
1032     WebKitWebFrame* frame = webkit_web_view_get_main_frame(view);
1033     g_signal_connect(frame, "notify::load-status", G_CALLBACK(webFrameLoadStatusNotified), NULL);
1034
1035     return view;
1036 }
1037
1038 static WebKitWebView* webViewCreate(WebKitWebView* view, WebKitWebFrame* frame)
1039 {
1040     if (!gLayoutTestController->canOpenWindows())
1041         return 0;
1042
1043     // Make sure that waitUntilDone has been called.
1044     ASSERT(gLayoutTestController->waitToDump());
1045
1046     WebKitWebView* newWebView = createWebView();
1047     g_object_ref_sink(G_OBJECT(newWebView));
1048     webViewList = g_slist_prepend(webViewList, newWebView);
1049     return newWebView;
1050 }
1051
1052 static void logHandler(const gchar* domain, GLogLevelFlags level, const gchar* message, gpointer data)
1053 {
1054     if (level < G_LOG_LEVEL_DEBUG)
1055         fprintf(stderr, "%s\n", message);
1056 }
1057
1058 int main(int argc, char* argv[])
1059 {
1060     g_thread_init(NULL);
1061     gtk_init(&argc, &argv);
1062
1063     // Some plugins might try to use the GLib logger for printing debug
1064     // messages. This will cause tests to fail because of unexpected output.
1065     // We squelch all debug messages sent to the logger.
1066     g_log_set_default_handler(logHandler, 0);
1067
1068     initializeGlobalsFromCommandLineOptions(argc, argv);
1069     initializeFonts();
1070
1071     window = gtk_window_new(GTK_WINDOW_POPUP);
1072     container = GTK_WIDGET(gtk_scrolled_window_new(NULL, NULL));
1073     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(container), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1074     gtk_container_add(GTK_CONTAINER(window), container);
1075     gtk_widget_show_all(window);
1076
1077     webView = createWebView();
1078     gtk_container_add(GTK_CONTAINER(container), GTK_WIDGET(webView));
1079     gtk_widget_realize(GTK_WIDGET(webView));
1080     gtk_widget_show_all(container);
1081     gtk_widget_grab_focus(GTK_WIDGET(webView));
1082     mainFrame = webkit_web_view_get_main_frame(webView);
1083
1084     setDefaultsToConsistentStateValuesForTesting();
1085
1086     gcController = new GCController();
1087     axController = new AccessibilityController();
1088
1089     if (useLongRunningServerMode(argc, argv)) {
1090         printSeparators = true;
1091         runTestingServerLoop();
1092     } else {
1093         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
1094         for (int i = optind; i != argc; ++i)
1095             runTest(argv[i]);
1096     }
1097
1098     delete gcController;
1099     gcController = 0;
1100
1101     delete axController;
1102     axController = 0;
1103
1104     gtk_widget_destroy(window);
1105
1106     return 0;
1107 }