2007-11-21 Eric Seidel <eric@webkit.org>
[WebKit-https.git] / WebKitTools / DumpRenderTree / mac / DumpRenderTree.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007 Apple, Inc.  All rights reserved.
3  *           (C) 2007 Graham Dennis (graham.dennis@gmail.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 #import "DumpRenderTree.h"
31
32 #import "DumpRenderTreePasteboard.h"
33 #import "DumpRenderTreeWindow.h"
34 #import "EditingDelegate.h"
35 #import "EventSendingController.h"
36 #import "FrameLoadDelegate.h"
37 #import "LayoutTestController.h"
38 #import "NavigationController.h"
39 #import "ObjCPlugin.h"
40 #import "ObjCPluginFunction.h"
41 #import "PolicyDelegate.h"
42 #import "ResourceLoadDelegate.h"
43 #import "UIDelegate.h"
44 #import "WorkQueue.h"
45 #import "WorkQueueItem.h"
46
47 #import <ApplicationServices/ApplicationServices.h> // for CMSetDefaultProfileBySpace
48 #import <CoreFoundation/CoreFoundation.h>
49 #import <JavaScriptCore/Assertions.h>
50 #import <JavaScriptCore/JavaScriptCore.h>
51 #import <WebKit/DOMElementPrivate.h>
52 #import <WebKit/DOMExtensions.h>
53 #import <WebKit/DOMRange.h>
54 #import <WebKit/WebBackForwardList.h>
55 #import <WebKit/WebCoreStatistics.h>
56 #import <WebKit/WebDataSourcePrivate.h>
57 #import <WebKit/WebDocumentPrivate.h>
58 #import <WebKit/WebEditingDelegate.h>
59 #import <WebKit/WebFrameView.h>
60 #import <WebKit/WebHistory.h>
61 #import <WebKit/WebHistoryItemPrivate.h>
62 #import <WebKit/WebPluginDatabase.h>
63 #import <WebKit/WebPreferences.h>
64 #import <WebKit/WebPreferencesPrivate.h>
65 #import <WebKit/WebResourceLoadDelegate.h>
66 #import <WebKit/WebViewPrivate.h>
67 #import <getopt.h>
68 #import <mach-o/getsect.h>
69 #import <malloc/malloc.h>
70 #import <objc/objc-runtime.h>                       // for class_poseAs
71 #import <pthread.h>
72
73 #define COMMON_DIGEST_FOR_OPENSSL
74 #import <CommonCrypto/CommonDigest.h>               // for MD5 functions
75
76 @interface DumpRenderTreeEvent : NSEvent
77 @end
78
79 static void runTest(const char *pathOrURL);
80 static NSString *md5HashStringForBitmap(CGImageRef bitmap);
81
82 // Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
83 // - There is no load in progress
84 // - There is no work queued up (see workQueue var, below)
85 // - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
86 //       and notifyDone was called subsequently.
87 // Note that the call to notifyDone and the end of the load can happen in either order.
88
89 volatile bool done;
90
91 NavigationController* navigationController = 0;
92 LayoutTestController* layoutTestController = 0;
93
94 WebFrame *mainFrame = 0;
95 // This is the topmost frame that is loading, during a given load, or nil when no load is 
96 // in progress.  Usually this is the same as the main frame, but not always.  In the case
97 // where a frameset is loaded, and then new content is loaded into one of the child frames,
98 // that child frame is the "topmost frame that is loading".
99 WebFrame *topLoadingFrame = nil;     // !nil iff a load is in progress
100
101
102 CFMutableSetRef disallowedURLs = 0;
103 CFRunLoopTimerRef waitToDumpWatchdog = 0;
104
105 // Delegates
106 static FrameLoadDelegate *frameLoadDelegate;
107 static UIDelegate *uiDelegate;
108 static EditingDelegate *editingDelegate;
109 static ResourceLoadDelegate *resourceLoadDelegate;
110 PolicyDelegate *policyDelegate;
111
112 static int dumpPixels;
113 static int dumpAllPixels;
114 static int threaded;
115 static int testRepaintDefault;
116 static int repaintSweepHorizontallyDefault;
117 static int dumpTree = YES;
118 static BOOL printSeparators;
119 static NSString *currentTest = nil;
120
121 static WebHistoryItem *prevTestBFItem = nil;  // current b/f item at the end of the previous test
122 static unsigned char* screenCaptureBuffer;
123 static CGColorSpaceRef sharedColorSpace;
124
125 const unsigned maxViewHeight = 600;
126 const unsigned maxViewWidth = 800;
127
128 static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER;
129 static BOOL javaScriptThreadsShouldTerminate;
130
131 static const int javaScriptThreadsCount = 4;
132 static CFMutableDictionaryRef javaScriptThreads()
133 {
134     assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY);
135     static CFMutableDictionaryRef staticJavaScriptThreads;
136     if (!staticJavaScriptThreads)
137         staticJavaScriptThreads = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
138     return staticJavaScriptThreads;
139 }
140
141 // Loops forever, running a script and randomly respawning, until 
142 // javaScriptThreadsShouldTerminate becomes true.
143 void* runJavaScriptThread(void* arg)
144 {
145     const char* const script =
146         "var array = [];"
147         "for (var i = 0; i < 10; i++) {"
148         "    array.push(String(i));"
149         "}";
150
151     while(1) {
152         JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
153         JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
154
155         JSValueRef exception = NULL;
156         JSEvaluateScript(ctx, scriptRef, NULL, NULL, 0, &exception);
157         assert(!exception);
158         
159         JSGlobalContextRelease(ctx);
160         JSStringRelease(scriptRef);
161         
162         JSGarbageCollect(ctx);
163
164         pthread_mutex_lock(&javaScriptThreadsMutex);
165
166         // Check for cancellation.
167         if (javaScriptThreadsShouldTerminate) {
168             pthread_mutex_unlock(&javaScriptThreadsMutex);
169             return 0;
170         }
171
172         // Respawn probabilistically.
173         if (random() % 5 == 0) {
174             pthread_t pthread;
175             pthread_create(&pthread, NULL, &runJavaScriptThread, NULL);
176             pthread_detach(pthread);
177
178             CFDictionaryRemoveValue(javaScriptThreads(), pthread_self());
179             CFDictionaryAddValue(javaScriptThreads(), pthread, NULL);
180
181             pthread_mutex_unlock(&javaScriptThreadsMutex);
182             return 0;
183         }
184
185         pthread_mutex_unlock(&javaScriptThreadsMutex);
186     }
187 }
188
189 static void startJavaScriptThreads()
190 {
191     pthread_mutex_lock(&javaScriptThreadsMutex);
192
193     for (int i = 0; i < javaScriptThreadsCount; i++) {
194         pthread_t pthread;
195         pthread_create(&pthread, NULL, &runJavaScriptThread, NULL);
196         pthread_detach(pthread);
197         CFDictionaryAddValue(javaScriptThreads(), pthread, NULL);
198     }
199
200     pthread_mutex_unlock(&javaScriptThreadsMutex);
201 }
202
203 static void stopJavaScriptThreads()
204 {
205     pthread_mutex_lock(&javaScriptThreadsMutex);
206
207     javaScriptThreadsShouldTerminate = true;
208
209     pthread_t* pthreads[javaScriptThreadsCount] = { 0 };
210     ASSERT(CFDictionaryGetCount(javaScriptThreads()) == javaScriptThreadsCount);
211     CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0);
212
213     pthread_mutex_unlock(&javaScriptThreadsMutex);
214
215     for (int i = 0; i < javaScriptThreadsCount; i++) {
216         pthread_t* pthread = pthreads[i];
217         pthread_join(*pthread, 0);
218         free(pthread);
219     }
220 }
221
222 static BOOL shouldIgnoreWebCoreNodeLeaks(CFStringRef URLString)
223 {
224     static CFStringRef const ignoreSet[] = {
225         // Keeping this infrastructure around in case we ever need it again.
226     };
227     static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(CFStringRef);
228     
229     for (int i = 0; i < ignoreSetCount; i++) {
230         CFStringRef ignoreString = ignoreSet[i];
231         CFRange range = CFRangeMake(0, CFStringGetLength(URLString));
232         CFOptionFlags flags = kCFCompareAnchored | kCFCompareBackwards | kCFCompareCaseInsensitive;
233         if (CFStringFindWithOptions(URLString, ignoreString, range, flags, NULL))
234             return YES;
235     }
236     return NO;
237 }
238
239 static CMProfileRef currentColorProfile = 0;
240 static void restoreColorSpace(int ignored)
241 {
242     if (currentColorProfile) {
243         int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
244         if (error)
245             fprintf(stderr, "Failed to retore previous color profile!  You may need to open System Preferences : Displays : Color and manually restore your color settings.  (Error: %i)", error);
246         currentColorProfile = 0;
247     }
248 }
249
250 static void crashHandler(int sig)
251 {
252     fprintf(stderr, "%s\n", strsignal(sig));
253     restoreColorSpace(0);
254     exit(128 + sig);
255 }
256
257 static void activateAhemFont()
258 {    
259     unsigned long fontDataLength;
260     char* fontData = getsectdata("__DATA", "Ahem", &fontDataLength);
261     if (!fontData) {
262         fprintf(stderr, "Failed to locate the Ahem font.\n");
263         exit(1);
264     }
265
266     ATSFontContainerRef fontContainer;
267     OSStatus status = ATSFontActivateFromMemory(fontData, fontDataLength, kATSFontContextLocal, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &fontContainer);
268
269     if (status != noErr) {
270         fprintf(stderr, "Failed to activate the Ahem font.\n");
271         exit(1);
272     }
273 }
274
275 static void setDefaultColorProfileToRGB()
276 {
277     CMProfileRef genericProfile = (CMProfileRef)[[NSColorSpace genericRGBColorSpace] colorSyncProfile];
278     CMProfileRef previousProfile;
279     int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
280     if (error) {
281         fprintf(stderr, "Failed to get current color profile.  I will not be able to restore your current profile, thus I'm not changing it.  Many pixel tests may fail as a result.  (Error: %i)\n", error);
282         return;
283     }
284     if (previousProfile == genericProfile)
285         return;
286     CFStringRef previousProfileName;
287     CFStringRef genericProfileName;
288     char previousProfileNameString[1024];
289     char genericProfileNameString[1024];
290     CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
291     CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
292     CFStringGetCString(previousProfileName, previousProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
293     CFStringGetCString(genericProfileName, genericProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
294     CFRelease(genericProfileName);
295     CFRelease(previousProfileName);
296     
297     fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n", previousProfileNameString, genericProfileNameString);
298     fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n");
299     fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n");
300     
301     if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile)))
302         fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result.  (Error: %i)",
303             genericProfileNameString, error);
304     else {
305         currentColorProfile = previousProfile;
306         signal(SIGINT, restoreColorSpace);
307         signal(SIGHUP, restoreColorSpace);
308         signal(SIGTERM, restoreColorSpace);
309     }
310 }
311
312 static void* (*savedMalloc)(malloc_zone_t*, size_t);
313 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
314
315 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
316 {
317     if (size >= 0x10000000)
318         return 0;
319     return savedMalloc(zone, size);
320 }
321
322 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
323 {
324     if (size >= 0x10000000)
325         return 0;
326     return savedRealloc(zone, ptr, size);
327 }
328
329 static void makeLargeMallocFailSilently()
330 {
331     malloc_zone_t* zone = malloc_default_zone();
332     savedMalloc = zone->malloc;
333     savedRealloc = zone->realloc;
334     zone->malloc = checkedMalloc;
335     zone->realloc = checkedRealloc;
336 }
337
338 WebView *createWebView()
339 {
340     NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
341     WebView *webView = [[WebView alloc] initWithFrame:rect frameName:nil groupName:@"org.webkit.DumpRenderTree"];
342         
343     [webView setUIDelegate:uiDelegate];
344     [webView setFrameLoadDelegate:frameLoadDelegate];
345     [webView setEditingDelegate:editingDelegate];
346     [webView setResourceLoadDelegate:resourceLoadDelegate];
347
348     // Register the same schemes that Safari does
349     [WebView registerURLSchemeAsLocal:@"feed"];
350     [WebView registerURLSchemeAsLocal:@"feeds"];
351     [WebView registerURLSchemeAsLocal:@"feedsearch"];
352
353     // The back/forward cache is causing problems due to layouts during transition from one page to another.
354     // So, turn it off for now, but we might want to turn it back on some day.
355     [[webView backForwardList] setPageCacheSize:0];
356     
357     [webView setContinuousSpellCheckingEnabled:YES];
358     
359     // To make things like certain NSViews, dragging, and plug-ins work, put the WebView a window, but put it off-screen so you don't see it.
360     // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
361     NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
362     DumpRenderTreeWindow *window = [[DumpRenderTreeWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
363     [[window contentView] addSubview:webView];
364     [window orderBack:nil];
365     [window setAutodisplay:NO];
366     
367     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
368     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
369     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
370     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
371         
372     return webView;
373 }
374
375 void testStringByEvaluatingJavaScriptFromString()
376 {
377     // maps expected result <= JavaScript expression
378     NSDictionary *expressions = [NSDictionary dictionaryWithObjectsAndKeys:
379         @"0", @"0", 
380         @"0", @"'0'", 
381         @"", @"",
382         @"", @"''", 
383         @"", @"new String()", 
384         @"", @"new String('0')", 
385         @"", @"throw 1", 
386         @"", @"{ }", 
387         @"", @"[ ]", 
388         @"", @"//", 
389         @"", @"a.b.c", 
390         @"", @"(function() { throw 'error'; })()", 
391         @"", @"null",
392         @"", @"undefined",
393         @"true", @"true",
394         @"false", @"false",
395         nil
396     ];
397
398     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
399     WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];
400
401     NSEnumerator *enumerator = [expressions keyEnumerator];
402     id expression;
403     while ((expression = [enumerator nextObject])) {
404         NSString *expectedResult = [expressions objectForKey:expression];
405         NSString *result = [webView stringByEvaluatingJavaScriptFromString:expression];
406         assert([result isEqualToString:expectedResult]);
407     }
408
409     [webView close];
410     [webView release];
411     [pool release];
412 }
413
414 void dumpRenderTree(int argc, const char *argv[])
415 {    
416     [NSApplication sharedApplication];
417
418     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
419     class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent"));
420
421     struct option options[] = {
422         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
423         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
424         {"notree", no_argument, &dumpTree, NO},
425         {"pixel-tests", no_argument, &dumpPixels, YES},
426         {"repaint", no_argument, &testRepaintDefault, YES},
427         {"tree", no_argument, &dumpTree, YES},
428         {"threaded", no_argument, &threaded, YES},
429         {NULL, 0, NULL, 0}
430     };
431
432     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
433     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
434     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
435     // 2 is the "Medium" font smoothing mode
436     [defaults setInteger:2 forKey:@"AppleFontSmoothing"];
437
438     [defaults setInteger:1 forKey:@"AppleAquaColorVariant"];
439     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
440     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
441     
442     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
443     
444     WebPreferences *preferences = [WebPreferences standardPreferences];
445     
446     [preferences setStandardFontFamily:@"Times"];
447     [preferences setFixedFontFamily:@"Courier"];
448     [preferences setSerifFontFamily:@"Times"];
449     [preferences setSansSerifFontFamily:@"Helvetica"];
450     [preferences setCursiveFontFamily:@"Apple Chancery"];
451     [preferences setFantasyFontFamily:@"Papyrus"];
452     [preferences setDefaultFontSize:16];
453     [preferences setDefaultFixedFontSize:13];
454     [preferences setMinimumFontSize:1];
455     [preferences setJavaEnabled:NO];
456     [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
457     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
458     [preferences setTabsToLinks:NO];
459     [preferences setDOMPasteAllowed:YES];
460     
461     int option;
462     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
463         switch (option) {
464             case '?':   // unknown or ambiguous option
465             case ':':   // missing argument
466                 exit(1);
467                 break;
468         }
469
470     activateAhemFont();
471
472     if (dumpPixels) {
473         setDefaultColorProfileToRGB();
474         screenCaptureBuffer = (unsigned char *)malloc(maxViewHeight * maxViewWidth * 4);
475         sharedColorSpace = CGColorSpaceCreateDeviceRGB();
476     }
477     
478     navigationController = [[NavigationController alloc] init];
479     frameLoadDelegate = [[FrameLoadDelegate alloc] init];
480     uiDelegate = [[UIDelegate alloc] init];
481     editingDelegate = [[EditingDelegate alloc] init];    
482     resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
483     policyDelegate = [[PolicyDelegate alloc] init];
484     
485     NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
486     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
487     [[WebPluginDatabase sharedDatabase] refresh];
488     
489     WebView *webView = createWebView();    
490     mainFrame = [webView mainFrame];
491     NSWindow *window = [webView window];
492
493     makeLargeMallocFailSilently();
494
495     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
496     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
497     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
498     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
499     signal(SIGBUS, crashHandler);    /* 10:  bus error */
500     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
501     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
502     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
503     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
504     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
505     
506     [[NSURLCache sharedURLCache] removeAllCachedResponses];
507     
508     // <rdar://problem/5222911>
509     testStringByEvaluatingJavaScriptFromString();
510
511     if (threaded)
512         startJavaScriptThreads();
513     
514     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
515         char filenameBuffer[2048];
516         printSeparators = YES;
517         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
518             char *newLineCharacter = strchr(filenameBuffer, '\n');
519             if (newLineCharacter)
520                 *newLineCharacter = '\0';
521             
522             if (strlen(filenameBuffer) == 0)
523                 continue;
524                 
525             runTest(filenameBuffer);
526         }
527     } else {
528         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
529         for (int i = optind; i != argc; ++i)
530             runTest(argv[i]);
531     }
532
533     if (threaded)
534         stopJavaScriptThreads();
535
536     [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts    
537     [webView close];
538     mainFrame = nil;
539
540     // Work around problem where registering drag types leaves an outstanding
541     // "perform selector" on the window, which retains the window. It's a bit
542     // inelegant and perhaps dangerous to just blow them all away, but in practice
543     // it probably won't cause any trouble (and this is just a test tool, after all).
544     [NSObject cancelPreviousPerformRequestsWithTarget:window];
545     
546     [window close]; // releases when closed
547     [webView release];
548     [frameLoadDelegate release];
549     [editingDelegate release];
550     [resourceLoadDelegate release];
551     [uiDelegate release];
552     [policyDelegate release];
553     
554     [DumpRenderTreePasteboard releaseLocalPasteboards];
555     
556     [navigationController release];
557     navigationController = nil;
558
559     if (disallowedURLs) {
560         CFRelease(disallowedURLs);
561         disallowedURLs = 0;
562     }
563
564     if (dumpPixels)
565         restoreColorSpace(0);
566 }
567
568 int main(int argc, const char *argv[])
569 {
570     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
571     dumpRenderTree(argc, argv);
572     [WebCoreStatistics garbageCollectJavaScriptObjects];
573     [pool release];
574     return 0;
575 }
576
577 static int compareHistoryItems(id item1, id item2, void *context)
578 {
579     return [[item1 target] caseInsensitiveCompare:[item2 target]];
580 }
581
582 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
583 {
584     int start = 0;
585     if (current) {
586         printf("curr->");
587         start = 6;
588     }
589     for (int i = start; i < indent; i++)
590         putchar(' ');
591     printf("%s", [[item URLString] UTF8String]);
592     NSString *target = [item target];
593     if (target && [target length] > 0)
594         printf(" (in frame \"%s\")", [target UTF8String]);
595     if ([item isTargetItem])
596         printf("  **nav target**");
597     putchar('\n');
598     NSArray *kids = [item children];
599     if (kids) {
600         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
601         kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
602         for (unsigned i = 0; i < [kids count]; i++)
603             dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
604     }
605 }
606
607 static void dumpFrameScrollPosition(WebFrame *f)
608 {
609     NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin;
610     if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
611         if ([f parentFrame] != nil)
612             printf("frame '%s' ", [[f name] UTF8String]);
613         printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
614     }
615
616     if (layoutTestController->dumpChildFrameScrollPositions()) {
617         NSArray *kids = [f childFrames];
618         if (kids)
619             for (unsigned i = 0; i < [kids count]; i++)
620                 dumpFrameScrollPosition([kids objectAtIndex:i]);
621     }
622 }
623
624 static NSString *dumpFramesAsText(WebFrame *frame)
625 {
626     if (!frame)
627         return @"";
628
629     DOMDocument *document = [frame DOMDocument];
630     if (!document)
631         return @"";
632
633     DOMElement *documentElement = [document documentElement];
634     if (!documentElement)
635         return @"";
636
637     NSMutableString *result = [[[NSMutableString alloc] init] autorelease];
638
639     // Add header for all but the main frame.
640     if ([frame parentFrame])
641         result = [NSMutableString stringWithFormat:@"\n--------\nFrame: '%@'\n--------\n", [frame name]];
642
643     [result appendFormat:@"%@\n", [documentElement innerText]];
644
645     if (layoutTestController->dumpChildFramesAsText()) {
646         NSArray *kids = [frame childFrames];
647         if (kids) {
648             for (unsigned i = 0; i < [kids count]; i++)
649                 [result appendString:dumpFramesAsText([kids objectAtIndex:i])];
650         }
651     }
652
653     return result;
654 }
655
656 static void convertMIMEType(NSMutableString *mimeType)
657 {
658     if ([mimeType isEqualToString:@"application/x-javascript"])
659         [mimeType setString:@"text/javascript"];
660 }
661
662 static void convertWebResourceDataToString(NSMutableDictionary *resource)
663 {
664     NSMutableString *mimeType = [resource objectForKey:@"WebResourceMIMEType"];
665     convertMIMEType(mimeType);
666     
667     if ([mimeType hasPrefix:@"text/"]) {
668         NSData *data = [resource objectForKey:@"WebResourceData"];
669         NSString *dataAsString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
670         [resource setObject:dataAsString forKey:@"WebResourceData"];
671     }
672 }
673
674 static void normalizeWebResourceURL(NSMutableString *webResourceURL, NSString *oldURLBase)
675 {
676     [webResourceURL replaceOccurrencesOfString:oldURLBase
677                                     withString:@"file://"
678                                        options:NSLiteralSearch
679                                          range:NSMakeRange(0, [webResourceURL length])];
680 }
681
682 static void convertWebResourceResponseToDictionary(NSMutableDictionary *propertyList, NSString *oldURLBase)
683 {
684     NSURLResponse *response = nil;
685     NSData *responseData = [propertyList objectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
686     if ([responseData isKindOfClass:[NSData class]]) {
687         // Decode NSURLResponse
688         NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:responseData];
689         response = [unarchiver decodeObjectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
690         [unarchiver finishDecoding];
691         [unarchiver release];
692     }        
693     
694     NSMutableDictionary *responseDictionary = [[NSMutableDictionary alloc] init];
695     
696     NSMutableString *urlString = [[[response URL] description] mutableCopy];
697     normalizeWebResourceURL(urlString, oldURLBase);
698     [responseDictionary setObject:urlString forKey:@"URL"];
699     [urlString release];
700     
701     NSMutableString *mimeTypeString = [[response MIMEType] mutableCopy];
702     convertMIMEType(mimeTypeString);
703     [responseDictionary setObject:mimeTypeString forKey:@"MIMEType"];
704     [mimeTypeString release];
705
706     NSString *textEncodingName = [response textEncodingName];
707     if (textEncodingName)
708         [responseDictionary setObject:textEncodingName forKey:@"textEncodingName"];
709     [responseDictionary setObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:@"expectedContentLength"];
710     
711     if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
712         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
713         
714         [responseDictionary setObject:[httpResponse allHeaderFields] forKey:@"allHeaderFields"];
715         [responseDictionary setObject:[NSNumber numberWithInt:[httpResponse statusCode]] forKey:@"statusCode"];
716     }
717     
718     [propertyList setObject:responseDictionary forKey:@"WebResourceResponse"];
719     [responseDictionary release];
720 }
721
722 static NSString *serializeWebArchiveToXML(WebArchive *webArchive)
723 {
724     NSString *errorString;
725     NSMutableDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data]
726                                                                          mutabilityOption:NSPropertyListMutableContainersAndLeaves
727                                                                                    format:NULL
728                                                                          errorDescription:&errorString];
729     if (!propertyList)
730         return errorString;
731
732     // Normalize WebResourceResponse and WebResourceURL values in plist for testing
733     NSString *cwdURL = [@"file://" stringByAppendingString:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
734     
735     NSMutableArray *resources = [NSMutableArray arrayWithCapacity:1];
736     [resources addObject:propertyList];
737
738     while ([resources count]) {
739         NSMutableDictionary *resourcePropertyList = [resources objectAtIndex:0];
740         [resources removeObjectAtIndex:0];
741
742         NSMutableDictionary *mainResource = [resourcePropertyList objectForKey:@"WebMainResource"];
743         normalizeWebResourceURL([mainResource objectForKey:@"WebResourceURL"], cwdURL);
744         convertWebResourceDataToString(mainResource);
745
746         // Add subframeArchives to list for processing
747         NSMutableArray *subframeArchives = [resourcePropertyList objectForKey:@"WebSubframeArchives"]; // WebSubframeArchivesKey in WebArchive.m
748         if (subframeArchives)
749             [resources addObjectsFromArray:subframeArchives];
750
751         NSMutableArray *subresources = [resourcePropertyList objectForKey:@"WebSubresources"]; // WebSubresourcesKey in WebArchive.m
752         NSEnumerator *enumerator = [subresources objectEnumerator];
753         NSMutableDictionary *subresourcePropertyList;
754         while ((subresourcePropertyList = [enumerator nextObject])) {
755             normalizeWebResourceURL([subresourcePropertyList objectForKey:@"WebResourceURL"], cwdURL);
756             convertWebResourceResponseToDictionary(subresourcePropertyList, cwdURL);
757             convertWebResourceDataToString(subresourcePropertyList);
758         }
759     }
760
761     NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList
762                                                                  format:NSPropertyListXMLFormat_v1_0
763                                                        errorDescription:&errorString];
764     if (!xmlData)
765         return errorString;
766
767     NSMutableString *string = [[[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] autorelease];
768
769     // Replace "Apple Computer" with "Apple" in the DTD declaration.
770     NSRange range = [string rangeOfString:@"-//Apple Computer//"];
771     if (range.location != NSNotFound)
772         [string replaceCharactersInRange:range withString:@"-//Apple//"];
773     
774     return string;
775 }
776
777 static void dumpBackForwardListForWebView(WebView *view)
778 {
779     printf("\n============== Back Forward List ==============\n");
780     WebBackForwardList *bfList = [view backForwardList];
781
782     // Print out all items in the list after prevTestBFItem, which was from the previous test
783     // Gather items from the end of the list, the print them out from oldest to newest
784     NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
785     for (int i = [bfList forwardListCount]; i > 0; i--) {
786         WebHistoryItem *item = [bfList itemAtIndex:i];
787         // something is wrong if the item from the last test is in the forward part of the b/f list
788         assert(item != prevTestBFItem);
789         [itemsToPrint addObject:item];
790     }
791             
792     assert([bfList currentItem] != prevTestBFItem);
793     [itemsToPrint addObject:[bfList currentItem]];
794     int currentItemIndex = [itemsToPrint count] - 1;
795
796     for (int i = -1; i >= -[bfList backListCount]; i--) {
797         WebHistoryItem *item = [bfList itemAtIndex:i];
798         if (item == prevTestBFItem)
799             break;
800         [itemsToPrint addObject:item];
801     }
802
803     for (int i = [itemsToPrint count]-1; i >= 0; i--) {
804         dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
805     }
806     [itemsToPrint release];
807     printf("===============================================\n");
808 }
809
810 void dump()
811 {
812     if (waitToDumpWatchdog) {
813         CFRunLoopTimerInvalidate(waitToDumpWatchdog);
814         CFRelease(waitToDumpWatchdog);
815         waitToDumpWatchdog = 0;
816     }
817
818     if (dumpTree) {
819         NSString *result = nil;
820         
821         bool dumpAsText = layoutTestController->dumpAsText();
822         dumpAsText |= [[[mainFrame dataSource] _responseMIMEType] isEqualToString:@"text/plain"];
823         layoutTestController->setDumpAsText(dumpAsText);
824         if (layoutTestController->dumpAsText()) {
825             result = dumpFramesAsText(mainFrame);
826         } else if (layoutTestController->dumpDOMAsWebArchive()) {
827             WebArchive *webArchive = [[mainFrame DOMDocument] webArchive];
828             result = serializeWebArchiveToXML(webArchive);
829         } else if (layoutTestController->dumpSourceAsWebArchive()) {
830             WebArchive *webArchive = [[mainFrame dataSource] webArchive];
831             result = serializeWebArchiveToXML(webArchive);
832         } else {
833             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
834             if (isSVGW3CTest)
835                 [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)];
836             else 
837                 [[mainFrame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
838             result = [mainFrame renderTreeAsExternalRepresentation];
839         }
840
841         if (!result) {
842             const char *errorMessage;
843             if (layoutTestController->dumpAsText())
844                 errorMessage = "[documentElement innerText]";
845             else if (layoutTestController->dumpDOMAsWebArchive())
846                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
847             else if (layoutTestController->dumpSourceAsWebArchive())
848                 errorMessage = "[[mainFrame dataSource] webArchive]";
849             else
850                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
851             printf("ERROR: nil result from %s", errorMessage);
852         } else {
853             NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding];
854             fwrite([data bytes], 1, [data length], stdout);
855             if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive())
856                 dumpFrameScrollPosition(mainFrame);
857         }
858
859         if (layoutTestController->dumpBackForwardList()) {
860             CFArrayRef allWindows = (CFArrayRef)[DumpRenderTreeWindow allWindows];
861             unsigned count = CFArrayGetCount(allWindows);
862             for (unsigned i = 0; i < count; i++) {
863                 NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(allWindows, i);
864                 WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
865                 dumpBackForwardListForWebView(webView);
866             }
867         }
868
869         if (printSeparators)
870             puts("#EOF");
871     }
872     
873     if (dumpPixels) {
874         if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive()) {
875             // grab a bitmap from the view
876             WebView* view = [mainFrame webView];
877             NSSize webViewSize = [view frame].size;
878
879             CGContextRef cgContext = CGBitmapContextCreate(screenCaptureBuffer, static_cast<size_t>(webViewSize.width), static_cast<size_t>(webViewSize.height), 8, static_cast<size_t>(webViewSize.width) * 4, sharedColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedLast);
880
881             NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
882             NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
883             [NSGraphicsContext setCurrentContext:nsContext];
884
885             if (!layoutTestController->testRepaint()) {
886                 NSBitmapImageRep *imageRep;
887                 [view displayIfNeeded];
888                 [view lockFocus];
889                 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
890                 [view unlockFocus];
891                 [imageRep draw];
892                 [imageRep release];
893             } else if (!layoutTestController->testRepaintSweepHorizontally()) {
894                 NSRect line = NSMakeRect(0, 0, webViewSize.width, 1);
895                 while (line.origin.y < webViewSize.height) {
896                     [view displayRectIgnoringOpacity:line inContext:nsContext];
897                     line.origin.y++;
898                 }
899             } else {
900                 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
901                 while (column.origin.x < webViewSize.width) {
902                     [view displayRectIgnoringOpacity:column inContext:nsContext];
903                     column.origin.x++;
904                 }
905             }
906             if (layoutTestController->dumpSelectionRect()) {
907                 NSView *documentView = [[mainFrame frameView] documentView];
908                 if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) {
909                     [[NSColor redColor] set];
910                     [NSBezierPath strokeRect:[documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]];
911                 }
912             }
913
914             [NSGraphicsContext setCurrentContext:savedContext];
915             
916             CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
917             CGContextRelease(cgContext);
918
919             // compute the actual hash to compare to the expected image's hash
920             NSString *actualHash = md5HashStringForBitmap(bitmapImage);
921             printf("\nActualHash: %s\n", [actualHash UTF8String]);
922
923             BOOL dumpImage;
924             if (dumpAllPixels)
925                 dumpImage = YES;
926             else {
927                 // FIXME: It's unfortunate that we hardcode the file naming scheme here.
928                 // At one time, the perl script had all the knowledge about file layout.
929                 // Some day we should restore that setup by passing in more parameters to this tool.
930                 NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
931                 NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
932                 NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
933                 NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
934
935                 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
936
937                 /// send the image to stdout if the hash mismatches or if there's no file in the file system
938                 dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0;
939             }
940             
941             if (dumpImage) {
942                 CFMutableDataRef imageData = CFDataCreateMutable(0, 0);
943                 CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0);
944                 CGImageDestinationAddImage(imageDest, bitmapImage, 0);
945                 CGImageDestinationFinalize(imageDest);
946                 CFRelease(imageDest);
947                 printf("Content-length: %lu\n", CFDataGetLength(imageData));
948                 fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout);
949                 CFRelease(imageData);
950             }
951
952             CGImageRelease(bitmapImage);
953         }
954
955         printf("#EOF\n");
956     }
957
958     fflush(stdout);
959
960     done = YES;
961 }
962
963 static bool shouldLogFrameLoadDelegates(const char *pathOrURL)
964 {
965     return strstr(pathOrURL, "loading/");
966 }    
967
968 static void runTest(const char *pathOrURL)
969 {
970     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
971     if (!pathOrURLString) {
972         fprintf(stderr, "can't parse filename as UTF-8\n");
973         return;
974     }
975     
976     CFURLRef URL;
977     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")) || CFStringHasPrefix(pathOrURLString, CFSTR("https://")))
978         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
979     else
980         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
981     
982     if (!URL) {
983         CFRelease(pathOrURLString);
984         fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
985         return;
986     }
987
988     layoutTestController = new LayoutTestController(testRepaintDefault, repaintSweepHorizontallyDefault);
989
990     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:YES];
991     [[mainFrame webView] makeTextStandardSize:nil];
992     [[mainFrame webView] setTabKeyCyclesThroughElements: YES];
993     [[mainFrame webView] setPolicyDelegate:nil];
994     [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO];
995     [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:NO];
996     [WebView _setUsesTestModeFocusRingColor:YES];
997
998     topLoadingFrame = nil;
999
1000     done = NO;
1001
1002     if (disallowedURLs)
1003         CFSetRemoveAllValues(disallowedURLs);
1004     if (shouldLogFrameLoadDelegates(pathOrURL))
1005         layoutTestController->setDumpFrameLoadCallbacks(true);
1006
1007     if ([WebHistory optionalSharedHistory])
1008         [WebHistory setOptionalSharedHistory:nil];
1009     lastMousePosition = NSMakePoint(0, 0);
1010
1011     if (currentTest != nil)
1012         CFRelease(currentTest);
1013     currentTest = (NSString *)pathOrURLString;
1014     [prevTestBFItem release];
1015     prevTestBFItem = [[[[mainFrame webView] backForwardList] currentItem] retain];
1016
1017     WorkQueue::shared()->clear();
1018     WorkQueue::shared()->setFrozen(false);
1019
1020     BOOL _shouldIgnoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(CFURLGetString(URL));
1021     if (_shouldIgnoreWebCoreNodeLeaks)
1022         [WebCoreStatistics startIgnoringWebCoreNodeLeaks];
1023
1024     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1025     [mainFrame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
1026     CFRelease(URL);
1027     [pool release];
1028     while (!done) {
1029         pool = [[NSAutoreleasePool alloc] init];
1030         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
1031         [pool release];
1032     }
1033     pool = [[NSAutoreleasePool alloc] init];
1034     [EventSendingController clearSavedEvents];
1035     [[mainFrame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1036
1037     WorkQueue::shared()->clear();
1038
1039     if (layoutTestController->closeRemainingWindowsWhenComplete()) {
1040         NSArray* array = [DumpRenderTreeWindow allWindows];
1041         
1042         unsigned count = [array count];
1043         for (unsigned i = 0; i < count; i++) {
1044             NSWindow *window = [array objectAtIndex:i];
1045
1046             // Don't try to close the main window
1047             if (window == [[mainFrame webView] window])
1048                 continue;
1049             
1050             WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
1051
1052             [webView close];
1053             [window close];
1054         }
1055     }
1056     
1057     [pool release];
1058
1059     // We should only have our main window left when we're done
1060     ASSERT(CFArrayGetCount(allWindowsRef) == 1);
1061     ASSERT(CFArrayGetValueAtIndex(allWindowsRef, 0) == [[mainFrame webView] window]);
1062
1063     delete layoutTestController;
1064     layoutTestController = 0;
1065
1066     if (_shouldIgnoreWebCoreNodeLeaks)
1067         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
1068 }
1069
1070 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
1071 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
1072 {
1073     MD5_CTX md5Context;
1074     unsigned char hash[16];
1075     
1076     unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap);
1077     assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
1078     unsigned bytesPerPixel = bitsPerPixel / 8;
1079     unsigned pixelsHigh = CGImageGetHeight(bitmap);
1080     unsigned pixelsWide = CGImageGetWidth(bitmap);
1081     unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap);
1082     assert(bytesPerRow >= (pixelsWide * bytesPerPixel));
1083     
1084     MD5_Init(&md5Context);
1085     unsigned char *bitmapData = screenCaptureBuffer;
1086     for (unsigned row = 0; row < pixelsHigh; row++) {
1087         MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel);
1088         bitmapData += bytesPerRow;
1089     }
1090     MD5_Final(hash, &md5Context);
1091     
1092     char hex[33] = "";
1093     for (int i = 0; i < 16; i++) {
1094        snprintf(hex, 33, "%s%02x", hex, hash[i]);
1095     }
1096
1097     return [NSString stringWithUTF8String:hex];
1098 }
1099
1100 void displayWebView()
1101 {
1102     NSView *webView = [mainFrame webView];
1103     [webView display];
1104     [webView lockFocus];
1105     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
1106     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
1107     [webView unlockFocus];
1108 }
1109
1110 @implementation DumpRenderTreeEvent
1111
1112 + (NSPoint)mouseLocation
1113 {
1114     return [[[mainFrame webView] window] convertBaseToScreen:lastMousePosition];
1115 }
1116
1117 @end