Add a watchdog timer to Gtk DumpRenderTree, and implement alert/prompt/confirm.
[WebKit-https.git] / WebKitTools / DumpRenderTree / gtk / DumpRenderTree.cpp
1 /*
2  * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "LayoutTestController.h"
30 #include "WorkQueue.h"
31 #include "WorkQueueItem.h"
32
33 #include <gtk/gtk.h>
34 #include <webkit.h>
35
36 #include <JavaScriptCore/JSBase.h>
37 #include <JavaScriptCore/JSContextRef.h>
38 #include <JavaScriptCore/JSObjectRef.h>
39 #include <JavaScriptCore/JSStringRef.h>
40 #include <JavaScriptCore/JSValueRef.h>
41
42 #include <cassert>
43 #include <getopt.h>
44 #include <stdlib.h>
45 #include <string.h>
46
47 extern "C" {
48 // This API is not yet public.
49 extern GSList* webkit_web_frame_get_children(WebKitWebFrame* frame);
50 extern gchar* webkit_web_frame_get_inner_text(WebKitWebFrame* frame);
51 }
52
53 volatile bool done;
54 static bool printSeparators;
55 static int testRepaintDefault;
56 static int repaintSweepHorizontallyDefault;
57 static int dumpPixels;
58 static int dumpTree = 1;
59 static gchar* currentTest;
60
61 LayoutTestController* layoutTestController = 0;
62 static WebKitWebView* webView;
63 WebKitWebFrame* mainFrame = 0;
64 WebKitWebFrame* topLoadingFrame = 0;
65 guint waitToDumpWatchdog = 0;
66
67 const unsigned maxViewHeight = 600;
68 const unsigned maxViewWidth = 800;
69
70 static gchar* autocorrectURL(const gchar* url)
71 {
72     if (strncmp("http://", url, 7) != 0 && strncmp("https://", url, 8) != 0) {
73         GString* string = g_string_new("file://");
74         g_string_append(string, url);
75         return g_string_free(string, FALSE);
76     }
77
78     return g_strdup(url);
79 }
80
81 static bool shouldLogFrameLoadDelegates(const char* pathOrURL)
82 {
83     return strstr(pathOrURL, "loading/");
84 }
85
86 void dumpFrameScrollPosition(WebKitWebFrame* frame)
87 {
88
89 }
90
91 void displayWebView()
92 {
93
94 }
95
96 static void appendString(gchar*& target, gchar* string)
97 {
98     gchar* oldString = target;
99     target = g_strconcat(target, string, NULL);
100     g_free(oldString);
101 }
102
103 static gchar* dumpFramesAsText(WebKitWebFrame* frame)
104 {
105     gchar* result = 0;
106
107     // Add header for all but the main frame.
108     bool isMainFrame = (webkit_web_view_get_main_frame(webView) == frame);
109
110     if (isMainFrame) {
111         gchar* innerText = webkit_web_frame_get_inner_text(frame);
112         result = g_strdup_printf("%s\n", innerText);
113         g_free(innerText);
114     } else {
115         const gchar* frameName = webkit_web_frame_get_name(frame);
116         gchar* innerText = webkit_web_frame_get_inner_text(frame);
117
118         result = g_strdup_printf("\n--------\nFrame: '%s'\n--------\n%s\n", frameName, innerText);
119
120         g_free(innerText);
121     }
122
123     if (layoutTestController->dumpChildFramesAsText()) {
124         GSList* children = webkit_web_frame_get_children(frame);
125         for (;children; children = g_slist_next(children))
126            appendString(result, dumpFramesAsText((WebKitWebFrame*)children->data));
127     }
128
129     return result;
130 }
131
132 static gchar* dumpRenderTreeAsText(WebKitWebFrame* frame)
133 {
134     // FIXME: this will require new WebKitGtk SPI
135     return strdup("foo");
136 }
137
138 static void invalidateAnyPreviousWaitToDumpWatchdog()
139 {
140     if (waitToDumpWatchdog) {
141         g_source_remove(waitToDumpWatchdog);
142         waitToDumpWatchdog = 0;
143     }
144 }
145
146 void dump()
147 {
148     invalidateAnyPreviousWaitToDumpWatchdog();
149     if (dumpTree) {
150         char* result = 0;
151
152         bool dumpAsText = layoutTestController->dumpAsText();
153         // FIXME: Also dump text resuls as text.
154         layoutTestController->setDumpAsText(dumpAsText);
155         if (layoutTestController->dumpAsText())
156             result = dumpFramesAsText(mainFrame);
157         else {
158             bool isSVGW3CTest = (g_strrstr(currentTest, "svg/W3C-SVG-1.1"));
159             if (isSVGW3CTest)
160                 gtk_widget_set_size_request(GTK_WIDGET(webView), 480, 360);
161             else
162                 gtk_widget_set_size_request(GTK_WIDGET(webView), maxViewWidth, maxViewHeight);
163             result = dumpRenderTreeAsText(mainFrame);
164         }
165
166         if (!result) {
167             const char* errorMessage;
168             if (layoutTestController->dumpAsText())
169                 errorMessage = "[documentElement innerText]";
170             else if (layoutTestController->dumpDOMAsWebArchive())
171                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
172             else if (layoutTestController->dumpSourceAsWebArchive())
173                 errorMessage = "[[mainFrame dataSource] webArchive]";
174             else
175                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
176             printf("ERROR: nil result from %s", errorMessage);
177         } else {
178             printf("%s", result);
179             g_free(result);
180             if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive())
181                 dumpFrameScrollPosition(mainFrame);
182         }
183
184         if (layoutTestController->dumpBackForwardList()) {
185             // FIXME: not implemented
186         }
187
188         if (printSeparators)
189             puts("#EOF");
190     }
191
192     if (dumpPixels) {
193         if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive()) {
194             // FIXME: Add support for dumping pixels
195         }
196     }
197
198     fflush(stdout);
199
200     // FIXME: call displayWebView here when we support --paint
201
202     done = true;
203 }
204
205 static void runTest(const char* pathOrURL)
206 {
207     gchar* url = autocorrectURL(pathOrURL);
208
209     layoutTestController = new LayoutTestController(testRepaintDefault, repaintSweepHorizontallyDefault);
210
211     done = false;
212     topLoadingFrame = 0;
213
214     if (shouldLogFrameLoadDelegates(pathOrURL))
215         layoutTestController->setDumpFrameLoadCallbacks(true);
216
217     if (currentTest)
218         g_free(currentTest);
219     currentTest = url;
220
221     WorkQueue::shared()->clear();
222     WorkQueue::shared()->setFrozen(false);
223
224     webkit_web_view_open(webView, url);
225
226     while (!done)
227         g_main_context_iteration(NULL, true);
228
229     WorkQueue::shared()->clear();
230
231     delete layoutTestController;
232     layoutTestController = 0;
233 }
234
235 void webViewLoadStarted(WebKitWebView* view, WebKitWebFrame* frame, void*)
236 {
237     // Make sure we only set this once per test.  If it gets cleared, and then set again, we might
238     // end up doing two dumps for one test.
239     if (!topLoadingFrame && !done)
240         topLoadingFrame = frame;
241 }
242
243 void webViewLoadFinished(WebKitWebView* view, WebKitWebFrame* frame, void*)
244 {
245     if (frame != topLoadingFrame)
246         return;
247
248     topLoadingFrame = 0;
249     WorkQueue::shared()->setFrozen(true); // first complete load freezes the queue for the rest of this test
250     if (layoutTestController->waitToDump())
251         return;
252
253     if (WorkQueue::shared()->count())
254         fprintf(stderr, "FIXME: [self performSelector:@selector(processWork:) withObject:nil afterDelay:0];\n");
255      else
256         dump();
257 }
258
259 void webViewWindowObjectCleared(WebKitWebView* view, WebKitWebFrame* frame, JSGlobalContextRef context, JSObjectRef globalObject)
260 {
261     JSValueRef exception = 0;
262     assert(layoutTestController);
263     layoutTestController->makeWindowObject(context, globalObject, &exception);
264     assert(!exception);
265 }
266
267 gboolean webViewConsoleMessage(WebKitWebView* view, const gchar* message, unsigned int line, const gchar* sourceId)
268 {
269     fprintf(stdout, "CONSOLE MESSAGE: line %d: %s\n", line, message);
270     return TRUE;
271 }
272
273
274 gboolean webViewScriptAlert(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message)
275 {
276     fprintf(stdout, "ALERT: %s\n", message);
277     return TRUE;
278 }
279
280 gboolean webViewScriptPrompt(WebKitWebView* webView, WebKitWebFrame* frame, const gchar* message, const gchar* defaultValue, gchar** value)
281 {
282     fprintf(stdout, "PROMPT: %s, default text: %s\n", message, defaultValue);
283     *value = g_strdup(defaultValue);
284     return TRUE;
285 }
286
287 gboolean webViewScriptConfirm(WebKitWebView* view, WebKitWebFrame* frame, const gchar* message, gboolean* didConfirm)
288 {
289     fprintf(stdout, "CONFIRM: %s\n", message);
290     *didConfirm = TRUE;
291     return TRUE;
292 }
293
294
295 int main(int argc, char* argv[])
296 {
297     struct option options[] = {
298         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, true},
299         {"notree", no_argument, &dumpTree, false},
300         {"pixel-tests", no_argument, &dumpPixels, true},
301         {"repaint", no_argument, &testRepaintDefault, true},
302         {"tree", no_argument, &dumpTree, true},
303         {NULL, 0, NULL, 0}
304     };
305
306     int option;
307     while ((option = getopt_long(argc, (char* const*)argv, "", options, NULL)) != -1)
308         switch (option) {
309             case '?':   // unknown or ambiguous option
310             case ':':   // missing argument
311                 exit(1);
312                 break;
313         }
314
315     gtk_init(&argc, &argv);
316     webkit_init();
317
318     GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
319     GtkContainer* container = GTK_CONTAINER(gtk_fixed_new());
320     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(container));
321     gtk_widget_realize(window);
322
323     webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
324     gtk_container_add(container, GTK_WIDGET(webView));
325     gtk_widget_realize(GTK_WIDGET(webView));
326     mainFrame = webkit_web_view_get_main_frame(webView);
327
328     g_signal_connect(G_OBJECT(webView), "load-started", G_CALLBACK(webViewLoadStarted), 0);
329     g_signal_connect(G_OBJECT(webView), "load-finished", G_CALLBACK(webViewLoadFinished), 0);
330     g_signal_connect(G_OBJECT(webView), "window-object-cleared", G_CALLBACK(webViewWindowObjectCleared), 0);
331     g_signal_connect(G_OBJECT(webView), "console-message", G_CALLBACK(webViewConsoleMessage), 0);
332     g_signal_connect(G_OBJECT(webView), "script-alert", G_CALLBACK(webViewScriptAlert), 0);
333     g_signal_connect(G_OBJECT(webView), "script-prompt", G_CALLBACK(webViewScriptPrompt), 0);
334     g_signal_connect(G_OBJECT(webView), "script-confirm", G_CALLBACK(webViewScriptConfirm), 0);
335
336     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
337         char filenameBuffer[2048];
338         printSeparators = true;
339         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
340             char* newLineCharacter = strchr(filenameBuffer, '\n');
341             if (newLineCharacter)
342                 *newLineCharacter = '\0';
343
344             if (strlen(filenameBuffer) == 0)
345                 continue;
346
347             runTest(filenameBuffer);
348         }
349     } else {
350         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
351         for (int i = optind; i != argc; ++i)
352             runTest(argv[i]);
353     }
354
355     return 0;
356 }