273bcdf1a03c961a92615a62ae4a8ac3a4a8afca
[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  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "DumpRenderTree.h"
33
34 #include "AccessibilityController.h"
35 #include "EventSender.h"
36 #include "GCController.h"
37 #include "LayoutTestController.h"
38 #include "WorkQueue.h"
39 #include "WorkQueueItem.h"
40
41 #include <gtk/gtk.h>
42 #include <webkit/webkit.h>
43 #include <JavaScriptCore/JavaScript.h>
44
45 #include <wtf/Assertions.h>
46
47 #if PLATFORM(X11)
48 #include <fontconfig/fontconfig.h>
49 #endif
50
51 #include <cassert>
52 #include <getopt.h>
53 #include <stdlib.h>
54 #include <string.h>
55
56 using namespace std;
57
58 extern "C" {
59 // This API is not yet public.
60 extern G_CONST_RETURN gchar* webkit_web_history_item_get_target(WebKitWebHistoryItem*);
61 extern gboolean webkit_web_history_item_is_target_item(WebKitWebHistoryItem*);
62 extern GList* webkit_web_history_item_get_children(WebKitWebHistoryItem*);
63 extern GSList* webkit_web_frame_get_children(WebKitWebFrame* frame);
64 extern gchar* webkit_web_frame_get_inner_text(WebKitWebFrame* frame);
65 extern gchar* webkit_web_frame_dump_render_tree(WebKitWebFrame* frame);
66 extern guint webkit_web_frame_get_pending_unload_event_count(WebKitWebFrame* frame);
67 extern void webkit_web_settings_add_extra_plugin_directory(WebKitWebView* view, const gchar* directory);
68 extern gchar* webkit_web_frame_get_response_mime_type(WebKitWebFrame* frame);
69 extern void webkit_web_frame_clear_main_frame_name(WebKitWebFrame* frame);
70 extern void webkit_web_view_set_group_name(WebKitWebView* view, const gchar* groupName);
71 extern void webkit_reset_origin_access_white_lists();
72 }
73
74 volatile bool done;
75 static bool printSeparators;
76 static int dumpPixels;
77 static int dumpTree = 1;
78
79 AccessibilityController* axController = 0;
80 LayoutTestController* gLayoutTestController = 0;
81 static GCController* gcController = 0;
82 static WebKitWebView* webView;
83 static GtkWidget* window;
84 static GtkWidget* container;
85 static GtkWidget* webInspectorWindow;
86 WebKitWebFrame* mainFrame = 0;
87 WebKitWebFrame* topLoadingFrame = 0;
88 guint waitToDumpWatchdog = 0;
89 bool waitForPolicy = false;
90
91 // This is a list of opened webviews
92 GSList* webViewList = 0;
93
94 // current b/f item at the end of the previous test
95 static WebKitWebHistoryItem* prevTestBFItem = NULL;
96
97 const unsigned historyItemIndent = 8;
98
99 static gchar* autocorrectURL(const gchar* url)
100 {
101     if (strncmp("http://", url, 7) != 0 && strncmp("https://", url, 8) != 0) {
102         GString* string = g_string_new("file://");
103         g_string_append(string, url);
104         return g_string_free(string, FALSE);
105     }
106
107     return g_strdup(url);
108 }
109
110 static bool shouldLogFrameLoadDelegates(const char* pathOrURL)
111 {
112     return strstr(pathOrURL, "loading/");
113 }
114
115 static bool shouldOpenWebInspector(const char* pathOrURL)
116 {
117     return strstr(pathOrURL, "inspector/");
118 }
119
120 static bool shouldEnableDeveloperExtras(const char* pathOrURL)
121 {
122     return shouldOpenWebInspector(pathOrURL) || strstr(pathOrURL, "inspector-enabled/");
123 }
124
125 void dumpFrameScrollPosition(WebKitWebFrame* frame)
126 {
127
128 }
129
130 void displayWebView()
131 {
132     gtk_widget_queue_draw(GTK_WIDGET(webView));
133 }
134
135 static void appendString(gchar*& target, gchar* string)
136 {
137     gchar* oldString = target;
138     target = g_strconcat(target, string, NULL);
139     g_free(oldString);
140 }
141
142 #if PLATFORM(X11)
143 static void initializeFonts()
144 {
145     static int numFonts = -1;
146
147     // Some tests may add or remove fonts via the @font-face rule.
148     // If that happens, font config should be re-created to suppress any unwanted change.
149     FcFontSet* appFontSet = FcConfigGetFonts(0, FcSetApplication);
150     if (appFontSet && numFonts >= 0 && appFontSet->nfont == numFonts)
151         return;
152
153     const char* fontDirEnv = g_getenv("WEBKIT_TESTFONTS");
154     if (!fontDirEnv)
155         g_error("WEBKIT_TESTFONTS environment variable is not set, but it should point to the directory "
156                 "containing the fonts you can clone from git://gitorious.org/qtwebkit/testfonts.git\n");
157
158     GFile* fontDir = g_file_new_for_path(fontDirEnv);
159     if (!fontDir || !g_file_query_exists(fontDir, NULL))
160         g_error("WEBKIT_TESTFONTS environment variable is not set correctly - it should point to the directory "
161                 "containing the fonts you can clone from git://gitorious.org/qtwebkit/testfonts.git\n");
162
163     FcConfig *config = FcConfigCreate();
164     if (!FcConfigParseAndLoad (config, (FcChar8*) FONTS_CONF_FILE, true))
165         g_error("Couldn't load font configuration file");
166     if (!FcConfigAppFontAddDir (config, (FcChar8*) g_file_get_path(fontDir)))
167         g_error("Couldn't add font dir!");
168     FcConfigSetCurrent(config);
169
170     g_object_unref(fontDir);
171
172     appFontSet = FcConfigGetFonts(config, FcSetApplication);
173     numFonts = appFontSet->nfont;
174 }
175 #endif
176
177 static gchar* dumpFramesAsText(WebKitWebFrame* frame)
178 {
179     gchar* result = 0;
180
181     // Add header for all but the main frame.
182     bool isMainFrame = (webkit_web_view_get_main_frame(webView) == frame);
183
184     gchar* innerText = webkit_web_frame_get_inner_text(frame);
185     if (isMainFrame)
186         result = g_strdup_printf("%s\n", innerText);
187     else {
188         const gchar* frameName = webkit_web_frame_get_name(frame);
189         result = g_strdup_printf("\n--------\nFrame: '%s'\n--------\n%s\n", frameName, innerText);
190     }
191     g_free(innerText);
192
193     if (gLayoutTestController->dumpChildFramesAsText()) {
194         GSList* children = webkit_web_frame_get_children(frame);
195         for (GSList* child = children; child; child = g_slist_next(child))
196             appendString(result, dumpFramesAsText(static_cast<WebKitWebFrame* >(child->data)));
197         g_slist_free(children);
198     }
199
200     return result;
201 }
202
203 static gint compareHistoryItems(gpointer* item1, gpointer* item2)
204 {
205     return g_ascii_strcasecmp(webkit_web_history_item_get_target(WEBKIT_WEB_HISTORY_ITEM(item1)),
206                               webkit_web_history_item_get_target(WEBKIT_WEB_HISTORY_ITEM(item2)));
207 }
208
209 static void dumpHistoryItem(WebKitWebHistoryItem* item, int indent, bool current)
210 {
211     ASSERT(item != NULL);
212     int start = 0;
213     g_object_ref(item);
214     if (current) {
215         printf("curr->");
216         start = 6;
217     }
218     for (int i = start; i < indent; i++)
219         putchar(' ');
220
221     // normalize file URLs.
222     const gchar* uri = webkit_web_history_item_get_uri(item);
223     gchar* uriScheme = g_uri_parse_scheme(uri);
224     if (g_strcmp0(uriScheme, "file") == 0) {
225         gchar* pos = g_strstr_len(uri, -1, "/LayoutTests/");
226         if (!pos)
227             return;
228
229         GString* result = g_string_sized_new(strlen(uri));
230         result = g_string_append(result, "(file test):");
231         result = g_string_append(result, pos + strlen("/LayoutTests/"));
232         printf("%s", result->str);
233         g_string_free(result, TRUE);
234     } else
235         printf("%s", uri);
236
237     g_free(uriScheme);
238
239     const gchar* target = webkit_web_history_item_get_target(item);
240     if (target && strlen(target) > 0)
241         printf(" (in frame \"%s\")", target);
242     if (webkit_web_history_item_is_target_item(item))
243         printf("  **nav target**");
244     putchar('\n');
245     GList* kids = webkit_web_history_item_get_children(item);
246     if (kids) {
247         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
248         kids = g_list_sort(kids, (GCompareFunc) compareHistoryItems);
249         for (unsigned i = 0; i < g_list_length(kids); i++)
250             dumpHistoryItem(WEBKIT_WEB_HISTORY_ITEM(g_list_nth_data(kids, i)), indent+4, FALSE);
251     }
252     g_object_unref(item);
253 }
254
255 static void dumpBackForwardListForWebView(WebKitWebView* view)
256 {
257     printf("\n============== Back Forward List ==============\n");
258     WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(view);
259
260     // Print out all items in the list after prevTestBFItem, which was from the previous test
261     // Gather items from the end of the list, the print them out from oldest to newest
262     GList* itemsToPrint = NULL;
263     gint forwardListCount = webkit_web_back_forward_list_get_forward_length(bfList);
264     for (int i = forwardListCount; i > 0; i--) {
265         WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
266         // something is wrong if the item from the last test is in the forward part of the b/f list
267         ASSERT(item != prevTestBFItem);
268         g_object_ref(item);
269         itemsToPrint = g_list_append(itemsToPrint, item);
270     }
271
272     WebKitWebHistoryItem* currentItem = webkit_web_back_forward_list_get_current_item(bfList);
273
274     g_object_ref(currentItem);
275     itemsToPrint = g_list_append(itemsToPrint, currentItem);
276
277     gint currentItemIndex = g_list_length(itemsToPrint) - 1;
278     gint backListCount = webkit_web_back_forward_list_get_back_length(bfList);
279     for (int i = -1; i >= -(backListCount); i--) {
280         WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
281         if (item == prevTestBFItem)
282             break;
283         g_object_ref(item);
284         itemsToPrint = g_list_append(itemsToPrint, item);
285     }
286
287     for (int i = g_list_length(itemsToPrint) - 1; i >= 0; i--) {
288         WebKitWebHistoryItem* item = WEBKIT_WEB_HISTORY_ITEM(g_list_nth_data(itemsToPrint, i));
289         dumpHistoryItem(item, historyItemIndent, i == currentItemIndex);
290         g_object_unref(item);
291     }
292     g_list_free(itemsToPrint);
293     printf("===============================================\n");
294 }
295
296 static void dumpBackForwardListForAllWebViews()
297 {
298     // Dump the back forward list of the main WebView first
299     dumpBackForwardListForWebView(webView);
300
301     // The view list is prepended. Reverse the list so we get the order right.
302     GSList* viewList = g_slist_reverse(webViewList);
303     for (unsigned i = 0; i < g_slist_length(viewList); ++i)
304         dumpBackForwardListForWebView(WEBKIT_WEB_VIEW(g_slist_nth_data(viewList, i)));
305 }
306
307 static void invalidateAnyPreviousWaitToDumpWatchdog()
308 {
309     if (waitToDumpWatchdog) {
310         g_source_remove(waitToDumpWatchdog);
311         waitToDumpWatchdog = 0;
312     }
313
314     waitForPolicy = false;
315 }
316
317 static void resetDefaultsToConsistentValues()
318 {
319     WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
320     g_object_set(G_OBJECT(settings),
321                  "enable-private-browsing", FALSE,
322                  "enable-developer-extras", FALSE,
323                  "enable-spell-checking", TRUE,
324                  "enable-html5-database", TRUE,
325                  "enable-html5-local-storage", TRUE,
326                  "enable-xss-auditor", FALSE,
327                  "enable-spatial-navigation", FALSE,
328                  "javascript-can-access-clipboard", TRUE,
329                  "javascript-can-open-windows-automatically", TRUE,
330                  "enable-offline-web-application-cache", TRUE,
331                  "enable-universal-access-from-file-uris", TRUE,
332                  "enable-scripts", TRUE,
333                  "enable-dom-paste", TRUE,
334                  "default-font-family", "Times",
335                  "monospace-font-family", "Courier",
336                  "serif-font-family", "Times",
337                  "sans-serif-font-family", "Helvetica",
338                  "default-font-size", 16,
339                  "default-monospace-font-size", 13,
340                  "minimum-font-size", 1,
341                  "enable-caret-browsing", FALSE,
342                  "enable-page-cache", FALSE,
343                  "auto-resize-window", TRUE,
344                  "enable-java-applet", FALSE,
345                  "enable-plugins", TRUE,
346                  NULL);
347
348     webkit_web_frame_clear_main_frame_name(mainFrame);
349
350     WebKitWebInspector* inspector = webkit_web_view_get_inspector(webView);
351     g_object_set(G_OBJECT(inspector), "javascript-profiling-enabled", FALSE, NULL);
352
353     webkit_web_view_set_zoom_level(webView, 1.0);
354
355     webkit_reset_origin_access_white_lists();
356
357 #ifdef HAVE_LIBSOUP_2_29_90
358     SoupSession* session = webkit_get_default_session();
359     SoupCookieJar* jar = reinterpret_cast<SoupCookieJar*>(soup_session_get_feature(session, SOUP_TYPE_COOKIE_JAR));
360
361     // We only create the jar when the soup backend needs to do
362     // HTTP. Should we initialize it earlier, perhaps?
363     if (jar)
364         g_object_set(G_OBJECT(jar), SOUP_COOKIE_JAR_ACCEPT_POLICY, SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY, NULL);
365 #endif
366
367     setlocale(LC_ALL, "");
368 }
369
370 void dump()
371 {
372     invalidateAnyPreviousWaitToDumpWatchdog();
373
374     bool dumpAsText = gLayoutTestController->dumpAsText();
375     if (dumpTree) {
376         char* result = 0;
377         gchar* responseMimeType = webkit_web_frame_get_response_mime_type(mainFrame);
378
379         dumpAsText = g_str_equal(responseMimeType, "text/plain");
380         g_free(responseMimeType);
381
382         // Test can request controller to be dumped as text even
383         // while test's response mime type is not text/plain.
384         // Overriding this behavior with dumpAsText being false is a bad idea.
385         if (dumpAsText)
386             gLayoutTestController->setDumpAsText(dumpAsText);
387
388         if (gLayoutTestController->dumpAsText())
389             result = dumpFramesAsText(mainFrame);
390         else
391             result = webkit_web_frame_dump_render_tree(mainFrame);
392
393         if (!result) {
394             const char* errorMessage;
395             if (gLayoutTestController->dumpAsText())
396                 errorMessage = "[documentElement innerText]";
397             else if (gLayoutTestController->dumpDOMAsWebArchive())
398                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
399             else if (gLayoutTestController->dumpSourceAsWebArchive())
400                 errorMessage = "[[mainFrame dataSource] webArchive]";
401             else
402                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
403             printf("ERROR: nil result from %s", errorMessage);
404         } else {
405             printf("%s", result);
406             g_free(result);
407             if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive())
408                 dumpFrameScrollPosition(mainFrame);
409
410             if (gLayoutTestController->dumpBackForwardList())
411                 dumpBackForwardListForAllWebViews();
412         }
413
414         if (printSeparators) {
415             puts("#EOF"); // terminate the content block
416             fputs("#EOF\n", stderr);
417             fflush(stdout);
418             fflush(stderr);
419         }
420     }
421
422     if (dumpPixels) {
423         if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive()) {
424             // FIXME: Add support for dumping pixels
425         }
426     }
427
428     // FIXME: call displayWebView here when we support --paint
429
430     done = true;
431     gtk_main_quit();
432 }
433
434 static void setDefaultsToConsistentStateValuesForTesting()
435 {
436     gdk_screen_set_resolution(gdk_screen_get_default(), 72.0);
437
438     resetDefaultsToConsistentValues();
439
440     /* Disable the default auth dialog for testing */
441     SoupSession* session = webkit_get_default_session();
442     soup_session_remove_feature_by_type(session, WEBKIT_TYPE_SOUP_AUTH_DIALOG);
443
444 #if PLATFORM(X11)
445     webkit_web_settings_add_extra_plugin_directory(webView, TEST_PLUGIN_DIR);
446 #endif
447
448     gchar* databaseDirectory = g_build_filename(g_get_user_data_dir(), "gtkwebkitdrt", "databases", NULL);
449     webkit_set_web_database_directory_path(databaseDirectory);
450     g_free(databaseDirectory);
451 }
452
453 static void sendPixelResultsEOF()
454 {
455     puts("#EOF");
456
457     fflush(stdout);
458     fflush(stderr);
459 }
460
461 static void runTest(const string& testPathOrURL)
462 {
463     ASSERT(!testPathOrURL.empty());
464
465     // Look for "'" as a separator between the path or URL, and the pixel dump hash that follows.
466     string pathOrURL(testPathOrURL);
467     string expectedPixelHash;
468
469     size_t separatorPos = pathOrURL.find("'");
470     if (separatorPos != string::npos) {
471         pathOrURL = string(testPathOrURL, 0, separatorPos);
472         expectedPixelHash = string(testPathOrURL, separatorPos + 1);
473     }
474
475     gchar* url = autocorrectURL(pathOrURL.c_str());
476     const string testURL(url);
477
478     resetDefaultsToConsistentValues();
479
480     gLayoutTestController = new LayoutTestController(testURL, expectedPixelHash);
481     topLoadingFrame = 0;
482     done = false;
483
484     gLayoutTestController->setIconDatabaseEnabled(false);
485
486     if (shouldLogFrameLoadDelegates(pathOrURL.c_str()))
487         gLayoutTestController->setDumpFrameLoadCallbacks(true);
488
489     if (shouldEnableDeveloperExtras(pathOrURL.c_str())) {
490         gLayoutTestController->setDeveloperExtrasEnabled(true);
491         if (shouldOpenWebInspector(pathOrURL.c_str()))
492             gLayoutTestController->showWebInspector();
493     }
494
495     WorkQueue::shared()->clear();
496     WorkQueue::shared()->setFrozen(false);
497
498     bool isSVGW3CTest = (gLayoutTestController->testPathOrURL().find("svg/W3C-SVG-1.1") != string::npos);
499     GtkAllocation size;
500     size.x = size.y = 0;
501     size.width = isSVGW3CTest ? 480 : LayoutTestController::maxViewWidth;
502     size.height = isSVGW3CTest ? 360 : LayoutTestController::maxViewHeight;
503     gtk_window_resize(GTK_WINDOW(window), size.width, size.height);
504     gtk_widget_size_allocate(container, &size);
505
506     if (prevTestBFItem)
507         g_object_unref(prevTestBFItem);
508     WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(webView);
509     prevTestBFItem = webkit_web_back_forward_list_get_current_item(bfList);
510     if (prevTestBFItem)
511         g_object_ref(prevTestBFItem);
512
513 #if PLATFORM(X11)
514     initializeFonts();
515 #endif
516
517     // Focus the web view before loading the test to avoid focusing problems
518     gtk_widget_grab_focus(GTK_WIDGET(webView));
519     webkit_web_view_open(webView, url);
520
521     g_free(url);
522     url = NULL;
523
524     gtk_main();
525
526     // If developer extras enabled Web Inspector may have been open by the test.
527     if (shouldEnableDeveloperExtras(pathOrURL.c_str()))
528         gLayoutTestController->closeWebInspector();
529
530     // Also check if we still have opened webViews and free them.
531     if (gLayoutTestController->closeRemainingWindowsWhenComplete() || webViewList) {
532         while (webViewList) {
533             g_object_unref(WEBKIT_WEB_VIEW(webViewList->data));
534             webViewList = g_slist_next(webViewList);
535         }
536         g_slist_free(webViewList);
537         webViewList = 0;
538     }
539
540     // A blank load seems to be necessary to reset state after certain tests.
541     webkit_web_view_open(webView, "about:blank");
542
543     gLayoutTestController->deref();
544     gLayoutTestController = 0;
545
546     // terminate the (possibly empty) pixels block after all the state reset
547     sendPixelResultsEOF();
548 }
549
550 void webViewLoadStarted(WebKitWebView* view, WebKitWebFrame* frame, void*)
551 {
552     // Make sure we only set this once per test.  If it gets cleared, and then set again, we might
553     // end up doing two dumps for one test.
554     if (!topLoadingFrame && !done)
555         topLoadingFrame = frame;
556 }
557
558 static gboolean processWork(void* data)
559 {
560     // if we finish all the commands, we're ready to dump state
561     if (WorkQueue::shared()->processWork() && !gLayoutTestController->waitToDump())
562         dump();
563
564     return FALSE;
565 }
566
567 static char* getFrameNameSuitableForTestResult(WebKitWebView* view, WebKitWebFrame* frame)
568 {
569     char* frameName = g_strdup(webkit_web_frame_get_name(frame));
570
571     if (frame == webkit_web_view_get_main_frame(view)) {
572         // This is a bit strange. Shouldn't web_frame_get_name return NULL?
573         if (frameName && (frameName[0] != '\0')) {
574             char* tmp = g_strdup_printf("main frame \"%s\"", frameName);
575             g_free(frameName);
576             frameName = tmp;
577         } else {
578             g_free(frameName);
579             frameName = g_strdup("main frame");
580         }
581     } else if (!frameName || (frameName[0] == '\0')) {
582         g_free(frameName);
583         frameName = g_strdup("frame (anonymous)");
584     } else {
585         char* tmp = g_strdup_printf("frame \"%s\"", frameName);
586         g_free(frameName);
587         frameName = tmp;
588     }
589
590     return frameName;
591 }
592
593 static void webViewLoadCommitted(WebKitWebView* view, WebKitWebFrame* frame, void*)
594 {
595     if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) {
596         char* frameName = getFrameNameSuitableForTestResult(view, frame);
597         printf("%s - didCommitLoadForFrame\n", frameName);
598         g_free(frameName);
599     }
600 }
601
602
603 static void webViewLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*)
604 {
605     if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) {
606         char* frameName = getFrameNameSuitableForTestResult(view, frame);
607         printf("%s - didFinishLoadForFrame\n", frameName);
608         g_free(frameName);
609     }
610
611     if (frame != topLoadingFrame)
612         return;
613
614     topLoadingFrame = 0;
615     WorkQueue::shared()->setFrozen(true); // first complete load freezes the queue for the rest of this test
616     if (gLayoutTestController->waitToDump())
617         return;
618
619     if (WorkQueue::shared()->count())
620         g_timeout_add(0, processWork, 0);
621     else
622         dump();
623 }
624
625 static void webViewDocumentLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*)
626 {
627     if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) {
628         char* frameName = getFrameNameSuitableForTestResult(view, frame);
629         printf("%s - didFinishDocumentLoadForFrame\n", frameName);
630         g_free(frameName);
631     } else if (!done) {
632         guint pendingFrameUnloadEvents = webkit_web_frame_get_pending_unload_event_count(frame);
633         if (pendingFrameUnloadEvents) {
634             char* frameName = getFrameNameSuitableForTestResult(view, frame);
635             printf("%s - has %u onunload handler(s)\n", frameName, pendingFrameUnloadEvents);
636             g_free(frameName);
637         }
638     }
639 }
640
641 static void webViewOnloadEvent(WebKitWebView* view, WebKitWebFrame* frame, void*)
642 {
643     if (!done && gLayoutTestController->dumpFrameLoadCallbacks()) {
644         char* frameName = getFrameNameSuitableForTestResult(view, frame);
645         printf("%s - didHandleOnloadEventsForFrame\n", frameName);
646         g_free(frameName);
647     }
648 }
649
650 static void webViewWindowObjectCleared(WebKitWebView* view, WebKitWebFrame* frame, JSGlobalContextRef context, JSObjectRef windowObject, gpointer data)
651 {
652     JSValueRef exception = 0;
653     ASSERT(gLayoutTestController);
654
655     gLayoutTestController->makeWindowObject(context, windowObject, &exception);
656     ASSERT(!exception);
657
658     gcController->makeWindowObject(context, windowObject, &exception);
659     ASSERT(!exception);
660
661     axController->makeWindowObject(context, windowObject, &exception);
662     ASSERT(!exception);
663
664     JSStringRef eventSenderStr = JSStringCreateWithUTF8CString("eventSender");
665     JSValueRef eventSender = makeEventSender(context);
666     JSObjectSetProperty(context, windowObject, eventSenderStr, eventSender, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, 0);
667     JSStringRelease(eventSenderStr);
668 }
669
670 static gboolean webViewConsoleMessage(WebKitWebView* view, const gchar* message, unsigned int line, const gchar* sourceId, gpointer data)
671 {
672     gchar* testMessage = 0;
673     const gchar* uriScheme;
674
675     // Tests expect only the filename part of local URIs
676     uriScheme = g_strstr_len(message, -1, "file://");
677     if (uriScheme) {
678         GString* tempString = g_string_sized_new(strlen(message));
679         gchar* filename = g_strrstr(uriScheme, G_DIR_SEPARATOR_S);
680
681         if (filename) {
682             filename += strlen(G_DIR_SEPARATOR_S);
683             tempString = g_string_append_len(tempString, message, (uriScheme - message));
684             tempString = g_string_append_len(tempString, filename, strlen(filename));
685             testMessage = g_string_free(tempString, FALSE);
686         }
687     }
688
689     fprintf(stdout, "CONSOLE MESSAGE: line %d: %s\n", line, testMessage ? testMessage : message);
690     g_free(testMessage);
691
692     return TRUE;
693 }
694
695
696 static gboolean webViewScriptAlert(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gpointer data)
697 {
698     fprintf(stdout, "ALERT: %s\n", message);
699     return TRUE;
700 }
701
702 static gboolean webViewScriptPrompt(WebKitWebView* webView, WebKitWebFrame* frame, const gchar* message, const gchar* defaultValue, gchar** value, gpointer data)
703 {
704     fprintf(stdout, "PROMPT: %s, default text: %s\n", message, defaultValue);
705     *value = g_strdup(defaultValue);
706     return TRUE;
707 }
708
709 static gboolean webViewScriptConfirm(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gboolean* didConfirm, gpointer data)
710 {
711     fprintf(stdout, "CONFIRM: %s\n", message);
712     *didConfirm = TRUE;
713     return TRUE;
714 }
715
716 static void webViewTitleChanged(WebKitWebView* view, WebKitWebFrame* frame, const gchar* title, gpointer data)
717 {
718     if (gLayoutTestController->dumpTitleChanges() && !done)
719         printf("TITLE CHANGED: %s\n", title ? title : "");
720 }
721
722 static bool webViewNavigationPolicyDecisionRequested(WebKitWebView* view, WebKitWebFrame* frame,
723                                                      WebKitNetworkRequest* request,
724                                                      WebKitWebNavigationAction* navAction,
725                                                      WebKitWebPolicyDecision* policyDecision)
726 {
727     // Use the default handler if we're not waiting for policy,
728     // i.e., LayoutTestController::waitForPolicyDelegate
729     if (!waitForPolicy)
730         return FALSE;
731
732     gchar* typeDescription;
733     WebKitWebNavigationReason reason;
734     g_object_get(G_OBJECT(navAction), "reason", &reason, NULL);
735
736     switch(reason) {
737         case WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED:
738             typeDescription = g_strdup("link clicked");
739             break;
740         case WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED:
741             typeDescription = g_strdup("form submitted");
742             break;
743         case WEBKIT_WEB_NAVIGATION_REASON_BACK_FORWARD:
744             typeDescription = g_strdup("back/forward");
745             break;
746         case WEBKIT_WEB_NAVIGATION_REASON_RELOAD:
747             typeDescription = g_strdup("reload");
748             break;
749         case WEBKIT_WEB_NAVIGATION_REASON_FORM_RESUBMITTED:
750             typeDescription = g_strdup("form resubmitted");
751             break;
752         case WEBKIT_WEB_NAVIGATION_REASON_OTHER:
753             typeDescription = g_strdup("other");
754             break;
755         default:
756             typeDescription = g_strdup("illegal value");
757     }
758
759     printf("Policy delegate: attempt to load %s with navigation type '%s'\n", webkit_network_request_get_uri(request), typeDescription);
760     g_free(typeDescription);
761
762     webkit_web_policy_decision_ignore(policyDecision);
763     gLayoutTestController->notifyDone();
764
765     return TRUE;
766 }
767
768 static void webViewStatusBarTextChanged(WebKitWebView* view, const gchar* message, gpointer data)
769 {
770     // Are we doing anything wrong? One test that does not call
771     // dumpStatusCallbacks gets true here
772     if (gLayoutTestController->dumpStatusCallbacks()) {
773         if (message && strcmp(message, ""))
774             printf("UI DELEGATE STATUS CALLBACK: setStatusText:%s\n", message);
775     }
776 }
777
778 static gboolean webViewClose(WebKitWebView* view)
779 {
780     ASSERT(view);
781
782     webViewList = g_slist_remove(webViewList, view);
783     g_object_unref(view);
784
785     return TRUE;
786 }
787
788 static void databaseQuotaExceeded(WebKitWebView* view, WebKitWebFrame* frame, WebKitWebDatabase *database)
789 {
790     ASSERT(view);
791     ASSERT(frame);
792     ASSERT(database);
793
794     WebKitSecurityOrigin* origin = webkit_web_database_get_security_origin(database);
795     if (gLayoutTestController->dumpDatabaseCallbacks()) {
796         printf("UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{%s, %s, %i} database:%s\n",
797             webkit_security_origin_get_protocol(origin),
798             webkit_security_origin_get_host(origin),
799             webkit_security_origin_get_port(origin),
800             webkit_web_database_get_name(database));
801     }
802     webkit_security_origin_set_web_database_quota(origin, 5 * 1024 * 1024);
803 }
804
805 static bool
806 geolocationPolicyDecisionRequested(WebKitWebView*, WebKitWebFrame*, WebKitGeolocationPolicyDecision* decision)
807 {
808     if (!gLayoutTestController->isGeolocationPermissionSet())
809         return FALSE;
810     if (gLayoutTestController->geolocationPermission())
811         webkit_geolocation_policy_allow(decision);
812     else
813         webkit_geolocation_policy_deny(decision);
814
815     return TRUE;
816 }
817
818
819 static WebKitWebView* webViewCreate(WebKitWebView*, WebKitWebFrame*);
820
821 static gboolean webInspectorShowWindow(WebKitWebInspector*, gpointer data)
822 {
823     gtk_window_set_default_size(GTK_WINDOW(webInspectorWindow), 800, 600);
824     gtk_widget_show_all(webInspectorWindow);
825     return TRUE;
826 }
827
828 static gboolean webInspectorCloseWindow(WebKitWebInspector*, gpointer data)
829 {
830     gtk_widget_destroy(webInspectorWindow);
831     webInspectorWindow = 0;
832     return TRUE;
833 }
834
835 static WebKitWebView* webInspectorInspectWebView(WebKitWebInspector*, gpointer data)
836 {
837     webInspectorWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
838
839     GtkWidget* webView = webkit_web_view_new();
840     gtk_container_add(GTK_CONTAINER(webInspectorWindow),
841                       webView);
842
843     return WEBKIT_WEB_VIEW(webView);
844 }
845
846 static void webViewLoadStatusNotified(WebKitWebView* view, gpointer user_data)
847 {
848     WebKitLoadStatus loadStatus = webkit_web_view_get_load_status(view);
849
850     if (gLayoutTestController->dumpFrameLoadCallbacks()) {
851         if (loadStatus == WEBKIT_LOAD_PROVISIONAL) {
852             char* frameName = getFrameNameSuitableForTestResult(view, mainFrame);
853             printf("%s - didStartProvisionalLoadForFrame\n", frameName);
854             g_free(frameName);
855         }
856     }
857 }
858
859 static WebKitWebView* createWebView()
860 {
861     WebKitWebView* view = WEBKIT_WEB_VIEW(webkit_web_view_new());
862
863     // From bug 11756: Use a frame group name for all WebViews created by
864     // DumpRenderTree to allow testing of cross-page frame lookup.
865     webkit_web_view_set_group_name(view, "org.webkit.gtk.DumpRenderTree");
866
867     g_object_connect(G_OBJECT(view),
868                      "signal::load-started", webViewLoadStarted, 0,
869                      "signal::load-finished", webViewLoadFinished, 0,
870                      "signal::load-committed", webViewLoadCommitted, 0,
871                      "signal::window-object-cleared", webViewWindowObjectCleared, 0,
872                      "signal::console-message", webViewConsoleMessage, 0,
873                      "signal::script-alert", webViewScriptAlert, 0,
874                      "signal::script-prompt", webViewScriptPrompt, 0,
875                      "signal::script-confirm", webViewScriptConfirm, 0,
876                      "signal::title-changed", webViewTitleChanged, 0,
877                      "signal::navigation-policy-decision-requested", webViewNavigationPolicyDecisionRequested, 0,
878                      "signal::status-bar-text-changed", webViewStatusBarTextChanged, 0,
879                      "signal::create-web-view", webViewCreate, 0,
880                      "signal::close-web-view", webViewClose, 0,
881                      "signal::database-quota-exceeded", databaseQuotaExceeded, 0,
882                      "signal::document-load-finished", webViewDocumentLoadFinished, 0,
883                      "signal::geolocation-policy-decision-requested", geolocationPolicyDecisionRequested, 0,
884                      "signal::onload-event", webViewOnloadEvent, 0,
885                      NULL);
886
887     g_signal_connect(view,
888                      "notify::load-status", G_CALLBACK(webViewLoadStatusNotified),
889                      NULL);
890
891     WebKitWebInspector* inspector = webkit_web_view_get_inspector(view);
892     g_object_connect(G_OBJECT(inspector),
893                      "signal::inspect-web-view", webInspectorInspectWebView, 0,
894                      "signal::show-window", webInspectorShowWindow, 0,
895                      "signal::close-window", webInspectorCloseWindow, 0,
896                      NULL);
897
898     if (webView) {
899         WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
900         webkit_web_view_set_settings(view, settings);
901     }
902
903     return view;
904 }
905
906 static WebKitWebView* webViewCreate(WebKitWebView* view, WebKitWebFrame* frame)
907 {
908     if (!gLayoutTestController->canOpenWindows())
909         return 0;
910
911     // Make sure that waitUntilDone has been called.
912     ASSERT(gLayoutTestController->waitToDump());
913
914     WebKitWebView* newWebView = createWebView();
915     g_object_ref_sink(G_OBJECT(newWebView));
916     webViewList = g_slist_prepend(webViewList, newWebView);
917     return newWebView;
918 }
919
920 static void logHandler(const gchar* domain, GLogLevelFlags level, const gchar* message, gpointer data)
921 {
922     if (level < G_LOG_LEVEL_DEBUG)
923         fprintf(stderr, "%s\n", message);
924 }
925
926 int main(int argc, char* argv[])
927 {
928     g_thread_init(NULL);
929     gtk_init(&argc, &argv);
930
931     // Some plugins might try to use the GLib logger for printing debug
932     // messages. This will cause tests to fail because of unexpected output.
933     // We squelch all debug messages sent to the logger.
934     g_log_set_default_handler(logHandler, 0);
935
936 #if PLATFORM(X11)
937     FcInit();
938     initializeFonts();
939 #endif
940
941     struct option options[] = {
942         {"notree", no_argument, &dumpTree, false},
943         {"pixel-tests", no_argument, &dumpPixels, true},
944         {"tree", no_argument, &dumpTree, true},
945         {NULL, 0, NULL, 0}
946     };
947
948     int option;
949     while ((option = getopt_long(argc, (char* const*)argv, "", options, NULL)) != -1)
950         switch (option) {
951             case '?':   // unknown or ambiguous option
952             case ':':   // missing argument
953                 exit(1);
954                 break;
955         }
956
957     window = gtk_window_new(GTK_WINDOW_POPUP);
958     container = GTK_WIDGET(gtk_scrolled_window_new(NULL, NULL));
959     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(container), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
960     gtk_container_add(GTK_CONTAINER(window), container);
961     gtk_widget_show_all(window);
962
963     webView = createWebView();
964     gtk_container_add(GTK_CONTAINER(container), GTK_WIDGET(webView));
965     gtk_widget_realize(GTK_WIDGET(webView));
966     gtk_widget_show_all(container);
967     gtk_widget_grab_focus(GTK_WIDGET(webView));
968     mainFrame = webkit_web_view_get_main_frame(webView);
969
970     setDefaultsToConsistentStateValuesForTesting();
971
972     gcController = new GCController();
973     axController = new AccessibilityController();
974
975     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
976         char filenameBuffer[2048];
977         printSeparators = true;
978         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
979             char* newLineCharacter = strchr(filenameBuffer, '\n');
980             if (newLineCharacter)
981                 *newLineCharacter = '\0';
982
983             if (strlen(filenameBuffer) == 0)
984                 continue;
985
986             runTest(filenameBuffer);
987         }
988     } else {
989         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
990         for (int i = optind; i != argc; ++i)
991             runTest(argv[i]);
992     }
993
994     delete gcController;
995     gcController = 0;
996
997     delete axController;
998     axController = 0;
999
1000     gtk_widget_destroy(window);
1001
1002     return 0;
1003 }