c306dfd4173631a6c16348aa16b601f39039f388
[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 <WebKit/DOMExtensions.h>
30 #import <WebKit/DOMRange.h>
31 #import <WebKit/WebBackForwardList.h>
32 #import <WebKit/WebCoreStatistics.h>
33 #import <WebKit/WebDataSource.h>
34 #import <WebKit/WebEditingDelegate.h>
35 #import <WebKit/WebFrameView.h>
36 #import <WebKit/WebPreferences.h>
37 #import <WebKit/WebView.h>
38 #import <WebKit/WebHTMLViewPrivate.h>
39 #import <WebKit/WebPluginDatabase.h>
40
41 #import <Carbon/Carbon.h>                           // for GetCurrentEventTime()
42 #import <ApplicationServices/ApplicationServices.h> // for CMSetDefaultProfileBySpace
43 #import <objc/objc-runtime.h>                       // for class_poseAs
44
45 #define COMMON_DIGEST_FOR_OPENSSL
46 #import <CommonCrypto/CommonDigest.h>               // for MD5 functions
47
48 #import <getopt.h>
49 #import <malloc/malloc.h>
50
51 #import "TextInputController.h"
52 #import "NavigationController.h"
53 #import "AppleScriptController.h"
54
55 @interface DumpRenderTreeWindow : NSWindow
56 @end
57
58 @interface DumpRenderTreeDraggingInfo : NSObject <NSDraggingInfo>
59 {
60 @private
61     NSSize offset;
62     NSImage *draggedImage;
63     NSPasteboard *draggingPasteboard;
64     id draggingSource;
65 }
66 - (id)initWithImage:(NSImage *)image offset:(NSSize)offset pasteboard:(NSPasteboard *)pasteboard source:(id)source; 
67 - (NSWindow *)draggingDestinationWindow;
68 - (NSDragOperation)draggingSourceOperationMask;
69 - (NSPoint)draggingLocation;
70 - (NSPoint)draggedImageLocation;
71 - (NSImage *)draggedImage;
72 - (NSPasteboard *)draggingPasteboard;
73 - (id)draggingSource;
74 - (int)draggingSequenceNumber;
75 - (void)slideDraggedImageTo:(NSPoint)screenPoint;
76 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination;
77 @end
78
79 @interface DumpRenderTreePasteboard : NSPasteboard
80 @end
81
82 @interface WaitUntilDoneDelegate : NSObject
83 @end
84
85 @interface EditingDelegate : NSObject
86 @end
87
88 @interface LayoutTestController : NSObject
89 @end
90
91 @interface EventSendingController : NSObject
92 {
93     BOOL down;
94     int clickCount;
95     NSTimeInterval lastClick;
96     int eventNumber;
97     double timeOffset;
98 }
99 @end
100
101 static void dumpRenderTree(const char *pathOrURL);
102 static NSString *md5HashStringForBitmap(NSBitmapImageRep *bitmap);
103
104 static volatile BOOL done;
105 static WebFrame *frame;
106 static NavigationController *navigationController;
107 static BOOL readyToDump;
108 static BOOL waitToDump;
109 static BOOL dumpAsText;
110 static BOOL dumpTitleChanges;
111 static int dumpPixels = NO;
112 static int dumpTree = YES;
113 static BOOL printSeparators;
114 static NSString *currentTest = nil;
115 static NSPasteboard *localPasteboard;
116 static BOOL windowIsKey = YES;
117 static NSPoint lastMousePosition;
118 static DumpRenderTreeDraggingInfo *draggingInfo;
119
120 static CMProfileRef currentColorProfile = 0;
121 static void restoreColorSpace(int ignored)
122 {
123     if (currentColorProfile) {
124         int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
125         if (error)
126             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);
127         currentColorProfile = 0;
128     }
129 }
130
131 static void setDefaultColorProfileToRGB(void)
132 {
133     CMProfileRef genericProfile = [[NSColorSpace genericRGBColorSpace] colorSyncProfile];
134     CMProfileRef previousProfile;
135     int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
136     if (error) {
137         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);
138         return;
139     }
140     if (previousProfile == genericProfile)
141         return;
142     CFStringRef previousProfileName;
143     CFStringRef genericProfileName;
144     CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
145     CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
146     
147     fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n",
148         CFStringGetCStringPtr(previousProfileName, kCFStringEncodingMacRoman),
149         CFStringGetCStringPtr(genericProfileName, kCFStringEncodingMacRoman));
150     fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n");
151     fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n");
152     
153     if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile)))
154         fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result.  (Error: %i)",
155             CFStringGetCStringPtr(genericProfileName, kCFStringEncodingMacRoman), error);
156     else {
157         currentColorProfile = previousProfile;
158         signal(SIGINT, restoreColorSpace);
159         signal(SIGHUP, restoreColorSpace);
160         signal(SIGTERM, restoreColorSpace);
161     }
162     CFRelease(genericProfileName);
163     CFRelease(previousProfileName);
164 }
165
166 static void* (*savedMalloc)(malloc_zone_t*, size_t);
167 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
168
169 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
170 {
171     if (size >= 0x10000000)
172         return 0;
173     return savedMalloc(zone, size);
174 }
175
176 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
177 {
178     if (size >= 0x10000000)
179         return 0;
180     return savedRealloc(zone, ptr, size);
181 }
182
183 static void makeLargeMallocFailSilently(void)
184 {
185     malloc_zone_t* zone = malloc_default_zone();
186     savedMalloc = zone->malloc;
187     savedRealloc = zone->realloc;
188     zone->malloc = checkedMalloc;
189     zone->realloc = checkedRealloc;
190 }
191
192 int main(int argc, const char *argv[])
193 {
194     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
195     
196     [NSApplication sharedApplication];
197
198     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
199     class_poseAs(objc_getClass("DumpRenderTreeWindow"), objc_getClass("NSWindow"));
200     
201     struct option options[] = {
202         {"pixel-tests", no_argument, &dumpPixels, YES},
203         {"tree", no_argument, &dumpTree, YES},
204         {"notree", no_argument, &dumpTree, NO},
205         {NULL, 0, NULL, 0}
206     };
207
208     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
209     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
210     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
211     // 2 is the "Medium" font smoothing mode
212     [defaults setInteger:2 forKey:@"AppleFontSmoothing"];
213
214     [defaults setInteger:1 forKey:@"AppleAquaColorVariant"];
215     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
216     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
217     
218     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
219     
220     WebPreferences *preferences = [WebPreferences standardPreferences];
221     
222     [preferences setStandardFontFamily:@"Times"];
223     [preferences setFixedFontFamily:@"Courier"];
224     [preferences setSerifFontFamily:@"Times"];
225     [preferences setSansSerifFontFamily:@"Helvetica"];
226     [preferences setCursiveFontFamily:@"Apple Chancery"];
227     [preferences setFantasyFontFamily:@"Papyrus"];
228     [preferences setDefaultFontSize:16];
229     [preferences setDefaultFixedFontSize:13];
230     [preferences setMinimumFontSize:9];
231     [preferences setJavaEnabled:NO];
232
233     int option;
234     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
235         switch (option) {
236             case '?':   // unknown or ambiguous option
237             case ':':   // missing argument
238                 exit(1);
239                 break;
240         }
241     
242     if ([[[NSFontManager sharedFontManager] availableMembersOfFontFamily:@"Ahem"] count] == 0) {
243         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.opendarwin.org/quality/Ahem.ttf>.\n");
244         exit(1);
245     }
246     
247     if (dumpPixels)
248         setDefaultColorProfileToRGB();
249     
250     localPasteboard = [NSPasteboard pasteboardWithUniqueName];
251     navigationController = [[NavigationController alloc] init];
252
253     NSRect rect = NSMakeRect(0, 0, 800, 600);
254     
255     WebView *webView = [[WebView alloc] initWithFrame:rect];
256     WaitUntilDoneDelegate *delegate = [[WaitUntilDoneDelegate alloc] init];
257     EditingDelegate *editingDelegate = [[EditingDelegate alloc] init];
258     [webView setFrameLoadDelegate:delegate];
259     [webView setEditingDelegate:editingDelegate];
260     [webView setUIDelegate:delegate];
261     frame = [webView mainFrame];
262     
263     NSString *pwd = [[NSString stringWithCString:argv[0]] stringByDeletingLastPathComponent];
264     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
265     [[WebPluginDatabase installedPlugins] refresh];
266
267     // The back/forward cache is causing problems due to layouts during transition from one page to another.
268     // So, turn it off for now, but we might want to turn it back on some day.
269     [[webView backForwardList] setPageCacheSize:0];
270
271     // 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.
272     NSRect windowRect = NSOffsetRect(rect, -10000, -10000);
273     NSWindow *window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
274     [[window contentView] addSubview:webView];
275     [window orderBack:nil];
276     [window setAutodisplay:NO];
277
278     [webView setContinuousSpellCheckingEnabled:YES];
279
280     makeLargeMallocFailSilently();
281     
282     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
283     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
284     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
285     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
286     
287     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
288         char filenameBuffer[2048];
289         printSeparators = YES;
290         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
291             char *newLineCharacter = strchr(filenameBuffer, '\n');
292             if (newLineCharacter)
293                 *newLineCharacter = '\0';
294             
295             if (strlen(filenameBuffer) == 0)
296                 continue;
297                 
298             dumpRenderTree(filenameBuffer);
299             fflush(stdout);
300         }
301     } else {
302         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
303         for (int i = optind; i != argc; ++i) {
304             dumpRenderTree(argv[i]);
305         }
306     }
307     
308     [webView setFrameLoadDelegate:nil];
309     [webView setEditingDelegate:nil];
310     [webView setUIDelegate:nil];
311     frame = nil;
312
313     // Work around problem where registering drag types leaves an outstanding
314     // "perform selector" on the window, which retains the window. It's a bit
315     // inelegant and perhaps dangerous to just blow them all away, but in practice
316     // it probably won't cause any trouble (and this is just a test tool, after all).
317     [NSObject cancelPreviousPerformRequestsWithTarget:window];
318     
319     [window release];
320     [webView release];
321     [delegate release];
322     [editingDelegate release];
323
324     [localPasteboard releaseGlobally];
325     localPasteboard = nil;
326     
327     [navigationController release];
328     navigationController = nil;
329     
330     if (dumpPixels)
331         restoreColorSpace(0);
332     
333     [pool release];
334
335     return 0;
336 }
337
338 static void dump(void)
339 {
340     NSString *result = nil;
341     if (dumpTree) {
342         if (dumpAsText) {
343             DOMElement *documentElement = [[frame DOMDocument] documentElement];
344             if ([documentElement isKindOfClass:[DOMHTMLElement class]])
345                 result = [[(DOMHTMLElement *)documentElement innerText] stringByAppendingString:@"\n"];
346         } else {
347             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
348             if (isSVGW3CTest)
349                 [[frame webView] setFrameSize:NSMakeSize(480, 360)];
350             else 
351                 [[frame webView] setFrameSize:NSMakeSize(800, 600)];
352             result = [frame renderTreeAsExternalRepresentation];
353         }
354         
355         if (!result)
356             printf("ERROR: nil result from %s", dumpAsText ? "[documentElement innerText]" : "[frame renderTreeAsExternalRepresentation]");
357         else
358             fputs([result UTF8String], stdout);
359         
360         if (printSeparators)
361             puts("#EOF");
362     }
363     
364     if (dumpPixels) {
365         if (!dumpAsText) {
366             // FIXME: It's unfortunate that we hardcode the file naming scheme here.
367             // At one time, the perl script had all the knowledge about file layout.
368             // Some day we should restore that setup by passing in more parameters to this tool.
369
370             NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
371             NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
372             NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
373             NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
374             
375             // grab a bitmap from the view
376             WebView *view = [frame webView];
377             NSBitmapImageRep *imageRep = [view bitmapImageRepForCachingDisplayInRect:[view frame]];
378             [view cacheDisplayInRect:[view frame] toBitmapImageRep:imageRep];
379             
380             // has the actual hash to compare to the expected image's hash
381             NSString *actualHash = md5HashStringForBitmap(imageRep);
382             printf("\nActualHash: %s\n", [actualHash UTF8String]);
383             printf("BaselineHash: %s\n", [baselineHash UTF8String]);
384             
385             // if the hashes don't match, send image back to stdout for diff comparision
386             if (![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0) {            
387                 NSData *imageData = [imageRep representationUsingType:NSPNGFileType properties:nil];
388                 printf("Content-length: %d\n", [imageData length]);
389                 fwrite([imageData bytes], 1, [imageData length], stdout);
390             }
391         }
392
393         printf("#EOF\n");
394     }
395
396     done = YES;
397 }
398
399 @implementation WaitUntilDoneDelegate
400
401 - (void)webView:(WebView *)c locationChangeDone:(NSError *)error forDataSource:(WebDataSource *)dataSource
402 {
403     if ([dataSource webFrame] == frame) {
404         if (waitToDump)
405             readyToDump = YES;
406         else
407             dump();
408     }
409 }
410
411 - (void)webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)f
412 {
413     if (frame == f)
414         readyToDump = NO;
415
416     windowIsKey = YES;
417     NSView *documentView = [[frame frameView] documentView];
418     [[[frame webView] window] makeFirstResponder:documentView];
419     if ([documentView isKindOfClass:[WebHTMLView class]])
420         [(WebHTMLView *)documentView _updateFocusState];
421 }
422
423 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
424 {
425     [self webView:sender locationChangeDone:error forDataSource:[frame provisionalDataSource]];
426 }
427
428 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
429 {
430     [self webView:sender locationChangeDone:nil forDataSource:[frame dataSource]];
431     [navigationController webView:sender didFinishLoadForFrame:frame];
432 }
433
434 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
435 {
436     [self webView:sender locationChangeDone:error forDataSource:[frame dataSource]];
437 }
438
439 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)obj 
440
441     LayoutTestController *ltc = [[LayoutTestController alloc] init];
442     [obj setValue:ltc forKey:@"layoutTestController"];
443     [ltc release];
444     EventSendingController *esc = [[EventSendingController alloc] init];
445     [obj setValue:esc forKey:@"eventSender"];
446     [esc release];
447     TextInputController *tic = [[TextInputController alloc] initWithWebView:sender];
448     [obj setValue:tic forKey:@"textInputController"];
449     [tic release];
450     AppleScriptController *asc = [[AppleScriptController alloc] initWithWebView:sender];
451     [obj setValue:asc forKey:@"appleScriptController"];
452     [asc release];
453     [obj setValue:navigationController forKey:@"navigationController"];
454 }
455
456 - (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message
457 {
458     printf("ALERT: %s\n", [message UTF8String]);
459 }
460
461 - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
462 {
463     if (dumpTitleChanges)
464         printf("TITLE CHANGED: %s\n", [title UTF8String]);
465 }
466
467 - (void)webView:(WebView *)sender dragImage:(NSImage *)anImage at:(NSPoint)viewLocation offset:(NSSize)initialOffset event:(NSEvent *)event pasteboard:(NSPasteboard *)pboard source:(id)sourceObj slideBack:(BOOL)slideFlag forView:(NSView *)view
468 {
469     // A new drag was started before the old one ended.  Probably shouldn't happen.
470     if (draggingInfo) {
471         [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:NSDragOperationNone];
472         [draggingInfo release];
473     }
474     draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:anImage offset:initialOffset pasteboard:pboard source:sourceObj];
475 }
476
477 - (void)webViewFocus:(WebView *)webView
478 {
479     windowIsKey = YES;
480     NSView *documentView = [[frame frameView] documentView];
481     if ([documentView isKindOfClass:[WebHTMLView class]])
482         [(WebHTMLView *)documentView _updateFocusState];
483 }
484
485 @end
486
487 @interface DOMNode (dumpPath)
488 - (NSString *)dumpPath;
489 @end
490
491 @implementation DOMNode (dumpPath)
492 - (NSString *)dumpPath
493 {
494     DOMNode *parent = [self parentNode];
495     NSString *str = [NSString stringWithFormat:@"%@", [self nodeName]];
496     if (parent != nil) {
497         str = [str stringByAppendingString:@" > "];
498         str = [str stringByAppendingString:[parent dumpPath]];
499     }
500     return str;
501 }
502 @end
503
504 @interface DOMRange (dump)
505 - (NSString *)dump;
506 @end
507
508 @implementation DOMRange (dump)
509 - (NSString *)dump
510 {
511     return [NSString stringWithFormat:@"range from %ld of %@ to %ld of %@", [self startOffset], [[self startContainer] dumpPath], [self endOffset], [[self endContainer] dumpPath]];
512 }
513 @end
514
515
516 @implementation EditingDelegate
517
518 - (BOOL)webView:(WebView *)webView shouldBeginEditingInDOMRange:(DOMRange *)range
519 {
520     printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", [[range dump] UTF8String]);
521     return YES;
522 }
523
524 - (BOOL)webView:(WebView *)webView shouldEndEditingInDOMRange:(DOMRange *)range
525 {
526     printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", [[range dump] UTF8String]);
527     return YES;
528 }
529
530 - (BOOL)webView:(WebView *)webView shouldInsertNode:(DOMNode *)node replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
531 {
532     static const char *insertactionstring[] = {
533         "WebViewInsertActionTyped",
534         "WebViewInsertActionPasted",
535         "WebViewInsertActionDropped",
536     };
537
538     printf("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", [[node dumpPath] UTF8String], [[range dump] UTF8String], insertactionstring[action]);
539     return YES;
540 }
541
542 - (BOOL)webView:(WebView *)webView shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
543 {
544     static const char *insertactionstring[] = {
545         "WebViewInsertActionTyped",
546         "WebViewInsertActionPasted",
547         "WebViewInsertActionDropped",
548     };
549
550     printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n", [[text description] UTF8String], [[range dump] UTF8String], insertactionstring[action]);
551     return YES;
552 }
553
554 - (BOOL)webView:(WebView *)webView shouldDeleteDOMRange:(DOMRange *)range
555 {
556     printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", [[range dump] UTF8String]);
557     return YES;
558 }
559
560 - (BOOL)webView:(WebView *)webView shouldChangeSelectedDOMRange:(DOMRange *)currentRange toDOMRange:(DOMRange *)proposedRange affinity:(NSSelectionAffinity)selectionAffinity stillSelecting:(BOOL)flag
561 {
562     static const char *affinitystring[] = {
563         "NSSelectionAffinityUpstream",
564         "NSSelectionAffinityDownstream"
565     };
566     static const char *boolstring[] = {
567         "FALSE",
568         "TRUE"
569     };
570
571     printf("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n", [[currentRange dump] UTF8String], [[proposedRange dump] UTF8String], affinitystring[selectionAffinity], boolstring[flag]);
572     return YES;
573 }
574
575 - (BOOL)webView:(WebView *)webView shouldApplyStyle:(DOMCSSStyleDeclaration *)style toElementsInDOMRange:(DOMRange *)range
576 {
577     printf("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n", [[style description] UTF8String], [[range dump] UTF8String]);
578     return YES;
579 }
580
581 - (BOOL)webView:(WebView *)webView shouldChangeTypingStyle:(DOMCSSStyleDeclaration *)currentStyle toStyle:(DOMCSSStyleDeclaration *)proposedStyle
582 {
583     printf("EDITING DELEGATE: shouldChangeTypingStyle:%s toStyle:%s\n", [[currentStyle description] UTF8String], [[proposedStyle description] UTF8String]);
584     return YES;
585 }
586
587 - (void)webViewDidBeginEditing:(NSNotification *)notification
588 {
589     printf("EDITING DELEGATE: webViewDidBeginEditing:%s\n", [[notification name] UTF8String]);
590 }
591
592 - (void)webViewDidChange:(NSNotification *)notification
593 {
594     printf("EDITING DELEGATE: webViewDidChange:%s\n", [[notification name] UTF8String]);
595 }
596
597 - (void)webViewDidEndEditing:(NSNotification *)notification
598 {
599     printf("EDITING DELEGATE: webViewDidEndEditing:%s\n", [[notification name] UTF8String]);
600 }
601
602 - (void)webViewDidChangeTypingStyle:(NSNotification *)notification
603 {
604     printf("EDITING DELEGATE: webViewDidChangeTypingStyle:%s\n", [[notification name] UTF8String]);
605 }
606
607 - (void)webViewDidChangeSelection:(NSNotification *)notification
608 {
609     if (!done)
610         printf("EDITING DELEGATE: webViewDidChangeSelection:%s\n", [[notification name] UTF8String]);
611 }
612
613 @end
614
615 @implementation LayoutTestController
616
617 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
618 {
619     if (aSelector == @selector(waitUntilDone)
620             || aSelector == @selector(notifyDone)
621             || aSelector == @selector(dumpAsText)
622             || aSelector == @selector(dumpTitleChanges)
623             || aSelector == @selector(setWindowIsKey:)
624             || aSelector == @selector(setMainFrameIsFirstResponder:))
625         return NO;
626     return YES;
627 }
628
629 + (NSString *)webScriptNameForSelector:(SEL)aSelector
630 {
631     if (aSelector == @selector(setWindowIsKey:))
632         return @"setWindowIsKey";
633     if (aSelector == @selector(setMainFrameIsFirstResponder:))
634         return @"setMainFrameIsFirstResponder";
635     return nil;
636 }
637
638 - (void)waitUntilDone 
639 {
640     waitToDump = YES;
641 }
642
643 - (void)notifyDone
644 {
645     if (waitToDump && readyToDump)
646         dump();
647     waitToDump = NO;
648 }
649
650 - (void)dumpAsText
651 {
652     dumpAsText = YES;
653 }
654
655 - (void)dumpTitleChanges
656 {
657     dumpTitleChanges = YES;
658 }
659
660 - (void)setWindowIsKey:(BOOL)flag
661 {
662     windowIsKey = flag;
663     NSView *documentView = [[frame frameView] documentView];
664     if ([documentView isKindOfClass:[WebHTMLView class]])
665         [(WebHTMLView *)documentView _updateFocusState];
666 }
667
668 - (void)setMainFrameIsFirstResponder:(BOOL)flag
669 {
670     NSView *documentView = [[frame frameView] documentView];
671     
672     NSResponder *firstResponder = flag ? documentView : nil;
673     [[[frame webView] window] makeFirstResponder:firstResponder];
674         
675     if ([documentView isKindOfClass:[WebHTMLView class]])
676         [(WebHTMLView *)documentView _updateFocusState];
677 }
678
679 - (id)invokeUndefinedMethodFromWebScript:(NSString *)name withArguments:(NSArray *)args
680 {
681     return nil;
682 }
683
684 @end
685
686 @implementation EventSendingController
687
688 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
689 {
690     if (aSelector == @selector(mouseDown)
691             || aSelector == @selector(mouseUp)
692             || aSelector == @selector(mouseClick)
693             || aSelector == @selector(mouseMoveToX:Y:)
694             || aSelector == @selector(leapForward:))
695         return NO;
696     return YES;
697 }
698
699 + (NSString *)webScriptNameForSelector:(SEL)aSelector
700 {
701     if (aSelector == @selector(mouseMoveToX:Y:))
702         return @"mouseMoveTo";
703     if (aSelector == @selector(leapForward:))
704         return @"leapForward";
705     return nil;
706 }
707
708 - (id)init
709 {
710     lastMousePosition = NSMakePoint(0, 0);
711     down = NO;
712     clickCount = 0;
713     lastClick = 0;
714     return self;
715 }
716
717 - (double)currentEventTime
718 {
719     return GetCurrentEventTime() + timeOffset;
720 }
721
722 - (void)leapForward:(int)milliseconds
723 {
724     timeOffset += milliseconds / 1000.0;
725 }
726
727 - (void)mouseDown
728 {
729     [[[frame frameView] documentView] layout];
730     if ([self currentEventTime] - lastClick >= 1)
731         clickCount = 1;
732     else
733         clickCount++;
734     NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouseDown 
735                                         location:lastMousePosition 
736                                    modifierFlags:nil 
737                                        timestamp:[self currentEventTime]
738                                     windowNumber:[[[frame webView] window] windowNumber] 
739                                          context:[NSGraphicsContext currentContext] 
740                                      eventNumber:++eventNumber 
741                                       clickCount:clickCount 
742                                         pressure:nil];
743
744     NSView *subView = [[frame webView] hitTest:[event locationInWindow]];
745     if (subView) {
746         [subView mouseDown:event];
747         down = YES;
748     }
749 }
750
751 - (void)mouseUp
752 {
753     [[[frame frameView] documentView] layout];
754     NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouseUp 
755                                         location:lastMousePosition 
756                                    modifierFlags:nil 
757                                        timestamp:[self currentEventTime]
758                                     windowNumber:[[[frame webView] window] windowNumber] 
759                                          context:[NSGraphicsContext currentContext] 
760                                      eventNumber:++eventNumber 
761                                       clickCount:clickCount 
762                                         pressure:nil];
763
764     NSView *subView = [[frame webView] hitTest:[event locationInWindow]];
765     if (subView) {
766         [subView mouseUp:event];
767         down = NO;
768         lastClick = [event timestamp];
769         if (draggingInfo) {
770             WebView *webView = [frame webView];
771             
772             NSDragOperation dragOperation = [webView draggingUpdated:draggingInfo];
773             
774             [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:dragOperation];
775             if (dragOperation != NSDragOperationNone)
776                 [webView performDragOperation:draggingInfo];
777             [draggingInfo release];
778             draggingInfo = nil;
779         }
780     }
781 }
782
783 - (void)mouseMoveToX:(int)x Y:(int)y
784 {
785     lastMousePosition = NSMakePoint(x, [[frame webView] frame].size.height - y);
786     NSEvent *event = [NSEvent mouseEventWithType:(down ? NSLeftMouseDragged : NSMouseMoved) 
787                                         location:lastMousePosition 
788                                    modifierFlags:nil 
789                                        timestamp:[self currentEventTime]
790                                     windowNumber:[[[frame webView] window] windowNumber] 
791                                          context:[NSGraphicsContext currentContext] 
792                                      eventNumber:++eventNumber 
793                                       clickCount:(down ? clickCount : 0) 
794                                         pressure:nil];
795
796     NSView *subView = [[frame webView] hitTest:[event locationInWindow]];
797     if (subView) {
798         if (down) {
799             [subView mouseDragged:event];
800             [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] movedTo:lastMousePosition];
801             [[frame webView] draggingUpdated:draggingInfo];
802         }
803         else
804             [subView mouseMoved:event];
805     }
806 }
807
808 - (void)mouseClick
809 {
810     [[[frame frameView] documentView] layout];
811     if ([self currentEventTime] - lastClick >= 1)
812         clickCount = 1;
813     else
814         clickCount++;
815     NSEvent *mouseDownEvent = [NSEvent mouseEventWithType:NSLeftMouseDown 
816                                         location:lastMousePosition 
817                                    modifierFlags:nil 
818                                        timestamp:[self currentEventTime]
819                                     windowNumber:[[[frame webView] window] windowNumber] 
820                                          context:[NSGraphicsContext currentContext] 
821                                      eventNumber:++eventNumber 
822                                       clickCount:clickCount 
823                                         pressure:nil];
824
825     NSView *subView = [[frame webView] hitTest:[mouseDownEvent locationInWindow]];
826     if (subView) {
827         [self leapForward:1];
828         NSEvent *mouseUpEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
829                                                    location:lastMousePosition
830                                               modifierFlags:nil
831                                                   timestamp:[self currentEventTime]
832                                                windowNumber:[[[frame webView] window] windowNumber]
833                                                     context:[NSGraphicsContext currentContext]
834                                                 eventNumber:++eventNumber
835                                                  clickCount:clickCount
836                                                    pressure:nil];
837         [NSApp postEvent:mouseUpEvent atStart:NO];
838         [subView mouseDown:mouseDownEvent];
839     }
840 }
841
842 @end
843
844 static void dumpRenderTree(const char *pathOrURL)
845 {
846     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
847     if (!pathOrURLString) {
848         fprintf(stderr, "can't parse filename as UTF-8\n");
849         return;
850     }
851
852     CFURLRef URL;
853     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")))
854         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
855     else
856         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
857     
858     if (!URL) {
859         CFRelease(pathOrURLString);
860         fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
861         return;
862     }
863
864     done = NO;
865     readyToDump = NO;
866     waitToDump = NO;
867     dumpAsText = NO;
868     dumpTitleChanges = NO;
869     if (currentTest != nil)
870         CFRelease(currentTest);
871     currentTest = (NSString *)pathOrURLString;
872
873     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
874     [frame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
875     CFRelease(URL);
876     [pool release];
877     while (!done) {
878         pool = [[NSAutoreleasePool alloc] init];
879         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
880         [pool release];
881     }
882     pool = [[NSAutoreleasePool alloc] init];
883     [[frame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
884     if (draggingInfo)
885         [draggingInfo release];
886     draggingInfo = nil;
887     [pool release];
888 }
889
890 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
891 static NSString *md5HashStringForBitmap(NSBitmapImageRep *bitmap)
892 {
893     MD5_CTX md5Context;
894     unsigned char hash[16];
895
896     MD5_Init(&md5Context);
897     MD5_Update(&md5Context, [bitmap bitmapData], [bitmap bytesPerPlane]);
898     MD5_Final(hash, &md5Context);
899     
900     char hex[33] = "";
901     for (int i = 0; i < 16; i++) {
902        snprintf(hex, 33, "%s%02x", hex, hash[i]);
903     }
904
905     return [NSString stringWithUTF8String:hex];
906 }
907
908 @implementation DumpRenderTreePasteboard
909
910 // Return a local pasteboard so we don't disturb the real pasteboard when running tests.
911 + (NSPasteboard *)generalPasteboard
912 {
913     return localPasteboard;
914 }
915
916 @end
917
918 @implementation DumpRenderTreeWindow
919
920 - (BOOL)isKeyWindow
921 {
922     return windowIsKey;
923 }
924
925 @end
926
927 @implementation DumpRenderTreeDraggingInfo
928
929 - (id)initWithImage:(NSImage *)anImage offset:(NSSize)o pasteboard:(NSPasteboard *)pboard source:(id)source
930 {
931     draggedImage = [anImage retain];
932     draggingPasteboard = [pboard retain];
933     draggingSource = [source retain];
934     offset = o;
935     
936     return [super init];
937 }
938
939 - (void)dealloc
940 {
941     [draggedImage release];
942     [draggingPasteboard release];
943     [draggingSource release];
944     [super dealloc];
945 }
946
947 - (NSWindow *)draggingDestinationWindow 
948 {
949     return [[frame webView] window];
950 }
951
952 - (NSDragOperation)draggingSourceOperationMask 
953 {
954     return [draggingSource draggingSourceOperationMaskForLocal:YES];
955 }
956
957 - (NSPoint)draggingLocation
958
959     return lastMousePosition; 
960 }
961
962 - (NSPoint)draggedImageLocation 
963 {
964     return NSMakePoint(lastMousePosition.x + offset.width, lastMousePosition.y + offset.height);
965 }
966
967 - (NSImage *)draggedImage
968 {
969     return draggedImage;
970 }
971
972 - (NSPasteboard *)draggingPasteboard
973 {
974     return draggingPasteboard;
975 }
976
977 - (id)draggingSource
978 {
979     return draggingSource;
980 }
981
982 - (int)draggingSequenceNumber
983 {
984     NSLog(@"DumpRenderTree doesn't support draggingSequenceNumber");
985     return 0;
986 }
987
988 - (void)slideDraggedImageTo:(NSPoint)screenPoint
989 {
990     NSLog(@"DumpRenderTree doesn't support slideDraggedImageTo:");
991 }
992
993 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
994 {
995     NSLog(@"DumpRenderTree doesn't support namesOfPromisedFilesDroppedAtDestination:");
996     return nil;
997 }
998
999 @end
1000