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