Rubber stamped by Darin.
[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 static void runTest(const char *pathOrURL);
93 static NSString *md5HashStringForBitmap(CGImageRef bitmap);
94
95 // Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
96 // - There is no load in progress
97 // - There is no work queued up (see workQueue var, below)
98 // - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
99 //       and notifyDone was called subsequently.
100 // Note that the call to notifyDone and the end of the load can happen in either order.
101
102 volatile bool done;
103
104 LayoutTestController* layoutTestController = 0;
105 NavigationController* navigationController = 0;
106
107 WebFrame *mainFrame = 0;
108 // This is the topmost frame that is loading, during a given load, or nil when no load is 
109 // in progress.  Usually this is the same as the main frame, but not always.  In the case
110 // where a frameset is loaded, and then new content is loaded into one of the child frames,
111 // that child frame is the "topmost frame that is loading".
112 WebFrame *topLoadingFrame = nil;     // !nil iff a load is in progress
113
114 bool addFileToPasteboardOnDrag = NO;
115 bool canOpenWindows;
116 bool closeRemainingWindowsWhenComplete = YES;
117 bool closeWebViews;
118 bool dumpAsText;
119 bool dumpBackForwardList;
120 bool dumpChildFramesAsText;
121 bool dumpChildFrameScrollPositions;
122 bool dumpDOMAsWebArchive;
123 bool dumpSelectionRect;
124 bool dumpSourceAsWebArchive;
125 bool dumpTitleChanges = NO;
126 bool repaintSweepHorizontally;
127 bool shouldDumpEditingCallbacks;
128 bool shouldDumpFrameLoadCallbacks;
129 bool shouldDumpResourceLoadCallbacks;
130 bool testRepaint;
131 bool waitToDump;     // TRUE if waitUntilDone() has been called, but notifyDone() has not yet been called
132 bool windowIsKey = YES;
133
134 CFMutableArrayRef allWindowsRef = 0;
135 CFMutableSetRef disallowedURLs = 0;
136 CFRunLoopTimerRef waitToDumpWatchdog = 0;
137 CFTimeInterval waitToDumpWatchdogInterval = 10.0; // seconds
138
139 // Delegates
140 FrameLoadDelegate *frameLoadDelegate;
141 UIDelegate *uiDelegate;
142 EditingDelegate *editingDelegate;
143 ResourceLoadDelegate *resourceLoadDelegate;
144 PolicyDelegate *policyDelegate;
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 static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER;
166 static BOOL javaScriptThreadsShouldTerminate;
167
168 static const int javaScriptThreadsCount = 4;
169 static CFMutableDictionaryRef javaScriptThreads()
170 {
171     assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY);
172     static CFMutableDictionaryRef staticJavaScriptThreads;
173     if (!staticJavaScriptThreads)
174         staticJavaScriptThreads = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
175     return staticJavaScriptThreads;
176 }
177
178 // Loops forever, running a script and randomly respawning, until 
179 // javaScriptThreadsShouldTerminate becomes true.
180 void* runJavaScriptThread(void* arg)
181 {
182     const char* const script =
183         "var array = [];"
184         "for (var i = 0; i < 10; i++) {"
185         "    array.push(String(i));"
186         "}";
187
188     while(1) {
189         JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
190         JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
191
192         JSValueRef exception = NULL;
193         JSEvaluateScript(ctx, scriptRef, NULL, NULL, 0, &exception);
194         assert(!exception);
195         
196         JSGlobalContextRelease(ctx);
197         JSStringRelease(scriptRef);
198         
199         JSGarbageCollect(ctx);
200
201         pthread_mutex_lock(&javaScriptThreadsMutex);
202
203         // Check for cancellation.
204         if (javaScriptThreadsShouldTerminate) {
205             pthread_mutex_unlock(&javaScriptThreadsMutex);
206             return 0;
207         }
208
209         // Respawn probabilistically.
210         if (random() % 5 == 0) {
211             pthread_t pthread;
212             pthread_create(&pthread, NULL, &runJavaScriptThread, NULL);
213             pthread_detach(pthread);
214
215             CFDictionaryRemoveValue(javaScriptThreads(), pthread_self());
216             CFDictionaryAddValue(javaScriptThreads(), pthread, NULL);
217
218             pthread_mutex_unlock(&javaScriptThreadsMutex);
219             return 0;
220         }
221
222         pthread_mutex_unlock(&javaScriptThreadsMutex);
223     }
224 }
225
226 static void startJavaScriptThreads(void)
227 {
228     pthread_mutex_lock(&javaScriptThreadsMutex);
229
230     for (int i = 0; i < javaScriptThreadsCount; i++) {
231         pthread_t pthread;
232         pthread_create(&pthread, NULL, &runJavaScriptThread, NULL);
233         pthread_detach(pthread);
234         CFDictionaryAddValue(javaScriptThreads(), pthread, NULL);
235     }
236
237     pthread_mutex_unlock(&javaScriptThreadsMutex);
238 }
239
240 static void stopJavaScriptThreads(void)
241 {
242     pthread_mutex_lock(&javaScriptThreadsMutex);
243
244     javaScriptThreadsShouldTerminate = true;
245
246     pthread_t* pthreads[javaScriptThreadsCount] = { 0 };
247     ASSERT(CFDictionaryGetCount(javaScriptThreads()) == javaScriptThreadsCount);
248     CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0);
249
250     pthread_mutex_unlock(&javaScriptThreadsMutex);
251
252     for (int i = 0; i < javaScriptThreadsCount; i++) {
253         pthread_t* pthread = pthreads[i];
254         pthread_join(*pthread, 0);
255         free(pthread);
256     }
257 }
258
259 static BOOL shouldIgnoreWebCoreNodeLeaks(CFStringRef URLString)
260 {
261     static CFStringRef const ignoreSet[] = {
262         // Keeping this infrastructure around in case we ever need it again.
263     };
264     static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(CFStringRef);
265     
266     for (int i = 0; i < ignoreSetCount; i++) {
267         CFStringRef ignoreString = ignoreSet[i];
268         CFRange range = CFRangeMake(0, CFStringGetLength(URLString));
269         CFOptionFlags flags = kCFCompareAnchored | kCFCompareBackwards | kCFCompareCaseInsensitive;
270         if (CFStringFindWithOptions(URLString, ignoreString, range, flags, NULL))
271             return YES;
272     }
273     return NO;
274 }
275
276 static CMProfileRef currentColorProfile = 0;
277 static void restoreColorSpace(int ignored)
278 {
279     if (currentColorProfile) {
280         int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
281         if (error)
282             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);
283         currentColorProfile = 0;
284     }
285 }
286
287 static void crashHandler(int sig)
288 {
289     fprintf(stderr, "%s\n", strsignal(sig));
290     restoreColorSpace(0);
291     exit(128 + sig);
292 }
293
294 static void activateAhemFont(void)
295 {    
296     unsigned long fontDataLength;
297     char* fontData = getsectdata("__DATA", "Ahem", &fontDataLength);
298     if (!fontData) {
299         fprintf(stderr, "Failed to locate the Ahem font.\n");
300         exit(1);
301     }
302
303     ATSFontContainerRef fontContainer;
304     OSStatus status = ATSFontActivateFromMemory(fontData, fontDataLength, kATSFontContextLocal, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &fontContainer);
305
306     if (status != noErr) {
307         fprintf(stderr, "Failed to activate the Ahem font.\n");
308         exit(1);
309     }
310 }
311
312 static void setDefaultColorProfileToRGB(void)
313 {
314     CMProfileRef genericProfile = (CMProfileRef)[[NSColorSpace genericRGBColorSpace] colorSyncProfile];
315     CMProfileRef previousProfile;
316     int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
317     if (error) {
318         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);
319         return;
320     }
321     if (previousProfile == genericProfile)
322         return;
323     CFStringRef previousProfileName;
324     CFStringRef genericProfileName;
325     char previousProfileNameString[1024];
326     char genericProfileNameString[1024];
327     CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
328     CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
329     CFStringGetCString(previousProfileName, previousProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
330     CFStringGetCString(genericProfileName, genericProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
331     CFRelease(genericProfileName);
332     CFRelease(previousProfileName);
333     
334     fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n", previousProfileNameString, genericProfileNameString);
335     fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n");
336     fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n");
337     
338     if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile)))
339         fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result.  (Error: %i)",
340             genericProfileNameString, error);
341     else {
342         currentColorProfile = previousProfile;
343         signal(SIGINT, restoreColorSpace);
344         signal(SIGHUP, restoreColorSpace);
345         signal(SIGTERM, restoreColorSpace);
346     }
347 }
348
349 static void* (*savedMalloc)(malloc_zone_t*, size_t);
350 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
351
352 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
353 {
354     if (size >= 0x10000000)
355         return 0;
356     return savedMalloc(zone, size);
357 }
358
359 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
360 {
361     if (size >= 0x10000000)
362         return 0;
363     return savedRealloc(zone, ptr, size);
364 }
365
366 static void makeLargeMallocFailSilently(void)
367 {
368     malloc_zone_t* zone = malloc_default_zone();
369     savedMalloc = zone->malloc;
370     savedRealloc = zone->realloc;
371     zone->malloc = checkedMalloc;
372     zone->realloc = checkedRealloc;
373 }
374
375 WebView *createWebView()
376 {
377     NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
378     WebView *webView = [[WebView alloc] initWithFrame:rect frameName:nil groupName:@"org.webkit.DumpRenderTree"];
379         
380     [webView setUIDelegate:uiDelegate];
381     [webView setFrameLoadDelegate:frameLoadDelegate];
382     [webView setEditingDelegate:editingDelegate];
383     [webView setResourceLoadDelegate:resourceLoadDelegate];
384
385     // Register the same schemes that Safari does
386     [WebView registerURLSchemeAsLocal:@"feed"];
387     [WebView registerURLSchemeAsLocal:@"feeds"];
388     [WebView registerURLSchemeAsLocal:@"feedsearch"];
389
390     // The back/forward cache is causing problems due to layouts during transition from one page to another.
391     // So, turn it off for now, but we might want to turn it back on some day.
392     [[webView backForwardList] setPageCacheSize:0];
393     
394     [webView setContinuousSpellCheckingEnabled:YES];
395     
396     // 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.
397     // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
398     NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
399     DumpRenderTreeWindow *window = [[DumpRenderTreeWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
400     [[window contentView] addSubview:webView];
401     [window orderBack:nil];
402     [window setAutodisplay:NO];
403     
404     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
405     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
406     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
407     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
408         
409     return webView;
410 }
411
412 void testStringByEvaluatingJavaScriptFromString()
413 {
414     // maps expected result <= JavaScript expression
415     NSDictionary *expressions = [NSDictionary dictionaryWithObjectsAndKeys:
416         @"0", @"0", 
417         @"0", @"'0'", 
418         @"", @"",
419         @"", @"''", 
420         @"", @"new String()", 
421         @"", @"new String('0')", 
422         @"", @"throw 1", 
423         @"", @"{ }", 
424         @"", @"[ ]", 
425         @"", @"//", 
426         @"", @"a.b.c", 
427         @"", @"(function() { throw 'error'; })()", 
428         @"", @"null",
429         @"", @"undefined",
430         @"true", @"true",
431         @"false", @"false",
432         nil
433     ];
434
435     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
436     WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];
437
438     NSEnumerator *enumerator = [expressions keyEnumerator];
439     id expression;
440     while ((expression = [enumerator nextObject])) {
441         NSString *expectedResult = [expressions objectForKey:expression];
442         NSString *result = [webView stringByEvaluatingJavaScriptFromString:expression];
443         assert([result isEqualToString:expectedResult]);
444     }
445
446     [webView close];
447     [webView release];
448     [pool release];
449 }
450
451 void dumpRenderTree(int argc, const char *argv[])
452 {    
453     [NSApplication sharedApplication];
454
455     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
456     class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent"));
457
458     struct option options[] = {
459         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
460         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
461         {"notree", no_argument, &dumpTree, NO},
462         {"pixel-tests", no_argument, &dumpPixels, YES},
463         {"paint", no_argument, &paint, YES},
464         {"repaint", no_argument, &testRepaintDefault, YES},
465         {"tree", no_argument, &dumpTree, YES},
466         {"threaded", no_argument, &threaded, YES},
467         {NULL, 0, NULL, 0}
468     };
469
470     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
471     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
472     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
473     // 2 is the "Medium" font smoothing mode
474     [defaults setInteger:2 forKey:@"AppleFontSmoothing"];
475
476     [defaults setInteger:1 forKey:@"AppleAquaColorVariant"];
477     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
478     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
479     
480     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
481     
482     WebPreferences *preferences = [WebPreferences standardPreferences];
483     
484     [preferences setStandardFontFamily:@"Times"];
485     [preferences setFixedFontFamily:@"Courier"];
486     [preferences setSerifFontFamily:@"Times"];
487     [preferences setSansSerifFontFamily:@"Helvetica"];
488     [preferences setCursiveFontFamily:@"Apple Chancery"];
489     [preferences setFantasyFontFamily:@"Papyrus"];
490     [preferences setDefaultFontSize:16];
491     [preferences setDefaultFixedFontSize:13];
492     [preferences setMinimumFontSize:1];
493     [preferences setJavaEnabled:NO];
494     [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
495     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
496     [preferences setTabsToLinks:NO];
497     [preferences setDOMPasteAllowed:YES];
498     
499     int option;
500     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
501         switch (option) {
502             case '?':   // unknown or ambiguous option
503             case ':':   // missing argument
504                 exit(1);
505                 break;
506         }
507
508     activateAhemFont();
509
510     if (dumpPixels) {
511         setDefaultColorProfileToRGB();
512         screenCaptureBuffer = (unsigned char *)malloc(maxViewHeight * maxViewWidth * 4);
513         sharedColorSpace = CGColorSpaceCreateDeviceRGB();
514     }
515     
516     localPasteboards = [[NSMutableDictionary alloc] init];
517     navigationController = [[NavigationController alloc] init];
518     frameLoadDelegate = [[FrameLoadDelegate alloc] init];
519     uiDelegate = [[UIDelegate alloc] init];
520     editingDelegate = [[EditingDelegate alloc] init];    
521     resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
522     policyDelegate = [[PolicyDelegate alloc] init];
523     
524     NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
525     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
526     [[WebPluginDatabase sharedDatabase] refresh];
527     
528     WebView *webView = createWebView();    
529     mainFrame = [webView mainFrame];
530     NSWindow *window = [webView window];
531
532     makeLargeMallocFailSilently();
533
534     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
535     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
536     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
537     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
538     signal(SIGBUS, crashHandler);    /* 10:  bus error */
539     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
540     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
541     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
542     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
543     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
544     
545     [[NSURLCache sharedURLCache] removeAllCachedResponses];
546     
547     // <rdar://problem/5222911>
548     testStringByEvaluatingJavaScriptFromString();
549
550     if (threaded)
551         startJavaScriptThreads();
552     
553     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
554         char filenameBuffer[2048];
555         printSeparators = YES;
556         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
557             char *newLineCharacter = strchr(filenameBuffer, '\n');
558             if (newLineCharacter)
559                 *newLineCharacter = '\0';
560             
561             if (strlen(filenameBuffer) == 0)
562                 continue;
563                 
564             runTest(filenameBuffer);
565         }
566     } else {
567         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
568         for (int i = optind; i != argc; ++i)
569             runTest(argv[i]);
570     }
571
572     if (threaded)
573         stopJavaScriptThreads();
574
575     [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts    
576     [webView close];
577     mainFrame = nil;
578
579     // Work around problem where registering drag types leaves an outstanding
580     // "perform selector" on the window, which retains the window. It's a bit
581     // inelegant and perhaps dangerous to just blow them all away, but in practice
582     // it probably won't cause any trouble (and this is just a test tool, after all).
583     [NSObject cancelPreviousPerformRequestsWithTarget:window];
584     
585     [window close]; // releases when closed
586     [webView release];
587     [frameLoadDelegate release];
588     [editingDelegate release];
589     [resourceLoadDelegate release];
590     [uiDelegate release];
591     [policyDelegate release];
592     
593     [localPasteboards release];
594     localPasteboards = nil;
595     
596     [navigationController release];
597     navigationController = nil;
598
599     if (disallowedURLs) {
600         CFRelease(disallowedURLs);
601         disallowedURLs = 0;
602     }
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     if (waitToDumpWatchdog) {
853         CFRunLoopTimerInvalidate(waitToDumpWatchdog);
854         CFRelease(waitToDumpWatchdog);
855         waitToDumpWatchdog = 0;
856     }
857
858     if (dumpTree) {
859         NSString *result = nil;
860
861         dumpAsText |= [[[[mainFrame dataSource] response] MIMEType] isEqualToString:@"text/plain"];
862         if (dumpAsText) {
863             result = dumpFramesAsText(mainFrame);
864         } else if (dumpDOMAsWebArchive) {
865             WebArchive *webArchive = [[mainFrame DOMDocument] webArchive];
866             result = serializeWebArchiveToXML(webArchive);
867         } else if (dumpSourceAsWebArchive) {
868             WebArchive *webArchive = [[mainFrame dataSource] webArchive];
869             result = serializeWebArchiveToXML(webArchive);
870         } else {
871             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
872             if (isSVGW3CTest)
873                 [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)];
874             else 
875                 [[mainFrame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
876             result = [mainFrame renderTreeAsExternalRepresentation];
877         }
878
879         if (!result) {
880             const char *errorMessage;
881             if (dumpAsText)
882                 errorMessage = "[documentElement innerText]";
883             else if (dumpDOMAsWebArchive)
884                 errorMessage = "[[mainFrame DOMDocument] webArchive]";
885             else if (dumpSourceAsWebArchive)
886                 errorMessage = "[[mainFrame dataSource] webArchive]";
887             else
888                 errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
889             printf("ERROR: nil result from %s", errorMessage);
890         } else {
891             NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding];
892             fwrite([data bytes], 1, [data length], stdout);
893             if (!dumpAsText && !dumpDOMAsWebArchive && !dumpSourceAsWebArchive)
894                 dumpFrameScrollPosition(mainFrame);
895         }
896
897         if (dumpBackForwardList) {
898             unsigned count = CFArrayGetCount(allWindowsRef);
899             for (unsigned i = 0; i < count; i++) {
900                 NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(allWindowsRef, i);
901                 WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
902                 dumpBackForwardListForWebView(webView);
903             }
904         }
905
906         if (printSeparators)
907             puts("#EOF");
908     }
909     
910     if (dumpPixels) {
911         if (!dumpAsText && !dumpDOMAsWebArchive && !dumpSourceAsWebArchive) {
912             // grab a bitmap from the view
913             WebView* view = [mainFrame webView];
914             NSSize webViewSize = [view frame].size;
915
916             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);
917
918             NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
919             NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
920             [NSGraphicsContext setCurrentContext:nsContext];
921
922             if (readFromWindow) {
923                 NSBitmapImageRep *imageRep;
924                 [view displayIfNeeded];
925                 [view lockFocus];
926                 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
927                 [view unlockFocus];
928                 [imageRep draw];
929                 [imageRep release];
930             } else if (!testRepaint)
931                 [view displayRectIgnoringOpacity:NSMakeRect(0, 0, webViewSize.width, webViewSize.height) inContext:nsContext];
932             else if (!repaintSweepHorizontally) {
933                 NSRect line = NSMakeRect(0, 0, webViewSize.width, 1);
934                 while (line.origin.y < webViewSize.height) {
935                     [view displayRectIgnoringOpacity:line inContext:nsContext];
936                     line.origin.y++;
937                 }
938             } else {
939                 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
940                 while (column.origin.x < webViewSize.width) {
941                     [view displayRectIgnoringOpacity:column inContext:nsContext];
942                     column.origin.x++;
943                 }
944             }
945             if (dumpSelectionRect) {
946                 NSView *documentView = [[mainFrame frameView] documentView];
947                 if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) {
948                     [[NSColor redColor] set];
949                     [NSBezierPath strokeRect:[documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]];
950                 }
951             }
952
953             [NSGraphicsContext setCurrentContext:savedContext];
954             
955             CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
956             CGContextRelease(cgContext);
957
958             // compute the actual hash to compare to the expected image's hash
959             NSString *actualHash = md5HashStringForBitmap(bitmapImage);
960             printf("\nActualHash: %s\n", [actualHash UTF8String]);
961
962             BOOL dumpImage;
963             if (dumpAllPixels)
964                 dumpImage = YES;
965             else {
966                 // FIXME: It's unfortunate that we hardcode the file naming scheme here.
967                 // At one time, the perl script had all the knowledge about file layout.
968                 // Some day we should restore that setup by passing in more parameters to this tool.
969                 NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
970                 NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
971                 NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
972                 NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
973
974                 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
975
976                 /// send the image to stdout if the hash mismatches or if there's no file in the file system
977                 dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0;
978             }
979             
980             if (dumpImage) {
981                 CFMutableDataRef imageData = CFDataCreateMutable(0, 0);
982                 CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0);
983                 CGImageDestinationAddImage(imageDest, bitmapImage, 0);
984                 CGImageDestinationFinalize(imageDest);
985                 CFRelease(imageDest);
986                 printf("Content-length: %lu\n", CFDataGetLength(imageData));
987                 fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout);
988                 CFRelease(imageData);
989             }
990
991             CGImageRelease(bitmapImage);
992         }
993
994         printf("#EOF\n");
995     }
996     
997     fflush(stdout);
998
999     if (paint)
1000         displayWebView();
1001     
1002     done = YES;
1003 }
1004
1005 static bool shouldLogFrameLoadDelegates(const char *pathOrURL)
1006 {
1007     return strstr(pathOrURL, "loading/");
1008 }    
1009
1010 static void runTest(const char *pathOrURL)
1011 {
1012     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
1013     if (!pathOrURLString) {
1014         fprintf(stderr, "can't parse filename as UTF-8\n");
1015         return;
1016     }
1017     
1018     CFURLRef URL;
1019     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")) || CFStringHasPrefix(pathOrURLString, CFSTR("https://")))
1020         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
1021     else
1022         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
1023     
1024     if (!URL) {
1025         CFRelease(pathOrURLString);
1026         fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
1027         return;
1028     }
1029
1030     
1031
1032     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:YES];
1033     [[mainFrame webView] makeTextStandardSize:nil];
1034     [[mainFrame webView] setTabKeyCyclesThroughElements: YES];
1035     [[mainFrame webView] setPolicyDelegate:nil];
1036     [WebView _setUsesTestModeFocusRingColor:YES];
1037     done = NO;
1038     topLoadingFrame = nil;
1039     waitToDump = NO;
1040     dumpAsText = NO;
1041     dumpDOMAsWebArchive = NO;
1042     dumpSourceAsWebArchive = NO;
1043     dumpChildFrameScrollPositions = NO;
1044     dumpChildFramesAsText = NO;
1045     shouldDumpEditingCallbacks = NO;
1046     shouldDumpResourceLoadCallbacks = NO;
1047     shouldDumpFrameLoadCallbacks = NO;
1048     dumpSelectionRect = NO;
1049     dumpTitleChanges = NO;
1050     dumpBackForwardList = NO;
1051     readFromWindow = NO;
1052     canOpenWindows = NO;
1053     closeWebViews = YES;
1054     addFileToPasteboardOnDrag = NO;
1055     [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO];
1056     testRepaint = testRepaintDefault;
1057     repaintSweepHorizontally = repaintSweepHorizontallyDefault;
1058     if ([WebHistory optionalSharedHistory])
1059         [WebHistory setOptionalSharedHistory:nil];
1060     lastMousePosition = NSMakePoint(0, 0);
1061     if (disallowedURLs)
1062         CFSetRemoveAllValues(disallowedURLs);
1063
1064     if (currentTest != nil)
1065         CFRelease(currentTest);
1066     currentTest = (NSString *)pathOrURLString;
1067     [prevTestBFItem release];
1068     prevTestBFItem = [[[[mainFrame webView] backForwardList] currentItem] retain];
1069
1070     WorkQueue::shared()->clear();
1071     WorkQueue::shared()->setFrozen(false);
1072
1073     BOOL _shouldIgnoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(CFURLGetString(URL));
1074     if (_shouldIgnoreWebCoreNodeLeaks)
1075         [WebCoreStatistics startIgnoringWebCoreNodeLeaks];
1076
1077     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1078     if (shouldLogFrameLoadDelegates(pathOrURL))
1079         shouldDumpFrameLoadCallbacks = YES;
1080     [mainFrame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
1081     CFRelease(URL);
1082     [pool release];
1083     while (!done) {
1084         pool = [[NSAutoreleasePool alloc] init];
1085         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
1086         [pool release];
1087     }
1088     pool = [[NSAutoreleasePool alloc] init];
1089     [EventSendingController clearSavedEvents];
1090     [[mainFrame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1091     
1092     if (closeRemainingWindowsWhenComplete) {
1093         NSArray* array = [(NSArray *)allWindowsRef copy];
1094         
1095         unsigned count = [array count];
1096         for (unsigned i = 0; i < count; i++) {
1097             NSWindow *window = [array objectAtIndex:i];
1098
1099             // Don't try to close the main window
1100             if (window == [[mainFrame webView] window])
1101                 continue;
1102             
1103             WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
1104
1105             [webView close];
1106             [window close];
1107         }
1108         [array release];
1109     }
1110     
1111     [pool release];
1112     
1113     // We should only have our main window left when we're done
1114     ASSERT(CFArrayGetCount(allWindowsRef) == 1);
1115     ASSERT(CFArrayGetValueAtIndex(allWindowsRef, 0) == [[mainFrame webView] window]);
1116     
1117     if (_shouldIgnoreWebCoreNodeLeaks)
1118         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
1119 }
1120
1121 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
1122 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
1123 {
1124     MD5_CTX md5Context;
1125     unsigned char hash[16];
1126     
1127     unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap);
1128     assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
1129     unsigned bytesPerPixel = bitsPerPixel / 8;
1130     unsigned pixelsHigh = CGImageGetHeight(bitmap);
1131     unsigned pixelsWide = CGImageGetWidth(bitmap);
1132     unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap);
1133     assert(bytesPerRow >= (pixelsWide * bytesPerPixel));
1134     
1135     MD5_Init(&md5Context);
1136     unsigned char *bitmapData = screenCaptureBuffer;
1137     for (unsigned row = 0; row < pixelsHigh; row++) {
1138         MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel);
1139         bitmapData += bytesPerRow;
1140     }
1141     MD5_Final(hash, &md5Context);
1142     
1143     char hex[33] = "";
1144     for (int i = 0; i < 16; i++) {
1145        snprintf(hex, 33, "%s%02x", hex, hash[i]);
1146     }
1147
1148     return [NSString stringWithUTF8String:hex];
1149 }
1150
1151 void displayWebView()
1152 {
1153     NSView *webView = [mainFrame webView];
1154     [webView display];
1155     [webView lockFocus];
1156     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
1157     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
1158     [webView unlockFocus];
1159     readFromWindow = YES;
1160 }
1161
1162 @implementation DumpRenderTreePasteboard
1163
1164 // Return a local pasteboard so we don't disturb the real pasteboards when running tests.
1165 + (NSPasteboard *)_pasteboardWithName:(NSString *)name
1166 {
1167     static int number = 0;
1168     if (!name)
1169         name = [NSString stringWithFormat:@"LocalPasteboard%d", ++number];
1170     LocalPasteboard *pasteboard = [localPasteboards objectForKey:name];
1171     if (pasteboard)
1172         return pasteboard;
1173     pasteboard = [[LocalPasteboard alloc] init];
1174     [localPasteboards setObject:pasteboard forKey:name];
1175     [pasteboard release];
1176     return pasteboard;
1177 }
1178
1179 // Convenience method for JS so that it doesn't have to try and create a NSArray on the objc side instead
1180 // of the usual WebScriptObject that is passed around
1181 - (int)declareType:(NSString *)type owner:(id)newOwner
1182 {
1183     return [self declareTypes:[NSArray arrayWithObject:type] owner:newOwner];
1184 }
1185
1186 @end
1187
1188 @implementation LocalPasteboard
1189
1190 + (id)alloc
1191 {
1192     return NSAllocateObject(self, 0, 0);
1193 }
1194
1195 - (id)init
1196 {
1197     typesArray = [[NSMutableArray alloc] init];
1198     typesSet = [[NSMutableSet alloc] init];
1199     dataByType = [[NSMutableDictionary alloc] init];
1200     return self;
1201 }
1202
1203 - (void)dealloc
1204 {
1205     [typesArray release];
1206     [typesSet release];
1207     [dataByType release];
1208     [super dealloc];
1209 }
1210
1211 - (NSString *)name
1212 {
1213     return nil;
1214 }
1215
1216 - (void)releaseGlobally
1217 {
1218 }
1219
1220 - (int)declareTypes:(NSArray *)newTypes owner:(id)newOwner
1221 {
1222     [typesArray removeAllObjects];
1223     [typesSet removeAllObjects];
1224     [dataByType removeAllObjects];
1225     return [self addTypes:newTypes owner:newOwner];
1226 }
1227
1228 - (int)addTypes:(NSArray *)newTypes owner:(id)newOwner
1229 {
1230     unsigned count = [newTypes count];
1231     unsigned i;
1232     for (i = 0; i < count; ++i) {
1233         NSString *type = [newTypes objectAtIndex:i];
1234         NSString *setType = [typesSet member:type];
1235         if (!setType) {
1236             setType = [type copy];
1237             [typesArray addObject:setType];
1238             [typesSet addObject:setType];
1239             [setType release];
1240         }
1241         if (newOwner && [newOwner respondsToSelector:@selector(pasteboard:provideDataForType:)])
1242             [newOwner pasteboard:self provideDataForType:setType];
1243     }
1244     return ++changeCount;
1245 }
1246
1247 - (int)changeCount
1248 {
1249     return changeCount;
1250 }
1251
1252 - (NSArray *)types
1253 {
1254     return typesArray;
1255 }
1256
1257 - (NSString *)availableTypeFromArray:(NSArray *)types
1258 {
1259     unsigned count = [types count];
1260     unsigned i;
1261     for (i = 0; i < count; ++i) {
1262         NSString *type = [types objectAtIndex:i];
1263         NSString *setType = [typesSet member:type];
1264         if (setType)
1265             return setType;
1266     }
1267     return nil;
1268 }
1269
1270 - (BOOL)setData:(NSData *)data forType:(NSString *)dataType
1271 {
1272     if (data == nil)
1273         data = [NSData data];
1274     if (![typesSet containsObject:dataType])
1275         return NO;
1276     [dataByType setObject:data forKey:dataType];
1277     ++changeCount;
1278     return YES;
1279 }
1280
1281 - (NSData *)dataForType:(NSString *)dataType
1282 {
1283     return [dataByType objectForKey:dataType];
1284 }
1285
1286 - (BOOL)setPropertyList:(id)propertyList forType:(NSString *)dataType;
1287 {
1288     CFDataRef data = NULL;
1289     if (propertyList)
1290         data = CFPropertyListCreateXMLData(NULL, propertyList);
1291     BOOL result = [self setData:(NSData *)data forType:dataType];
1292     if (data)
1293         CFRelease(data);
1294     return result;
1295 }
1296
1297 - (BOOL)setString:(NSString *)string forType:(NSString *)dataType
1298 {
1299     CFDataRef data = NULL;
1300     if (string) {
1301         if ([string length] == 0)
1302             data = CFDataCreate(NULL, NULL, 0);
1303         else
1304             data = CFStringCreateExternalRepresentation(NULL, (CFStringRef)string, kCFStringEncodingUTF8, 0);
1305     }
1306     BOOL result = [self setData:(NSData *)data forType:dataType];
1307     if (data)
1308         CFRelease(data);
1309     return result;
1310 }
1311
1312 @end
1313
1314 static CFArrayCallBacks NonRetainingArrayCallbacks = {
1315     0,
1316     NULL,
1317     NULL,
1318     CFCopyDescription,
1319     CFEqual
1320 };
1321
1322 @implementation DumpRenderTreeWindow
1323
1324 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
1325 {
1326     if (!allWindowsRef)
1327         allWindowsRef = CFArrayCreateMutable(NULL, 0, &NonRetainingArrayCallbacks);
1328
1329     CFArrayAppendValue(allWindowsRef, self);
1330             
1331     return [super initWithContentRect:contentRect styleMask:styleMask backing:bufferingType defer:deferCreation];
1332 }
1333
1334 - (void)dealloc
1335 {
1336     CFRange arrayRange = CFRangeMake(0, CFArrayGetCount(allWindowsRef));
1337     CFIndex i = CFArrayGetFirstIndexOfValue(allWindowsRef, arrayRange, self);
1338     assert(i != -1);
1339
1340     CFArrayRemoveValueAtIndex(allWindowsRef, i);
1341     [super dealloc];
1342 }
1343
1344 - (BOOL)isKeyWindow
1345 {
1346     return windowIsKey;
1347 }
1348
1349 - (void)keyDown:(id)sender
1350 {
1351     // Do nothing, avoiding the beep we'd otherwise get from NSResponder,
1352     // once we get to the end of the responder chain.
1353 }
1354
1355 @end
1356
1357 @implementation DumpRenderTreeEvent
1358
1359 + (NSPoint)mouseLocation
1360 {
1361     return [[[mainFrame webView] window] convertBaseToScreen:lastMousePosition];
1362 }
1363
1364 @end