1d777462ce8863f510cc0b5b87600c034d09cc0a
[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 <WebKit/DOMExtensions.h>
32 #import <WebKit/DOMRange.h>
33 #import <WebKit/WebBackForwardList.h>
34 #import <WebKit/WebCoreStatistics.h>
35 #import <WebKit/WebDataSource.h>
36 #import <WebKit/WebEditingDelegate.h>
37 #import <WebKit/WebFrameView.h>
38 #import <WebKit/WebPreferences.h>
39 #import <WebKit/WebView.h>
40 #import <WebKit/WebHTMLViewPrivate.h>
41 #import <WebKit/WebDocumentPrivate.h>
42 #import <WebKit/WebPluginDatabase.h>
43
44 #import <ApplicationServices/ApplicationServices.h> // for CMSetDefaultProfileBySpace
45 #import <objc/objc-runtime.h>                       // for class_poseAs
46
47 #define COMMON_DIGEST_FOR_OPENSSL
48 #import <CommonCrypto/CommonDigest.h>               // for MD5 functions
49
50 #import <getopt.h>
51 #import <malloc/malloc.h>
52
53 #import "AppleScriptController.h"
54 #import "DumpRenderTreeDraggingInfo.h"
55 #import "EditingDelegate.h"
56 #import "EventSendingController.h"
57 #import "NavigationController.h"
58 #import "ObjCPlugin.h"
59 #import "ObjCPluginFunction.h"
60 #import "TextInputController.h"
61
62 @interface DumpRenderTreeWindow : NSWindow
63 @end
64
65 @interface DumpRenderTreePasteboard : NSPasteboard
66 @end
67
68 @interface WaitUntilDoneDelegate : NSObject
69 @end
70
71 @interface LayoutTestController : NSObject
72 @end
73
74 static void dumpRenderTree(const char *pathOrURL);
75 static NSString *md5HashStringForBitmap(CGImageRef bitmap);
76
77 WebFrame *frame = 0;
78 DumpRenderTreeDraggingInfo *draggingInfo = 0;
79
80 static volatile BOOL done;
81 static NavigationController *navigationController;
82 static BOOL readyToDump;
83 static BOOL waitToDump;
84 static BOOL dumpAsText;
85 static BOOL dumpSelectionRect;
86 static BOOL dumpTitleChanges;
87 static int dumpPixels = NO;
88 static int dumpAllPixels = NO;
89 static BOOL readFromWindow = NO;
90 static int testRepaintDefault = NO;
91 static BOOL testRepaint = NO;
92 static int repaintSweepHorizontallyDefault = NO;
93 static BOOL repaintSweepHorizontally = NO;
94 static int dumpTree = YES;
95 static BOOL printSeparators;
96 static NSString *currentTest = nil;
97 static NSPasteboard *localPasteboard;
98 static BOOL windowIsKey = YES;
99 static unsigned char* screenCaptureBuffer;
100 static CGColorSpaceRef sharedColorSpace;
101
102 const unsigned maxViewHeight = 600;
103 const unsigned maxViewWidth = 800;
104
105 BOOL doneLoading(void)
106 {
107     return done;
108 }
109
110 static CMProfileRef currentColorProfile = 0;
111 static void restoreColorSpace(int ignored)
112 {
113     if (currentColorProfile) {
114         int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
115         if (error)
116             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);
117         currentColorProfile = 0;
118     }
119 }
120
121 static void crashHandler(int sig)
122 {
123     fprintf(stderr, "%s\n", strsignal(sig));
124     restoreColorSpace(0);
125     exit(128 + sig);
126 }
127
128 static void setDefaultColorProfileToRGB(void)
129 {
130     CMProfileRef genericProfile = [[NSColorSpace genericRGBColorSpace] colorSyncProfile];
131     CMProfileRef previousProfile;
132     int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
133     if (error) {
134         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);
135         return;
136     }
137     if (previousProfile == genericProfile)
138         return;
139     CFStringRef previousProfileName;
140     CFStringRef genericProfileName;
141     char previousProfileNameString[1024];
142     char genericProfileNameString[1024];
143     CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
144     CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
145     CFStringGetCString(previousProfileName, previousProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
146     CFStringGetCString(genericProfileName, genericProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8);
147     CFRelease(genericProfileName);
148     CFRelease(previousProfileName);
149     
150     fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n", previousProfileNameString, genericProfileNameString);
151     fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n");
152     fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n");
153     
154     if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile)))
155         fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result.  (Error: %i)",
156             genericProfileNameString, error);
157     else {
158         currentColorProfile = previousProfile;
159         signal(SIGINT, restoreColorSpace);
160         signal(SIGHUP, restoreColorSpace);
161         signal(SIGTERM, restoreColorSpace);
162     }
163 }
164
165 static void* (*savedMalloc)(malloc_zone_t*, size_t);
166 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
167
168 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
169 {
170     if (size >= 0x10000000)
171         return 0;
172     return savedMalloc(zone, size);
173 }
174
175 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
176 {
177     if (size >= 0x10000000)
178         return 0;
179     return savedRealloc(zone, ptr, size);
180 }
181
182 static void makeLargeMallocFailSilently(void)
183 {
184     malloc_zone_t* zone = malloc_default_zone();
185     savedMalloc = zone->malloc;
186     savedRealloc = zone->realloc;
187     zone->malloc = checkedMalloc;
188     zone->realloc = checkedRealloc;
189 }
190
191 int main(int argc, const char *argv[])
192 {
193     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
194     
195     [NSApplication sharedApplication];
196
197     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
198     class_poseAs(objc_getClass("DumpRenderTreeWindow"), objc_getClass("NSWindow"));
199     
200     struct option options[] = {
201         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
202         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
203         {"notree", no_argument, &dumpTree, NO},
204         {"pixel-tests", no_argument, &dumpPixels, YES},
205         {"repaint", no_argument, &testRepaintDefault, YES},
206         {"tree", no_argument, &dumpTree, YES},
207         {NULL, 0, NULL, 0}
208     };
209
210     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
211     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
212     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
213     // 2 is the "Medium" font smoothing mode
214     [defaults setInteger:2 forKey:@"AppleFontSmoothing"];
215
216     [defaults setInteger:1 forKey:@"AppleAquaColorVariant"];
217     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
218     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
219     
220     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
221     
222     WebPreferences *preferences = [WebPreferences standardPreferences];
223     
224     [preferences setStandardFontFamily:@"Times"];
225     [preferences setFixedFontFamily:@"Courier"];
226     [preferences setSerifFontFamily:@"Times"];
227     [preferences setSansSerifFontFamily:@"Helvetica"];
228     [preferences setCursiveFontFamily:@"Apple Chancery"];
229     [preferences setFantasyFontFamily:@"Papyrus"];
230     [preferences setDefaultFontSize:16];
231     [preferences setDefaultFixedFontSize:13];
232     [preferences setMinimumFontSize:9];
233     [preferences setJavaEnabled:NO];
234     [preferences setJavaScriptCanOpenWindowsAutomatically:NO];
235
236     int option;
237     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
238         switch (option) {
239             case '?':   // unknown or ambiguous option
240             case ':':   // missing argument
241                 exit(1);
242                 break;
243         }
244     
245     if ([[[NSFontManager sharedFontManager] availableMembersOfFontFamily:@"Ahem"] count] == 0) {
246         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");
247         exit(1);
248     }
249     
250     if (dumpPixels) {
251         setDefaultColorProfileToRGB();
252         screenCaptureBuffer = malloc(maxViewHeight * maxViewWidth * 4);
253         sharedColorSpace = CGColorSpaceCreateDeviceRGB();
254     }
255     
256     localPasteboard = [NSPasteboard pasteboardWithUniqueName];
257     navigationController = [[NavigationController alloc] init];
258
259     NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
260     
261     WebView *webView = [[WebView alloc] initWithFrame:rect];
262     WaitUntilDoneDelegate *delegate = [[WaitUntilDoneDelegate alloc] init];
263     EditingDelegate *editingDelegate = [[EditingDelegate alloc] init];
264     [webView setFrameLoadDelegate:delegate];
265     [webView setEditingDelegate:editingDelegate];
266     [webView setUIDelegate:delegate];
267     frame = [webView mainFrame];
268     
269     [[webView preferences] setTabsToLinks:YES];
270     
271     NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
272     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
273     [[WebPluginDatabase installedPlugins] refresh];
274
275     // The back/forward cache is causing problems due to layouts during transition from one page to another.
276     // So, turn it off for now, but we might want to turn it back on some day.
277     [[webView backForwardList] setPageCacheSize:0];
278
279     // 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.
280     NSRect windowRect = NSOffsetRect(rect, -10000, -10000);
281     NSWindow *window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
282     [[window contentView] addSubview:webView];
283     [window orderBack:nil];
284     [window setAutodisplay:NO];
285
286     [webView setContinuousSpellCheckingEnabled:YES];
287
288     makeLargeMallocFailSilently();
289
290     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
291     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
292     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
293     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
294     signal(SIGBUS, crashHandler);    /* 10:  bus error */
295     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
296     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
297     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
298     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
299     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
300     
301     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
302     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
303     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
304     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
305     
306     if (argc == optind+1 && strcmp(argv[optind], "-") == 0) {
307         char filenameBuffer[2048];
308         printSeparators = YES;
309         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
310             char *newLineCharacter = strchr(filenameBuffer, '\n');
311             if (newLineCharacter)
312                 *newLineCharacter = '\0';
313             
314             if (strlen(filenameBuffer) == 0)
315                 continue;
316                 
317             dumpRenderTree(filenameBuffer);
318             fflush(stdout);
319         }
320     } else {
321         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
322         for (int i = optind; i != argc; ++i) {
323             dumpRenderTree(argv[i]);
324         }
325     }
326     
327     [webView setFrameLoadDelegate:nil];
328     [webView setEditingDelegate:nil];
329     [webView setUIDelegate:nil];
330     frame = nil;
331
332     // Work around problem where registering drag types leaves an outstanding
333     // "perform selector" on the window, which retains the window. It's a bit
334     // inelegant and perhaps dangerous to just blow them all away, but in practice
335     // it probably won't cause any trouble (and this is just a test tool, after all).
336     [NSObject cancelPreviousPerformRequestsWithTarget:window];
337     
338     [window release];
339     [webView release];
340     [delegate release];
341     [editingDelegate release];
342
343     [localPasteboard releaseGlobally];
344     localPasteboard = nil;
345     
346     [navigationController release];
347     navigationController = nil;
348     
349     if (dumpPixels)
350         restoreColorSpace(0);
351     
352     [pool release];
353
354     return 0;
355 }
356
357 static void dump(void)
358 {
359     NSString *result = nil;
360     if (dumpTree) {
361         if (dumpAsText) {
362             DOMElement *documentElement = [[frame DOMDocument] documentElement];
363             if ([documentElement isKindOfClass:[DOMHTMLElement class]])
364                 result = [[(DOMHTMLElement *)documentElement innerText] stringByAppendingString:@"\n"];
365         } else {
366             bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
367             if (isSVGW3CTest)
368                 [[frame webView] setFrameSize:NSMakeSize(480, 360)];
369             else 
370                 [[frame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
371             result = [frame renderTreeAsExternalRepresentation];
372         }
373         
374         if (!result)
375             printf("ERROR: nil result from %s", dumpAsText ? "[documentElement innerText]" : "[frame renderTreeAsExternalRepresentation]");
376         else {
377             fputs([result UTF8String], stdout);
378             if (!dumpAsText) {
379                 NSPoint scrollPosition = [[[[frame frameView] documentView] superview] bounds].origin;
380                 if (scrollPosition.x != 0 || scrollPosition.y != 0)
381                     printf("scrolled to %0.f,%0.f\n", scrollPosition.x, scrollPosition.y);
382             }
383         }
384
385         if (printSeparators)
386             puts("#EOF");
387     }
388     
389     if (dumpPixels) {
390         if (!dumpAsText) {
391             // grab a bitmap from the view
392             WebView* view = [frame webView];
393             NSSize webViewSize = [view frame].size;
394
395             CGContextRef cgContext = CGBitmapContextCreate(screenCaptureBuffer, webViewSize.width, webViewSize.height, 8, webViewSize.width * 4, sharedColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedLast);
396
397             NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
398             NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
399             [NSGraphicsContext setCurrentContext:nsContext];
400
401             if (readFromWindow) {
402                 NSBitmapImageRep *imageRep;
403                 [view displayIfNeeded];
404                 [view lockFocus];
405                 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
406                 [view unlockFocus];
407                 [imageRep draw];
408                 [imageRep release];
409             } else if (!testRepaint)
410                 [view displayRectIgnoringOpacity:NSMakeRect(0, 0, webViewSize.width, webViewSize.height) inContext:nsContext];
411             else if (!repaintSweepHorizontally) {
412                 NSRect line = NSMakeRect(0, 0, webViewSize.width, 1);
413                 while (line.origin.y < webViewSize.height) {
414                     [view displayRectIgnoringOpacity:line inContext:nsContext];
415                     line.origin.y++;
416                 }
417             } else {
418                 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
419                 while (column.origin.x < webViewSize.width) {
420                     [view displayRectIgnoringOpacity:column inContext:nsContext];
421                     column.origin.x++;
422                 }
423             }
424             if (dumpSelectionRect) {
425                 NSView *documentView = [[frame frameView] documentView];
426                 if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) {
427                     [[NSColor redColor] set];
428                     [NSBezierPath strokeRect:[documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]];
429                 }
430             }
431
432             [NSGraphicsContext setCurrentContext:savedContext];
433             
434             CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
435             CGContextRelease(cgContext);
436
437             // compute the actual hash to compare to the expected image's hash
438             NSString *actualHash = md5HashStringForBitmap(bitmapImage);
439             printf("\nActualHash: %s\n", [actualHash UTF8String]);
440
441             BOOL dumpImage;
442             if (dumpAllPixels)
443                 dumpImage = YES;
444             else {
445                 // FIXME: It's unfortunate that we hardcode the file naming scheme here.
446                 // At one time, the perl script had all the knowledge about file layout.
447                 // Some day we should restore that setup by passing in more parameters to this tool.
448                 NSString *baseTestPath = [currentTest stringByDeletingPathExtension];
449                 NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"];
450                 NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil];
451                 NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"];
452
453                 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
454
455                 /// send the image to stdout if the hash mismatches or if there's no file in the file system
456                 dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0;
457             }
458             
459             if (dumpImage) {
460                 CFMutableDataRef imageData = CFDataCreateMutable(0, 0);
461                 CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0);
462                 CGImageDestinationAddImage(imageDest, bitmapImage, 0);
463                 CGImageDestinationFinalize(imageDest);
464                 CFRelease(imageDest);
465                 printf("Content-length: %lu\n", CFDataGetLength(imageData));
466                 fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout);
467                 CFRelease(imageData);
468             }
469
470             CGImageRelease(bitmapImage);
471         }
472
473         printf("#EOF\n");
474     }
475
476     done = YES;
477 }
478
479 @implementation WaitUntilDoneDelegate
480
481 - (void)webView:(WebView *)c locationChangeDone:(NSError *)error forDataSource:(WebDataSource *)dataSource
482 {
483     if ([dataSource webFrame] == frame) {
484         if (waitToDump)
485             readyToDump = YES;
486         else
487             dump();
488     }
489 }
490
491 - (void)webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)f
492 {
493     if (frame == f)
494         readyToDump = NO;
495
496     windowIsKey = YES;
497     NSView *documentView = [[frame frameView] documentView];
498     [[[frame webView] window] makeFirstResponder:documentView];
499     if ([documentView isKindOfClass:[WebHTMLView class]])
500         [(WebHTMLView *)documentView _updateFocusState];
501 }
502
503 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
504 {
505     [self webView:sender locationChangeDone:error forDataSource:[frame provisionalDataSource]];
506 }
507
508 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
509 {
510     [self webView:sender locationChangeDone:nil forDataSource:[frame dataSource]];
511     [navigationController webView:sender didFinishLoadForFrame:frame];
512 }
513
514 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
515 {
516     [self webView:sender locationChangeDone:error forDataSource:[frame dataSource]];
517 }
518
519 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)obj 
520
521     LayoutTestController *ltc = [[LayoutTestController alloc] init];
522     [obj setValue:ltc forKey:@"layoutTestController"];
523     [ltc release];
524     
525     EventSendingController *esc = [[EventSendingController alloc] init];
526     [obj setValue:esc forKey:@"eventSender"];
527     [esc release];
528     
529     TextInputController *tic = [[TextInputController alloc] initWithWebView:sender];
530     [obj setValue:tic forKey:@"textInputController"];
531     [tic release];
532     
533     AppleScriptController *asc = [[AppleScriptController alloc] initWithWebView:sender];
534     [obj setValue:asc forKey:@"appleScriptController"];
535     [asc release];
536     
537     [obj setValue:navigationController forKey:@"navigationController"];
538     
539     ObjCPlugin *plugin = [[ObjCPlugin alloc] init];
540     [obj setValue:plugin forKey:@"objCPlugin"];
541     [plugin release];
542     
543     ObjCPluginFunction *pluginFunction = [[ObjCPluginFunction alloc] init];
544     [obj setValue:pluginFunction forKey:@"objCPluginFunction"];
545     [pluginFunction release];
546 }
547
548 - (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message
549 {
550     printf("ALERT: %s\n", [message UTF8String]);
551 }
552
553 - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
554 {
555     if (dumpTitleChanges)
556         printf("TITLE CHANGED: %s\n", [title UTF8String]);
557 }
558
559 - (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
560 {
561     // A new drag was started before the old one ended.  Probably shouldn't happen.
562     if (draggingInfo) {
563         [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:NSDragOperationNone];
564         [draggingInfo release];
565     }
566     draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:anImage offset:initialOffset pasteboard:pboard source:sourceObj];
567 }
568
569 - (void)webViewFocus:(WebView *)webView
570 {
571     windowIsKey = YES;
572     NSView *documentView = [[frame frameView] documentView];
573     if ([documentView isKindOfClass:[WebHTMLView class]])
574         [(WebHTMLView *)documentView _updateFocusState];
575 }
576
577 @end
578
579 @implementation LayoutTestController
580
581 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
582 {
583     if (aSelector == @selector(waitUntilDone)
584             || aSelector == @selector(notifyDone)
585             || aSelector == @selector(dumpAsText)
586             || aSelector == @selector(dumpTitleChanges)
587             || aSelector == @selector(setWindowIsKey:)
588             || aSelector == @selector(setMainFrameIsFirstResponder:)
589             || aSelector == @selector(dumpSelectionRect)
590             || aSelector == @selector(display)
591             || aSelector == @selector(testRepaint)
592             || aSelector == @selector(repaintSweepHorizontally))
593         return NO;
594     return YES;
595 }
596
597 + (NSString *)webScriptNameForSelector:(SEL)aSelector
598 {
599     if (aSelector == @selector(setWindowIsKey:))
600         return @"setWindowIsKey";
601     if (aSelector == @selector(setMainFrameIsFirstResponder:))
602         return @"setMainFrameIsFirstResponder";
603     return nil;
604 }
605
606 - (void)waitUntilDone 
607 {
608     waitToDump = YES;
609 }
610
611 - (void)notifyDone
612 {
613     if (waitToDump && readyToDump)
614         dump();
615     waitToDump = NO;
616 }
617
618 - (void)dumpAsText
619 {
620     dumpAsText = YES;
621 }
622
623 - (void)dumpSelectionRect
624 {
625     dumpSelectionRect = YES;
626 }
627
628 - (void)dumpTitleChanges
629 {
630     dumpTitleChanges = YES;
631 }
632
633 - (void)setWindowIsKey:(BOOL)flag
634 {
635     windowIsKey = flag;
636     NSView *documentView = [[frame frameView] documentView];
637     if ([documentView isKindOfClass:[WebHTMLView class]])
638         [(WebHTMLView *)documentView _updateFocusState];
639 }
640
641 - (void)setMainFrameIsFirstResponder:(BOOL)flag
642 {
643     NSView *documentView = [[frame frameView] documentView];
644     
645     NSResponder *firstResponder = flag ? documentView : nil;
646     [[[frame webView] window] makeFirstResponder:firstResponder];
647         
648     if ([documentView isKindOfClass:[WebHTMLView class]])
649         [(WebHTMLView *)documentView _updateFocusState];
650 }
651
652 - (void)display
653 {
654     NSView *webView = [frame webView];
655     [webView display];
656     [webView lockFocus];
657     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
658     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
659     [webView unlockFocus];
660     readFromWindow = YES;
661 }
662
663 - (void)testRepaint
664 {
665     testRepaint = YES;
666 }
667
668 - (void)repaintSweepHorizontally
669 {
670     repaintSweepHorizontally = YES;
671 }
672
673 - (id)invokeUndefinedMethodFromWebScript:(NSString *)name withArguments:(NSArray *)args
674 {
675     return nil;
676 }
677
678 @end
679
680 static void dumpRenderTree(const char *pathOrURL)
681 {
682     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
683     if (!pathOrURLString) {
684         fprintf(stderr, "can't parse filename as UTF-8\n");
685         return;
686     }
687
688     CFURLRef URL;
689     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")))
690         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
691     else
692         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
693     
694     if (!URL) {
695         CFRelease(pathOrURLString);
696         fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
697         return;
698     }
699
700     done = NO;
701     readyToDump = NO;
702     waitToDump = NO;
703     dumpAsText = NO;
704     dumpSelectionRect = NO;
705     dumpTitleChanges = NO;
706     readFromWindow = NO;
707     testRepaint = testRepaintDefault;
708     repaintSweepHorizontally = repaintSweepHorizontallyDefault;
709     if (currentTest != nil)
710         CFRelease(currentTest);
711     currentTest = (NSString *)pathOrURLString;
712
713     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
714     [frame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
715     CFRelease(URL);
716     [pool release];
717     while (!done) {
718         pool = [[NSAutoreleasePool alloc] init];
719         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
720         [pool release];
721     }
722     pool = [[NSAutoreleasePool alloc] init];
723     [[frame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
724     if (draggingInfo)
725         [draggingInfo release];
726     draggingInfo = nil;
727     [pool release];
728 }
729
730 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
731 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
732 {
733     MD5_CTX md5Context;
734     unsigned char hash[16];
735     
736     unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap);
737     assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
738     unsigned bytesPerPixel = bitsPerPixel / 8;
739     unsigned pixelsHigh = CGImageGetHeight(bitmap);
740     unsigned pixelsWide = CGImageGetWidth(bitmap);
741     unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap);
742     assert(bytesPerRow >= (pixelsWide * bytesPerPixel));
743     
744     MD5_Init(&md5Context);
745     unsigned char *bitmapData = screenCaptureBuffer;
746     for (unsigned row = 0; row < pixelsHigh; row++) {
747         MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel);
748         bitmapData += bytesPerRow;
749     }
750     MD5_Final(hash, &md5Context);
751     
752     char hex[33] = "";
753     for (int i = 0; i < 16; i++) {
754        snprintf(hex, 33, "%s%02x", hex, hash[i]);
755     }
756
757     return [NSString stringWithUTF8String:hex];
758 }
759
760 @implementation DumpRenderTreePasteboard
761
762 // Return a local pasteboard so we don't disturb the real pasteboard when running tests.
763 + (NSPasteboard *)generalPasteboard
764 {
765     return localPasteboard;
766 }
767
768 @end
769
770 @implementation DumpRenderTreeWindow
771
772 - (BOOL)isKeyWindow
773 {
774     return windowIsKey;
775 }
776
777 @end