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