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