e6ab3ddbc6775624d28966fcbfa05d9f833f7256
[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 static void setDefaultsToConsistentValuesForTesting()
415 {
416     // Give some clear to undocumented defaults values
417     static const int MediumFontSmoothing = 2;
418     static const int BlueTintedAppearance = 1;
419
420     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
421     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
422     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; // smallest font size to CG should perform antialiasing on
423     [defaults setInteger:MediumFontSmoothing forKey:@"AppleFontSmoothing"];
424     [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"];
425     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
426     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
427     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
428
429     WebPreferences *preferences = [WebPreferences standardPreferences];
430
431     [preferences setStandardFontFamily:@"Times"];
432     [preferences setFixedFontFamily:@"Courier"];
433     [preferences setSerifFontFamily:@"Times"];
434     [preferences setSansSerifFontFamily:@"Helvetica"];
435     [preferences setCursiveFontFamily:@"Apple Chancery"];
436     [preferences setFantasyFontFamily:@"Papyrus"];
437     [preferences setDefaultFontSize:16];
438     [preferences setDefaultFixedFontSize:13];
439     [preferences setMinimumFontSize:1];
440     [preferences setJavaEnabled:NO];
441     [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
442     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
443     [preferences setTabsToLinks:NO];
444     [preferences setDOMPasteAllowed:YES];
445 }
446
447 static void setupSignalHandlers()
448 {
449     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
450     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
451     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
452     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
453     signal(SIGBUS, crashHandler);    /* 10:  bus error */
454     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
455     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
456     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
457     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
458     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
459 }
460
461 static void allocateGlobalControllers()
462 {
463     // FIXME: We should remove these and move to the ObjC standard [Foo sharedInstance] model
464     navigationController = [[NavigationController alloc] init];
465     frameLoadDelegate = [[FrameLoadDelegate alloc] init];
466     uiDelegate = [[UIDelegate alloc] init];
467     editingDelegate = [[EditingDelegate alloc] init];
468     resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
469     policyDelegate = [[PolicyDelegate alloc] init];
470 }
471
472 // ObjC++ doens't seem to let me pass NSObject*& sadly.
473 static inline void releaseAndZero(NSObject** object)
474 {
475     [*object release];
476     *object = nil;
477 }
478
479 static void releaseGlobalControllers()
480 {
481     releaseAndZero(&navigationController);
482     releaseAndZero(&frameLoadDelegate);
483     releaseAndZero(&editingDelegate);
484     releaseAndZero(&resourceLoadDelegate);
485     releaseAndZero(&uiDelegate);
486     releaseAndZero(&policyDelegate);
487 }
488
489 void dumpRenderTree(int argc, const char *argv[])
490 {    
491     [NSApplication sharedApplication];
492
493     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
494     class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent"));
495
496     struct option options[] = {
497         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
498         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
499         {"notree", no_argument, &dumpTree, NO},
500         {"pixel-tests", no_argument, &dumpPixels, YES},
501         {"repaint", no_argument, &testRepaintDefault, YES},
502         {"tree", no_argument, &dumpTree, YES},
503         {"threaded", no_argument, &threaded, YES},
504         {NULL, 0, NULL, 0}
505     };
506
507     setDefaultsToConsistentValuesForTesting();
508     
509     int option;
510     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
511         switch (option) {
512             case '?':   // unknown or ambiguous option
513             case ':':   // missing argument
514                 exit(1);
515                 break;
516         }
517
518     activateAhemFont();
519
520     if (dumpPixels) {
521         setDefaultColorProfileToRGB();
522         screenCaptureBuffer = (unsigned char *)malloc(maxViewHeight * maxViewWidth * 4);
523         sharedColorSpace = CGColorSpaceCreateDeviceRGB();
524     }
525     
526     allocateGlobalControllers();
527     
528     NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
529     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
530     [[WebPluginDatabase sharedDatabase] refresh];
531     
532     WebView *webView = createWebView();    
533     mainFrame = [webView mainFrame];
534     NSWindow *window = [webView window];
535
536     makeLargeMallocFailSilently();
537
538     setupSignalHandlers();
539     
540     [[NSURLCache sharedURLCache] removeAllCachedResponses];
541     
542     // <rdar://problem/5222911>
543     testStringByEvaluatingJavaScriptFromString();
544
545     if (threaded)
546         startJavaScriptThreads();
547     
548     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
549         char filenameBuffer[2048];
550         printSeparators = YES;
551         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
552             char *newLineCharacter = strchr(filenameBuffer, '\n');
553             if (newLineCharacter)
554                 *newLineCharacter = '\0';
555             
556             if (strlen(filenameBuffer) == 0)
557                 continue;
558                 
559             runTest(filenameBuffer);
560         }
561     } else {
562         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
563         for (int i = optind; i != argc; ++i)
564             runTest(argv[i]);
565     }
566
567     if (threaded)
568         stopJavaScriptThreads();
569
570     [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts    
571     [webView close];
572     mainFrame = nil;
573
574     // Work around problem where registering drag types leaves an outstanding
575     // "perform selector" on the window, which retains the window. It's a bit
576     // inelegant and perhaps dangerous to just blow them all away, but in practice
577     // it probably won't cause any trouble (and this is just a test tool, after all).
578     [NSObject cancelPreviousPerformRequestsWithTarget:window];
579     
580     [window close]; // releases when closed
581     [webView release];
582     
583     releaseGlobalControllers();
584     
585     [DumpRenderTreePasteboard releaseLocalPasteboards];
586
587     if (disallowedURLs) {
588         CFRelease(disallowedURLs);
589         disallowedURLs = 0;
590     }
591
592     if (dumpPixels)
593         restoreColorSpace(0);
594 }
595
596 int main(int argc, const char *argv[])
597 {
598     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
599     dumpRenderTree(argc, argv);
600     [WebCoreStatistics garbageCollectJavaScriptObjects];
601     [pool release];
602     return 0;
603 }
604
605 static int compareHistoryItems(id item1, id item2, void *context)
606 {
607     return [[item1 target] caseInsensitiveCompare:[item2 target]];
608 }
609
610 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
611 {
612     int start = 0;
613     if (current) {
614         printf("curr->");
615         start = 6;
616     }
617     for (int i = start; i < indent; i++)
618         putchar(' ');
619     printf("%s", [[item URLString] UTF8String]);
620     NSString *target = [item target];
621     if (target && [target length] > 0)
622         printf(" (in frame \"%s\")", [target UTF8String]);
623     if ([item isTargetItem])
624         printf("  **nav target**");
625     putchar('\n');
626     NSArray *kids = [item children];
627     if (kids) {
628         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
629         kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
630         for (unsigned i = 0; i < [kids count]; i++)
631             dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
632     }
633 }
634
635 static void dumpFrameScrollPosition(WebFrame *f)
636 {
637     NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin;
638     if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
639         if ([f parentFrame] != nil)
640             printf("frame '%s' ", [[f name] UTF8String]);
641         printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
642     }
643
644     if (layoutTestController->dumpChildFrameScrollPositions()) {
645         NSArray *kids = [f childFrames];
646         if (kids)
647             for (unsigned i = 0; i < [kids count]; i++)
648                 dumpFrameScrollPosition([kids objectAtIndex:i]);
649     }
650 }
651
652 static NSString *dumpFramesAsText(WebFrame *frame)
653 {
654     if (!frame)
655         return @"";
656
657     DOMDocument *document = [frame DOMDocument];
658     if (!document)
659         return @"";
660
661     DOMElement *documentElement = [document documentElement];
662     if (!documentElement)
663         return @"";
664
665     NSMutableString *result = [[[NSMutableString alloc] init] autorelease];
666
667     // Add header for all but the main frame.
668     if ([frame parentFrame])
669         result = [NSMutableString stringWithFormat:@"\n--------\nFrame: '%@'\n--------\n", [frame name]];
670
671     [result appendFormat:@"%@\n", [documentElement innerText]];
672
673     if (layoutTestController->dumpChildFramesAsText()) {
674         NSArray *kids = [frame childFrames];
675         if (kids) {
676             for (unsigned i = 0; i < [kids count]; i++)
677                 [result appendString:dumpFramesAsText([kids objectAtIndex:i])];
678         }
679     }
680
681     return result;
682 }
683
684 static void convertMIMEType(NSMutableString *mimeType)
685 {
686     if ([mimeType isEqualToString:@"application/x-javascript"])
687         [mimeType setString:@"text/javascript"];
688 }
689
690 static void convertWebResourceDataToString(NSMutableDictionary *resource)
691 {
692     NSMutableString *mimeType = [resource objectForKey:@"WebResourceMIMEType"];
693     convertMIMEType(mimeType);
694     
695     if ([mimeType hasPrefix:@"text/"]) {
696         NSData *data = [resource objectForKey:@"WebResourceData"];
697         NSString *dataAsString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
698         [resource setObject:dataAsString forKey:@"WebResourceData"];
699     }
700 }
701
702 static void normalizeWebResourceURL(NSMutableString *webResourceURL, NSString *oldURLBase)
703 {
704     [webResourceURL replaceOccurrencesOfString:oldURLBase
705                                     withString:@"file://"
706                                        options:NSLiteralSearch
707                                          range:NSMakeRange(0, [webResourceURL length])];
708 }
709
710 static void convertWebResourceResponseToDictionary(NSMutableDictionary *propertyList, NSString *oldURLBase)
711 {
712     NSURLResponse *response = nil;
713     NSData *responseData = [propertyList objectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
714     if ([responseData isKindOfClass:[NSData class]]) {
715         // Decode NSURLResponse
716         NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:responseData];
717         response = [unarchiver decodeObjectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
718         [unarchiver finishDecoding];
719         [unarchiver release];
720     }        
721     
722     NSMutableDictionary *responseDictionary = [[NSMutableDictionary alloc] init];
723     
724     NSMutableString *urlString = [[[response URL] description] mutableCopy];
725     normalizeWebResourceURL(urlString, oldURLBase);
726     [responseDictionary setObject:urlString forKey:@"URL"];
727     [urlString release];
728     
729     NSMutableString *mimeTypeString = [[response MIMEType] mutableCopy];
730     convertMIMEType(mimeTypeString);
731     [responseDictionary setObject:mimeTypeString forKey:@"MIMEType"];
732     [mimeTypeString release];
733
734     NSString *textEncodingName = [response textEncodingName];
735     if (textEncodingName)
736         [responseDictionary setObject:textEncodingName forKey:@"textEncodingName"];
737     [responseDictionary setObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:@"expectedContentLength"];
738     
739     if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
740         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
741         
742         [responseDictionary setObject:[httpResponse allHeaderFields] forKey:@"allHeaderFields"];
743         [responseDictionary setObject:[NSNumber numberWithInt:[httpResponse statusCode]] forKey:@"statusCode"];
744     }
745     
746     [propertyList setObject:responseDictionary forKey:@"WebResourceResponse"];
747     [responseDictionary release];
748 }
749
750 static NSString *serializeWebArchiveToXML(WebArchive *webArchive)
751 {
752     NSString *errorString;
753     NSMutableDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data]
754                                                                          mutabilityOption:NSPropertyListMutableContainersAndLeaves
755                                                                                    format:NULL
756                                                                          errorDescription:&errorString];
757     if (!propertyList)
758         return errorString;
759
760     // Normalize WebResourceResponse and WebResourceURL values in plist for testing
761     NSString *cwdURL = [@"file://" stringByAppendingString:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
762     
763     NSMutableArray *resources = [NSMutableArray arrayWithCapacity:1];
764     [resources addObject:propertyList];
765
766     while ([resources count]) {
767         NSMutableDictionary *resourcePropertyList = [resources objectAtIndex:0];
768         [resources removeObjectAtIndex:0];
769
770         NSMutableDictionary *mainResource = [resourcePropertyList objectForKey:@"WebMainResource"];
771         normalizeWebResourceURL([mainResource objectForKey:@"WebResourceURL"], cwdURL);
772         convertWebResourceDataToString(mainResource);
773
774         // Add subframeArchives to list for processing
775         NSMutableArray *subframeArchives = [resourcePropertyList objectForKey:@"WebSubframeArchives"]; // WebSubframeArchivesKey in WebArchive.m
776         if (subframeArchives)
777             [resources addObjectsFromArray:subframeArchives];
778
779         NSMutableArray *subresources = [resourcePropertyList objectForKey:@"WebSubresources"]; // WebSubresourcesKey in WebArchive.m
780         NSEnumerator *enumerator = [subresources objectEnumerator];
781         NSMutableDictionary *subresourcePropertyList;
782         while ((subresourcePropertyList = [enumerator nextObject])) {
783             normalizeWebResourceURL([subresourcePropertyList objectForKey:@"WebResourceURL"], cwdURL);
784             convertWebResourceResponseToDictionary(subresourcePropertyList, cwdURL);
785             convertWebResourceDataToString(subresourcePropertyList);
786         }
787     }
788
789     NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList
790                                                                  format:NSPropertyListXMLFormat_v1_0
791                                                        errorDescription:&errorString];
792     if (!xmlData)
793         return errorString;
794
795     NSMutableString *string = [[[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] autorelease];
796
797     // Replace "Apple Computer" with "Apple" in the DTD declaration.
798     NSRange range = [string rangeOfString:@"-//Apple Computer//"];
799     if (range.location != NSNotFound)
800         [string replaceCharactersInRange:range withString:@"-//Apple//"];
801     
802     return string;
803 }
804
805 static void dumpBackForwardListForWebView(WebView *view)
806 {
807     printf("\n============== Back Forward List ==============\n");
808     WebBackForwardList *bfList = [view backForwardList];
809
810     // Print out all items in the list after prevTestBFItem, which was from the previous test
811     // Gather items from the end of the list, the print them out from oldest to newest
812     NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
813     for (int i = [bfList forwardListCount]; i > 0; i--) {
814         WebHistoryItem *item = [bfList itemAtIndex:i];
815         // something is wrong if the item from the last test is in the forward part of the b/f list
816         assert(item != prevTestBFItem);
817         [itemsToPrint addObject:item];
818     }
819             
820     assert([bfList currentItem] != prevTestBFItem);
821     [itemsToPrint addObject:[bfList currentItem]];
822     int currentItemIndex = [itemsToPrint count] - 1;
823
824     for (int i = -1; i >= -[bfList backListCount]; i--) {
825         WebHistoryItem *item = [bfList itemAtIndex:i];
826         if (item == prevTestBFItem)
827             break;
828         [itemsToPrint addObject:item];
829     }
830
831     for (int i = [itemsToPrint count]-1; i >= 0; i--) {
832         dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
833     }
834     [itemsToPrint release];
835     printf("===============================================\n");
836 }
837
838 void dump()
839 {
840     if (waitToDumpWatchdog) {
841         CFRunLoopTimerInvalidate(waitToDumpWatchdog);
842         CFRelease(waitToDumpWatchdog);
843         waitToDumpWatchdog = 0;
844     }
845
846     if (dumpTree) {
847         NSString *result = nil;
848         
849         bool dumpAsText = layoutTestController->dumpAsText();
850         dumpAsText |= [[[mainFrame dataSource] _responseMIMEType] isEqualToString:@"text/plain"];
851         layoutTestController->setDumpAsText(dumpAsText);
852         if (layoutTestController->dumpAsText()) {
853             result = dumpFramesAsText(mainFrame);
854         } else if (layoutTestController->dumpDOMAsWebArchive()) {
855             WebArchive *webArchive = [[mainFrame DOMDocument] webArchive];
856             result = serializeWebArchiveToXML(webArchive);
857         } else if (layoutTestController->dumpSourceAsWebArchive()) {
858             WebArchive *webArchive = [[mainFrame dataSource] webArchive];
859             result = serializeWebArchiveToXML(webArchive);
860         } else {
861             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
862             if (isSVGW3CTest)
863                 [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)];
864             else 
865                 [[mainFrame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
866             result = [mainFrame renderTreeAsExternalRepresentation];
867         }
868
869         if (!result) {
870             const char *errorMessage;
871             if (layoutTestController->dumpAsText())
872                 errorMessage = "[documentElement innerText]";
873             else if (layoutTestController->dumpDOMAsWebArchive())
874                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
875             else if (layoutTestController->dumpSourceAsWebArchive())
876                 errorMessage = "[[mainFrame dataSource] webArchive]";
877             else
878                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
879             printf("ERROR: nil result from %s", errorMessage);
880         } else {
881             NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding];
882             fwrite([data bytes], 1, [data length], stdout);
883             if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive())
884                 dumpFrameScrollPosition(mainFrame);
885         }
886
887         if (layoutTestController->dumpBackForwardList()) {
888             CFArrayRef allWindows = (CFArrayRef)[DumpRenderTreeWindow allWindows];
889             unsigned count = CFArrayGetCount(allWindows);
890             for (unsigned i = 0; i < count; i++) {
891                 NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(allWindows, i);
892                 WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
893                 dumpBackForwardListForWebView(webView);
894             }
895         }
896
897         if (printSeparators)
898             puts("#EOF");
899     }
900     
901     if (dumpPixels) {
902         if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive()) {
903             // grab a bitmap from the view
904             WebView* view = [mainFrame webView];
905             NSSize webViewSize = [view frame].size;
906
907             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);
908
909             NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
910             NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
911             [NSGraphicsContext setCurrentContext:nsContext];
912
913             if (!layoutTestController->testRepaint()) {
914                 NSBitmapImageRep *imageRep;
915                 [view displayIfNeeded];
916                 [view lockFocus];
917                 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
918                 [view unlockFocus];
919                 [imageRep draw];
920                 [imageRep release];
921             } else if (!layoutTestController->testRepaintSweepHorizontally()) {
922                 NSRect line = NSMakeRect(0, 0, webViewSize.width, 1);
923                 while (line.origin.y < webViewSize.height) {
924                     [view displayRectIgnoringOpacity:line inContext:nsContext];
925                     line.origin.y++;
926                 }
927             } else {
928                 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
929                 while (column.origin.x < webViewSize.width) {
930                     [view displayRectIgnoringOpacity:column inContext:nsContext];
931                     column.origin.x++;
932                 }
933             }
934             if (layoutTestController->dumpSelectionRect()) {
935                 NSView *documentView = [[mainFrame frameView] documentView];
936                 if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) {
937                     [[NSColor redColor] set];
938                     [NSBezierPath strokeRect:[documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]];
939                 }
940             }
941
942             [NSGraphicsContext setCurrentContext:savedContext];
943             
944             CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
945             CGContextRelease(cgContext);
946
947             // compute the actual hash to compare to the expected image's hash
948             NSString *actualHash = md5HashStringForBitmap(bitmapImage);
949             printf("\nActualHash: %s\n", [actualHash UTF8String]);
950
951             BOOL dumpImage;
952             if (dumpAllPixels)
953                 dumpImage = YES;
954             else {
955                 // FIXME: It's unfortunate that we hardcode the file naming scheme here.
956                 // At one time, the perl script had all the knowledge about file layout.
957                 // Some day we should restore that setup by passing in more parameters to this tool.
958                 NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
959                 NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
960                 NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
961                 NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
962
963                 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
964
965                 /// send the image to stdout if the hash mismatches or if there's no file in the file system
966                 dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0;
967             }
968             
969             if (dumpImage) {
970                 CFMutableDataRef imageData = CFDataCreateMutable(0, 0);
971                 CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0);
972                 CGImageDestinationAddImage(imageDest, bitmapImage, 0);
973                 CGImageDestinationFinalize(imageDest);
974                 CFRelease(imageDest);
975                 printf("Content-length: %lu\n", CFDataGetLength(imageData));
976                 fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout);
977                 CFRelease(imageData);
978             }
979
980             CGImageRelease(bitmapImage);
981         }
982
983         printf("#EOF\n");
984     }
985
986     fflush(stdout);
987
988     done = YES;
989 }
990
991 static bool shouldLogFrameLoadDelegates(const char *pathOrURL)
992 {
993     return strstr(pathOrURL, "loading/");
994 }
995
996 static CFURLRef createCFURLFromPathOrURL(CFStringRef pathOrURLString)
997 {
998     CFURLRef URL;
999     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")) || CFStringHasPrefix(pathOrURLString, CFSTR("https://")))
1000         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
1001     else
1002         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
1003     return URL;
1004 }
1005
1006 static void resetWebViewToConsistentStateBeforeTesting()
1007 {
1008     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:YES];
1009     [[mainFrame webView] makeTextStandardSize:nil];
1010     [[mainFrame webView] setTabKeyCyclesThroughElements: YES];
1011     [[mainFrame webView] setPolicyDelegate:nil];
1012     [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO];
1013     [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:NO];
1014     [WebView _setUsesTestModeFocusRingColor:YES];
1015 }
1016
1017 static void runTest(const char *pathOrURL)
1018 {
1019     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
1020     if (!pathOrURLString) {
1021         fprintf(stderr, "Failed to parse filename as UTF-8: %s\n", pathOrURL);
1022         return;
1023     }
1024
1025     CFURLRef URL = createCFURLFromPathOrURL(pathOrURLString);
1026     if (!URL) {
1027         CFRelease(pathOrURLString);
1028         fprintf(stderr, "Can't turn %s into a CFURL\n", pathOrURL);
1029         return;
1030     }
1031
1032     resetWebViewToConsistentStateBeforeTesting();
1033
1034     layoutTestController = new LayoutTestController(testRepaintDefault, repaintSweepHorizontallyDefault);
1035     topLoadingFrame = nil;
1036     done = NO;
1037
1038     if (disallowedURLs)
1039         CFSetRemoveAllValues(disallowedURLs);
1040     if (shouldLogFrameLoadDelegates(pathOrURL))
1041         layoutTestController->setDumpFrameLoadCallbacks(true);
1042
1043     if ([WebHistory optionalSharedHistory])
1044         [WebHistory setOptionalSharedHistory:nil];
1045     lastMousePosition = NSMakePoint(0, 0);
1046
1047     if (currentTest != nil)
1048         CFRelease(currentTest);
1049     currentTest = (NSString *)pathOrURLString;
1050     [prevTestBFItem release];
1051     prevTestBFItem = [[[[mainFrame webView] backForwardList] currentItem] retain];
1052
1053     WorkQueue::shared()->clear();
1054     WorkQueue::shared()->setFrozen(false);
1055
1056     BOOL _shouldIgnoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(CFURLGetString(URL));
1057     if (_shouldIgnoreWebCoreNodeLeaks)
1058         [WebCoreStatistics startIgnoringWebCoreNodeLeaks];
1059
1060     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1061     [mainFrame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
1062     CFRelease(URL);
1063     [pool release];
1064     while (!done) {
1065         pool = [[NSAutoreleasePool alloc] init];
1066         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
1067         [pool release];
1068     }
1069     pool = [[NSAutoreleasePool alloc] init];
1070     [EventSendingController clearSavedEvents];
1071     [[mainFrame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1072
1073     WorkQueue::shared()->clear();
1074
1075     if (layoutTestController->closeRemainingWindowsWhenComplete()) {
1076         NSArray* array = [DumpRenderTreeWindow allWindows];
1077         
1078         unsigned count = [array count];
1079         for (unsigned i = 0; i < count; i++) {
1080             NSWindow *window = [array objectAtIndex:i];
1081
1082             // Don't try to close the main window
1083             if (window == [[mainFrame webView] window])
1084                 continue;
1085             
1086             WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
1087
1088             [webView close];
1089             [window close];
1090         }
1091     }
1092     
1093     [pool release];
1094
1095     // We should only have our main window left when we're done
1096     ASSERT(CFArrayGetCount(allWindowsRef) == 1);
1097     ASSERT(CFArrayGetValueAtIndex(allWindowsRef, 0) == [[mainFrame webView] window]);
1098
1099     delete layoutTestController;
1100     layoutTestController = 0;
1101
1102     if (_shouldIgnoreWebCoreNodeLeaks)
1103         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
1104 }
1105
1106 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
1107 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
1108 {
1109     MD5_CTX md5Context;
1110     unsigned char hash[16];
1111     
1112     unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap);
1113     assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
1114     unsigned bytesPerPixel = bitsPerPixel / 8;
1115     unsigned pixelsHigh = CGImageGetHeight(bitmap);
1116     unsigned pixelsWide = CGImageGetWidth(bitmap);
1117     unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap);
1118     assert(bytesPerRow >= (pixelsWide * bytesPerPixel));
1119     
1120     MD5_Init(&md5Context);
1121     unsigned char *bitmapData = screenCaptureBuffer;
1122     for (unsigned row = 0; row < pixelsHigh; row++) {
1123         MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel);
1124         bitmapData += bytesPerRow;
1125     }
1126     MD5_Final(hash, &md5Context);
1127     
1128     char hex[33] = "";
1129     for (int i = 0; i < 16; i++) {
1130        snprintf(hex, 33, "%s%02x", hex, hash[i]);
1131     }
1132
1133     return [NSString stringWithUTF8String:hex];
1134 }
1135
1136 void displayWebView()
1137 {
1138     NSView *webView = [mainFrame webView];
1139     [webView display];
1140     [webView lockFocus];
1141     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
1142     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
1143     [webView unlockFocus];
1144 }
1145
1146 @implementation DumpRenderTreeEvent
1147
1148 + (NSPoint)mouseLocation
1149 {
1150     return [[[mainFrame webView] window] convertBaseToScreen:lastMousePosition];
1151 }
1152
1153 @end