LayoutTests:
[WebKit-https.git] / WebKitTools / DumpRenderTree / DumpRenderTree.m
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 "AppleScriptController.h"
33 #import "EditingDelegate.h"
34 #import "EventSendingController.h"
35 #import "GCController.h"
36 #import "NavigationController.h"
37 #import "ObjCPlugin.h"
38 #import "ObjCPluginFunction.h"
39 #import "ResourceLoadDelegate.h"
40 #import "TextInputController.h"
41 #import "UIDelegate.h"
42 #import <ApplicationServices/ApplicationServices.h> // for CMSetDefaultProfileBySpace
43 #import <CoreFoundation/CoreFoundation.h>
44 #import <JavaScriptCore/JavaScriptCore.h>
45 #import <WebKit/DOMElementPrivate.h>
46 #import <WebKit/DOMExtensions.h>
47 #import <WebKit/DOMRange.h>
48 #import <WebKit/WebBackForwardList.h>
49 #import <WebKit/WebCoreStatistics.h>
50 #import <WebKit/WebDataSource.h>
51 #import <WebKit/WebDocumentPrivate.h>
52 #import <WebKit/WebEditingDelegate.h>
53 #import <WebKit/WebFramePrivate.h>
54 #import <WebKit/WebFrameView.h>
55 #import <WebKit/WebHTMLViewPrivate.h>
56 #import <WebKit/WebHistory.h>
57 #import <WebKit/WebHistoryItemPrivate.h>
58 #import <WebKit/WebNSURLExtras.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 <JavaScriptCore/Assertions.h>
65 #import <getopt.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 WaitUntilDoneDelegate : NSObject
84 @end
85
86 @interface LayoutTestController : NSObject
87 {
88     WebScriptObject *storedWebScriptObject;
89 }
90 - (void)dealloc;
91 @end
92
93 @interface LocalPasteboard : NSPasteboard
94 {
95     NSMutableArray *typesArray;
96     NSMutableSet *typesSet;
97     NSMutableDictionary *dataByType;
98     int changeCount;
99 }
100 @end
101
102 BOOL windowIsKey = YES;
103 WebFrame *frame = 0;
104 BOOL shouldDumpEditingCallbacks;
105 BOOL shouldDumpResourceLoadCallbacks;
106 NSMutableSet *disallowedURLs = 0;
107 BOOL waitToDump;     // TRUE if waitUntilDone() has been called, but notifyDone() has not yet been called
108 BOOL canOpenWindows;
109 BOOL closeWebViews;
110 BOOL closeRemainingWindowsWhenComplete = YES;
111
112 static void runTest(const char *pathOrURL);
113 static NSString *md5HashStringForBitmap(CGImageRef bitmap);
114 static void displayWebView();
115
116 volatile BOOL done;
117 static NavigationController *navigationController;
118
119 // Delegates
120 static WaitUntilDoneDelegate *waitUntilDoneDelegate;
121 static UIDelegate *uiDelegate;
122 static EditingDelegate *editingDelegate;
123 static ResourceLoadDelegate *resourceLoadDelegate;
124
125 // Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
126 // - There is no load in progress
127 // - There is no work queued up (see workQueue var, below)
128 // - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
129 //       and notifyDone was called subsequently.
130 // Note that the call to notifyDone and the end of the load can happen in either order.
131
132 // This is the topmost frame that is loading, during a given load, or nil when no load is 
133 // in progress.  Usually this is the same as the main frame, but not always.  In the case
134 // where a frameset is loaded, and then new content is loaded into one of the child frames,
135 // that child frame is the "topmost frame that is loading".
136 static WebFrame *topLoadingFrame;     // !nil iff a load is in progress
137
138 static BOOL dumpAsText;
139 static BOOL dumpDOMAsWebArchive;
140 static BOOL dumpSourceAsWebArchive;
141 static BOOL dumpSelectionRect;
142 static BOOL dumpTitleChanges;
143 static BOOL dumpBackForwardList;
144 static BOOL dumpChildFrameScrollPositions;
145 static int dumpPixels;
146 static int paint;
147 static int dumpAllPixels;
148 static int threaded;
149 static BOOL readFromWindow;
150 static int testRepaintDefault;
151 static BOOL testRepaint;
152 static int repaintSweepHorizontallyDefault;
153 static BOOL repaintSweepHorizontally;
154 static int dumpTree = YES;
155 static BOOL printSeparators;
156 static NSString *currentTest = nil;
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 // a queue of NSInvocations, queued by callouts from the test, to be exec'ed when the load is done
162 static NSMutableArray *workQueue = nil;
163 // to prevent infinite loops, only the first page of a test can add to a work queue
164 // (since we may well come back to that same page)
165 static BOOL workQueueFrozen;
166
167 const unsigned maxViewHeight = 600;
168 const unsigned maxViewWidth = 800;
169
170 static CFMutableArrayRef allWindowsRef;
171
172 static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER;
173 static BOOL javaScriptThreadsShouldTerminate;
174
175 static const int javaScriptThreadsCount = 4;
176 static CFMutableDictionaryRef javaScriptThreads()
177 {
178     assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY);
179     static CFMutableDictionaryRef staticJavaScriptThreads;
180     if (!staticJavaScriptThreads)
181         staticJavaScriptThreads = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL);
182     return staticJavaScriptThreads;
183 }
184
185 // Loops forever, running a script and randomly respawning, until 
186 // javaScriptThreadsShouldTerminate becomes true.
187 void* runJavaScriptThread(void* arg)
188 {
189     const char* const script =
190     " \
191     var array = []; \
192     for (var i = 0; i < 10; i++) { \
193         array.push(String(i)); \
194     } \
195     ";
196
197     while(1) {
198         JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
199         JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
200
201         JSValueRef exception = NULL;
202         JSEvaluateScript(ctx, scriptRef, NULL, NULL, 0, &exception);
203         assert(!exception);
204         
205         JSGlobalContextRelease(ctx);
206         JSStringRelease(scriptRef);
207         
208         JSGarbageCollect(ctx);
209
210         pthread_mutex_lock(&javaScriptThreadsMutex);
211
212         // Check for cancellation.
213         if (javaScriptThreadsShouldTerminate) {
214             pthread_mutex_unlock(&javaScriptThreadsMutex);
215             return 0;
216         }
217
218         // Respawn probabilistically.
219         if (random() % 5 == 0) {
220             pthread_t pthread;
221             pthread_create(&pthread, NULL, &runJavaScriptThread, NULL);
222             pthread_detach(pthread);
223
224             CFDictionaryRemoveValue(javaScriptThreads(), pthread_self());
225             CFDictionaryAddValue(javaScriptThreads(), pthread, NULL);
226
227             pthread_mutex_unlock(&javaScriptThreadsMutex);
228             return 0;
229         }
230
231         pthread_mutex_unlock(&javaScriptThreadsMutex);
232     }
233 }
234
235 static void startJavaScriptThreads(void)
236 {
237     pthread_mutex_lock(&javaScriptThreadsMutex);
238
239     for (int i = 0; i < javaScriptThreadsCount; i++) {
240         pthread_t pthread;
241         pthread_create(&pthread, NULL, &runJavaScriptThread, NULL);
242         pthread_detach(pthread);
243         CFDictionaryAddValue(javaScriptThreads(), pthread, NULL);
244     }
245
246     pthread_mutex_unlock(&javaScriptThreadsMutex);
247 }
248
249 static void stopJavaScriptThreads(void)
250 {
251     pthread_mutex_lock(&javaScriptThreadsMutex);
252
253     javaScriptThreadsShouldTerminate = YES;
254
255     const pthread_t pthreads[javaScriptThreadsCount];
256     assert(CFDictionaryGetCount(javaScriptThreads()) == javaScriptThreadsCount);
257     CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, NULL);
258
259     pthread_mutex_unlock(&javaScriptThreadsMutex);
260
261     for (int i = 0; i < javaScriptThreadsCount; i++) {
262         pthread_t pthread = pthreads[i];
263         pthread_join(pthread, NULL);
264     }
265 }
266
267 static BOOL shouldIgnoreWebCoreNodeLeaks(CFStringRef URLString)
268 {
269     static CFStringRef const ignoreSet[] = {
270         // Keeping this infrastructure around in case we ever need it again.
271     };
272     static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(CFStringRef);
273     
274     for (int i = 0; i < ignoreSetCount; i++) {
275         CFStringRef ignoreString = ignoreSet[i];
276         CFRange range = CFRangeMake(0, CFStringGetLength(URLString));
277         CFOptionFlags flags = kCFCompareAnchored | kCFCompareBackwards | kCFCompareCaseInsensitive;
278         if (CFStringFindWithOptions(URLString, ignoreString, range, flags, NULL))
279             return YES;
280     }
281     return NO;
282 }
283
284 static CMProfileRef currentColorProfile = 0;
285 static void restoreColorSpace(int ignored)
286 {
287     if (currentColorProfile) {
288         int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
289         if (error)
290             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);
291         currentColorProfile = 0;
292     }
293 }
294
295 static void crashHandler(int sig)
296 {
297     fprintf(stderr, "%s\n", strsignal(sig));
298     restoreColorSpace(0);
299     exit(128 + sig);
300 }
301
302 static void setDefaultColorProfileToRGB(void)
303 {
304     CMProfileRef genericProfile = [[NSColorSpace genericRGBColorSpace] colorSyncProfile];
305     CMProfileRef previousProfile;
306     int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
307     if (error) {
308         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);
309         return;
310     }
311     if (previousProfile == genericProfile)
312         return;
313     CFStringRef previousProfileName;
314     CFStringRef genericProfileName;
315     char previousProfileNameString[1024];
316     char genericProfileNameString[1024];
317     CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
318     CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
319     CFStringGetCString(previousProfileName, previousProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
320     CFStringGetCString(genericProfileName, genericProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
321     CFRelease(genericProfileName);
322     CFRelease(previousProfileName);
323     
324     fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n", previousProfileNameString, genericProfileNameString);
325     fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n");
326     fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n");
327     
328     if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile)))
329         fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result.  (Error: %i)",
330             genericProfileNameString, error);
331     else {
332         currentColorProfile = previousProfile;
333         signal(SIGINT, restoreColorSpace);
334         signal(SIGHUP, restoreColorSpace);
335         signal(SIGTERM, restoreColorSpace);
336     }
337 }
338
339 static void* (*savedMalloc)(malloc_zone_t*, size_t);
340 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
341
342 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
343 {
344     if (size >= 0x10000000)
345         return 0;
346     return savedMalloc(zone, size);
347 }
348
349 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
350 {
351     if (size >= 0x10000000)
352         return 0;
353     return savedRealloc(zone, ptr, size);
354 }
355
356 static void makeLargeMallocFailSilently(void)
357 {
358     malloc_zone_t* zone = malloc_default_zone();
359     savedMalloc = zone->malloc;
360     savedRealloc = zone->realloc;
361     zone->malloc = checkedMalloc;
362     zone->realloc = checkedRealloc;
363 }
364
365 WebView *createWebView()
366 {
367     NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
368     WebView *webView = [[WebView alloc] initWithFrame:rect];
369         
370     [webView setUIDelegate:uiDelegate];
371     [webView setFrameLoadDelegate:waitUntilDoneDelegate];
372     [webView setEditingDelegate:editingDelegate];
373     [webView setResourceLoadDelegate:resourceLoadDelegate];
374     
375     // The back/forward cache is causing problems due to layouts during transition from one page to another.
376     // So, turn it off for now, but we might want to turn it back on some day.
377     [[webView backForwardList] setPageCacheSize:0];
378     
379     [webView setContinuousSpellCheckingEnabled:YES];
380     
381     // 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.
382     // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
383     NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
384     NSWindow *window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
385     [[window contentView] addSubview:webView];
386     [window orderBack:nil];
387     [window setAutodisplay:NO];
388     
389     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
390     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
391     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
392     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
393         
394     return webView;
395 }
396
397 void dumpRenderTree(int argc, const char *argv[])
398 {    
399     [NSApplication sharedApplication];
400
401     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
402     class_poseAs(objc_getClass("DumpRenderTreeWindow"), objc_getClass("NSWindow"));
403     class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent"));
404
405     struct option options[] = {
406         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
407         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
408         {"notree", no_argument, &dumpTree, NO},
409         {"pixel-tests", no_argument, &dumpPixels, YES},
410         {"paint", no_argument, &paint, YES},
411         {"repaint", no_argument, &testRepaintDefault, YES},
412         {"tree", no_argument, &dumpTree, YES},
413         {"threaded", no_argument, &threaded, YES},
414         {NULL, 0, NULL, 0}
415     };
416
417     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
418     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
419     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
420     // 2 is the "Medium" font smoothing mode
421     [defaults setInteger:2 forKey:@"AppleFontSmoothing"];
422
423     [defaults setInteger:1 forKey:@"AppleAquaColorVariant"];
424     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
425     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
426     
427     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
428     
429     WebPreferences *preferences = [WebPreferences standardPreferences];
430     
431     [preferences setStandardFontFamily:@"Times"];
432     [preferences setFixedFontFamily:@"Courier"];
433     [preferences setSerifFontFamily:@"Times"];
434     [preferences setSansSerifFontFamily:@"Helvetica"];
435     [preferences setCursiveFontFamily:@"Apple Chancery"];
436     [preferences setFantasyFontFamily:@"Papyrus"];
437     [preferences setDefaultFontSize:16];
438     [preferences setDefaultFixedFontSize:13];
439     [preferences setMinimumFontSize:1];
440     [preferences setJavaEnabled:NO];
441     [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
442     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
443     [preferences setTabsToLinks:NO];
444     [preferences setDOMPasteAllowed:YES];
445     
446     int option;
447     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
448         switch (option) {
449             case '?':   // unknown or ambiguous option
450             case ':':   // missing argument
451                 exit(1);
452                 break;
453         }
454     
455     if ([[[NSFontManager sharedFontManager] availableMembersOfFontFamily:@"Ahem"] count] == 0) {
456         fprintf(stderr, "\nAhem font is not available. This special simple font is used to construct certain types of predictable tests.\n\nTo run regression tests, please get it from <http://webkit.org/quality/Ahem.ttf>.\n");
457         exit(1);
458     }
459     
460     if (dumpPixels) {
461         setDefaultColorProfileToRGB();
462         screenCaptureBuffer = malloc(maxViewHeight * maxViewWidth * 4);
463         sharedColorSpace = CGColorSpaceCreateDeviceRGB();
464     }
465     
466     localPasteboards = [[NSMutableDictionary alloc] init];
467     navigationController = [[NavigationController alloc] init];
468     waitUntilDoneDelegate = [[WaitUntilDoneDelegate alloc] init];
469     uiDelegate = [[UIDelegate alloc] init];
470     editingDelegate = [[EditingDelegate alloc] init];    
471     resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
472     
473     NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
474     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
475     [[WebPluginDatabase sharedDatabase] refresh];
476     
477     WebView *webView = createWebView();    
478     frame = [webView mainFrame];
479     NSWindow *window = [webView window];
480     
481     workQueue = [[NSMutableArray alloc] init];
482
483     makeLargeMallocFailSilently();
484
485     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
486     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
487     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
488     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
489     signal(SIGBUS, crashHandler);    /* 10:  bus error */
490     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
491     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
492     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
493     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
494     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
495     
496     [[NSURLCache sharedURLCache] removeAllCachedResponses];
497     
498     if (threaded)
499         startJavaScriptThreads();
500     
501     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
502         char filenameBuffer[2048];
503         printSeparators = YES;
504         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
505             char *newLineCharacter = strchr(filenameBuffer, '\n');
506             if (newLineCharacter)
507                 *newLineCharacter = '\0';
508             
509             if (strlen(filenameBuffer) == 0)
510                 continue;
511                 
512             runTest(filenameBuffer);
513         }
514     } else {
515         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
516         for (int i = optind; i != argc; ++i)
517             runTest(argv[i]);
518     }
519
520     if (threaded)
521         stopJavaScriptThreads();
522     
523     [workQueue release];
524
525     [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts    
526     [webView close];
527     frame = nil;
528
529     // Work around problem where registering drag types leaves an outstanding
530     // "perform selector" on the window, which retains the window. It's a bit
531     // inelegant and perhaps dangerous to just blow them all away, but in practice
532     // it probably won't cause any trouble (and this is just a test tool, after all).
533     [NSObject cancelPreviousPerformRequestsWithTarget:window];
534     
535     [window close]; // releases when closed
536     [webView release];
537     [waitUntilDoneDelegate release];
538     [editingDelegate release];
539     [resourceLoadDelegate release];
540     [uiDelegate release];
541     
542     [localPasteboards release];
543     localPasteboards = nil;
544     
545     [navigationController release];
546     navigationController = nil;
547     
548     [disallowedURLs release];
549     disallowedURLs = nil;
550     
551     if (dumpPixels)
552         restoreColorSpace(0);
553 }
554
555 int main(int argc, const char *argv[])
556 {
557     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
558     dumpRenderTree(argc, argv);
559     [WebCoreStatistics garbageCollectJavaScriptObjects];
560     [pool release];
561     return 0;
562 }
563
564 static int compareHistoryItems(id item1, id item2, void *context)
565 {
566     return [[item1 target] caseInsensitiveCompare:[item2 target]];
567 }
568
569 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
570 {
571     int start = 0;
572     if (current) {
573         printf("curr->");
574         start = 6;
575     }
576     for (int i = start; i < indent; i++)
577         putchar(' ');
578     printf("%s", [[item URLString] UTF8String]);
579     NSString *target = [item target];
580     if (target && [target length] > 0)
581         printf(" (in frame \"%s\")", [target UTF8String]);
582     if ([item isTargetItem])
583         printf("  **nav target**");
584     putchar('\n');
585     NSArray *kids = [item children];
586     if (kids) {
587         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
588         kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
589         for (unsigned i = 0; i < [kids count]; i++)
590             dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
591     }
592 }
593
594 static void dumpFrameScrollPosition(WebFrame *f)
595 {
596     NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin;
597     if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
598         if ([f parentFrame] != nil)
599             printf("frame '%s' ", [[f name] UTF8String]);
600         printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
601     }
602
603     if (dumpChildFrameScrollPositions) {
604         NSArray *kids = [f childFrames];
605         if (kids)
606             for (unsigned i = 0; i < [kids count]; i++)
607                 dumpFrameScrollPosition([kids objectAtIndex:i]);
608     }
609 }
610
611 static void convertWebResourceDataToString(NSMutableDictionary *resource)
612 {
613     NSString *mimeType = [resource objectForKey:@"WebResourceMIMEType"];
614     if ([mimeType hasPrefix:@"text/"] || [mimeType isEqualToString:@"application/x-javascript"]) {
615         NSData *data = [resource objectForKey:@"WebResourceData"];
616         NSString *dataAsString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
617         [resource setObject:dataAsString forKey:@"WebResourceData"];
618     }
619 }
620
621 static void normalizeWebResourceURL(NSMutableString *webResourceURL, NSString *oldURLBase)
622 {
623     [webResourceURL replaceOccurrencesOfString:oldURLBase
624                                     withString:@"file://"
625                                        options:NSLiteralSearch
626                                          range:NSMakeRange(0, [webResourceURL length])];
627 }
628
629 static void normalizeWebResourceResponse(NSMutableDictionary *propertyList, NSString *oldURLBase)
630 {
631     NSURLResponse *response = nil;
632     NSData *responseData = [propertyList objectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
633     if ([responseData isKindOfClass:[NSData class]]) {
634         // Decode NSURLResponse
635         NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:responseData];
636         response = [unarchiver decodeObjectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
637         [unarchiver finishDecoding];
638         [unarchiver release];
639
640         // Create replacement NSURLReponse
641         NSMutableString *URL = [[[NSMutableString alloc] initWithContentsOfURL:[response URL]] autorelease];
642         normalizeWebResourceURL(URL, oldURLBase);
643         NSURLResponse *newResponse = [[NSURLResponse alloc] initWithURL:[[[NSURL alloc] initWithString:URL] autorelease]
644                                                                MIMEType:[response MIMEType]
645                                                   expectedContentLength:[response expectedContentLength]
646                                                        textEncodingName:[response textEncodingName]];
647         [newResponse autorelease];
648
649         // Encode replacement NSURLResponse
650         NSMutableData *newResponseData = [[NSMutableData alloc] init];
651         NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:newResponseData];
652         [archiver encodeObject:newResponse forKey:@"WebResourceResponse"];
653         [archiver finishEncoding];
654         [archiver release];
655         [propertyList setObject:newResponseData forKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
656         [newResponseData release];
657     }
658 }
659
660 static NSString *serializeWebArchiveToXML(WebArchive *webArchive)
661 {
662     NSString *errorString;
663     NSMutableDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data]
664                                                                          mutabilityOption:NSPropertyListMutableContainersAndLeaves
665                                                                                    format:NULL
666                                                                          errorDescription:&errorString];
667     if (!propertyList)
668         return errorString;
669
670     // Normalize WebResourceResponse and WebResourceURL values in plist for testing
671     NSString *cwdURL = [@"file://" stringByAppendingString:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
672
673     NSMutableArray *resources = [NSMutableArray arrayWithCapacity:1];
674     [resources addObject:propertyList];
675
676     while ([resources count]) {
677         NSMutableDictionary *resourcePropertyList = [resources objectAtIndex:0];
678         [resources removeObjectAtIndex:0];
679
680         NSMutableDictionary *mainResource = [resourcePropertyList objectForKey:@"WebMainResource"];
681         normalizeWebResourceURL([mainResource objectForKey:@"WebResourceURL"], cwdURL);
682         convertWebResourceDataToString(mainResource);
683
684         // Add subframeArchives to list for processing
685         NSMutableArray *subframeArchives = [resourcePropertyList objectForKey:@"WebSubframeArchives"]; // WebSubframeArchivesKey in WebArchive.m
686         if (subframeArchives)
687             [resources addObjectsFromArray:subframeArchives];
688
689         NSMutableArray *subresources = [resourcePropertyList objectForKey:@"WebSubresources"]; // WebSubresourcesKey in WebArchive.m
690         NSEnumerator *enumerator = [subresources objectEnumerator];
691         NSMutableDictionary *subresourcePropertyList;
692         while ((subresourcePropertyList = [enumerator nextObject])) {
693             normalizeWebResourceURL([subresourcePropertyList objectForKey:@"WebResourceURL"], cwdURL);
694             normalizeWebResourceResponse(subresourcePropertyList, cwdURL);
695             convertWebResourceDataToString(subresourcePropertyList);
696         }
697     }
698
699     NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList
700                                                                  format:NSPropertyListXMLFormat_v1_0
701                                                        errorDescription:&errorString];
702     if (!xmlData)
703         return errorString;
704
705     return [[[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] autorelease];
706 }
707
708 static void dump(void)
709 {
710     if (dumpTree) {
711         NSString *result = nil;
712
713         dumpAsText |= [[[[frame dataSource] response] MIMEType] isEqualToString:@"text/plain"];
714         if (dumpAsText) {
715             DOMElement *documentElement = [[frame DOMDocument] documentElement];
716             result = [[(DOMElement *)documentElement innerText] stringByAppendingString:@"\n"];
717         } else if (dumpDOMAsWebArchive) {
718             WebArchive *webArchive = [[frame DOMDocument] webArchive];
719             result = serializeWebArchiveToXML(webArchive);
720         } else if (dumpSourceAsWebArchive) {
721             WebArchive *webArchive = [[frame dataSource] webArchive];
722             result = serializeWebArchiveToXML(webArchive);
723         } else {
724             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
725             if (isSVGW3CTest)
726                 [[frame webView] setFrameSize:NSMakeSize(480, 360)];
727             else 
728                 [[frame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
729             result = [frame renderTreeAsExternalRepresentation];
730         }
731
732         if (!result) {
733             const char *errorMessage;
734             if (dumpAsText)
735                 errorMessage = "[documentElement innerText]";
736             else if (dumpDOMAsWebArchive)
737                 errorMessage = "[[frame DOMDocument] webArchive]";
738             else if (dumpSourceAsWebArchive)
739                 errorMessage = "[[frame dataSource] webArchive]";
740             else
741                 errorMessage = "[frame renderTreeAsExternalRepresentation]";
742             printf("ERROR: nil result from %s", errorMessage);
743         } else {
744             fputs([result UTF8String], stdout);
745             if (!dumpAsText && !dumpDOMAsWebArchive && !dumpSourceAsWebArchive)
746                 dumpFrameScrollPosition(frame);
747         }
748
749         if (dumpBackForwardList) {
750             printf("\n============== Back Forward List ==============\n");
751             WebBackForwardList *bfList = [[frame webView] backForwardList];
752
753             // Print out all items in the list after prevTestBFItem, which was from the previous test
754             // Gather items from the end of the list, the print them out from oldest to newest
755             NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
756             for (int i = [bfList forwardListCount]; i > 0; i--) {
757                 WebHistoryItem *item = [bfList itemAtIndex:i];
758                 // something is wrong if the item from the last test is in the forward part of the b/f list
759                 assert(item != prevTestBFItem);
760                 [itemsToPrint addObject:item];
761             }
762             
763             assert([bfList currentItem] != prevTestBFItem);
764             [itemsToPrint addObject:[bfList currentItem]];
765             int currentItemIndex = [itemsToPrint count] - 1;
766
767             for (int i = -1; i >= -[bfList backListCount]; i--) {
768                 WebHistoryItem *item = [bfList itemAtIndex:i];
769                 if (item == prevTestBFItem)
770                     break;
771                 [itemsToPrint addObject:item];
772             }
773
774             for (int i = [itemsToPrint count]-1; i >= 0; i--) {
775                 dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
776             }
777             [itemsToPrint release];
778             printf("===============================================\n");
779         }
780
781         if (printSeparators)
782             puts("#EOF");
783     }
784     
785     if (dumpPixels) {
786         if (!dumpAsText && !dumpDOMAsWebArchive && !dumpSourceAsWebArchive) {
787             // grab a bitmap from the view
788             WebView* view = [frame webView];
789             NSSize webViewSize = [view frame].size;
790
791             CGContextRef cgContext = CGBitmapContextCreate(screenCaptureBuffer, webViewSize.width, webViewSize.height, 8, webViewSize.width * 4, sharedColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedLast);
792
793             NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
794             NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
795             [NSGraphicsContext setCurrentContext:nsContext];
796
797             if (readFromWindow) {
798                 NSBitmapImageRep *imageRep;
799                 [view displayIfNeeded];
800                 [view lockFocus];
801                 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
802                 [view unlockFocus];
803                 [imageRep draw];
804                 [imageRep release];
805             } else if (!testRepaint)
806                 [view displayRectIgnoringOpacity:NSMakeRect(0, 0, webViewSize.width, webViewSize.height) inContext:nsContext];
807             else if (!repaintSweepHorizontally) {
808                 NSRect line = NSMakeRect(0, 0, webViewSize.width, 1);
809                 while (line.origin.y < webViewSize.height) {
810                     [view displayRectIgnoringOpacity:line inContext:nsContext];
811                     line.origin.y++;
812                 }
813             } else {
814                 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
815                 while (column.origin.x < webViewSize.width) {
816                     [view displayRectIgnoringOpacity:column inContext:nsContext];
817                     column.origin.x++;
818                 }
819             }
820             if (dumpSelectionRect) {
821                 NSView *documentView = [[frame frameView] documentView];
822                 if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) {
823                     [[NSColor redColor] set];
824                     [NSBezierPath strokeRect:[documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]];
825                 }
826             }
827
828             [NSGraphicsContext setCurrentContext:savedContext];
829             
830             CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
831             CGContextRelease(cgContext);
832
833             // compute the actual hash to compare to the expected image's hash
834             NSString *actualHash = md5HashStringForBitmap(bitmapImage);
835             printf("\nActualHash: %s\n", [actualHash UTF8String]);
836
837             BOOL dumpImage;
838             if (dumpAllPixels)
839                 dumpImage = YES;
840             else {
841                 // FIXME: It's unfortunate that we hardcode the file naming scheme here.
842                 // At one time, the perl script had all the knowledge about file layout.
843                 // Some day we should restore that setup by passing in more parameters to this tool.
844                 NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
845                 NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
846                 NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
847                 NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
848
849                 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
850
851                 /// send the image to stdout if the hash mismatches or if there's no file in the file system
852                 dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0;
853             }
854             
855             if (dumpImage) {
856                 CFMutableDataRef imageData = CFDataCreateMutable(0, 0);
857                 CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0);
858                 CGImageDestinationAddImage(imageDest, bitmapImage, 0);
859                 CGImageDestinationFinalize(imageDest);
860                 CFRelease(imageDest);
861                 printf("Content-length: %lu\n", CFDataGetLength(imageData));
862                 fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout);
863                 CFRelease(imageData);
864             }
865
866             CGImageRelease(bitmapImage);
867         }
868
869         printf("#EOF\n");
870     }
871     
872     fflush(stdout);
873
874     if (paint)
875         displayWebView();
876     
877     done = YES;
878 }
879
880 @implementation WaitUntilDoneDelegate
881
882 // Exec messages in the work queue until they're all done, or one of them starts a new load
883 - (void)processWork:(id)dummy
884 {
885     // quit doing work once a load is in progress
886     while ([workQueue count] > 0 && !topLoadingFrame) {
887         [[workQueue objectAtIndex:0] invoke];
888         [workQueue removeObjectAtIndex:0];
889     }
890     
891     // if we didn't start a new load, then we finished all the commands, so we're ready to dump state
892     if (!topLoadingFrame && !waitToDump)
893         dump();
894 }
895
896 - (void)webView:(WebView *)c locationChangeDone:(NSError *)error forDataSource:(WebDataSource *)dataSource
897 {
898     if ([dataSource webFrame] == topLoadingFrame) {
899         topLoadingFrame = nil;
900         workQueueFrozen = YES;      // first complete load freezes the queue for the rest of this test
901         if (!waitToDump) {
902             if ([workQueue count] > 0)
903                 [self performSelector:@selector(processWork:) withObject:nil afterDelay:0];
904             else
905                 dump();
906         }
907     }
908 }
909
910 - (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)f
911 {
912     ASSERT([f provisionalDataSource]);
913     // Make sure we only set this once per test.  If it gets cleared, and then set again, we might
914     // end up doing two dumps for one test.
915     if (!topLoadingFrame && !done)
916         topLoadingFrame = f;
917 }
918
919 - (void)webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)f
920 {
921     ASSERT(![f provisionalDataSource]);
922     ASSERT([f dataSource]);
923
924     windowIsKey = YES;
925     NSView *documentView = [[frame frameView] documentView];
926     [[[frame webView] window] makeFirstResponder:documentView];
927     if ([documentView isKindOfClass:[WebHTMLView class]])
928         [(WebHTMLView *)documentView _updateActiveState];
929 }
930
931 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
932 {
933     ASSERT([frame provisionalDataSource]);
934
935     [self webView:sender locationChangeDone:error forDataSource:[frame provisionalDataSource]];
936 }
937
938 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
939 {
940     // FIXME: This call to displayIfNeeded can be removed when <rdar://problem/5092361> is fixed.
941     // After that is fixed, we will reenable painting after WebCore is done loading the document, 
942     // and this call will no longer be needed.
943     if ([[sender mainFrame] isEqual:frame])
944         [sender displayIfNeeded];
945     [self webView:sender locationChangeDone:nil forDataSource:[frame dataSource]];
946     [navigationController webView:sender didFinishLoadForFrame:frame];
947 }
948
949 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
950 {
951     ASSERT(![frame provisionalDataSource]);
952     ASSERT([frame dataSource]);
953
954     [self webView:sender locationChangeDone:error forDataSource:[frame dataSource]];
955 }
956
957 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)obj 
958
959     LayoutTestController *ltc = [[LayoutTestController alloc] init];
960     [obj setValue:ltc forKey:@"layoutTestController"];
961     [ltc release];
962     
963     EventSendingController *esc = [[EventSendingController alloc] init];
964     [obj setValue:esc forKey:@"eventSender"];
965     [esc release];
966     
967     TextInputController *tic = [[TextInputController alloc] initWithWebView:sender];
968     [obj setValue:tic forKey:@"textInputController"];
969     [tic release];
970     
971     AppleScriptController *asc = [[AppleScriptController alloc] initWithWebView:sender];
972     [obj setValue:asc forKey:@"appleScriptController"];
973     [asc release];
974     
975     GCController *gcc = [[GCController alloc] init];
976     [obj setValue:gcc forKey:@"GCController"];
977     [gcc release];
978     
979     [obj setValue:navigationController forKey:@"navigationController"];
980     
981     ObjCPlugin *plugin = [[ObjCPlugin alloc] init];
982     [obj setValue:plugin forKey:@"objCPlugin"];
983     [plugin release];
984     
985     ObjCPluginFunction *pluginFunction = [[ObjCPluginFunction alloc] init];
986     [obj setValue:pluginFunction forKey:@"objCPluginFunction"];
987     [pluginFunction release];
988 }
989
990 - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
991 {
992     if (dumpTitleChanges)
993         printf("TITLE CHANGED: %s\n", [title UTF8String]);
994 }
995
996 @end
997
998 @implementation LayoutTestController
999
1000 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
1001 {
1002     if (aSelector == @selector(waitUntilDone)
1003             || aSelector == @selector(notifyDone)
1004             || aSelector == @selector(dumpAsText)
1005             || aSelector == @selector(dumpDOMAsWebArchive)
1006             || aSelector == @selector(dumpSourceAsWebArchive)
1007             || aSelector == @selector(dumpTitleChanges)
1008             || aSelector == @selector(dumpBackForwardList)
1009             || aSelector == @selector(dumpChildFrameScrollPositions)
1010             || aSelector == @selector(dumpEditingCallbacks)
1011             || aSelector == @selector(dumpResourceLoadCallbacks)
1012             || aSelector == @selector(setWindowIsKey:)
1013             || aSelector == @selector(setMainFrameIsFirstResponder:)
1014             || aSelector == @selector(dumpSelectionRect)
1015             || aSelector == @selector(display)
1016             || aSelector == @selector(testRepaint)
1017             || aSelector == @selector(repaintSweepHorizontally)
1018             || aSelector == @selector(queueBackNavigation:)
1019             || aSelector == @selector(queueForwardNavigation:)
1020             || aSelector == @selector(queueReload)
1021             || aSelector == @selector(queueScript:)
1022             || aSelector == @selector(queueLoad:target:)
1023             || aSelector == @selector(clearBackForwardList)
1024             || aSelector == @selector(keepWebHistory)
1025             || aSelector == @selector(setAcceptsEditing:)
1026             || aSelector == @selector(setTabKeyCyclesThroughElements:)
1027             || aSelector == @selector(storeWebScriptObject:)
1028             || aSelector == @selector(accessStoredWebScriptObject)
1029             || aSelector == @selector(setUserStyleSheetLocation:)
1030             || aSelector == @selector(setUserStyleSheetEnabled:)
1031             || aSelector == @selector(objCClassNameOf:)
1032             || aSelector == @selector(addDisallowedURL:)    
1033             || aSelector == @selector(setCanOpenWindows)
1034             || aSelector == @selector(setCallCloseOnWebViews:)
1035             || aSelector == @selector(setCloseRemainingWindowsWhenComplete:))
1036         return NO;
1037     return YES;
1038 }
1039
1040 + (NSString *)webScriptNameForSelector:(SEL)aSelector
1041 {
1042     if (aSelector == @selector(setWindowIsKey:))
1043         return @"setWindowIsKey";
1044     if (aSelector == @selector(setMainFrameIsFirstResponder:))
1045         return @"setMainFrameIsFirstResponder";
1046     if (aSelector == @selector(queueBackNavigation:))
1047         return @"queueBackNavigation";
1048     if (aSelector == @selector(queueForwardNavigation:))
1049         return @"queueForwardNavigation";
1050     if (aSelector == @selector(queueScript:))
1051         return @"queueScript";
1052     if (aSelector == @selector(queueLoad:target:))
1053         return @"queueLoad";
1054     if (aSelector == @selector(setAcceptsEditing:))
1055         return @"setAcceptsEditing";
1056     if (aSelector == @selector(setTabKeyCyclesThroughElements:))
1057         return @"setTabKeyCyclesThroughElements";
1058     if (aSelector == @selector(storeWebScriptObject:))
1059         return @"storeWebScriptObject";
1060     if (aSelector == @selector(setUserStyleSheetLocation:))
1061         return @"setUserStyleSheetLocation";
1062     if (aSelector == @selector(setUserStyleSheetEnabled:))
1063         return @"setUserStyleSheetEnabled";
1064     if (aSelector == @selector(objCClassNameOf:))
1065         return @"objCClassName";
1066     if (aSelector == @selector(addDisallowedURL:))
1067         return @"addDisallowedURL";
1068     if (aSelector == @selector(setCallCloseOnWebViews:))
1069         return @"setCallCloseOnWebViews";
1070     if (aSelector == @selector(setCloseRemainingWindowsWhenComplete:))
1071         return @"setCloseRemainingWindowsWhenComplete";
1072
1073     return nil;
1074 }
1075
1076 - (void)clearBackForwardList
1077 {
1078     WebBackForwardList *backForwardList = [[frame webView] backForwardList];
1079     WebHistoryItem *item = [[backForwardList currentItem] retain];
1080
1081     // We clear the history by setting the back/forward list's capacity to 0
1082     // then restoring it back and adding back the current item.
1083     int capacity = [backForwardList capacity];
1084     [backForwardList setCapacity:0];
1085     [backForwardList setCapacity:capacity];
1086     [backForwardList addItem:item];
1087     [backForwardList goToItem:item];
1088     [item release];
1089 }
1090
1091 - (void)setCloseRemainingWindowsWhenComplete:(BOOL)closeWindows
1092 {
1093     closeRemainingWindowsWhenComplete = closeWindows;
1094 }
1095
1096 - (void)keepWebHistory
1097 {
1098     if (![WebHistory optionalSharedHistory]) {
1099         WebHistory *history = [[WebHistory alloc] init];
1100         [WebHistory setOptionalSharedHistory:history];
1101         [history release];
1102     }
1103 }
1104
1105 - (void)setCallCloseOnWebViews:(BOOL)callClose
1106 {
1107     closeWebViews = callClose;
1108 }
1109
1110 - (void)setCanOpenWindows
1111 {
1112     canOpenWindows = YES;
1113 }
1114
1115 - (void)waitUntilDone 
1116 {
1117     waitToDump = YES;
1118 }
1119
1120 - (void)notifyDone
1121 {
1122     if (waitToDump && !topLoadingFrame && [workQueue count] == 0)
1123         dump();
1124     waitToDump = NO;
1125 }
1126
1127 - (void)dumpAsText
1128 {
1129     dumpAsText = YES;
1130 }
1131
1132 - (void)addDisallowedURL:(NSString *)urlString
1133 {
1134     if (!disallowedURLs)
1135         disallowedURLs = [[NSMutableSet alloc] init];
1136     
1137     
1138     // Canonicalize the URL
1139     NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
1140     request = [NSURLProtocol canonicalRequestForRequest:request];
1141     
1142     [disallowedURLs addObject:[request URL]];
1143 }
1144
1145 - (void)setUserStyleSheetLocation:(NSString *)path
1146 {
1147     NSURL *url = [NSURL URLWithString:path];
1148     [[WebPreferences standardPreferences] setUserStyleSheetLocation:url];
1149 }
1150
1151 - (void)setUserStyleSheetEnabled:(BOOL)flag
1152 {
1153     [[WebPreferences standardPreferences] setUserStyleSheetEnabled:flag];
1154 }
1155
1156 - (void)dumpDOMAsWebArchive
1157 {
1158     dumpDOMAsWebArchive = YES;
1159 }
1160
1161 - (void)dumpSourceAsWebArchive
1162 {
1163     dumpSourceAsWebArchive = YES;
1164 }
1165
1166 - (void)dumpSelectionRect
1167 {
1168     dumpSelectionRect = YES;
1169 }
1170
1171 - (void)dumpTitleChanges
1172 {
1173     dumpTitleChanges = YES;
1174 }
1175
1176 - (void)dumpBackForwardList
1177 {
1178     dumpBackForwardList = YES;
1179 }
1180
1181 - (void)dumpChildFrameScrollPositions
1182 {
1183     dumpChildFrameScrollPositions = YES;
1184 }
1185
1186 - (void)dumpEditingCallbacks
1187 {
1188     shouldDumpEditingCallbacks = YES;
1189 }
1190
1191 - (void)dumpResourceLoadCallbacks
1192 {
1193     shouldDumpResourceLoadCallbacks = YES;
1194 }
1195
1196 - (void)setWindowIsKey:(BOOL)flag
1197 {
1198     windowIsKey = flag;
1199     NSView *documentView = [[frame frameView] documentView];
1200     if ([documentView isKindOfClass:[WebHTMLView class]])
1201         [(WebHTMLView *)documentView _updateActiveState];
1202 }
1203
1204 - (void)setMainFrameIsFirstResponder:(BOOL)flag
1205 {
1206     NSView *documentView = [[frame frameView] documentView];
1207     
1208     NSResponder *firstResponder = flag ? documentView : nil;
1209     [[[frame webView] window] makeFirstResponder:firstResponder];
1210         
1211     if ([documentView isKindOfClass:[WebHTMLView class]])
1212         [(WebHTMLView *)documentView _updateActiveState];
1213 }
1214
1215 - (void)display
1216 {
1217     displayWebView();
1218 }
1219
1220 - (void)testRepaint
1221 {
1222     testRepaint = YES;
1223 }
1224
1225 - (void)repaintSweepHorizontally
1226 {
1227     repaintSweepHorizontally = YES;
1228 }
1229
1230 - (id)invokeUndefinedMethodFromWebScript:(NSString *)name withArguments:(NSArray *)args
1231 {
1232     return nil;
1233 }
1234
1235 - (void)_addWorkForTarget:(id)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2
1236 {
1237     if (workQueueFrozen)
1238         return;
1239     NSMethodSignature *sig = [target methodSignatureForSelector:selector];
1240     NSInvocation *work = [NSInvocation invocationWithMethodSignature:sig];
1241     [work retainArguments];
1242     [work setTarget:target];
1243     [work setSelector:selector];
1244     if (arg1) {
1245         [work setArgument:&arg1 atIndex:2];
1246         if (arg2)
1247             [work setArgument:&arg2 atIndex:3];
1248     }
1249     [workQueue addObject:work];
1250 }
1251
1252 - (void)_doLoad:(NSURL *)url target:(NSString *)target
1253 {
1254     WebFrame *targetFrame;
1255     if (target && ![target isKindOfClass:[WebUndefined class]])
1256         targetFrame = [frame findFrameNamed:target];
1257     else
1258         targetFrame = frame;
1259     [targetFrame loadRequest:[NSURLRequest requestWithURL:url]];
1260 }
1261
1262 - (void)_doBackOrForwardNavigation:(NSNumber *)index
1263 {
1264     int bfIndex = [index intValue];
1265     if (bfIndex == 1)
1266         [[frame webView] goForward];
1267     if (bfIndex == -1)
1268         [[frame webView] goBack];
1269     else {        
1270         WebBackForwardList *bfList = [[frame webView] backForwardList];
1271         [[frame webView] goToBackForwardItem:[bfList itemAtIndex:bfIndex]];
1272     }
1273 }
1274
1275 - (void)queueBackNavigation:(int)howFarBack
1276 {
1277     [self _addWorkForTarget:self selector:@selector(_doBackOrForwardNavigation:) arg1:[NSNumber numberWithInt:-howFarBack] arg2:nil];
1278 }
1279
1280 - (void)queueForwardNavigation:(int)howFarForward
1281 {
1282     [self _addWorkForTarget:self selector:@selector(_doBackOrForwardNavigation:) arg1:[NSNumber numberWithInt:howFarForward] arg2:nil];
1283 }
1284
1285 - (void)queueReload
1286 {
1287     [self _addWorkForTarget:[frame webView] selector:@selector(reload:) arg1:self arg2:nil];
1288 }
1289
1290 - (void)queueScript:(NSString *)script
1291 {
1292     [self _addWorkForTarget:[frame webView] selector:@selector(stringByEvaluatingJavaScriptFromString:) arg1:script arg2:nil];
1293 }
1294
1295 - (void)queueLoad:(NSString *)URLString target:(NSString *)target
1296 {
1297     NSURL *URL = [NSURL URLWithString:URLString relativeToURL:[[[frame dataSource] response] URL]];
1298     [self _addWorkForTarget:self selector:@selector(_doLoad:target:) arg1:URL arg2:target];
1299 }
1300
1301 - (void)setAcceptsEditing:(BOOL)newAcceptsEditing
1302 {
1303     [(EditingDelegate *)[[frame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing];
1304 }
1305
1306 - (void)setTabKeyCyclesThroughElements:(BOOL)newTabKeyCyclesThroughElements
1307 {
1308     [[frame webView] setTabKeyCyclesThroughElements:newTabKeyCyclesThroughElements];
1309 }
1310
1311 - (void)storeWebScriptObject:(WebScriptObject *)webScriptObject
1312 {
1313     if (webScriptObject == storedWebScriptObject)
1314         return;
1315
1316     [storedWebScriptObject release];
1317     storedWebScriptObject = [webScriptObject retain];
1318 }
1319
1320 - (void)accessStoredWebScriptObject
1321 {
1322     [storedWebScriptObject callWebScriptMethod:@"" withArguments:nil];
1323     [storedWebScriptObject evaluateWebScript:@""];
1324     [storedWebScriptObject setValue:[WebUndefined undefined] forKey:@"key"];
1325     [storedWebScriptObject valueForKey:@"key"];
1326     [storedWebScriptObject removeWebScriptKey:@"key"];
1327     [storedWebScriptObject stringRepresentation];
1328     [storedWebScriptObject webScriptValueAtIndex:0];
1329     [storedWebScriptObject setWebScriptValueAtIndex:0 value:[WebUndefined undefined]];
1330     [storedWebScriptObject setException:@"exception"];
1331 }
1332
1333 - (void)dealloc
1334 {
1335     [storedWebScriptObject release];
1336     [super dealloc];
1337 }
1338
1339 - (NSString *)objCClassNameOf:(id)object
1340 {
1341     return NSStringFromClass([object class]);
1342 }
1343
1344 @end
1345
1346 static void runTest(const char *pathOrURL)
1347 {
1348     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
1349     if (!pathOrURLString) {
1350         fprintf(stderr, "can't parse filename as UTF-8\n");
1351         return;
1352     }
1353
1354     CFURLRef URL;
1355     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")))
1356         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
1357     else
1358         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
1359     
1360     if (!URL) {
1361         CFRelease(pathOrURLString);
1362         fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
1363         return;
1364     }
1365
1366     [(EditingDelegate *)[[frame webView] editingDelegate] setAcceptsEditing:YES];
1367     [[frame webView] setTabKeyCyclesThroughElements: YES];
1368     done = NO;
1369     topLoadingFrame = nil;
1370     waitToDump = NO;
1371     dumpAsText = NO;
1372     dumpDOMAsWebArchive = NO;
1373     dumpSourceAsWebArchive = NO;
1374     dumpChildFrameScrollPositions = NO;
1375     shouldDumpEditingCallbacks = NO;
1376     shouldDumpResourceLoadCallbacks = NO;
1377     dumpSelectionRect = NO;
1378     dumpTitleChanges = NO;
1379     dumpBackForwardList = NO;
1380     readFromWindow = NO;
1381     canOpenWindows = NO;
1382     closeWebViews = YES;
1383     testRepaint = testRepaintDefault;
1384     repaintSweepHorizontally = repaintSweepHorizontallyDefault;
1385     if ([WebHistory optionalSharedHistory])
1386         [WebHistory setOptionalSharedHistory:nil];
1387     lastMousePosition = NSMakePoint(0, 0);
1388     [disallowedURLs removeAllObjects];
1389     
1390     if (currentTest != nil)
1391         CFRelease(currentTest);
1392     currentTest = (NSString *)pathOrURLString;
1393     [prevTestBFItem release];
1394     prevTestBFItem = [[[[frame webView] backForwardList] currentItem] retain];
1395     [workQueue removeAllObjects];
1396     workQueueFrozen = NO;
1397
1398     BOOL _shouldIgnoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(CFURLGetString(URL));
1399     if (_shouldIgnoreWebCoreNodeLeaks)
1400         [WebCoreStatistics startIgnoringWebCoreNodeLeaks];
1401
1402     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1403     [frame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
1404     CFRelease(URL);
1405     [pool release];
1406     while (!done) {
1407         pool = [[NSAutoreleasePool alloc] init];
1408         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
1409         [pool release];
1410     }
1411     pool = [[NSAutoreleasePool alloc] init];
1412     [[frame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1413     
1414     if (closeRemainingWindowsWhenComplete) {
1415         NSArray* array = [(NSArray *)allWindowsRef copy];
1416         
1417         unsigned count = [array count];
1418         for (unsigned i = 0; i < count; i++) {
1419             NSWindow *window = [array objectAtIndex:i];
1420
1421             // Don't try to close the main window
1422             if (window == [[frame webView] window])
1423                 continue;
1424             
1425             WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
1426
1427             [webView close];
1428             [window close];
1429         }
1430         [array release];
1431     }
1432     
1433     [pool release];
1434     
1435     // We should only have our main window left when we're done
1436     assert(CFArrayGetCount(allWindowsRef) == 1);
1437     assert(CFArrayGetValueAtIndex(allWindowsRef, 0) == [[frame webView] window]);
1438     
1439     if (_shouldIgnoreWebCoreNodeLeaks)
1440         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
1441 }
1442
1443 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
1444 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
1445 {
1446     MD5_CTX md5Context;
1447     unsigned char hash[16];
1448     
1449     unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap);
1450     assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
1451     unsigned bytesPerPixel = bitsPerPixel / 8;
1452     unsigned pixelsHigh = CGImageGetHeight(bitmap);
1453     unsigned pixelsWide = CGImageGetWidth(bitmap);
1454     unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap);
1455     assert(bytesPerRow >= (pixelsWide * bytesPerPixel));
1456     
1457     MD5_Init(&md5Context);
1458     unsigned char *bitmapData = screenCaptureBuffer;
1459     for (unsigned row = 0; row < pixelsHigh; row++) {
1460         MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel);
1461         bitmapData += bytesPerRow;
1462     }
1463     MD5_Final(hash, &md5Context);
1464     
1465     char hex[33] = "";
1466     for (int i = 0; i < 16; i++) {
1467        snprintf(hex, 33, "%s%02x", hex, hash[i]);
1468     }
1469
1470     return [NSString stringWithUTF8String:hex];
1471 }
1472
1473 static void displayWebView()
1474 {
1475     NSView *webView = [frame webView];
1476     [webView display];
1477     [webView lockFocus];
1478     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
1479     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
1480     [webView unlockFocus];
1481     readFromWindow = YES;
1482 }
1483
1484 @implementation DumpRenderTreePasteboard
1485
1486 // Return a local pasteboard so we don't disturb the real pasteboards when running tests.
1487 + (NSPasteboard *)_pasteboardWithName:(NSString *)name
1488 {
1489     static int number = 0;
1490     if (!name)
1491         name = [NSString stringWithFormat:@"LocalPasteboard%d", ++number];
1492     LocalPasteboard *pasteboard = [localPasteboards objectForKey:name];
1493     if (pasteboard)
1494         return pasteboard;
1495     pasteboard = [[LocalPasteboard alloc] init];
1496     [localPasteboards setObject:pasteboard forKey:name];
1497     [pasteboard release];
1498     return pasteboard;
1499 }
1500
1501 // Convenience method for JS so that it doesn't have to try and create a NSArray on the objc side instead
1502 // of the usual WebScriptObject that is passed around
1503 - (int)declareType:(NSString *)type owner:(id)newOwner
1504 {
1505     return [self declareTypes:[NSArray arrayWithObject:type] owner:newOwner];
1506 }
1507
1508 @end
1509
1510 @implementation LocalPasteboard
1511
1512 + (id)alloc
1513 {
1514     return NSAllocateObject(self, 0, 0);
1515 }
1516
1517 - (id)init
1518 {
1519     typesArray = [[NSMutableArray alloc] init];
1520     typesSet = [[NSMutableSet alloc] init];
1521     dataByType = [[NSMutableDictionary alloc] init];
1522     return self;
1523 }
1524
1525 - (void)dealloc
1526 {
1527     [typesArray release];
1528     [typesSet release];
1529     [dataByType release];
1530     [super dealloc];
1531 }
1532
1533 - (NSString *)name
1534 {
1535     return nil;
1536 }
1537
1538 - (void)releaseGlobally
1539 {
1540 }
1541
1542 - (int)declareTypes:(NSArray *)newTypes owner:(id)newOwner
1543 {
1544     [typesArray removeAllObjects];
1545     [typesSet removeAllObjects];
1546     [dataByType removeAllObjects];
1547     return [self addTypes:newTypes owner:newOwner];
1548 }
1549
1550 - (int)addTypes:(NSArray *)newTypes owner:(id)newOwner
1551 {
1552     unsigned count = [newTypes count];
1553     unsigned i;
1554     for (i = 0; i < count; ++i) {
1555         NSString *type = [newTypes objectAtIndex:i];
1556         NSString *setType = [typesSet member:type];
1557         if (!setType) {
1558             setType = [type copy];
1559             [typesArray addObject:setType];
1560             [typesSet addObject:setType];
1561             [setType release];
1562         }
1563         if (newOwner && [newOwner respondsToSelector:@selector(pasteboard:provideDataForType:)])
1564             [newOwner pasteboard:self provideDataForType:setType];
1565     }
1566     return ++changeCount;
1567 }
1568
1569 - (int)changeCount
1570 {
1571     return changeCount;
1572 }
1573
1574 - (NSArray *)types
1575 {
1576     return typesArray;
1577 }
1578
1579 - (NSString *)availableTypeFromArray:(NSArray *)types
1580 {
1581     unsigned count = [types count];
1582     unsigned i;
1583     for (i = 0; i < count; ++i) {
1584         NSString *type = [types objectAtIndex:i];
1585         NSString *setType = [typesSet member:type];
1586         if (setType)
1587             return setType;
1588     }
1589     return nil;
1590 }
1591
1592 - (BOOL)setData:(NSData *)data forType:(NSString *)dataType
1593 {
1594     if (data == nil)
1595         data = [NSData data];
1596     if (![typesSet containsObject:dataType])
1597         return NO;
1598     [dataByType setObject:data forKey:dataType];
1599     ++changeCount;
1600     return YES;
1601 }
1602
1603 - (NSData *)dataForType:(NSString *)dataType
1604 {
1605     return [dataByType objectForKey:dataType];
1606 }
1607
1608 - (BOOL)setPropertyList:(id)propertyList forType:(NSString *)dataType;
1609 {
1610     CFDataRef data = NULL;
1611     if (propertyList)
1612         data = CFPropertyListCreateXMLData(NULL, propertyList);
1613     BOOL result = [self setData:(NSData *)data forType:dataType];
1614     if (data)
1615         CFRelease(data);
1616     return result;
1617 }
1618
1619 - (BOOL)setString:(NSString *)string forType:(NSString *)dataType
1620 {
1621     CFDataRef data = NULL;
1622     if (string) {
1623         if ([string length] == 0)
1624             data = CFDataCreate(NULL, NULL, 0);
1625         else
1626             data = CFStringCreateExternalRepresentation(NULL, (CFStringRef)string, kCFStringEncodingUTF8, 0);
1627     }
1628     BOOL result = [self setData:(NSData *)data forType:dataType];
1629     if (data)
1630         CFRelease(data);
1631     return result;
1632 }
1633
1634 @end
1635
1636 static CFArrayCallBacks NonRetainingArrayCallbacks = {
1637     0,
1638     NULL,
1639     NULL,
1640     CFCopyDescription,
1641     CFEqual
1642 };
1643
1644 @implementation DumpRenderTreeWindow
1645
1646 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
1647 {
1648     if (!allWindowsRef)
1649         allWindowsRef = CFArrayCreateMutable(NULL, 0, &NonRetainingArrayCallbacks);
1650
1651     CFArrayAppendValue(allWindowsRef, self);
1652             
1653     return [super initWithContentRect:contentRect styleMask:styleMask backing:bufferingType defer:deferCreation];
1654 }
1655
1656 - (void)dealloc
1657 {
1658     CFRange arrayRange = CFRangeMake(0, CFArrayGetCount(allWindowsRef));
1659     CFIndex i = CFArrayGetFirstIndexOfValue(allWindowsRef, arrayRange, self);
1660     assert(i != -1);
1661
1662     CFArrayRemoveValueAtIndex(allWindowsRef, i);
1663     [super dealloc];
1664 }
1665
1666 - (BOOL)isKeyWindow
1667 {
1668     return windowIsKey;
1669 }
1670
1671 - (void)keyDown:(id)sender
1672 {
1673     // Do nothing, avoiding the beep we'd otherwise get from NSResponder,
1674     // once we get to the end of the responder chain.
1675 }
1676
1677 @end
1678
1679 @implementation DumpRenderTreeEvent
1680
1681 + (NSPoint)mouseLocation
1682 {
1683     return [[[frame webView] window] convertBaseToScreen:lastMousePosition];
1684 }
1685
1686 @end