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