2 * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #import "DumpRenderTree.h"
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>
58 #import <malloc/malloc.h>
59 #import <CoreFoundation/CoreFoundation.h>
60 #import <objc/objc-runtime.h> // for class_poseAs
62 #define COMMON_DIGEST_FOR_OPENSSL
63 #import <CommonCrypto/CommonDigest.h> // for MD5 functions
65 @interface DumpRenderTreeWindow : NSWindow
68 @interface DumpRenderTreePasteboard : NSPasteboard
71 @interface DumpRenderTreeEvent : NSEvent
74 @interface WaitUntilDoneDelegate : NSObject
77 @interface LayoutTestController : NSObject
80 @interface LocalPasteboard : NSPasteboard
82 NSMutableArray *typesArray;
83 NSMutableSet *typesSet;
84 NSMutableDictionary *dataByType;
89 BOOL windowIsKey = YES;
91 BOOL shouldDumpEditingCallbacks;
93 static void runTest(const char *pathOrURL);
94 static NSString *md5HashStringForBitmap(CGImageRef bitmap);
96 static volatile BOOL done;
97 static NavigationController *navigationController;
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.
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
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;
138 const unsigned maxViewHeight = 600;
139 const unsigned maxViewWidth = 800;
141 BOOL doneLoading(void)
146 static CMProfileRef currentColorProfile = 0;
147 static void restoreColorSpace(int ignored)
149 if (currentColorProfile) {
150 int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile);
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;
157 static void crashHandler(int sig)
159 fprintf(stderr, "%s\n", strsignal(sig));
160 restoreColorSpace(0);
164 static void setDefaultColorProfileToRGB(void)
166 CMProfileRef genericProfile = [[NSColorSpace genericRGBColorSpace] colorSyncProfile];
167 CMProfileRef previousProfile;
168 int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile);
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);
173 if (previousProfile == genericProfile)
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);
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");
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);
194 currentColorProfile = previousProfile;
195 signal(SIGINT, restoreColorSpace);
196 signal(SIGHUP, restoreColorSpace);
197 signal(SIGTERM, restoreColorSpace);
201 static void* (*savedMalloc)(malloc_zone_t*, size_t);
202 static void* (*savedRealloc)(malloc_zone_t*, void*, size_t);
204 static void* checkedMalloc(malloc_zone_t* zone, size_t size)
206 if (size >= 0x10000000)
208 return savedMalloc(zone, size);
211 static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size)
213 if (size >= 0x10000000)
215 return savedRealloc(zone, ptr, size);
218 static void makeLargeMallocFailSilently(void)
220 malloc_zone_t* zone = malloc_default_zone();
221 savedMalloc = zone->malloc;
222 savedRealloc = zone->realloc;
223 zone->malloc = checkedMalloc;
224 zone->realloc = checkedRealloc;
227 int main(int argc, const char *argv[])
229 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
231 [NSApplication sharedApplication];
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"));
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},
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"];
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"];
257 [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
259 WebPreferences *preferences = [WebPreferences standardPreferences];
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];
275 while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1)
277 case '?': // unknown or ambiguous option
278 case ':': // missing argument
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");
289 setDefaultColorProfileToRGB();
290 screenCaptureBuffer = malloc(maxViewHeight * maxViewWidth * 4);
291 sharedColorSpace = CGColorSpaceCreateDeviceRGB();
294 localPasteboards = [[NSMutableDictionary alloc] init];
296 navigationController = [[NavigationController alloc] init];
298 NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
299 WebView *webView = [[WebView alloc] initWithFrame:rect];
300 frame = [webView mainFrame];
302 WaitUntilDoneDelegate *waitUntilDoneDelegate = [[WaitUntilDoneDelegate alloc] init];
303 [webView setFrameLoadDelegate:waitUntilDoneDelegate];
305 UIDelegate *uiDelegate = [[UIDelegate alloc] init];
306 [webView setUIDelegate:uiDelegate];
308 EditingDelegate *editingDelegate = [[EditingDelegate alloc] init];
309 [webView setEditingDelegate:editingDelegate];
311 NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent];
312 [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
313 [[WebPluginDatabase installedPlugins] refresh];
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];
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];
327 workQueue = [[NSMutableArray alloc] init];
329 [webView setContinuousSpellCheckingEnabled:YES];
331 makeLargeMallocFailSilently();
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 */
344 [[NSURLCache sharedURLCache] removeAllCachedResponses];
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];
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';
359 if (strlen(filenameBuffer) == 0)
362 runTest(filenameBuffer);
366 printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
367 for (int i = optind; i != argc; ++i)
373 [webView setFrameLoadDelegate:nil];
374 [webView setEditingDelegate:nil];
375 [webView setUIDelegate:nil];
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];
384 [window close]; // releases when closed
386 [waitUntilDoneDelegate release];
387 [editingDelegate release];
388 [uiDelegate release];
390 [localPasteboards release];
391 localPasteboards = nil;
393 [navigationController release];
394 navigationController = nil;
397 restoreColorSpace(0);
404 static int compareHistoryItems(id item1, id item2, void *context)
406 return [[item1 target] caseInsensitiveCompare:[item2 target]];
409 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
416 for (int i = start; i < indent; i++)
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**");
425 NSArray *kids = [item children];
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);
434 static void dumpFrameScrollPosition(WebFrame *f)
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);
443 if (dumpChildFrameScrollPositions) {
444 NSArray *kids = [f childFrames];
446 for (unsigned i = 0; i < [kids count]; i++)
447 dumpFrameScrollPosition([kids objectAtIndex:i]);
451 static void dump(void)
453 NSString *result = nil;
455 dumpAsText |= [[[[frame dataSource] response] MIMEType] isEqualToString:@"text/plain"];
457 DOMElement *documentElement = [[frame DOMDocument] documentElement];
458 if ([documentElement isKindOfClass:[DOMHTMLElement class]])
459 result = [[(DOMHTMLElement *)documentElement innerText] stringByAppendingString:@"\n"];
461 result = [[documentElement valueForKey:@"textContent"] stringByAppendingString:@"\n"];
463 bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
465 [[frame webView] setFrameSize:NSMakeSize(480, 360)];
467 [[frame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
468 result = [frame renderTreeAsExternalRepresentation];
472 printf("ERROR: nil result from %s", dumpAsText ? "[documentElement innerText]" : "[frame renderTreeAsExternalRepresentation]");
474 fputs([result UTF8String], stdout);
476 dumpFrameScrollPosition(frame);
479 if (dumpBackForwardList) {
480 printf("\n============== Back Forward List ==============\n");
481 WebBackForwardList *bfList = [[frame webView] backForwardList];
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];
493 assert([bfList currentItem] != prevTestBFItem);
494 [itemsToPrint addObject:[bfList currentItem]];
495 int currentItemIndex = [itemsToPrint count] - 1;
497 for (int i = -1; i >= -[bfList backListCount]; i--) {
498 WebHistoryItem *item = [bfList itemAtIndex:i];
499 if (item == prevTestBFItem)
501 [itemsToPrint addObject:item];
504 for (int i = [itemsToPrint count]-1; i >= 0; i--) {
505 dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
507 [itemsToPrint release];
508 printf("===============================================\n");
517 // grab a bitmap from the view
518 WebView* view = [frame webView];
519 NSSize webViewSize = [view frame].size;
521 CGContextRef cgContext = CGBitmapContextCreate(screenCaptureBuffer, webViewSize.width, webViewSize.height, 8, webViewSize.width * 4, sharedColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedLast);
523 NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease];
524 NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
525 [NSGraphicsContext setCurrentContext:nsContext];
527 if (readFromWindow) {
528 NSBitmapImageRep *imageRep;
529 [view displayIfNeeded];
531 imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]];
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];
544 NSRect column = NSMakeRect(0, 0, 1, webViewSize.height);
545 while (column.origin.x < webViewSize.width) {
546 [view displayRectIgnoringOpacity:column inContext:nsContext];
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]];
558 [NSGraphicsContext setCurrentContext:savedContext];
560 CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext);
561 CGContextRelease(cgContext);
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]);
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"];
579 printf("BaselineHash: %s\n", [baselineHash UTF8String]);
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;
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);
596 CGImageRelease(bitmapImage);
605 @implementation WaitUntilDoneDelegate
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
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];
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)
621 - (void)webView:(WebView *)c locationChangeDone:(NSError *)error forDataSource:(WebDataSource *)dataSource
623 if ([dataSource webFrame] == topLoadingFrame) {
624 topLoadingFrame = nil;
625 workQueueFrozen = YES; // first complete load freezes the queue for the rest of this test
627 if ([workQueue count] > 0)
628 [self performSelector:@selector(processWork:) withObject:nil afterDelay:0];
635 - (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)f
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)
643 - (void)webView:(WebView *)sender didCommitLoadForFrame:(WebFrame *)f
646 NSView *documentView = [[frame frameView] documentView];
647 [[[frame webView] window] makeFirstResponder:documentView];
648 if ([documentView isKindOfClass:[WebHTMLView class]])
649 [(WebHTMLView *)documentView _updateActiveState];
652 - (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
654 [self webView:sender locationChangeDone:error forDataSource:[frame provisionalDataSource]];
657 - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
659 [self webView:sender locationChangeDone:nil forDataSource:[frame dataSource]];
660 [navigationController webView:sender didFinishLoadForFrame:frame];
663 - (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
665 [self webView:sender locationChangeDone:error forDataSource:[frame dataSource]];
668 - (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)obj
670 LayoutTestController *ltc = [[LayoutTestController alloc] init];
671 [obj setValue:ltc forKey:@"layoutTestController"];
674 EventSendingController *esc = [[EventSendingController alloc] init];
675 [obj setValue:esc forKey:@"eventSender"];
678 TextInputController *tic = [[TextInputController alloc] initWithWebView:sender];
679 [obj setValue:tic forKey:@"textInputController"];
682 AppleScriptController *asc = [[AppleScriptController alloc] initWithWebView:sender];
683 [obj setValue:asc forKey:@"appleScriptController"];
686 GCController *gcc = [[GCController alloc] init];
687 [obj setValue:gcc forKey:@"GCController"];
690 [obj setValue:navigationController forKey:@"navigationController"];
692 ObjCPlugin *plugin = [[ObjCPlugin alloc] init];
693 [obj setValue:plugin forKey:@"objCPlugin"];
696 ObjCPluginFunction *pluginFunction = [[ObjCPluginFunction alloc] init];
697 [obj setValue:pluginFunction forKey:@"objCPluginFunction"];
698 [pluginFunction release];
701 - (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
703 if (dumpTitleChanges)
704 printf("TITLE CHANGED: %s\n", [title UTF8String]);
709 @implementation LayoutTestController
711 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
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:))
738 + (NSString *)webScriptNameForSelector:(SEL)aSelector
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:))
752 if (aSelector == @selector(setAcceptsEditing:))
753 return @"setAcceptsEditing";
757 - (void)clearBackForwardList
759 WebBackForwardList *backForwardList = [[frame webView] backForwardList];
760 WebHistoryItem *item = [[backForwardList currentItem] retain];
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];
772 - (void)keepWebHistory
774 if (![WebHistory optionalSharedHistory]) {
775 WebHistory *history = [[WebHistory alloc] init];
776 [WebHistory setOptionalSharedHistory:history];
781 - (void)waitUntilDone
788 if (waitToDump && !topLoadingFrame && [workQueue count] == 0)
798 - (void)dumpSelectionRect
800 dumpSelectionRect = YES;
803 - (void)dumpTitleChanges
805 dumpTitleChanges = YES;
808 - (void)dumpBackForwardList
810 dumpBackForwardList = YES;
813 - (void)dumpChildFrameScrollPositions
815 dumpChildFrameScrollPositions = YES;
818 - (void)dumpEditingCallbacks
820 shouldDumpEditingCallbacks = YES;
823 - (void)setWindowIsKey:(BOOL)flag
826 NSView *documentView = [[frame frameView] documentView];
827 if ([documentView isKindOfClass:[WebHTMLView class]])
828 [(WebHTMLView *)documentView _updateActiveState];
831 - (void)setMainFrameIsFirstResponder:(BOOL)flag
833 NSView *documentView = [[frame frameView] documentView];
835 NSResponder *firstResponder = flag ? documentView : nil;
836 [[[frame webView] window] makeFirstResponder:firstResponder];
838 if ([documentView isKindOfClass:[WebHTMLView class]])
839 [(WebHTMLView *)documentView _updateActiveState];
844 NSView *webView = [frame webView];
847 [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
848 NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
849 [webView unlockFocus];
850 readFromWindow = YES;
858 - (void)repaintSweepHorizontally
860 repaintSweepHorizontally = YES;
863 - (id)invokeUndefinedMethodFromWebScript:(NSString *)name withArguments:(NSArray *)args
868 - (void)_addWorkForTarget:(id)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2
872 NSMethodSignature *sig = [target methodSignatureForSelector:selector];
873 NSInvocation *work = [NSInvocation invocationWithMethodSignature:sig];
874 [work retainArguments];
875 [work setTarget:target];
876 [work setSelector:selector];
878 [work setArgument:&arg1 atIndex:2];
880 [work setArgument:&arg2 atIndex:3];
882 [workQueue addObject:work];
885 - (void)_doLoad:(NSURL *)url target:(NSString *)target
887 WebFrame *targetFrame;
888 if (target && ![target isKindOfClass:[WebUndefined class]])
889 targetFrame = [frame findFrameNamed:target];
892 [targetFrame loadRequest:[NSURLRequest requestWithURL:url]];
895 - (void)_doBackOrForwardNavigation:(NSNumber *)index
897 int bfIndex = [index intValue];
899 [[frame webView] goForward];
901 [[frame webView] goBack];
903 WebBackForwardList *bfList = [[frame webView] backForwardList];
904 [[frame webView] goToBackForwardItem:[bfList itemAtIndex:bfIndex]];
908 - (void)queueBackNavigation:(int)howFarBack
910 [self _addWorkForTarget:self selector:@selector(_doBackOrForwardNavigation:) arg1:[NSNumber numberWithInt:-howFarBack] arg2:nil];
913 - (void)queueForwardNavigation:(int)howFarForward
915 [self _addWorkForTarget:self selector:@selector(_doBackOrForwardNavigation:) arg1:[NSNumber numberWithInt:howFarForward] arg2:nil];
920 [self _addWorkForTarget:[frame webView] selector:@selector(reload:) arg1:self arg2:nil];
923 - (void)queueScript:(NSString *)script
925 [self _addWorkForTarget:[frame webView] selector:@selector(stringByEvaluatingJavaScriptFromString:) arg1:script arg2:nil];
928 - (void)queueLoad:(NSString *)URLString target:(NSString *)target
930 NSURL *URL = [NSURL URLWithString:URLString relativeToURL:[[[frame dataSource] response] URL]];
931 [self _addWorkForTarget:self selector:@selector(_doLoad:target:) arg1:URL arg2:target];
934 - (void)setAcceptsEditing:(BOOL)newAcceptsEditing
936 [(EditingDelegate *)[[frame webView] editingDelegate] setAcceptsEditing:newAcceptsEditing];
941 static void runTest(const char *pathOrURL)
943 CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
944 if (!pathOrURLString) {
945 fprintf(stderr, "can't parse filename as UTF-8\n");
950 if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")))
951 URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
953 URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
956 CFRelease(pathOrURLString);
957 fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL);
961 [(EditingDelegate *)[[frame webView] editingDelegate] setAcceptsEditing:YES];
963 topLoadingFrame = nil;
966 dumpChildFrameScrollPositions = NO;
967 shouldDumpEditingCallbacks = NO;
968 dumpSelectionRect = NO;
969 dumpTitleChanges = NO;
970 dumpBackForwardList = NO;
972 testRepaint = testRepaintDefault;
973 repaintSweepHorizontally = repaintSweepHorizontallyDefault;
974 if ([WebHistory optionalSharedHistory])
975 [WebHistory setOptionalSharedHistory:nil];
976 lastMousePosition = NSMakePoint(0, 0);
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;
986 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
987 [frame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
991 pool = [[NSAutoreleasePool alloc] init];
992 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
995 pool = [[NSAutoreleasePool alloc] init];
996 [[frame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1000 /* Hashes a bitmap and returns a text string for comparison and saving to a file */
1001 static NSString *md5HashStringForBitmap(CGImageRef bitmap)
1004 unsigned char hash[16];
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));
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;
1020 MD5_Final(hash, &md5Context);
1023 for (int i = 0; i < 16; i++) {
1024 snprintf(hex, 33, "%s%02x", hex, hash[i]);
1027 return [NSString stringWithUTF8String:hex];
1030 @implementation DumpRenderTreePasteboard
1032 // Return a local pasteboard so we don't disturb the real pasteboards when running tests.
1033 + (NSPasteboard *)_pasteboardWithName:(NSString *)name
1035 static int number = 0;
1037 name = [NSString stringWithFormat:@"LocalPasteboard%d", ++number];
1038 LocalPasteboard *pasteboard = [localPasteboards objectForKey:name];
1041 pasteboard = [[LocalPasteboard alloc] init];
1042 [localPasteboards setObject:pasteboard forKey:name];
1043 [pasteboard release];
1049 @implementation LocalPasteboard
1053 return NSAllocateObject(self, 0, 0);
1058 typesArray = [[NSMutableArray alloc] init];
1059 typesSet = [[NSMutableSet alloc] init];
1060 dataByType = [[NSMutableDictionary alloc] init];
1066 [typesArray release];
1068 [dataByType release];
1077 - (void)releaseGlobally
1081 - (int)declareTypes:(NSArray *)newTypes owner:(id)newOwner
1083 [typesArray removeAllObjects];
1084 [typesSet removeAllObjects];
1085 [dataByType removeAllObjects];
1086 return [self addTypes:newTypes owner:newOwner];
1089 - (int)addTypes:(NSArray *)newTypes owner:(id)newOwner
1091 unsigned count = [newTypes count];
1093 for (i = 0; i < count; ++i) {
1094 NSString *type = [newTypes objectAtIndex:i];
1095 NSString *setType = [typesSet member:type];
1097 setType = [type copy];
1098 [typesArray addObject:setType];
1099 [typesSet addObject:setType];
1102 if (newOwner && [newOwner respondsToSelector:@selector(pasteboard:provideDataForType:)])
1103 [newOwner pasteboard:self provideDataForType:setType];
1105 return ++changeCount;
1118 - (NSString *)availableTypeFromArray:(NSArray *)types
1120 unsigned count = [types count];
1122 for (i = 0; i < count; ++i) {
1123 NSString *type = [types objectAtIndex:i];
1124 NSString *setType = [typesSet member:type];
1131 - (BOOL)setData:(NSData *)data forType:(NSString *)dataType
1134 data = [NSData data];
1135 if (![typesSet containsObject:dataType])
1137 [dataByType setObject:data forKey:dataType];
1142 - (NSData *)dataForType:(NSString *)dataType
1144 return [dataByType objectForKey:dataType];
1147 - (BOOL)setPropertyList:(id)propertyList forType:(NSString *)dataType;
1149 CFDataRef data = NULL;
1151 data = CFPropertyListCreateXMLData(NULL, propertyList);
1152 BOOL result = [self setData:(NSData *)data forType:dataType];
1158 - (BOOL)setString:(NSString *)string forType:(NSString *)dataType
1160 CFDataRef data = NULL;
1162 if ([string length] == 0)
1163 data = CFDataCreate(NULL, NULL, 0);
1165 data = CFStringCreateExternalRepresentation(NULL, (CFStringRef)string, kCFStringEncodingUTF8, 0);
1167 BOOL result = [self setData:(NSData *)data forType:dataType];
1175 @implementation DumpRenderTreeWindow
1182 - (void)keyDown:(id)sender
1184 // Do nothing, avoiding the beep we'd otherwise get from NSResponder,
1185 // once we get to the end of the responder chain.
1190 @implementation DumpRenderTreeEvent
1192 + (NSPoint)mouseLocation
1194 return [[[frame webView] window] convertBaseToScreen:lastMousePosition];