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