2009-04-16 Zan Dobersek <zandobersek@gmail.com>
[WebKit-https.git] / WebKitTools / DumpRenderTree / gtk / DumpRenderTree.cpp
1 /*
2  * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
3  * Copyright (C) 2008 Alp Toker <alp@nuanti.com>
4  * Copyright (C) 2009 Jan Alonzo <jmalonzo@gmail.com>
5  *
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 "LayoutTestController.h"
35 #include "WorkQueue.h"
36 #include "WorkQueueItem.h"
37
38 #include <gtk/gtk.h>
39 #include <webkit/webkit.h>
40 #include <JavaScriptCore/JavaScript.h>
41
42 #include <wtf/Assertions.h>
43
44 #include <cassert>
45 #include <getopt.h>
46 #include <stdlib.h>
47 #include <string.h>
48
49 using namespace std;
50
51 extern "C" {
52 // This API is not yet public.
53 extern G_CONST_RETURN gchar* webkit_web_history_item_get_target(WebKitWebHistoryItem*);
54 extern gboolean webkit_web_history_item_is_target_item(WebKitWebHistoryItem*);
55 extern GList* webkit_web_history_item_get_children(WebKitWebHistoryItem*);
56 extern GSList* webkit_web_frame_get_children(WebKitWebFrame* frame);
57 extern gchar* webkit_web_frame_get_inner_text(WebKitWebFrame* frame);
58 extern gchar* webkit_web_frame_dump_render_tree(WebKitWebFrame* frame);
59 extern void webkit_web_settings_add_extra_plugin_directory(WebKitWebView* view, const gchar* directory);
60 extern gchar* webkit_web_frame_get_response_mime_type(WebKitWebFrame* frame);
61 }
62
63 volatile bool done;
64 static bool printSeparators;
65 static int dumpPixels;
66 static int dumpTree = 1;
67
68 LayoutTestController* gLayoutTestController = 0;
69 static WebKitWebView* webView;
70 WebKitWebFrame* mainFrame = 0;
71 WebKitWebFrame* topLoadingFrame = 0;
72 guint waitToDumpWatchdog = 0;
73
74 // current b/f item at the end of the previous test
75 static WebKitWebHistoryItem* prevTestBFItem = NULL;
76
77 const unsigned maxViewHeight = 600;
78 const unsigned maxViewWidth = 800;
79 const unsigned historyItemIndent = 8;
80
81 static gchar* autocorrectURL(const gchar* url)
82 {
83     if (strncmp("http://", url, 7) != 0 && strncmp("https://", url, 8) != 0) {
84         GString* string = g_string_new("file://");
85         g_string_append(string, url);
86         return g_string_free(string, FALSE);
87     }
88
89     return g_strdup(url);
90 }
91
92 static bool shouldLogFrameLoadDelegates(const char* pathOrURL)
93 {
94     return strstr(pathOrURL, "loading/");
95 }
96
97 void dumpFrameScrollPosition(WebKitWebFrame* frame)
98 {
99
100 }
101
102 void displayWebView()
103 {
104
105 }
106
107 static void appendString(gchar*& target, gchar* string)
108 {
109     gchar* oldString = target;
110     target = g_strconcat(target, string, NULL);
111     g_free(oldString);
112 }
113
114 static gchar* dumpFramesAsText(WebKitWebFrame* frame)
115 {
116     gchar* result = 0;
117
118     // Add header for all but the main frame.
119     bool isMainFrame = (webkit_web_view_get_main_frame(webView) == frame);
120
121     gchar* innerText = webkit_web_frame_get_inner_text(frame);
122     if (isMainFrame)
123         result = g_strdup_printf("%s\n", innerText);
124     else {
125         const gchar* frameName = webkit_web_frame_get_name(frame);
126         result = g_strdup_printf("\n--------\nFrame: '%s'\n--------\n%s\n", frameName, innerText);
127     }
128     g_free(innerText);
129
130     if (gLayoutTestController->dumpChildFramesAsText()) {
131         GSList* children = webkit_web_frame_get_children(frame);
132         for (GSList* child = children; child; child = g_slist_next(child))
133            appendString(result, dumpFramesAsText((WebKitWebFrame*)children->data));
134         g_slist_free(children);
135     }
136
137     return result;
138 }
139
140 static gint compareHistoryItems(gpointer* item1, gpointer* item2)
141 {
142     return g_ascii_strcasecmp(webkit_web_history_item_get_target(WEBKIT_WEB_HISTORY_ITEM(item1)),
143                               webkit_web_history_item_get_target(WEBKIT_WEB_HISTORY_ITEM(item2)));
144 }
145
146 static void dumpHistoryItem(WebKitWebHistoryItem* item, int indent, bool current)
147 {
148     ASSERT(item != NULL);
149     int start = 0;
150     g_object_ref(item);
151     if (current) {
152         printf("curr->");
153         start = 6;
154     }
155     for (int i = start; i < indent; i++)
156         putchar(' ');
157     printf("%s", webkit_web_history_item_get_uri(item));
158     const gchar* target = webkit_web_history_item_get_target(item);
159     if (target && g_utf8_strlen(target, 0) > 0)
160         printf(" (in frame \"%s\")", target);
161     if (webkit_web_history_item_is_target_item(item))
162         printf("  **nav target**");
163     putchar('\n');
164     GList* kids = webkit_web_history_item_get_children(item);
165     if (kids) {
166         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
167         kids = g_list_sort(kids, (GCompareFunc) compareHistoryItems);
168         for (unsigned i = 0; i < g_list_length(kids); i++)
169             dumpHistoryItem(WEBKIT_WEB_HISTORY_ITEM(g_list_nth_data(kids, i)), indent+4, FALSE);
170     }
171     g_object_unref(item);
172 }
173
174 static void dumpBackForwardListForWebView(WebKitWebView* view)
175 {
176     printf("\n============== Back Forward List ==============\n");
177     WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(view);
178
179     // Print out all items in the list after prevTestBFItem, which was from the previous test
180     // Gather items from the end of the list, the print them out from oldest to newest
181     GList* itemsToPrint = NULL;
182     gint forwardListCount = webkit_web_back_forward_list_get_forward_length(bfList);
183     for (int i = forwardListCount; i > 0; i--) {
184         WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
185         // something is wrong if the item from the last test is in the forward part of the b/f list
186         ASSERT(item != prevTestBFItem);
187         g_object_ref(item);
188         itemsToPrint = g_list_append(itemsToPrint, item);
189     }
190
191     WebKitWebHistoryItem* currentItem = webkit_web_back_forward_list_get_current_item(bfList);
192
193     g_object_ref(currentItem);
194     itemsToPrint = g_list_append(itemsToPrint, currentItem);
195
196     gint currentItemIndex = g_list_length(itemsToPrint) - 1;
197     gint backListCount = webkit_web_back_forward_list_get_back_length(bfList);
198     for (int i = -1; i >= -(backListCount); i--) {
199         WebKitWebHistoryItem* item = webkit_web_back_forward_list_get_nth_item(bfList, i);
200         if (item == prevTestBFItem)
201             break;
202         g_object_ref(item);
203         itemsToPrint = g_list_append(itemsToPrint, item);
204     }
205
206     for (int i = g_list_length(itemsToPrint) - 1; i >= 0; i--) {
207         WebKitWebHistoryItem* item = WEBKIT_WEB_HISTORY_ITEM(g_list_nth_data(itemsToPrint, i));
208         dumpHistoryItem(item, historyItemIndent, i == currentItemIndex);
209         g_object_unref(item);
210     }
211     g_list_free(itemsToPrint);
212     printf("===============================================\n");
213 }
214
215 static void invalidateAnyPreviousWaitToDumpWatchdog()
216 {
217     if (waitToDumpWatchdog) {
218         g_source_remove(waitToDumpWatchdog);
219         waitToDumpWatchdog = 0;
220     }
221 }
222
223 static void resetWebViewToConsistentStateBeforeTesting()
224 {
225     WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
226     g_object_set(G_OBJECT(settings),
227                  "enable-private-browsing", FALSE,
228                  "enable-developer-extras", FALSE,
229                  NULL);
230
231     WebKitWebInspector* inspector = webkit_web_view_get_inspector(webView);
232     g_object_set(G_OBJECT(inspector), "javascript-profiling-enabled", FALSE, NULL);
233 }
234
235 void dump()
236 {
237     invalidateAnyPreviousWaitToDumpWatchdog();
238
239     bool dumpAsText = gLayoutTestController->dumpAsText();
240     if (dumpTree) {
241         char* result = 0;
242         gchar* responseMimeType = webkit_web_frame_get_response_mime_type(mainFrame);
243
244         dumpAsText = g_str_equal(responseMimeType, "text/plain");
245         g_free(responseMimeType);
246
247         // Test can request controller to be dumped as text even
248         // while test's response mime type is not text/plain.
249         // Overriding this behavior with dumpAsText being false is a bad idea.
250         if (dumpAsText)
251             gLayoutTestController->setDumpAsText(dumpAsText);
252
253         if (gLayoutTestController->dumpAsText())
254             result = dumpFramesAsText(mainFrame);
255         else
256             result = webkit_web_frame_dump_render_tree(mainFrame);
257
258         if (!result) {
259             const char* errorMessage;
260             if (gLayoutTestController->dumpAsText())
261                 errorMessage = "[documentElement innerText]";
262             else if (gLayoutTestController->dumpDOMAsWebArchive())
263                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
264             else if (gLayoutTestController->dumpSourceAsWebArchive())
265                 errorMessage = "[[mainFrame dataSource] webArchive]";
266             else
267                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
268             printf("ERROR: nil result from %s", errorMessage);
269         } else {
270             printf("%s", result);
271             g_free(result);
272             if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive())
273                 dumpFrameScrollPosition(mainFrame);
274
275             if (gLayoutTestController->dumpBackForwardList()) {
276                 // FIXME: multiple windows support
277                 dumpBackForwardListForWebView(webView);
278
279             }
280         }
281
282         if (printSeparators) {
283             puts("#EOF"); // terminate the content block
284             fputs("#EOF\n", stderr);
285             fflush(stdout);
286             fflush(stderr);
287         }
288     }
289
290     if (dumpPixels) {
291         if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive()) {
292             // FIXME: Add support for dumping pixels
293         }
294     }
295
296     // FIXME: call displayWebView here when we support --paint
297
298     puts("#EOF"); // terminate the (possibly empty) pixels block
299
300     fflush(stdout);
301     fflush(stderr);
302
303     done = true;
304 }
305
306 static void setDefaultsToConsistentStateValuesForTesting()
307 {
308     gdk_screen_set_resolution(gdk_screen_get_default(), 72.0);
309
310     WebKitWebSettings* settings = webkit_web_view_get_settings(webView);
311     g_object_set(G_OBJECT(settings),
312                  "default-font-family", "Times",
313                  "monospace-font-family", "Courier",
314                  "serif-font-family", "Times",
315                  "sans-serif-font-family", "Helvetica",
316                  "default-font-size", 16,
317                  "default-monospace-font-size", 13,
318                  "minimum-font-size", 1,
319                  NULL);
320
321     /* Disable the default auth dialog for testing */
322     SoupSession* session = webkit_get_default_session();
323     soup_session_remove_feature_by_type(session, WEBKIT_TYPE_SOUP_AUTH_DIALOG);
324
325 #if PLATFORM(X11)
326     webkit_web_settings_add_extra_plugin_directory(webView, TEST_PLUGIN_DIR);
327 #endif
328 }
329
330 static void runTest(const string& testPathOrURL)
331 {
332     ASSERT(!testPathOrURL.empty());
333
334     // Look for "'" as a separator between the path or URL, and the pixel dump hash that follows.
335     string pathOrURL(testPathOrURL);
336     string expectedPixelHash;
337
338     size_t separatorPos = pathOrURL.find("'");
339     if (separatorPos != string::npos) {
340         pathOrURL = string(testPathOrURL, 0, separatorPos);
341         expectedPixelHash = string(testPathOrURL, separatorPos + 1);
342     }
343
344     gchar* url = autocorrectURL(pathOrURL.c_str());
345     const string testURL(url);
346
347     resetWebViewToConsistentStateBeforeTesting();
348
349     gLayoutTestController = new LayoutTestController(testURL, expectedPixelHash);
350     topLoadingFrame = 0;
351     done = false;
352
353     gLayoutTestController->setIconDatabaseEnabled(false);
354
355     if (shouldLogFrameLoadDelegates(pathOrURL.c_str()))
356         gLayoutTestController->setDumpFrameLoadCallbacks(true);
357
358     WorkQueue::shared()->clear();
359     WorkQueue::shared()->setFrozen(false);
360
361     bool isSVGW3CTest = (gLayoutTestController->testPathOrURL().find("svg/W3C-SVG-1.1") != string::npos);
362     GtkAllocation size;
363     size.width = isSVGW3CTest ? 480 : maxViewWidth;
364     size.height = isSVGW3CTest ? 360 : maxViewHeight;
365     gtk_widget_size_allocate(GTK_WIDGET(webView), &size);
366
367     if (prevTestBFItem)
368         g_object_unref(prevTestBFItem);
369     WebKitWebBackForwardList* bfList = webkit_web_view_get_back_forward_list(webView);
370     prevTestBFItem = webkit_web_back_forward_list_get_current_item(bfList);
371     if (prevTestBFItem)
372         g_object_ref(prevTestBFItem);
373
374
375     webkit_web_view_open(webView, url);
376
377     g_free(url);
378     url = NULL;
379
380     while (!done)
381         g_main_context_iteration(NULL, TRUE);
382
383     // A blank load seems to be necessary to reset state after certain tests.
384     webkit_web_view_open(webView, "about:blank");
385
386     gLayoutTestController->deref();
387     gLayoutTestController = 0;
388 }
389
390 void webViewLoadStarted(WebKitWebView* view, WebKitWebFrame* frame, void*)
391 {
392     // Make sure we only set this once per test.  If it gets cleared, and then set again, we might
393     // end up doing two dumps for one test.
394     if (!topLoadingFrame && !done)
395         topLoadingFrame = frame;
396 }
397
398 static gboolean processWork(void* data)
399 {
400     // if we finish all the commands, we're ready to dump state
401     if (WorkQueue::shared()->processWork() && !gLayoutTestController->waitToDump())
402         dump();
403
404     return FALSE;
405 }
406
407 static void webViewLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*)
408 {
409     if (frame != topLoadingFrame)
410         return;
411
412     topLoadingFrame = 0;
413     WorkQueue::shared()->setFrozen(true); // first complete load freezes the queue for the rest of this test
414     if (gLayoutTestController->waitToDump())
415         return;
416
417     if (WorkQueue::shared()->count())
418         g_timeout_add(0, processWork, 0);
419      else
420         dump();
421 }
422
423 static void webViewWindowObjectCleared(WebKitWebView* view, WebKitWebFrame* frame, JSGlobalContextRef context, JSObjectRef windowObject, gpointer data)
424 {
425     JSValueRef exception = 0;
426     assert(gLayoutTestController);
427
428     gLayoutTestController->makeWindowObject(context, windowObject, &exception);
429     assert(!exception);
430 }
431
432 static gboolean webViewConsoleMessage(WebKitWebView* view, const gchar* message, unsigned int line, const gchar* sourceId, gpointer data)
433 {
434     fprintf(stdout, "CONSOLE MESSAGE: line %d: %s\n", line, message);
435     return TRUE;
436 }
437
438
439 static gboolean webViewScriptAlert(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gpointer data)
440 {
441     fprintf(stdout, "ALERT: %s\n", message);
442     return TRUE;
443 }
444
445 static gboolean webViewScriptPrompt(WebKitWebView* webView, WebKitWebFrame* frame, const gchar* message, const gchar* defaultValue, gchar** value, gpointer data)
446 {
447     fprintf(stdout, "PROMPT: %s, default text: %s\n", message, defaultValue);
448     *value = g_strdup(defaultValue);
449     return TRUE;
450 }
451
452 static gboolean webViewScriptConfirm(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gboolean* didConfirm, gpointer data)
453 {
454     fprintf(stdout, "CONFIRM: %s\n", message);
455     *didConfirm = TRUE;
456     return TRUE;
457 }
458
459 static void webViewTitleChanged(WebKitWebView* view, WebKitWebFrame* frame, const gchar* title, gpointer data)
460 {
461     if (gLayoutTestController->dumpTitleChanges() && !done)
462         printf("TITLE CHANGED: %s\n", title ? title : "");
463 }
464
465 int main(int argc, char* argv[])
466 {
467     g_thread_init(NULL);
468     gtk_init(&argc, &argv);
469
470     struct option options[] = {
471         {"notree", no_argument, &dumpTree, false},
472         {"pixel-tests", no_argument, &dumpPixels, true},
473         {"tree", no_argument, &dumpTree, true},
474         {NULL, 0, NULL, 0}
475     };
476
477     int option;
478     while ((option = getopt_long(argc, (char* const*)argv, "", options, NULL)) != -1)
479         switch (option) {
480             case '?':   // unknown or ambiguous option
481             case ':':   // missing argument
482                 exit(1);
483                 break;
484         }
485
486     GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
487     GtkContainer* container = GTK_CONTAINER(gtk_fixed_new());
488     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(container));
489     gtk_widget_realize(window);
490
491     webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
492     gtk_container_add(container, GTK_WIDGET(webView));
493     gtk_widget_realize(GTK_WIDGET(webView));
494     mainFrame = webkit_web_view_get_main_frame(webView);
495
496     g_signal_connect(G_OBJECT(webView), "load-started", G_CALLBACK(webViewLoadStarted), 0);
497     g_signal_connect(G_OBJECT(webView), "load-finished", G_CALLBACK(webViewLoadFinished), 0);
498     g_signal_connect(G_OBJECT(webView), "window-object-cleared", G_CALLBACK(webViewWindowObjectCleared), 0);
499     g_signal_connect(G_OBJECT(webView), "console-message", G_CALLBACK(webViewConsoleMessage), 0);
500     g_signal_connect(G_OBJECT(webView), "script-alert", G_CALLBACK(webViewScriptAlert), 0);
501     g_signal_connect(G_OBJECT(webView), "script-prompt", G_CALLBACK(webViewScriptPrompt), 0);
502     g_signal_connect(G_OBJECT(webView), "script-confirm", G_CALLBACK(webViewScriptConfirm), 0);
503     g_signal_connect(G_OBJECT(webView), "title-changed", G_CALLBACK(webViewTitleChanged), 0);
504
505     setDefaultsToConsistentStateValuesForTesting();
506
507     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
508         char filenameBuffer[2048];
509         printSeparators = true;
510         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
511             char* newLineCharacter = strchr(filenameBuffer, '\n');
512             if (newLineCharacter)
513                 *newLineCharacter = '\0';
514
515             if (strlen(filenameBuffer) == 0)
516                 continue;
517
518             runTest(filenameBuffer);
519         }
520     } else {
521         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
522         for (int i = optind; i != argc; ++i)
523             runTest(argv[i]);
524     }
525
526     return 0;
527 }