diff -r 000000000000 -r dd21522fd290 webengine/osswebengine/WebKitTools/DumpRenderTree/mac/DumpRenderTree.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webengine/osswebengine/WebKitTools/DumpRenderTree/mac/DumpRenderTree.mm Mon Mar 30 12:54:55 2009 +0300 @@ -0,0 +1,1339 @@ +/* + * Copyright (C) 2005, 2006, 2007 Apple, Inc. All rights reserved. + * (C) 2007 Graham Dennis (graham.dennis@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "DumpRenderTree.h" + +#import "EditingDelegate.h" +#import "EventSendingController.h" +#import "FrameLoadDelegate.h" +#import "LayoutTestController.h" +#import "NavigationController.h" +#import "ObjCPlugin.h" +#import "ObjCPluginFunction.h" +#import "PolicyDelegate.h" +#import "ResourceLoadDelegate.h" +#import "UIDelegate.h" +#import "WorkQueue.h" +#import "WorkQueueItem.h" + +#import // for CMSetDefaultProfileBySpace +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // for class_poseAs +#import + +#define COMMON_DIGEST_FOR_OPENSSL +#import // for MD5 functions + +@interface DumpRenderTreeWindow : NSWindow +@end + +@interface DumpRenderTreePasteboard : NSPasteboard +- (int)declareType:(NSString *)type owner:(id)newOwner; +@end + +@interface DumpRenderTreeEvent : NSEvent +@end + +@interface LocalPasteboard : NSPasteboard +{ + NSMutableArray *typesArray; + NSMutableSet *typesSet; + NSMutableDictionary *dataByType; + int changeCount; +} +@end + +static void runTest(const char *pathOrURL); +static NSString *md5HashStringForBitmap(CGImageRef bitmap); + +// Deciding when it's OK to dump out the state is a bit tricky. All these must be true: +// - There is no load in progress +// - There is no work queued up (see workQueue var, below) +// - waitToDump==NO. This means either waitUntilDone was never called, or it was called +// and notifyDone was called subsequently. +// Note that the call to notifyDone and the end of the load can happen in either order. + +volatile bool done; + +NavigationController* navigationController = 0; +LayoutTestController* layoutTestController = 0; + +WebFrame *mainFrame = 0; +// This is the topmost frame that is loading, during a given load, or nil when no load is +// in progress. Usually this is the same as the main frame, but not always. In the case +// where a frameset is loaded, and then new content is loaded into one of the child frames, +// that child frame is the "topmost frame that is loading". +WebFrame *topLoadingFrame = nil; // !nil iff a load is in progress + + +CFMutableArrayRef allWindowsRef = 0; +CFMutableSetRef disallowedURLs = 0; +CFRunLoopTimerRef waitToDumpWatchdog = 0; + +// Delegates +static FrameLoadDelegate *frameLoadDelegate; +static UIDelegate *uiDelegate; +static EditingDelegate *editingDelegate; +static ResourceLoadDelegate *resourceLoadDelegate; +PolicyDelegate *policyDelegate; + +static int dumpPixels; +static int paint; +static int dumpAllPixels; +static int threaded; +static BOOL readFromWindow; +static int testRepaintDefault; +static int repaintSweepHorizontallyDefault; +static int dumpTree = YES; +static BOOL printSeparators; +static NSString *currentTest = nil; + +static NSMutableDictionary *localPasteboards; +static WebHistoryItem *prevTestBFItem = nil; // current b/f item at the end of the previous test +static unsigned char* screenCaptureBuffer; +static CGColorSpaceRef sharedColorSpace; + +const unsigned maxViewHeight = 600; +const unsigned maxViewWidth = 800; + +static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER; +static BOOL javaScriptThreadsShouldTerminate; + +static const int javaScriptThreadsCount = 4; +static CFMutableDictionaryRef javaScriptThreads() +{ + assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY); + static CFMutableDictionaryRef staticJavaScriptThreads; + if (!staticJavaScriptThreads) + staticJavaScriptThreads = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, NULL); + return staticJavaScriptThreads; +} + +// Loops forever, running a script and randomly respawning, until +// javaScriptThreadsShouldTerminate becomes true. +void* runJavaScriptThread(void* arg) +{ + const char* const script = + "var array = [];" + "for (var i = 0; i < 10; i++) {" + " array.push(String(i));" + "}"; + + while(1) { + JSGlobalContextRef ctx = JSGlobalContextCreate(NULL); + JSStringRef scriptRef = JSStringCreateWithUTF8CString(script); + + JSValueRef exception = NULL; + JSEvaluateScript(ctx, scriptRef, NULL, NULL, 0, &exception); + assert(!exception); + + JSGlobalContextRelease(ctx); + JSStringRelease(scriptRef); + + JSGarbageCollect(ctx); + + pthread_mutex_lock(&javaScriptThreadsMutex); + + // Check for cancellation. + if (javaScriptThreadsShouldTerminate) { + pthread_mutex_unlock(&javaScriptThreadsMutex); + return 0; + } + + // Respawn probabilistically. + if (random() % 5 == 0) { + pthread_t pthread; + pthread_create(&pthread, NULL, &runJavaScriptThread, NULL); + pthread_detach(pthread); + + CFDictionaryRemoveValue(javaScriptThreads(), pthread_self()); + CFDictionaryAddValue(javaScriptThreads(), pthread, NULL); + + pthread_mutex_unlock(&javaScriptThreadsMutex); + return 0; + } + + pthread_mutex_unlock(&javaScriptThreadsMutex); + } +} + +static void startJavaScriptThreads() +{ + pthread_mutex_lock(&javaScriptThreadsMutex); + + for (int i = 0; i < javaScriptThreadsCount; i++) { + pthread_t pthread; + pthread_create(&pthread, NULL, &runJavaScriptThread, NULL); + pthread_detach(pthread); + CFDictionaryAddValue(javaScriptThreads(), pthread, NULL); + } + + pthread_mutex_unlock(&javaScriptThreadsMutex); +} + +static void stopJavaScriptThreads() +{ + pthread_mutex_lock(&javaScriptThreadsMutex); + + javaScriptThreadsShouldTerminate = true; + + pthread_t* pthreads[javaScriptThreadsCount] = { 0 }; + ASSERT(CFDictionaryGetCount(javaScriptThreads()) == javaScriptThreadsCount); + CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0); + + pthread_mutex_unlock(&javaScriptThreadsMutex); + + for (int i = 0; i < javaScriptThreadsCount; i++) { + pthread_t* pthread = pthreads[i]; + pthread_join(*pthread, 0); + free(pthread); + } +} + +static BOOL shouldIgnoreWebCoreNodeLeaks(CFStringRef URLString) +{ + static CFStringRef const ignoreSet[] = { + // Keeping this infrastructure around in case we ever need it again. + }; + static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(CFStringRef); + + for (int i = 0; i < ignoreSetCount; i++) { + CFStringRef ignoreString = ignoreSet[i]; + CFRange range = CFRangeMake(0, CFStringGetLength(URLString)); + CFOptionFlags flags = kCFCompareAnchored | kCFCompareBackwards | kCFCompareCaseInsensitive; + if (CFStringFindWithOptions(URLString, ignoreString, range, flags, NULL)) + return YES; + } + return NO; +} + +static CMProfileRef currentColorProfile = 0; +static void restoreColorSpace(int ignored) +{ + if (currentColorProfile) { + int error = CMSetDefaultProfileByUse(cmDisplayUse, currentColorProfile); + if (error) + 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); + currentColorProfile = 0; + } +} + +static void crashHandler(int sig) +{ + fprintf(stderr, "%s\n", strsignal(sig)); + restoreColorSpace(0); + exit(128 + sig); +} + +static void activateAhemFont() +{ + unsigned long fontDataLength; + char* fontData = getsectdata("__DATA", "Ahem", &fontDataLength); + if (!fontData) { + fprintf(stderr, "Failed to locate the Ahem font.\n"); + exit(1); + } + + ATSFontContainerRef fontContainer; + OSStatus status = ATSFontActivateFromMemory(fontData, fontDataLength, kATSFontContextLocal, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &fontContainer); + + if (status != noErr) { + fprintf(stderr, "Failed to activate the Ahem font.\n"); + exit(1); + } +} + +static void setDefaultColorProfileToRGB() +{ + CMProfileRef genericProfile = (CMProfileRef)[[NSColorSpace genericRGBColorSpace] colorSyncProfile]; + CMProfileRef previousProfile; + int error = CMGetDefaultProfileByUse(cmDisplayUse, &previousProfile); + if (error) { + 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); + return; + } + if (previousProfile == genericProfile) + return; + CFStringRef previousProfileName; + CFStringRef genericProfileName; + char previousProfileNameString[1024]; + char genericProfileNameString[1024]; + CMCopyProfileDescriptionString(previousProfile, &previousProfileName); + CMCopyProfileDescriptionString(genericProfile, &genericProfileName); + CFStringGetCString(previousProfileName, previousProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8); + CFStringGetCString(genericProfileName, genericProfileNameString, sizeof(previousProfileNameString), kCFStringEncodingUTF8); + CFRelease(genericProfileName); + CFRelease(previousProfileName); + + fprintf(stderr, "\n\nWARNING: Temporarily changing your system color profile from \"%s\" to \"%s\".\n", previousProfileNameString, genericProfileNameString); + fprintf(stderr, "This allows the WebKit pixel-based regression tests to have consistent color values across all machines.\n"); + fprintf(stderr, "The colors on your screen will change for the duration of the testing.\n\n"); + + if ((error = CMSetDefaultProfileByUse(cmDisplayUse, genericProfile))) + fprintf(stderr, "Failed to set color profile to \"%s\"! Many pixel tests will fail as a result. (Error: %i)", + genericProfileNameString, error); + else { + currentColorProfile = previousProfile; + signal(SIGINT, restoreColorSpace); + signal(SIGHUP, restoreColorSpace); + signal(SIGTERM, restoreColorSpace); + } +} + +static void* (*savedMalloc)(malloc_zone_t*, size_t); +static void* (*savedRealloc)(malloc_zone_t*, void*, size_t); + +static void* checkedMalloc(malloc_zone_t* zone, size_t size) +{ + if (size >= 0x10000000) + return 0; + return savedMalloc(zone, size); +} + +static void* checkedRealloc(malloc_zone_t* zone, void* ptr, size_t size) +{ + if (size >= 0x10000000) + return 0; + return savedRealloc(zone, ptr, size); +} + +static void makeLargeMallocFailSilently() +{ + malloc_zone_t* zone = malloc_default_zone(); + savedMalloc = zone->malloc; + savedRealloc = zone->realloc; + zone->malloc = checkedMalloc; + zone->realloc = checkedRealloc; +} + +WebView *createWebView() +{ + NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight); + WebView *webView = [[WebView alloc] initWithFrame:rect frameName:nil groupName:@"org.webkit.DumpRenderTree"]; + + [webView setUIDelegate:uiDelegate]; + [webView setFrameLoadDelegate:frameLoadDelegate]; + [webView setEditingDelegate:editingDelegate]; + [webView setResourceLoadDelegate:resourceLoadDelegate]; + + // Register the same schemes that Safari does + [WebView registerURLSchemeAsLocal:@"feed"]; + [WebView registerURLSchemeAsLocal:@"feeds"]; + [WebView registerURLSchemeAsLocal:@"feedsearch"]; + + // The back/forward cache is causing problems due to layouts during transition from one page to another. + // So, turn it off for now, but we might want to turn it back on some day. + [[webView backForwardList] setPageCacheSize:0]; + + [webView setContinuousSpellCheckingEnabled:YES]; + + // 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. + // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates. + NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000); + DumpRenderTreeWindow *window = [[DumpRenderTreeWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES]; + [[window contentView] addSubview:webView]; + [window orderBack:nil]; + [window setAutodisplay:NO]; + + // For reasons that are not entirely clear, the following pair of calls makes WebView handle its + // dynamic scrollbars properly. Without it, every frame will always have scrollbars. + NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]]; + [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep]; + + return webView; +} + +void testStringByEvaluatingJavaScriptFromString() +{ + // maps expected result <= JavaScript expression + NSDictionary *expressions = [NSDictionary dictionaryWithObjectsAndKeys: + @"0", @"0", + @"0", @"'0'", + @"", @"", + @"", @"''", + @"", @"new String()", + @"", @"new String('0')", + @"", @"throw 1", + @"", @"{ }", + @"", @"[ ]", + @"", @"//", + @"", @"a.b.c", + @"", @"(function() { throw 'error'; })()", + @"", @"null", + @"", @"undefined", + @"true", @"true", + @"false", @"false", + nil + ]; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""]; + + NSEnumerator *enumerator = [expressions keyEnumerator]; + id expression; + while ((expression = [enumerator nextObject])) { + NSString *expectedResult = [expressions objectForKey:expression]; + NSString *result = [webView stringByEvaluatingJavaScriptFromString:expression]; + assert([result isEqualToString:expectedResult]); + } + + [webView close]; + [webView release]; + [pool release]; +} + +void dumpRenderTree(int argc, const char *argv[]) +{ + [NSApplication sharedApplication]; + + class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard")); + class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent")); + + struct option options[] = { + {"dump-all-pixels", no_argument, &dumpAllPixels, YES}, + {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES}, + {"notree", no_argument, &dumpTree, NO}, + {"pixel-tests", no_argument, &dumpPixels, YES}, + {"paint", no_argument, &paint, YES}, + {"repaint", no_argument, &testRepaintDefault, YES}, + {"tree", no_argument, &dumpTree, YES}, + {"threaded", no_argument, &threaded, YES}, + {NULL, 0, NULL, 0} + }; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"]; + [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; + // 2 is the "Medium" font smoothing mode + [defaults setInteger:2 forKey:@"AppleFontSmoothing"]; + + [defaults setInteger:1 forKey:@"AppleAquaColorVariant"]; + [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"]; + [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"]; + + [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"]; + + WebPreferences *preferences = [WebPreferences standardPreferences]; + + [preferences setStandardFontFamily:@"Times"]; + [preferences setFixedFontFamily:@"Courier"]; + [preferences setSerifFontFamily:@"Times"]; + [preferences setSansSerifFontFamily:@"Helvetica"]; + [preferences setCursiveFontFamily:@"Apple Chancery"]; + [preferences setFantasyFontFamily:@"Papyrus"]; + [preferences setDefaultFontSize:16]; + [preferences setDefaultFixedFontSize:13]; + [preferences setMinimumFontSize:1]; + [preferences setJavaEnabled:NO]; + [preferences setJavaScriptCanOpenWindowsAutomatically:YES]; + [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey]; + [preferences setTabsToLinks:NO]; + [preferences setDOMPasteAllowed:YES]; + + int option; + while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) + switch (option) { + case '?': // unknown or ambiguous option + case ':': // missing argument + exit(1); + break; + } + + activateAhemFont(); + + if (dumpPixels) { + setDefaultColorProfileToRGB(); + screenCaptureBuffer = (unsigned char *)malloc(maxViewHeight * maxViewWidth * 4); + sharedColorSpace = CGColorSpaceCreateDeviceRGB(); + } + + localPasteboards = [[NSMutableDictionary alloc] init]; + navigationController = [[NavigationController alloc] init]; + frameLoadDelegate = [[FrameLoadDelegate alloc] init]; + uiDelegate = [[UIDelegate alloc] init]; + editingDelegate = [[EditingDelegate alloc] init]; + resourceLoadDelegate = [[ResourceLoadDelegate alloc] init]; + policyDelegate = [[PolicyDelegate alloc] init]; + + NSString *pwd = [[NSString stringWithUTF8String:argv[0]] stringByDeletingLastPathComponent]; + [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]]; + [[WebPluginDatabase sharedDatabase] refresh]; + + WebView *webView = createWebView(); + mainFrame = [webView mainFrame]; + NSWindow *window = [webView window]; + + makeLargeMallocFailSilently(); + + signal(SIGILL, crashHandler); /* 4: illegal instruction (not reset when caught) */ + signal(SIGTRAP, crashHandler); /* 5: trace trap (not reset when caught) */ + signal(SIGEMT, crashHandler); /* 7: EMT instruction */ + signal(SIGFPE, crashHandler); /* 8: floating point exception */ + signal(SIGBUS, crashHandler); /* 10: bus error */ + signal(SIGSEGV, crashHandler); /* 11: segmentation violation */ + signal(SIGSYS, crashHandler); /* 12: bad argument to system call */ + signal(SIGPIPE, crashHandler); /* 13: write on a pipe with no reader */ + signal(SIGXCPU, crashHandler); /* 24: exceeded CPU time limit */ + signal(SIGXFSZ, crashHandler); /* 25: exceeded file size limit */ + + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + + // + testStringByEvaluatingJavaScriptFromString(); + + if (threaded) + startJavaScriptThreads(); + + if (argc == optind+1 && strcmp(argv[optind], "-") == 0) { + char filenameBuffer[2048]; + printSeparators = YES; + while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) { + char *newLineCharacter = strchr(filenameBuffer, '\n'); + if (newLineCharacter) + *newLineCharacter = '\0'; + + if (strlen(filenameBuffer) == 0) + continue; + + runTest(filenameBuffer); + } + } else { + printSeparators = (optind < argc-1 || (dumpPixels && dumpTree)); + for (int i = optind; i != argc; ++i) + runTest(argv[i]); + } + + if (threaded) + stopJavaScriptThreads(); + + [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts + [webView close]; + mainFrame = nil; + + // Work around problem where registering drag types leaves an outstanding + // "perform selector" on the window, which retains the window. It's a bit + // inelegant and perhaps dangerous to just blow them all away, but in practice + // it probably won't cause any trouble (and this is just a test tool, after all). + [NSObject cancelPreviousPerformRequestsWithTarget:window]; + + [window close]; // releases when closed + [webView release]; + [frameLoadDelegate release]; + [editingDelegate release]; + [resourceLoadDelegate release]; + [uiDelegate release]; + [policyDelegate release]; + + [localPasteboards release]; + localPasteboards = nil; + + [navigationController release]; + navigationController = nil; + + if (disallowedURLs) { + CFRelease(disallowedURLs); + disallowedURLs = 0; + } + + if (dumpPixels) + restoreColorSpace(0); +} + +int main(int argc, const char *argv[]) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + dumpRenderTree(argc, argv); + [WebCoreStatistics garbageCollectJavaScriptObjects]; + [pool release]; + return 0; +} + +static int compareHistoryItems(id item1, id item2, void *context) +{ + return [[item1 target] caseInsensitiveCompare:[item2 target]]; +} + +static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current) +{ + int start = 0; + if (current) { + printf("curr->"); + start = 6; + } + for (int i = start; i < indent; i++) + putchar(' '); + printf("%s", [[item URLString] UTF8String]); + NSString *target = [item target]; + if (target && [target length] > 0) + printf(" (in frame \"%s\")", [target UTF8String]); + if ([item isTargetItem]) + printf(" **nav target**"); + putchar('\n'); + NSArray *kids = [item children]; + if (kids) { + // must sort to eliminate arbitrary result ordering which defeats reproducible testing + kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil]; + for (unsigned i = 0; i < [kids count]; i++) + dumpHistoryItem([kids objectAtIndex:i], indent+4, NO); + } +} + +static void dumpFrameScrollPosition(WebFrame *f) +{ + NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin; + if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) { + if ([f parentFrame] != nil) + printf("frame '%s' ", [[f name] UTF8String]); + printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y); + } + + if (layoutTestController->dumpChildFrameScrollPositions()) { + NSArray *kids = [f childFrames]; + if (kids) + for (unsigned i = 0; i < [kids count]; i++) + dumpFrameScrollPosition([kids objectAtIndex:i]); + } +} + +static NSString *dumpFramesAsText(WebFrame *frame) +{ + if (!frame) + return @""; + + DOMDocument *document = [frame DOMDocument]; + if (!document) + return @""; + + DOMElement *documentElement = [document documentElement]; + if (!documentElement) + return @""; + + NSMutableString *result = [[[NSMutableString alloc] init] autorelease]; + + // Add header for all but the main frame. + if ([frame parentFrame]) + result = [NSMutableString stringWithFormat:@"\n--------\nFrame: '%@'\n--------\n", [frame name]]; + + [result appendFormat:@"%@\n", [documentElement innerText]]; + + if (layoutTestController->dumpChildFramesAsText()) { + NSArray *kids = [frame childFrames]; + if (kids) { + for (unsigned i = 0; i < [kids count]; i++) + [result appendString:dumpFramesAsText([kids objectAtIndex:i])]; + } + } + + return result; +} + +static void convertMIMEType(NSMutableString *mimeType) +{ + if ([mimeType isEqualToString:@"application/x-javascript"]) + [mimeType setString:@"text/javascript"]; +} + +static void convertWebResourceDataToString(NSMutableDictionary *resource) +{ + NSMutableString *mimeType = [resource objectForKey:@"WebResourceMIMEType"]; + convertMIMEType(mimeType); + + if ([mimeType hasPrefix:@"text/"]) { + NSData *data = [resource objectForKey:@"WebResourceData"]; + NSString *dataAsString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + [resource setObject:dataAsString forKey:@"WebResourceData"]; + } +} + +static void normalizeWebResourceURL(NSMutableString *webResourceURL, NSString *oldURLBase) +{ + [webResourceURL replaceOccurrencesOfString:oldURLBase + withString:@"file://" + options:NSLiteralSearch + range:NSMakeRange(0, [webResourceURL length])]; +} + +static void convertWebResourceResponseToDictionary(NSMutableDictionary *propertyList, NSString *oldURLBase) +{ + NSURLResponse *response = nil; + NSData *responseData = [propertyList objectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m + if ([responseData isKindOfClass:[NSData class]]) { + // Decode NSURLResponse + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:responseData]; + response = [unarchiver decodeObjectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m + [unarchiver finishDecoding]; + [unarchiver release]; + } + + NSMutableDictionary *responseDictionary = [[NSMutableDictionary alloc] init]; + + NSMutableString *urlString = [[[response URL] description] mutableCopy]; + normalizeWebResourceURL(urlString, oldURLBase); + [responseDictionary setObject:urlString forKey:@"URL"]; + [urlString release]; + + NSMutableString *mimeTypeString = [[response MIMEType] mutableCopy]; + convertMIMEType(mimeTypeString); + [responseDictionary setObject:mimeTypeString forKey:@"MIMEType"]; + [mimeTypeString release]; + + NSString *textEncodingName = [response textEncodingName]; + if (textEncodingName) + [responseDictionary setObject:textEncodingName forKey:@"textEncodingName"]; + [responseDictionary setObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:@"expectedContentLength"]; + + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + [responseDictionary setObject:[httpResponse allHeaderFields] forKey:@"allHeaderFields"]; + [responseDictionary setObject:[NSNumber numberWithInt:[httpResponse statusCode]] forKey:@"statusCode"]; + } + + [propertyList setObject:responseDictionary forKey:@"WebResourceResponse"]; + [responseDictionary release]; +} + +static NSString *serializeWebArchiveToXML(WebArchive *webArchive) +{ + NSString *errorString; + NSMutableDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data] + mutabilityOption:NSPropertyListMutableContainersAndLeaves + format:NULL + errorDescription:&errorString]; + if (!propertyList) + return errorString; + + // Normalize WebResourceResponse and WebResourceURL values in plist for testing + NSString *cwdURL = [@"file://" stringByAppendingString:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]]; + + NSMutableArray *resources = [NSMutableArray arrayWithCapacity:1]; + [resources addObject:propertyList]; + + while ([resources count]) { + NSMutableDictionary *resourcePropertyList = [resources objectAtIndex:0]; + [resources removeObjectAtIndex:0]; + + NSMutableDictionary *mainResource = [resourcePropertyList objectForKey:@"WebMainResource"]; + normalizeWebResourceURL([mainResource objectForKey:@"WebResourceURL"], cwdURL); + convertWebResourceDataToString(mainResource); + + // Add subframeArchives to list for processing + NSMutableArray *subframeArchives = [resourcePropertyList objectForKey:@"WebSubframeArchives"]; // WebSubframeArchivesKey in WebArchive.m + if (subframeArchives) + [resources addObjectsFromArray:subframeArchives]; + + NSMutableArray *subresources = [resourcePropertyList objectForKey:@"WebSubresources"]; // WebSubresourcesKey in WebArchive.m + NSEnumerator *enumerator = [subresources objectEnumerator]; + NSMutableDictionary *subresourcePropertyList; + while ((subresourcePropertyList = [enumerator nextObject])) { + normalizeWebResourceURL([subresourcePropertyList objectForKey:@"WebResourceURL"], cwdURL); + convertWebResourceResponseToDictionary(subresourcePropertyList, cwdURL); + convertWebResourceDataToString(subresourcePropertyList); + } + } + + NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList + format:NSPropertyListXMLFormat_v1_0 + errorDescription:&errorString]; + if (!xmlData) + return errorString; + + NSMutableString *string = [[[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] autorelease]; + + // Replace "Apple Computer" with "Apple" in the DTD declaration. + NSRange range = [string rangeOfString:@"-//Apple Computer//"]; + if (range.location != NSNotFound) + [string replaceCharactersInRange:range withString:@"-//Apple//"]; + + return string; +} + +static void dumpBackForwardListForWebView(WebView *view) +{ + printf("\n============== Back Forward List ==============\n"); + WebBackForwardList *bfList = [view backForwardList]; + + // Print out all items in the list after prevTestBFItem, which was from the previous test + // Gather items from the end of the list, the print them out from oldest to newest + NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init]; + for (int i = [bfList forwardListCount]; i > 0; i--) { + WebHistoryItem *item = [bfList itemAtIndex:i]; + // something is wrong if the item from the last test is in the forward part of the b/f list + assert(item != prevTestBFItem); + [itemsToPrint addObject:item]; + } + + assert([bfList currentItem] != prevTestBFItem); + [itemsToPrint addObject:[bfList currentItem]]; + int currentItemIndex = [itemsToPrint count] - 1; + + for (int i = -1; i >= -[bfList backListCount]; i--) { + WebHistoryItem *item = [bfList itemAtIndex:i]; + if (item == prevTestBFItem) + break; + [itemsToPrint addObject:item]; + } + + for (int i = [itemsToPrint count]-1; i >= 0; i--) { + dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex); + } + [itemsToPrint release]; + printf("===============================================\n"); +} + +void dump() +{ + if (waitToDumpWatchdog) { + CFRunLoopTimerInvalidate(waitToDumpWatchdog); + CFRelease(waitToDumpWatchdog); + waitToDumpWatchdog = 0; + } + + if (dumpTree) { + NSString *result = nil; + + bool dumpAsText = layoutTestController->dumpAsText(); + dumpAsText |= [[[[mainFrame dataSource] response] MIMEType] isEqualToString:@"text/plain"]; + layoutTestController->setDumpAsText(dumpAsText); + if (layoutTestController->dumpAsText()) { + result = dumpFramesAsText(mainFrame); + } else if (layoutTestController->dumpDOMAsWebArchive()) { + WebArchive *webArchive = [[mainFrame DOMDocument] webArchive]; + result = serializeWebArchiveToXML(webArchive); + } else if (layoutTestController->dumpSourceAsWebArchive()) { + WebArchive *webArchive = [[mainFrame dataSource] webArchive]; + result = serializeWebArchiveToXML(webArchive); + } else { + bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length); + if (isSVGW3CTest) + [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)]; + else + [[mainFrame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)]; + result = [mainFrame renderTreeAsExternalRepresentation]; + } + + if (!result) { + const char *errorMessage; + if (layoutTestController->dumpAsText()) + errorMessage = "[documentElement innerText]"; + else if (layoutTestController->dumpDOMAsWebArchive()) + errorMessage = "[[mainFrame DOMDocument] webArchive]"; + else if (layoutTestController->dumpSourceAsWebArchive()) + errorMessage = "[[mainFrame dataSource] webArchive]"; + else + errorMessage = "[mainFrame renderTreeAsExternalRepresentation]"; + printf("ERROR: nil result from %s", errorMessage); + } else { + NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding]; + fwrite([data bytes], 1, [data length], stdout); + if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive()) + dumpFrameScrollPosition(mainFrame); + } + + if (layoutTestController->dumpBackForwardList()) { + unsigned count = CFArrayGetCount(allWindowsRef); + for (unsigned i = 0; i < count; i++) { + NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(allWindowsRef, i); + WebView *webView = [[[window contentView] subviews] objectAtIndex:0]; + dumpBackForwardListForWebView(webView); + } + } + + if (printSeparators) + puts("#EOF"); + } + + if (dumpPixels) { + if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive()) { + // grab a bitmap from the view + WebView* view = [mainFrame webView]; + NSSize webViewSize = [view frame].size; + + CGContextRef cgContext = CGBitmapContextCreate(screenCaptureBuffer, static_cast(webViewSize.width), static_cast(webViewSize.height), 8, static_cast(webViewSize.width) * 4, sharedColorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedLast); + + NSGraphicsContext* savedContext = [[[NSGraphicsContext currentContext] retain] autorelease]; + NSGraphicsContext* nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO]; + [NSGraphicsContext setCurrentContext:nsContext]; + + if (readFromWindow) { + NSBitmapImageRep *imageRep; + [view displayIfNeeded]; + [view lockFocus]; + imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]]; + [view unlockFocus]; + [imageRep draw]; + [imageRep release]; + } else if (!layoutTestController->testRepaint()) + [view displayRectIgnoringOpacity:NSMakeRect(0, 0, webViewSize.width, webViewSize.height) inContext:nsContext]; + else if (!layoutTestController->testRepaintSweepHorizontally()) { + NSRect line = NSMakeRect(0, 0, webViewSize.width, 1); + while (line.origin.y < webViewSize.height) { + [view displayRectIgnoringOpacity:line inContext:nsContext]; + line.origin.y++; + } + } else { + NSRect column = NSMakeRect(0, 0, 1, webViewSize.height); + while (column.origin.x < webViewSize.width) { + [view displayRectIgnoringOpacity:column inContext:nsContext]; + column.origin.x++; + } + } + if (layoutTestController->dumpSelectionRect()) { + NSView *documentView = [[mainFrame frameView] documentView]; + if ([documentView conformsToProtocol:@protocol(WebDocumentSelection)]) { + [[NSColor redColor] set]; + [NSBezierPath strokeRect:[documentView convertRect:[(id )documentView selectionRect] fromView:nil]]; + } + } + + [NSGraphicsContext setCurrentContext:savedContext]; + + CGImageRef bitmapImage = CGBitmapContextCreateImage(cgContext); + CGContextRelease(cgContext); + + // compute the actual hash to compare to the expected image's hash + NSString *actualHash = md5HashStringForBitmap(bitmapImage); + printf("\nActualHash: %s\n", [actualHash UTF8String]); + + BOOL dumpImage; + if (dumpAllPixels) + dumpImage = YES; + else { + // FIXME: It's unfortunate that we hardcode the file naming scheme here. + // At one time, the perl script had all the knowledge about file layout. + // Some day we should restore that setup by passing in more parameters to this tool. + NSString *baseTestPath = [currentTest stringByDeletingPathExtension]; + NSString *baselineHashPath = [baseTestPath stringByAppendingString:@"-expected.checksum"]; + NSString *baselineHash = [NSString stringWithContentsOfFile:baselineHashPath encoding:NSUTF8StringEncoding error:nil]; + NSString *baselineImagePath = [baseTestPath stringByAppendingString:@"-expected.png"]; + + printf("BaselineHash: %s\n", [baselineHash UTF8String]); + + /// send the image to stdout if the hash mismatches or if there's no file in the file system + dumpImage = ![baselineHash isEqualToString:actualHash] || access([baselineImagePath fileSystemRepresentation], F_OK) != 0; + } + + if (dumpImage) { + CFMutableDataRef imageData = CFDataCreateMutable(0, 0); + CGImageDestinationRef imageDest = CGImageDestinationCreateWithData(imageData, CFSTR("public.png"), 1, 0); + CGImageDestinationAddImage(imageDest, bitmapImage, 0); + CGImageDestinationFinalize(imageDest); + CFRelease(imageDest); + printf("Content-length: %lu\n", CFDataGetLength(imageData)); + fwrite(CFDataGetBytePtr(imageData), 1, CFDataGetLength(imageData), stdout); + CFRelease(imageData); + } + + CGImageRelease(bitmapImage); + } + + printf("#EOF\n"); + } + + fflush(stdout); + + if (paint) + displayWebView(); + + done = YES; +} + +static bool shouldLogFrameLoadDelegates(const char *pathOrURL) +{ + return strstr(pathOrURL, "loading/"); +} + +static void runTest(const char *pathOrURL) +{ + CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8); + if (!pathOrURLString) { + fprintf(stderr, "can't parse filename as UTF-8\n"); + return; + } + + CFURLRef URL; + if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")) || CFStringHasPrefix(pathOrURLString, CFSTR("https://"))) + URL = CFURLCreateWithString(NULL, pathOrURLString, NULL); + else + URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE); + + if (!URL) { + CFRelease(pathOrURLString); + fprintf(stderr, "can't turn %s into a CFURL\n", pathOrURL); + return; + } + + layoutTestController = new LayoutTestController(testRepaintDefault, repaintSweepHorizontallyDefault); + + [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:YES]; + [[mainFrame webView] makeTextStandardSize:nil]; + [[mainFrame webView] setTabKeyCyclesThroughElements: YES]; + [[mainFrame webView] setPolicyDelegate:nil]; + [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO]; + [WebView _setUsesTestModeFocusRingColor:YES]; + + topLoadingFrame = nil; + + done = NO; + readFromWindow = NO; + + if (disallowedURLs) + CFSetRemoveAllValues(disallowedURLs); + if (shouldLogFrameLoadDelegates(pathOrURL)) + layoutTestController->setDumpFrameLoadCallbacks(true); + + if ([WebHistory optionalSharedHistory]) + [WebHistory setOptionalSharedHistory:nil]; + lastMousePosition = NSMakePoint(0, 0); + + if (currentTest != nil) + CFRelease(currentTest); + currentTest = (NSString *)pathOrURLString; + [prevTestBFItem release]; + prevTestBFItem = [[[[mainFrame webView] backForwardList] currentItem] retain]; + + WorkQueue::shared()->clear(); + WorkQueue::shared()->setFrozen(false); + + BOOL _shouldIgnoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(CFURLGetString(URL)); + if (_shouldIgnoreWebCoreNodeLeaks) + [WebCoreStatistics startIgnoringWebCoreNodeLeaks]; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [mainFrame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]]; + CFRelease(URL); + [pool release]; + while (!done) { + pool = [[NSAutoreleasePool alloc] init]; + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]; + [pool release]; + } + pool = [[NSAutoreleasePool alloc] init]; + [EventSendingController clearSavedEvents]; + [[mainFrame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream]; + + WorkQueue::shared()->clear(); + + if (layoutTestController->closeRemainingWindowsWhenComplete()) { + NSArray* array = [(NSArray *)allWindowsRef copy]; + + unsigned count = [array count]; + for (unsigned i = 0; i < count; i++) { + NSWindow *window = [array objectAtIndex:i]; + + // Don't try to close the main window + if (window == [[mainFrame webView] window]) + continue; + + WebView *webView = [[[window contentView] subviews] objectAtIndex:0]; + + [webView close]; + [window close]; + } + [array release]; + } + + [pool release]; + + // We should only have our main window left when we're done + ASSERT(CFArrayGetCount(allWindowsRef) == 1); + ASSERT(CFArrayGetValueAtIndex(allWindowsRef, 0) == [[mainFrame webView] window]); + + delete layoutTestController; + layoutTestController = 0; + + if (_shouldIgnoreWebCoreNodeLeaks) + [WebCoreStatistics stopIgnoringWebCoreNodeLeaks]; +} + +/* Hashes a bitmap and returns a text string for comparison and saving to a file */ +static NSString *md5HashStringForBitmap(CGImageRef bitmap) +{ + MD5_CTX md5Context; + unsigned char hash[16]; + + unsigned bitsPerPixel = CGImageGetBitsPerPixel(bitmap); + assert(bitsPerPixel == 32); // ImageDiff assumes 32 bit RGBA, we must as well. + unsigned bytesPerPixel = bitsPerPixel / 8; + unsigned pixelsHigh = CGImageGetHeight(bitmap); + unsigned pixelsWide = CGImageGetWidth(bitmap); + unsigned bytesPerRow = CGImageGetBytesPerRow(bitmap); + assert(bytesPerRow >= (pixelsWide * bytesPerPixel)); + + MD5_Init(&md5Context); + unsigned char *bitmapData = screenCaptureBuffer; + for (unsigned row = 0; row < pixelsHigh; row++) { + MD5_Update(&md5Context, bitmapData, pixelsWide * bytesPerPixel); + bitmapData += bytesPerRow; + } + MD5_Final(hash, &md5Context); + + char hex[33] = ""; + for (int i = 0; i < 16; i++) { + snprintf(hex, 33, "%s%02x", hex, hash[i]); + } + + return [NSString stringWithUTF8String:hex]; +} + +void displayWebView() +{ + NSView *webView = [mainFrame webView]; + [webView display]; + [webView lockFocus]; + [[[NSColor blackColor] colorWithAlphaComponent:0.66] set]; + NSRectFillUsingOperation([webView frame], NSCompositeSourceOver); + [webView unlockFocus]; + readFromWindow = YES; +} + +@implementation DumpRenderTreePasteboard + +// Return a local pasteboard so we don't disturb the real pasteboards when running tests. ++ (NSPasteboard *)_pasteboardWithName:(NSString *)name +{ + static int number = 0; + if (!name) + name = [NSString stringWithFormat:@"LocalPasteboard%d", ++number]; + LocalPasteboard *pasteboard = [localPasteboards objectForKey:name]; + if (pasteboard) + return pasteboard; + pasteboard = [[LocalPasteboard alloc] init]; + [localPasteboards setObject:pasteboard forKey:name]; + [pasteboard release]; + return pasteboard; +} + +// Convenience method for JS so that it doesn't have to try and create a NSArray on the objc side instead +// of the usual WebScriptObject that is passed around +- (int)declareType:(NSString *)type owner:(id)newOwner +{ + return [self declareTypes:[NSArray arrayWithObject:type] owner:newOwner]; +} + +@end + +@implementation LocalPasteboard + ++ (id)alloc +{ + return NSAllocateObject(self, 0, 0); +} + +- (id)init +{ + typesArray = [[NSMutableArray alloc] init]; + typesSet = [[NSMutableSet alloc] init]; + dataByType = [[NSMutableDictionary alloc] init]; + return self; +} + +- (void)dealloc +{ + [typesArray release]; + [typesSet release]; + [dataByType release]; + [super dealloc]; +} + +- (NSString *)name +{ + return nil; +} + +- (void)releaseGlobally +{ +} + +- (int)declareTypes:(NSArray *)newTypes owner:(id)newOwner +{ + [typesArray removeAllObjects]; + [typesSet removeAllObjects]; + [dataByType removeAllObjects]; + return [self addTypes:newTypes owner:newOwner]; +} + +- (int)addTypes:(NSArray *)newTypes owner:(id)newOwner +{ + unsigned count = [newTypes count]; + unsigned i; + for (i = 0; i < count; ++i) { + NSString *type = [newTypes objectAtIndex:i]; + NSString *setType = [typesSet member:type]; + if (!setType) { + setType = [type copy]; + [typesArray addObject:setType]; + [typesSet addObject:setType]; + [setType release]; + } + if (newOwner && [newOwner respondsToSelector:@selector(pasteboard:provideDataForType:)]) + [newOwner pasteboard:self provideDataForType:setType]; + } + return ++changeCount; +} + +- (int)changeCount +{ + return changeCount; +} + +- (NSArray *)types +{ + return typesArray; +} + +- (NSString *)availableTypeFromArray:(NSArray *)types +{ + unsigned count = [types count]; + unsigned i; + for (i = 0; i < count; ++i) { + NSString *type = [types objectAtIndex:i]; + NSString *setType = [typesSet member:type]; + if (setType) + return setType; + } + return nil; +} + +- (BOOL)setData:(NSData *)data forType:(NSString *)dataType +{ + if (data == nil) + data = [NSData data]; + if (![typesSet containsObject:dataType]) + return NO; + [dataByType setObject:data forKey:dataType]; + ++changeCount; + return YES; +} + +- (NSData *)dataForType:(NSString *)dataType +{ + return [dataByType objectForKey:dataType]; +} + +- (BOOL)setPropertyList:(id)propertyList forType:(NSString *)dataType; +{ + CFDataRef data = NULL; + if (propertyList) + data = CFPropertyListCreateXMLData(NULL, propertyList); + BOOL result = [self setData:(NSData *)data forType:dataType]; + if (data) + CFRelease(data); + return result; +} + +- (BOOL)setString:(NSString *)string forType:(NSString *)dataType +{ + CFDataRef data = NULL; + if (string) { + if ([string length] == 0) + data = CFDataCreate(NULL, NULL, 0); + else + data = CFStringCreateExternalRepresentation(NULL, (CFStringRef)string, kCFStringEncodingUTF8, 0); + } + BOOL result = [self setData:(NSData *)data forType:dataType]; + if (data) + CFRelease(data); + return result; +} + +@end + +static CFArrayCallBacks NonRetainingArrayCallbacks = { + 0, + NULL, + NULL, + CFCopyDescription, + CFEqual +}; + +@implementation DumpRenderTreeWindow + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation +{ + if (!allWindowsRef) + allWindowsRef = CFArrayCreateMutable(NULL, 0, &NonRetainingArrayCallbacks); + + CFArrayAppendValue(allWindowsRef, self); + + return [super initWithContentRect:contentRect styleMask:styleMask backing:bufferingType defer:deferCreation]; +} + +- (void)dealloc +{ + CFRange arrayRange = CFRangeMake(0, CFArrayGetCount(allWindowsRef)); + CFIndex i = CFArrayGetFirstIndexOfValue(allWindowsRef, arrayRange, self); + assert(i != -1); + + CFArrayRemoveValueAtIndex(allWindowsRef, i); + [super dealloc]; +} + +- (BOOL)isKeyWindow +{ + return layoutTestController ? layoutTestController->windowIsKey() : YES; +} + +- (void)keyDown:(id)sender +{ + // Do nothing, avoiding the beep we'd otherwise get from NSResponder, + // once we get to the end of the responder chain. +} + +@end + +@implementation DumpRenderTreeEvent + ++ (NSPoint)mouseLocation +{ + return [[[mainFrame webView] window] convertBaseToScreen:lastMousePosition]; +} + +@end