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