LayoutTests:
[WebKit-https.git] / WebKitTools / DumpRenderTree / DumpRenderTree.m
1 /*
2  * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28  
29 #import "DumpRenderTree.h"
30
31 #import "AppleScriptController.h"
32 #import "EditingDelegate.h"
33 #import "EventSendingController.h"
34 #import "GCController.h"
35 #import "NavigationController.h"
36 #import "ObjCPlugin.h"
37 #import "ObjCPluginFunction.h"
38 #import "TextInputController.h"
39 #import "UIDelegate.h"
40 #import <ApplicationServices/ApplicationServices.h> // for CMSetDefaultProfileBySpace
41 #import <CoreFoundation/CoreFoundation.h>
42 #import <JavaScriptCore/JavaScriptCore.h>
43 #import <WebKit/DOMElementPrivate.h>
44 #import <WebKit/DOMExtensions.h>
45 #import <WebKit/DOMRange.h>
46 #import <WebKit/WebBackForwardList.h>
47 #import <WebKit/WebCoreStatistics.h>
48 #import <WebKit/WebDataSource.h>
49 #import <WebKit/WebDocumentPrivate.h>
50 #import <WebKit/WebEditingDelegate.h>
51 #import <WebKit/WebFramePrivate.h>
52 #import <WebKit/WebFrameView.h>
53 #import <WebKit/WebHTMLViewPrivate.h>
54 #import <WebKit/WebHistory.h>
55 #import <WebKit/WebHistoryItemPrivate.h>
56 #import <WebKit/WebPluginDatabase.h>
57 #import <WebKit/WebPreferences.h>
58 #import <WebKit/WebPreferencesPrivate.h>
59 #import <WebKit/WebView.h>
60 #import <getopt.h>
61 #import <malloc/malloc.h>
62 #import <objc/objc-runtime.h>                       // for class_poseAs
63 #import <pthread.h>
64
65 #define COMMON_DIGEST_FOR_OPENSSL
66 #import <CommonCrypto/CommonDigest.h>               // for MD5 functions
67
68 @interface DumpRenderTreeWindow : NSWindow
69 @end
70
71 @interface DumpRenderTreePasteboard : NSPasteboard
72 @end
73
74 @interface DumpRenderTreeEvent : NSEvent
75 @end
76
77 @interface WaitUntilDoneDelegate : NSObject
78 @end
79
80 @interface LayoutTestController : NSObject
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 BOOL windowIsKey = YES;
93 WebFrame *frame = 0;
94 BOOL shouldDumpEditingCallbacks;
95
96 static void runTest(const char *pathOrURL);
97 static NSString *md5HashStringForBitmap(CGImageRef bitmap);
98
99 volatile BOOL done;
100 static NavigationController *navigationController;
101
102 // Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
103 // - There is no load in progress
104 // - There is no work queued up (see workQueue var, below)
105 // - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
106 //       and notifyDone was called subsequently.
107 // Note that the call to notifyDone and the end of the load can happen in either order.
108
109 // This is the topmost frame that is loading, during a given load, or nil when no load is 
110 // in progress.  Usually this is the same as the main frame, but not always.  In the case
111 // where a frameset is loaded, and then new content is loaded into one of the child frames,
112 // that child frame is the "topmost frame that is loading".
113 static WebFrame *topLoadingFrame;     // !nil iff a load is in progress
114 static BOOL waitToDump;     // TRUE if waitUntilDone() has been called, but notifyDone() has not yet been called
115
116 static BOOL dumpAsText;
117 static BOOL dumpAsWebArchive;
118 static BOOL dumpSelectionRect;
119 static BOOL dumpTitleChanges;
120 static BOOL dumpBackForwardList;
121 static BOOL dumpChildFrameScrollPositions;
122 static int dumpPixels = NO;
123 static int dumpAllPixels = NO;
124 static int threaded = NO;
125 static BOOL readFromWindow = NO;
126 static int testRepaintDefault = NO;
127 static BOOL testRepaint = NO;
128 static int repaintSweepHorizontallyDefault = NO;
129 static BOOL repaintSweepHorizontally = NO;
130 static int dumpTree = YES;
131 static BOOL printSeparators;
132 static NSString *currentTest = nil;
133 static NSMutableDictionary *localPasteboards;
134 static WebHistoryItem *prevTestBFItem = nil;  // current b/f item at the end of the previous test
135 static unsigned char* screenCaptureBuffer;
136 static CGColorSpaceRef sharedColorSpace;
137 // a queue of NSInvocations, queued by callouts from the test, to be exec'ed when the load is done
138 static NSMutableArray *workQueue = nil;
139 // to prevent infinite loops, only the first page of a test can add to a work queue
140 // (since we may well come back to that same page)
141 static BOOL workQueueFrozen;
142
143 const unsigned maxViewHeight = 600;
144 const unsigned maxViewWidth = 800;
145
146 // Loops forever, running a script
147 void* runJavaScriptThread(void* arg)
148 {
149     char* script =
150     " \
151     var array = []; \
152     for (var i = 0; i < 1000; i++) { \
153         array.push(String(i)); \
154     } \
155     ";
156
157     while(1) {
158         JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
159         JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
160
161         JSValueRef exception = NULL;
162         JSEvaluateScript(ctx, scriptRef, NULL, NULL, 0, &exception);
163         assert(!exception);
164         
165         JSGlobalContextRelease(ctx);
166         JSStringRelease(scriptRef);
167         
168         JSGarbageCollect(ctx);
169
170         pthread_testcancel(); // Allow thread termination
171     }
172 }
173
174 static pthread_t javaScriptThread;
175
176 static void startJavaScriptThread(void)
177 {
178     assert(!javaScriptThread);
179     pthread_create(&javaScriptThread, NULL, runJavaScriptThread, NULL);
180 }
181
182 static void stopJavaScriptThread(void)
183 {
184     assert(javaScriptThread);
185     pthread_cancel(javaScriptThread);
186     javaScriptThread = NULL;
187 }
188
189 static CMProfileRef currentColorProfile = 0;
190 static void restoreColorSpace(int ignored)
191 {
192     if (currentColorProfile) {
193         int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
194         if (error)
195             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);
196         currentColorProfile = 0;
197     }
198 }
199
200 static void crashHandler(int sig)
201 {
202     fprintf(stderr, "%s\n", strsignal(sig));
203     restoreColorSpace(0);
204     exit(128 + sig);
205 }
206
207 static void setDefaultColorProfileToRGB(void)
208 {
209     CMProfileRef genericProfile = [[NSColorSpace genericRGBColorSpace] colorSyncProfile];
210     CMProfileRef previousProfile;
211     int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
212     if (error) {
213         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);
214         return;
215     }
216     if (previousProfile == genericProfile)
217         return;
218     CFStringRef previousProfileName;
219     CFStringRef genericProfileName;
220     char previousProfileNameString[1024];
221     char genericProfileNameString[1024];
222     CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
223     CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
224     CFStringGetCString(previousProfileName, previousProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
225     CFStringGetCString(genericProfileName, genericProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
226     CFRelease(genericProfileName);
227     CFRelease(previousProfileName);
228     
229     fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n", previousProfileNameString, genericProfileNameString);
230     fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n");
231     fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n");
232     
233     if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile)))
234         fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result.  (Error: %i)",
235             genericProfileNameString, error);
236     else {
237         currentColorProfile = previousProfile;
238         signal(SIGINT, restoreColorSpace);
239         signal(SIGHUP, restoreColorSpace);
240         signal(SIGTERM, restoreColorSpace);
241     }
242 }
243
244 static void* (*savedMalloc)(malloc_zone_t*, size_t);
245 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
246
247 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
248 {
249     if (size >= 0x10000000)
250         return 0;
251     return savedMalloc(zone, size);
252 }
253
254 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
255 {
256     if (size >= 0x10000000)
257         return 0;
258     return savedRealloc(zone, ptr, size);
259 }
260
261 static void makeLargeMallocFailSilently(void)
262 {
263     malloc_zone_t* zone = malloc_default_zone();
264     savedMalloc = zone->malloc;
265     savedRealloc = zone->realloc;
266     zone->malloc = checkedMalloc;
267     zone->realloc = checkedRealloc;
268 }
269
270 void dumpRenderTree(int argc, const char *argv[])
271 {    
272     [NSApplication sharedApplication];
273
274     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
275     class_poseAs(objc_getClass("DumpRenderTreeWindow"), objc_getClass("NSWindow"));
276     class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent"));
277
278     struct option options[] = {
279         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
280         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
281         {"notree", no_argument, &dumpTree, NO},
282         {"pixel-tests", no_argument, &dumpPixels, YES},
283         {"repaint", no_argument, &testRepaintDefault, YES},
284         {"tree", no_argument, &dumpTree, YES},
285         {"threaded", no_argument, &threaded, YES},
286         {NULL, 0, NULL, 0}
287     };
288
289     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
290     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
291     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
292     // 2 is the "Medium" font smoothing mode
293     [defaults setInteger:2 forKey:@"AppleFontSmoothing"];
294
295     [defaults setInteger:1 forKey:@"AppleAquaColorVariant"];
296     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
297     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
298     
299     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
300     
301     WebPreferences *preferences = [WebPreferences standardPreferences];
302     
303     [preferences setStandardFontFamily:@"Times"];
304     [preferences setFixedFontFamily:@"Courier"];
305     [preferences setSerifFontFamily:@"Times"];
306     [preferences setSansSerifFontFamily:@"Helvetica"];
307     [preferences setCursiveFontFamily:@"Apple Chancery"];
308     [preferences setFantasyFontFamily:@"Papyrus"];
309     [preferences setDefaultFontSize:16];
310     [preferences setDefaultFixedFontSize:13];
311     [preferences setMinimumFontSize:9];
312     [preferences setJavaEnabled:NO];
313     [preferences setJavaScriptCanOpenWindowsAutomatically:NO];
314     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
315     [preferences setTabsToLinks:NO];
316
317     int option;
318     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
319         switch (option) {
320             case '?':   // unknown or ambiguous option
321             case ':':   // missing argument
322                 exit(1);
323                 break;
324         }
325     
326     if ([[[NSFontManager sharedFontManager] availableMembersOfFontFamily:@"Ahem"] count] == 0) {
327         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");
328         exit(1);
329     }
330     
331     if (dumpPixels) {
332         setDefaultColorProfileToRGB();
333         screenCaptureBuffer = malloc(maxViewHeight * maxViewWidth * 4);
334         sharedColorSpace = CGColorSpaceCreateDeviceRGB();
335     }
336     
337     localPasteboards = [[NSMutableDictionary alloc] init];
338
339     navigationController = [[NavigationController alloc] init];
340
341     NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
342     WebView *webView = [[WebView alloc] initWithFrame:rect];
343     frame = [webView mainFrame];
344     
345     WaitUntilDoneDelegate *waitUntilDoneDelegate = [[WaitUntilDoneDelegate alloc] init];
346     [webView setFrameLoadDelegate:waitUntilDoneDelegate];
347     
348     UIDelegate *uiDelegate = [[UIDelegate alloc] init];
349     [webView setUIDelegate:uiDelegate];
350
351     EditingDelegate *editingDelegate = [[EditingDelegate alloc] init];
352     [webView setEditingDelegate:editingDelegate];
353     
354     NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
355     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
356     [[WebPluginDatabase sharedDatabase] refresh];
357
358     // The back/forward cache is causing problems due to layouts during transition from one page to another.
359     // So, turn it off for now, but we might want to turn it back on some day.
360     [[webView backForwardList] setPageCacheSize:0];
361
362     // 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.
363     // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
364     NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
365     NSWindow *window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
366     [[window contentView] addSubview:webView];
367     [window orderBack:nil];
368     [window setAutodisplay:NO];
369
370     workQueue = [[NSMutableArray alloc] init];
371
372     [webView setContinuousSpellCheckingEnabled:YES];
373
374     makeLargeMallocFailSilently();
375
376     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
377     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
378     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
379     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
380     signal(SIGBUS, crashHandler);    /* 10:  bus error */
381     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
382     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
383     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
384     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
385     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
386     
387     [[NSURLCache sharedURLCache] removeAllCachedResponses];
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     if (threaded)
395         startJavaScriptThread();
396     
397     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
398         char filenameBuffer[2048];
399         printSeparators = YES;
400         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
401             char *newLineCharacter = strchr(filenameBuffer, '\n');
402             if (newLineCharacter)
403                 *newLineCharacter = '\0';
404             
405             if (strlen(filenameBuffer) == 0)
406                 continue;
407                 
408             runTest(filenameBuffer);
409         }
410     } else {
411         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
412         for (int i = optind; i != argc; ++i)
413             runTest(argv[i]);
414     }
415
416     if (threaded)
417         stopJavaScriptThread();
418     
419     [workQueue release];
420
421     [webView close];
422     frame = nil;
423
424     // Work around problem where registering drag types leaves an outstanding
425     // "perform selector" on the window, which retains the window. It's a bit
426     // inelegant and perhaps dangerous to just blow them all away, but in practice
427     // it probably won't cause any trouble (and this is just a test tool, after all).
428     [NSObject cancelPreviousPerformRequestsWithTarget:window];
429     
430     [window close]; // releases when closed
431     [webView release];
432     [waitUntilDoneDelegate release];
433     [editingDelegate release];
434     [uiDelegate release];
435     
436     [localPasteboards release];
437     localPasteboards = nil;
438     
439     [navigationController release];
440     navigationController = nil;
441     
442     if (dumpPixels)
443         restoreColorSpace(0);
444 }
445
446 int main(int argc, const char *argv[])
447 {
448     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
449     dumpRenderTree(argc, argv);
450     [WebCoreStatistics garbageCollectJavaScriptObjects];
451     [pool release];
452     return 0;
453 }
454
455 static int compareHistoryItems(id item1, id item2, void *context)
456 {
457     return [[item1 target] caseInsensitiveCompare:[item2 target]];
458 }
459
460 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
461 {
462     int start = 0;
463     if (current) {
464         printf("curr->");
465         start = 6;
466     }
467     for (int i = start; i < indent; i++)
468         putchar(' ');
469     printf("%s", [[item URLString] UTF8String]);
470     NSString *target = [item target];
471     if (target && [target length] > 0)
472         printf(" (in frame \"%s\")", [target UTF8String]);
473     if ([item isTargetItem])
474         printf("  **nav target**");
475     putchar('\n');
476     NSArray *kids = [item children];
477     if (kids) {
478         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
479         kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
480         for (unsigned i = 0; i < [kids count]; i++)
481             dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
482     }
483 }
484
485 static void dumpFrameScrollPosition(WebFrame *f)
486 {
487     NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin;
488     if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
489         if ([f parentFrame] != nil)
490             printf("frame '%s' ", [[f name] UTF8String]);
491         printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
492     }
493
494     if (dumpChildFrameScrollPositions) {
495         NSArray *kids = [f childFrames];
496         if (kids)
497             for (unsigned i = 0; i < [kids count]; i++)
498                 dumpFrameScrollPosition([kids objectAtIndex:i]);
499     }
500 }
501
502 static NSString *serializeWebArchiveToXML(WebArchive *webArchive)
503 {
504     NSString *errorString;
505     NSDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data]
506                                                                   mutabilityOption:NSPropertyListImmutable
507                                                                             format:NULL
508                                                                   errorDescription:&errorString];
509     if (!propertyList)
510         return errorString;
511
512     NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList
513                                                                  format:NSPropertyListXMLFormat_v1_0
514                                                        errorDescription:&errorString];
515     if (!xmlData)
516         return errorString;
517
518     // Normalize URLs in XML for testing
519     NSMutableString *result = [[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
520     NSString *cwdURL = [@"file://" stringByAppendingString:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
521     [result replaceOccurrencesOfString:cwdURL
522                             withString:@"file://"
523                                options:NSLiteralSearch
524                                  range:NSMakeRange(0, [result length])];
525
526     return [result autorelease];
527 }
528
529 static void dump(void)
530 {
531     NSString *result = nil;
532     if (dumpTree) {
533         dumpAsText |= [[[[frame dataSource] response] MIMEType] isEqualToString:@"text/plain"];
534         if (dumpAsText) {
535             DOMElement *documentElement = [[frame DOMDocument] documentElement];
536             result = [[(DOMElement *)documentElement innerText] stringByAppendingString:@"\n"];
537         } else if (dumpAsWebArchive) {
538             WebArchive *webArchive = [[frame DOMDocument] webArchive];
539             result = serializeWebArchiveToXML(webArchive);
540         } else {
541             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
542             if (isSVGW3CTest)
543                 [[frame webView] setFrameSize:NSMakeSize(480, 360)];
544             else 
545                 [[frame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
546             result = [frame renderTreeAsExternalRepresentation];
547         }
548
549         if (!result) {
550             const char *errorMessage;
551             if (dumpAsText)
552                 errorMessage = "[documentElement innerText]";
553             else if (dumpAsWebArchive)
554                 errorMessage = "[[frame DOMDocument] webArchive]";
555             else
556                 errorMessage = "[frame renderTreeAsExternalRepresentation]";
557             printf("ERROR: nil result from %s", errorMessage);
558         } else {
559             fputs([result UTF8String], stdout);
560             if (!dumpAsText && !dumpAsWebArchive)
561                 dumpFrameScrollPosition(frame);
562         }
563
564         if (dumpBackForwardList) {
565             printf("\n============== Back Forward List ==============\n");
566             WebBackForwardList *bfList = [[frame webView] backForwardList];
567
568             // Print out all items in the list after prevTestBFItem, which was from the previous test
569             // Gather items from the end of the list, the print them out from oldest to newest
570             NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
571             for (int i = [bfList forwardListCount]; i > 0; i--) {
572                 WebHistoryItem *item = [bfList itemAtIndex:i];
573                 // something is wrong if the item from the last test is in the forward part of the b/f list
574                 assert(item != prevTestBFItem);
575                 [itemsToPrint addObject:item];
576             }
577             
578             assert([bfList currentItem] != prevTestBFItem);
579             [itemsToPrint addObject:[bfList currentItem]];
580             int currentItemIndex = [itemsToPrint count] - 1;
581
582             for (int i = -1; i >= -[bfList backListCount]; i--) {
583                 WebHistoryItem *item = [bfList itemAtIndex:i];
584                 if (item == prevTestBFItem)
585                     break;
586                 [itemsToPrint addObject:item];
587             }
588
589             for (int i = [itemsToPrint count]-1; i >= 0; i--) {
590                 dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
591             }
592             [itemsToPrint release];
593             printf("===============================================\n");
594         }
595
596         if (printSeparators)
597             puts("#EOF");
598     }
599     
600     if (dumpPixels) {
601         if (!dumpAsText && !dumpAsWebArchive) {
602             // grab a bitmap from the view
603             WebView* view = [frame webView];
604             NSSize webViewSize = [view frame].size;
605
606             CGContextRef cgContext = CGBitmapContextCreate(screenCaptureBuffer, webViewSize.width, webViewSize.height, 8, webViewSize.width * 4, sharedColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedLast);
607
608             NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
609             NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
610             [NSGraphicsContext setCurrentContext:nsContext];
611
612             if (readFromWindow) {
613                 NSBitmapImageRep *imageRep;
614                 [view displayIfNeeded];
615                 [view lockFocus];
616                 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
617                 [view unlockFocus];
618                 [imageRep draw];
619                 [imageRep release];
620             } else if (!testRepaint)
621                 [view displayRectIgnoringOpacity:NSMakeRect(0, 0, webViewSize.width, webViewSize.height) inContext:nsContext];
622             else if (!repaintSweepHorizontally) {
623                 NSRect line = NSMakeRect(0, 0, webViewSize.width, 1);
624                 while (line.origin.y < webViewSize.height) {
625                     [view displayRectIgnoringOpacity:line inContext:nsContext];
626                     line.origin.y++;
627                 }
628             } else {
629                 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
630                 while (column.origin.x < webViewSize.width) {
631                     [view displayRectIgnoringOpacity:column inContext:nsContext];
632                     column.origin.x++;
633                 }
634             }
635             if (dumpSelectionRect) {
636                 NSView *documentView = [[frame frameView] documentView];
637                 if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) {
638                     [[NSColor redColor] set];
639                     [NSBezierPath strokeRect:[documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]];
640                 }
641             }
642
643             [NSGraphicsContext setCurrentContext:savedContext];
644             
645             CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
646             CGContextRelease(cgContext);
647
648             // compute the actual hash to compare to the expected image's hash
649             NSString *actualHash = md5HashStringForBitmap(bitmapImage);
650             printf("\nActualHash: %s\n", [actualHash UTF8String]);
651
652             BOOL dumpImage;
653             if (dumpAllPixels)
654                 dumpImage = YES;
655             else {
656                 // FIXME: It's unfortunate that we hardcode the file naming scheme here.
657                 // At one time, the perl script had all the knowledge about file layout.
658                 // Some day we should restore that setup by passing in more parameters to this tool.
659                 NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
660                 NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
661                 NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
662                 NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
663
664                 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
665
666                 /// send the image to stdout if the hash mismatches or if there's no file in the file system
667                 dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0;
668             }
669             
670             if (dumpImage) {
671                 CFMutableDataRef imageData = CFDataCreateMutable(0, 0);
672                 CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0);
673                 CGImageDestinationAddImage(imageDest, bitmapImage, 0);
674                 CGImageDestinationFinalize(imageDest);
675                 CFRelease(imageDest);
676                 printf("Content-length: %lu\n", CFDataGetLength(imageData));
677                 fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout);
678                 CFRelease(imageData);
679             }
680
681             CGImageRelease(bitmapImage);
682         }
683
684         printf("#EOF\n");
685     }
686     fflush(stdout);
687
688     done = YES;
689 }
690
691 @implementation WaitUntilDoneDelegate
692
693 // Exec messages in the work queue until they're all done, or one of them starts a new load
694 - (void)processWork:(id)dummy
695 {
696     // quit doing work once a load is in progress
697     while ([workQueue count] > 0 && !topLoadingFrame) {
698         [[workQueue objectAtIndex:0] invoke];
699         [workQueue removeObjectAtIndex:0];
700     }
701     
702     // if we didn't start a new load, then we finished all the commands, so we're ready to dump state
703     if (!topLoadingFrame && !waitToDump)
704         dump();
705 }
706
707 - (void)webView:(WebView *)c locationChangeDone:(NSError *)error forDataSource:(WebDataSource *)dataSource
708 {
709     if ([dataSource webFrame] == topLoadingFrame) {
710         topLoadingFrame = nil;
711         workQueueFrozen = YES;      // first complete load freezes the queue for the rest of this test
712         if (!waitToDump) {
713             if ([workQueue count] > 0)
714                 [self performSelector:@selector(processWork:) withObject:nil afterDelay:0];
715             else
716                 dump();
717         }
718     }
719 }
720
721 - (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)f
722 {
723     // Make sure we only set this once per test.  If it gets cleared, and then set again, we might
724     // end up doing two dumps for one test.
725     if (!topLoadingFrame && !done)
726         topLoadingFrame = f;
727 }
728
729 - (void)webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)f
730 {
731     windowIsKey = YES;
732     NSView *documentView = [[frame frameView] documentView];
733     [[[frame webView] window] makeFirstResponder:documentView];
734     if ([documentView isKindOfClass:[WebHTMLView class]])
735         [(WebHTMLView *)documentView _updateActiveState];
736 }
737
738 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
739 {
740     [self webView:sender locationChangeDone:error forDataSource:[frame provisionalDataSource]];
741 }
742
743 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
744 {
745     [self webView:sender locationChangeDone:nil forDataSource:[frame dataSource]];
746     [navigationController webView:sender didFinishLoadForFrame:frame];
747 }
748
749 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
750 {
751     [self webView:sender locationChangeDone:error forDataSource:[frame dataSource]];
752 }
753
754 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)obj 
755
756     LayoutTestController *ltc = [[LayoutTestController alloc] init];
757     [obj setValue:ltc forKey:@"layoutTestController"];
758     [ltc release];
759     
760     EventSendingController *esc = [[EventSendingController alloc] init];
761     [obj setValue:esc forKey:@"eventSender"];
762     [esc release];
763     
764     TextInputController *tic = [[TextInputController alloc] initWithWebView:sender];
765     [obj setValue:tic forKey:@"textInputController"];
766     [tic release];
767     
768     AppleScriptController *asc = [[AppleScriptController alloc] initWithWebView:sender];
769     [obj setValue:asc forKey:@"appleScriptController"];
770     [asc release];
771     
772     GCController *gcc = [[GCController alloc] init];
773     [obj setValue:gcc forKey:@"GCController"];
774     [gcc release];
775     
776     [obj setValue:navigationController forKey:@"navigationController"];
777     
778     ObjCPlugin *plugin = [[ObjCPlugin alloc] init];
779     [obj setValue:plugin forKey:@"objCPlugin"];
780     [plugin release];
781     
782     ObjCPluginFunction *pluginFunction = [[ObjCPluginFunction alloc] init];
783     [obj setValue:pluginFunction forKey:@"objCPluginFunction"];
784     [pluginFunction release];
785 }
786
787 - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
788 {
789     if (dumpTitleChanges)
790         printf("TITLE CHANGED: %s\n", [title UTF8String]);
791 }
792
793 @end
794
795 @implementation LayoutTestController
796
797 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
798 {
799     if (aSelector == @selector(waitUntilDone)
800             || aSelector == @selector(notifyDone)
801             || aSelector == @selector(dumpAsText)
802             || aSelector == @selector(dumpAsWebArchive)
803             || aSelector == @selector(dumpTitleChanges)
804             || aSelector == @selector(dumpBackForwardList)
805             || aSelector == @selector(dumpChildFrameScrollPositions)
806             || aSelector == @selector(dumpEditingCallbacks)
807             || aSelector == @selector(setWindowIsKey:)
808             || aSelector == @selector(setMainFrameIsFirstResponder:)
809             || aSelector == @selector(dumpSelectionRect)
810             || aSelector == @selector(display)
811             || aSelector == @selector(testRepaint)
812             || aSelector == @selector(repaintSweepHorizontally)
813             || aSelector == @selector(queueBackNavigation:)
814             || aSelector == @selector(queueForwardNavigation:)
815             || aSelector == @selector(queueReload)
816             || aSelector == @selector(queueScript:)
817             || aSelector == @selector(queueLoad:target:)
818             || aSelector == @selector(clearBackForwardList)
819             || aSelector == @selector(keepWebHistory)
820             || aSelector == @selector(setAcceptsEditing:))
821         return NO;
822     return YES;
823 }
824
825 + (NSString *)webScriptNameForSelector:(SEL)aSelector
826 {
827     if (aSelector == @selector(setWindowIsKey:))
828         return @"setWindowIsKey";
829     if (aSelector == @selector(setMainFrameIsFirstResponder:))
830         return @"setMainFrameIsFirstResponder";
831     if (aSelector == @selector(queueBackNavigation:))
832         return @"queueBackNavigation";
833     if (aSelector == @selector(queueForwardNavigation:))
834         return @"queueForwardNavigation";
835     if (aSelector == @selector(queueScript:))
836         return @"queueScript";
837     if (aSelector == @selector(queueLoad:target:))
838         return @"queueLoad";
839     if (aSelector == @selector(setAcceptsEditing:))
840         return @"setAcceptsEditing";
841     return nil;
842 }
843
844 - (void)clearBackForwardList
845 {
846     WebBackForwardList *backForwardList = [[frame webView] backForwardList];
847     WebHistoryItem *item = [[backForwardList currentItem] retain];
848
849     // We clear the history by setting the back/forward list's capacity to 0
850     // then restoring it back and adding back the current item.
851     int capacity = [backForwardList capacity];
852     [backForwardList setCapacity:0];
853     [backForwardList setCapacity:capacity];
854     [backForwardList addItem:item];
855     [backForwardList goToItem:item];
856     [item release];
857 }
858
859 - (void)keepWebHistory
860 {
861     if (![WebHistory optionalSharedHistory]) {
862         WebHistory *history = [[WebHistory alloc] init];
863         [WebHistory setOptionalSharedHistory:history];
864         [history release];
865     }
866 }
867
868 - (void)waitUntilDone 
869 {
870     waitToDump = YES;
871 }
872
873 - (void)notifyDone
874 {
875     if (waitToDump && !topLoadingFrame && [workQueue count] == 0)
876         dump();
877     waitToDump = NO;
878 }
879
880 - (void)dumpAsText
881 {
882     dumpAsText = YES;
883 }
884
885 - (void)dumpAsWebArchive
886 {
887     dumpAsWebArchive = YES;
888 }
889
890 - (void)dumpSelectionRect
891 {
892     dumpSelectionRect = YES;
893 }
894
895 - (void)dumpTitleChanges
896 {
897     dumpTitleChanges = YES;
898 }
899
900 - (void)dumpBackForwardList
901 {
902     dumpBackForwardList = YES;
903 }
904
905 - (void)dumpChildFrameScrollPositions
906 {
907     dumpChildFrameScrollPositions = YES;
908 }
909
910 - (void)dumpEditingCallbacks
911 {
912     shouldDumpEditingCallbacks = YES;
913 }
914
915 - (void)setWindowIsKey:(BOOL)flag
916 {
917     windowIsKey = flag;
918     NSView *documentView = [[frame frameView] documentView];
919     if ([documentView isKindOfClass:[WebHTMLView class]])
920         [(WebHTMLView *)documentView _updateActiveState];
921 }
922
923 - (void)setMainFrameIsFirstResponder:(BOOL)flag
924 {
925     NSView *documentView = [[frame frameView] documentView];
926     
927     NSResponder *firstResponder = flag ? documentView : nil;
928     [[[frame webView] window] makeFirstResponder:firstResponder];
929         
930     if ([documentView isKindOfClass:[WebHTMLView class]])
931         [(WebHTMLView *)documentView _updateActiveState];
932 }
933
934 - (void)display
935 {
936     NSView *webView = [frame webView];
937     [webView display];
938     [webView lockFocus];
939     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
940     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
941     [webView unlockFocus];
942     readFromWindow = YES;
943 }
944
945 - (void)testRepaint
946 {
947     testRepaint = YES;
948 }
949
950 - (void)repaintSweepHorizontally
951 {
952     repaintSweepHorizontally = YES;
953 }
954
955 - (id)invokeUndefinedMethodFromWebScript:(NSString *)name withArguments:(NSArray *)args
956 {
957     return nil;
958 }
959
960 - (void)_addWorkForTarget:(id)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2
961 {
962     if (workQueueFrozen)
963         return;
964     NSMethodSignature *sig = [target methodSignatureForSelector:selector];
965     NSInvocation *work = [NSInvocation invocationWithMethodSignature:sig];
966     [work retainArguments];
967     [work setTarget:target];
968     [work setSelector:selector];
969     if (arg1) {
970         [work setArgument:&arg1 atIndex:2];
971         if (arg2)
972             [work setArgument:&arg2 atIndex:3];
973     }
974     [workQueue addObject:work];
975 }
976
977 - (void)_doLoad:(NSURL *)url target:(NSString *)target
978 {
979     WebFrame *targetFrame;
980     if (target && ![target isKindOfClass:[WebUndefined class]])
981         targetFrame = [frame findFrameNamed:target];
982     else
983         targetFrame = frame;
984     [targetFrame loadRequest:[NSURLRequest requestWithURL:url]];
985 }
986
987 - (void)_doBackOrForwardNavigation:(NSNumber *)index
988 {
989     int bfIndex = [index intValue];
990     if (bfIndex == 1)
991         [[frame webView] goForward];
992     if (bfIndex == -1)
993         [[frame webView] goBack];
994     else {        
995         WebBackForwardList *bfList = [[frame webView] backForwardList];
996         [[frame webView] goToBackForwardItem:[bfList itemAtIndex:bfIndex]];
997     }
998 }
999
1000 - (void)queueBackNavigation:(int)howFarBack
1001 {
1002     [self _addWorkForTarget:self selector:@selector(_doBackOrForwardNavigation:) arg1:[NSNumber numberWithInt:-howFarBack] arg2:nil];
1003 }
1004
1005 - (void)queueForwardNavigation:(int)howFarForward
1006 {
1007     [self _addWorkForTarget:self selector:@selector(_doBackOrForwardNavigation:) arg1:[NSNumber numberWithInt:howFarForward] arg2:nil];
1008 }
1009
1010 - (void)queueReload
1011 {
1012     [self _addWorkForTarget:[frame webView] selector:@selector(reload:) arg1:self arg2:nil];
1013 }
1014
1015 - (void)queueScript:(NSString *)script
1016 {
1017     [self _addWorkForTarget:[frame webView] selector:@selector(stringByEvaluatingJavaScriptFromString:) arg1:script arg2:nil];
1018 }
1019
1020 - (void)queueLoad:(NSString *)URLString target:(NSString *)target
1021 {
1022     NSURL *URL = [NSURL URLWithString:URLString relativeToURL:[[[frame dataSource] response] URL]];
1023     [self _addWorkForTarget:self selector:@selector(_doLoad:target:) arg1:URL arg2:target];
1024 }
1025
1026 - (void)setAcceptsEditing:(BOOL)newAcceptsEditing
1027 {
1028     [(EditingDelegate *)[[frame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing];
1029 }
1030
1031 @end
1032
1033 static void runTest(const char *pathOrURL)
1034 {
1035     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
1036     if (!pathOrURLString) {
1037         fprintf(stderr, "can't parse filename as UTF-8\n");
1038         return;
1039     }
1040
1041     CFURLRef URL;
1042     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")))
1043         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
1044     else
1045         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
1046     
1047     if (!URL) {
1048         CFRelease(pathOrURLString);
1049         fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
1050         return;
1051     }
1052
1053     [(EditingDelegate *)[[frame webView] editingDelegate] setAcceptsEditing:YES];
1054     done = NO;
1055     topLoadingFrame = nil;
1056     waitToDump = NO;
1057     dumpAsText = NO;
1058     dumpAsWebArchive = NO;
1059     dumpChildFrameScrollPositions = NO;
1060     shouldDumpEditingCallbacks = NO;
1061     dumpSelectionRect = NO;
1062     dumpTitleChanges = NO;
1063     dumpBackForwardList = NO;
1064     readFromWindow = NO;
1065     testRepaint = testRepaintDefault;
1066     repaintSweepHorizontally = repaintSweepHorizontallyDefault;
1067     if ([WebHistory optionalSharedHistory])
1068         [WebHistory setOptionalSharedHistory:nil];
1069     lastMousePosition = NSMakePoint(0, 0);
1070
1071     if (currentTest != nil)
1072         CFRelease(currentTest);
1073     currentTest = (NSString *)pathOrURLString;
1074     [prevTestBFItem release];
1075     prevTestBFItem = [[[[frame webView] backForwardList] currentItem] retain];
1076     [workQueue removeAllObjects];
1077     workQueueFrozen = NO;
1078
1079     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1080     [frame 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     [[frame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1090     [pool release];
1091 }
1092
1093 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
1094 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
1095 {
1096     MD5_CTX md5Context;
1097     unsigned char hash[16];
1098     
1099     unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap);
1100     assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
1101     unsigned bytesPerPixel = bitsPerPixel / 8;
1102     unsigned pixelsHigh = CGImageGetHeight(bitmap);
1103     unsigned pixelsWide = CGImageGetWidth(bitmap);
1104     unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap);
1105     assert(bytesPerRow >= (pixelsWide * bytesPerPixel));
1106     
1107     MD5_Init(&md5Context);
1108     unsigned char *bitmapData = screenCaptureBuffer;
1109     for (unsigned row = 0; row < pixelsHigh; row++) {
1110         MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel);
1111         bitmapData += bytesPerRow;
1112     }
1113     MD5_Final(hash, &md5Context);
1114     
1115     char hex[33] = "";
1116     for (int i = 0; i < 16; i++) {
1117        snprintf(hex, 33, "%s%02x", hex, hash[i]);
1118     }
1119
1120     return [NSString stringWithUTF8String:hex];
1121 }
1122
1123 @implementation DumpRenderTreePasteboard
1124
1125 // Return a local pasteboard so we don't disturb the real pasteboards when running tests.
1126 + (NSPasteboard *)_pasteboardWithName:(NSString *)name
1127 {
1128     static int number = 0;
1129     if (!name)
1130         name = [NSString stringWithFormat:@"LocalPasteboard%d", ++number];
1131     LocalPasteboard *pasteboard = [localPasteboards objectForKey:name];
1132     if (pasteboard)
1133         return pasteboard;
1134     pasteboard = [[LocalPasteboard alloc] init];
1135     [localPasteboards setObject:pasteboard forKey:name];
1136     [pasteboard release];
1137     return pasteboard;
1138 }
1139
1140 @end
1141
1142 @implementation LocalPasteboard
1143
1144 + (id)alloc
1145 {
1146     return NSAllocateObject(self, 0, 0);
1147 }
1148
1149 - (id)init
1150 {
1151     typesArray = [[NSMutableArray alloc] init];
1152     typesSet = [[NSMutableSet alloc] init];
1153     dataByType = [[NSMutableDictionary alloc] init];
1154     return self;
1155 }
1156
1157 - (void)dealloc
1158 {
1159     [typesArray release];
1160     [typesSet release];
1161     [dataByType release];
1162     [super dealloc];
1163 }
1164
1165 - (NSString *)name
1166 {
1167     return nil;
1168 }
1169
1170 - (void)releaseGlobally
1171 {
1172 }
1173
1174 - (int)declareTypes:(NSArray *)newTypes owner:(id)newOwner
1175 {
1176     [typesArray removeAllObjects];
1177     [typesSet removeAllObjects];
1178     [dataByType removeAllObjects];
1179     return [self addTypes:newTypes owner:newOwner];
1180 }
1181
1182 - (int)addTypes:(NSArray *)newTypes owner:(id)newOwner
1183 {
1184     unsigned count = [newTypes count];
1185     unsigned i;
1186     for (i = 0; i < count; ++i) {
1187         NSString *type = [newTypes objectAtIndex:i];
1188         NSString *setType = [typesSet member:type];
1189         if (!setType) {
1190             setType = [type copy];
1191             [typesArray addObject:setType];
1192             [typesSet addObject:setType];
1193             [setType release];
1194         }
1195         if (newOwner && [newOwner respondsToSelector:@selector(pasteboard:provideDataForType:)])
1196             [newOwner pasteboard:self provideDataForType:setType];
1197     }
1198     return ++changeCount;
1199 }
1200
1201 - (int)changeCount
1202 {
1203     return changeCount;
1204 }
1205
1206 - (NSArray *)types
1207 {
1208     return typesArray;
1209 }
1210
1211 - (NSString *)availableTypeFromArray:(NSArray *)types
1212 {
1213     unsigned count = [types count];
1214     unsigned i;
1215     for (i = 0; i < count; ++i) {
1216         NSString *type = [types objectAtIndex:i];
1217         NSString *setType = [typesSet member:type];
1218         if (setType)
1219             return setType;
1220     }
1221     return nil;
1222 }
1223
1224 - (BOOL)setData:(NSData *)data forType:(NSString *)dataType
1225 {
1226     if (data == nil)
1227         data = [NSData data];
1228     if (![typesSet containsObject:dataType])
1229         return NO;
1230     [dataByType setObject:data forKey:dataType];
1231     ++changeCount;
1232     return YES;
1233 }
1234
1235 - (NSData *)dataForType:(NSString *)dataType
1236 {
1237     return [dataByType objectForKey:dataType];
1238 }
1239
1240 - (BOOL)setPropertyList:(id)propertyList forType:(NSString *)dataType;
1241 {
1242     CFDataRef data = NULL;
1243     if (propertyList)
1244         data = CFPropertyListCreateXMLData(NULL, propertyList);
1245     BOOL result = [self setData:(NSData *)data forType:dataType];
1246     if (data)
1247         CFRelease(data);
1248     return result;
1249 }
1250
1251 - (BOOL)setString:(NSString *)string forType:(NSString *)dataType
1252 {
1253     CFDataRef data = NULL;
1254     if (string) {
1255         if ([string length] == 0)
1256             data = CFDataCreate(NULL, NULL, 0);
1257         else
1258             data = CFStringCreateExternalRepresentation(NULL, (CFStringRef)string, kCFStringEncodingUTF8, 0);
1259     }
1260     BOOL result = [self setData:(NSData *)data forType:dataType];
1261     if (data)
1262         CFRelease(data);
1263     return result;
1264 }
1265
1266 @end
1267
1268 @implementation DumpRenderTreeWindow
1269
1270 - (BOOL)isKeyWindow
1271 {
1272     return windowIsKey;
1273 }
1274
1275 - (void)keyDown:(id)sender
1276 {
1277     // Do nothing, avoiding the beep we'd otherwise get from NSResponder,
1278     // once we get to the end of the responder chain.
1279 }
1280
1281 @end
1282
1283 @implementation DumpRenderTreeEvent
1284
1285 + (NSPoint)mouseLocation
1286 {
1287     return [[[frame webView] window] convertBaseToScreen:lastMousePosition];
1288 }
1289
1290 @end