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