2007-11-21 Eric Seidel <eric@webkit.org>
[WebKit-https.git] / WebKitTools / DumpRenderTree / mac / DumpRenderTree.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007 Apple, Inc.  All rights reserved.
3  *           (C) 2007 Graham Dennis (graham.dennis@gmail.com)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer. 
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution. 
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission. 
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29  
30 #import "DumpRenderTree.h"
31
32 #import "CheckedMalloc.h"
33 #import "DumpRenderTreePasteboard.h"
34 #import "DumpRenderTreeWindow.h"
35 #import "EditingDelegate.h"
36 #import "EventSendingController.h"
37 #import "FrameLoadDelegate.h"
38 #import "JavaScriptThreading.h"
39 #import "LayoutTestController.h"
40 #import "NavigationController.h"
41 #import "ObjCPlugin.h"
42 #import "ObjCPluginFunction.h"
43 #import "PixelDumpSupport.h"
44 #import "PolicyDelegate.h"
45 #import "ResourceLoadDelegate.h"
46 #import "UIDelegate.h"
47 #import "WorkQueue.h"
48 #import "WorkQueueItem.h"
49
50 #import <CoreFoundation/CoreFoundation.h>
51 #import <JavaScriptCore/Assertions.h>
52 #import <WebKit/DOMElementPrivate.h>
53 #import <WebKit/DOMExtensions.h>
54 #import <WebKit/DOMRange.h>
55 #import <WebKit/WebBackForwardList.h>
56 #import <WebKit/WebCoreStatistics.h>
57 #import <WebKit/WebDataSourcePrivate.h>
58 #import <WebKit/WebDocumentPrivate.h>
59 #import <WebKit/WebEditingDelegate.h>
60 #import <WebKit/WebFrameView.h>
61 #import <WebKit/WebHistory.h>
62 #import <WebKit/WebHistoryItemPrivate.h>
63 #import <WebKit/WebPluginDatabase.h>
64 #import <WebKit/WebPreferences.h>
65 #import <WebKit/WebPreferencesPrivate.h>
66 #import <WebKit/WebResourceLoadDelegate.h>
67 #import <WebKit/WebViewPrivate.h>
68 #import <getopt.h>
69 #import <mach-o/getsect.h>
70 #import <objc/objc-runtime.h>                       // for class_poseAs
71
72 @interface DumpRenderTreeEvent : NSEvent
73 @end
74
75 static void runTest(const char *pathOrURL);
76
77 // Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
78 // - There is no load in progress
79 // - There is no work queued up (see workQueue var, below)
80 // - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
81 //       and notifyDone was called subsequently.
82 // Note that the call to notifyDone and the end of the load can happen in either order.
83
84 volatile bool done;
85
86 NavigationController* navigationController = 0;
87 LayoutTestController* layoutTestController = 0;
88
89 WebFrame *mainFrame = 0;
90 // This is the topmost frame that is loading, during a given load, or nil when no load is 
91 // in progress.  Usually this is the same as the main frame, but not always.  In the case
92 // where a frameset is loaded, and then new content is loaded into one of the child frames,
93 // that child frame is the "topmost frame that is loading".
94 WebFrame *topLoadingFrame = nil;     // !nil iff a load is in progress
95
96
97 CFMutableSetRef disallowedURLs = 0;
98 CFRunLoopTimerRef waitToDumpWatchdog = 0;
99
100 // Delegates
101 static FrameLoadDelegate *frameLoadDelegate;
102 static UIDelegate *uiDelegate;
103 static EditingDelegate *editingDelegate;
104 static ResourceLoadDelegate *resourceLoadDelegate;
105 PolicyDelegate *policyDelegate;
106
107 static int dumpPixels;
108 static int dumpAllPixels;
109 static int threaded;
110 static int testRepaintDefault;
111 static int repaintSweepHorizontallyDefault;
112 static int dumpTree = YES;
113 static BOOL printSeparators;
114 static NSString *currentTest = nil;
115
116 static WebHistoryItem *prevTestBFItem = nil;  // current b/f item at the end of the previous test
117
118 const unsigned maxViewHeight = 600;
119 const unsigned maxViewWidth = 800;
120
121
122 static BOOL shouldIgnoreWebCoreNodeLeaks(CFStringRef URLString)
123 {
124     static CFStringRef const ignoreSet[] = {
125         // Keeping this infrastructure around in case we ever need it again.
126     };
127     static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(CFStringRef);
128     
129     for (int i = 0; i < ignoreSetCount; i++) {
130         CFStringRef ignoreString = ignoreSet[i];
131         CFRange range = CFRangeMake(0, CFStringGetLength(URLString));
132         CFOptionFlags flags = kCFCompareAnchored | kCFCompareBackwards | kCFCompareCaseInsensitive;
133         if (CFStringFindWithOptions(URLString, ignoreString, range, flags, NULL))
134             return YES;
135     }
136     return NO;
137 }
138
139 static void activateAhemFont()
140 {    
141     unsigned long fontDataLength;
142     char* fontData = getsectdata("__DATA", "Ahem", &fontDataLength);
143     if (!fontData) {
144         fprintf(stderr, "Failed to locate the Ahem font.\n");
145         exit(1);
146     }
147
148     ATSFontContainerRef fontContainer;
149     OSStatus status = ATSFontActivateFromMemory(fontData, fontDataLength, kATSFontContextLocal, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &fontContainer);
150
151     if (status != noErr) {
152         fprintf(stderr, "Failed to activate the Ahem font.\n");
153         exit(1);
154     }
155 }
156
157 WebView *createWebViewAndOffscreenWindow()
158 {
159     NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
160     WebView *webView = [[WebView alloc] initWithFrame:rect frameName:nil groupName:@"org.webkit.DumpRenderTree"];
161         
162     [webView setUIDelegate:uiDelegate];
163     [webView setFrameLoadDelegate:frameLoadDelegate];
164     [webView setEditingDelegate:editingDelegate];
165     [webView setResourceLoadDelegate:resourceLoadDelegate];
166
167     // Register the same schemes that Safari does
168     [WebView registerURLSchemeAsLocal:@"feed"];
169     [WebView registerURLSchemeAsLocal:@"feeds"];
170     [WebView registerURLSchemeAsLocal:@"feedsearch"];
171
172     // The back/forward cache is causing problems due to layouts during transition from one page to another.
173     // So, turn it off for now, but we might want to turn it back on some day.
174     [[webView backForwardList] setPageCacheSize:0];
175     
176     [webView setContinuousSpellCheckingEnabled:YES];
177     
178     // 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.
179     // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
180     NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
181     DumpRenderTreeWindow *window = [[DumpRenderTreeWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
182     [[window contentView] addSubview:webView];
183     [window orderBack:nil];
184     [window setAutodisplay:NO];
185     
186     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
187     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
188     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
189     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
190         
191     return webView;
192 }
193
194 void testStringByEvaluatingJavaScriptFromString()
195 {
196     // maps expected result <= JavaScript expression
197     NSDictionary *expressions = [NSDictionary dictionaryWithObjectsAndKeys:
198         @"0", @"0", 
199         @"0", @"'0'", 
200         @"", @"",
201         @"", @"''", 
202         @"", @"new String()", 
203         @"", @"new String('0')", 
204         @"", @"throw 1", 
205         @"", @"{ }", 
206         @"", @"[ ]", 
207         @"", @"//", 
208         @"", @"a.b.c", 
209         @"", @"(function() { throw 'error'; })()", 
210         @"", @"null",
211         @"", @"undefined",
212         @"true", @"true",
213         @"false", @"false",
214         nil
215     ];
216
217     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
218     WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];
219
220     NSEnumerator *enumerator = [expressions keyEnumerator];
221     id expression;
222     while ((expression = [enumerator nextObject])) {
223         NSString *expectedResult = [expressions objectForKey:expression];
224         NSString *result = [webView stringByEvaluatingJavaScriptFromString:expression];
225         assert([result isEqualToString:expectedResult]);
226     }
227
228     [webView close];
229     [webView release];
230     [pool release];
231 }
232
233 static void setDefaultsToConsistentValuesForTesting()
234 {
235     // Give some clear to undocumented defaults values
236     static const int MediumFontSmoothing = 2;
237     static const int BlueTintedAppearance = 1;
238
239     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
240     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
241     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; // smallest font size to CG should perform antialiasing on
242     [defaults setInteger:MediumFontSmoothing forKey:@"AppleFontSmoothing"];
243     [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"];
244     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
245     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
246     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
247
248     WebPreferences *preferences = [WebPreferences standardPreferences];
249
250     [preferences setStandardFontFamily:@"Times"];
251     [preferences setFixedFontFamily:@"Courier"];
252     [preferences setSerifFontFamily:@"Times"];
253     [preferences setSansSerifFontFamily:@"Helvetica"];
254     [preferences setCursiveFontFamily:@"Apple Chancery"];
255     [preferences setFantasyFontFamily:@"Papyrus"];
256     [preferences setDefaultFontSize:16];
257     [preferences setDefaultFixedFontSize:13];
258     [preferences setMinimumFontSize:1];
259     [preferences setJavaEnabled:NO];
260     [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
261     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
262     [preferences setTabsToLinks:NO];
263     [preferences setDOMPasteAllowed:YES];
264 }
265
266 static void crashHandler(int sig)
267 {
268     fprintf(stderr, "%s\n", strsignal(sig));
269     restoreColorSpace(0);
270     exit(128 + sig);
271 }
272
273 static void installSignalHandlers()
274 {
275     signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
276     signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
277     signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
278     signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
279     signal(SIGBUS, crashHandler);    /* 10:  bus error */
280     signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
281     signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
282     signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
283     signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
284     signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
285 }
286
287 static void allocateGlobalControllers()
288 {
289     // FIXME: We should remove these and move to the ObjC standard [Foo sharedInstance] model
290     navigationController = [[NavigationController alloc] init];
291     frameLoadDelegate = [[FrameLoadDelegate alloc] init];
292     uiDelegate = [[UIDelegate alloc] init];
293     editingDelegate = [[EditingDelegate alloc] init];
294     resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
295     policyDelegate = [[PolicyDelegate alloc] init];
296 }
297
298 // ObjC++ doens't seem to let me pass NSObject*& sadly.
299 static inline void releaseAndZero(NSObject** object)
300 {
301     [*object release];
302     *object = nil;
303 }
304
305 static void releaseGlobalControllers()
306 {
307     releaseAndZero(&navigationController);
308     releaseAndZero(&frameLoadDelegate);
309     releaseAndZero(&editingDelegate);
310     releaseAndZero(&resourceLoadDelegate);
311     releaseAndZero(&uiDelegate);
312     releaseAndZero(&policyDelegate);
313 }
314
315 static void initializeGlobalsFromCommandLineOptions(int argc, const char *argv[])
316 {
317     struct option options[] = {
318         {"dump-all-pixels", no_argument, &dumpAllPixels, YES},
319         {"horizontal-sweep", no_argument, &repaintSweepHorizontallyDefault, YES},
320         {"notree", no_argument, &dumpTree, NO},
321         {"pixel-tests", no_argument, &dumpPixels, YES},
322         {"repaint", no_argument, &testRepaintDefault, YES},
323         {"tree", no_argument, &dumpTree, YES},
324         {"threaded", no_argument, &threaded, YES},
325         {NULL, 0, NULL, 0}
326     };
327     
328     int option;
329     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) {
330         switch (option) {
331             case '?':   // unknown or ambiguous option
332             case ':':   // missing argument
333                 exit(1);
334                 break;
335         }
336     }
337 }
338
339 static void addTestPluginsToPluginSearchPath(const char* executablePath)
340 {
341     NSString *pwd = [[NSString stringWithUTF8String:executablePath] stringByDeletingLastPathComponent];
342     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
343     [[WebPluginDatabase sharedDatabase] refresh];
344 }
345
346 static bool useLongRunningServerMode(int argc, const char *argv[])
347 {
348     // This assumes you've already called getopt_long
349     return (argc == optind+1 && strcmp(argv[optind], "-") == 0);
350 }
351
352 static void runTestingServerLoop()
353 {
354     // When DumpRenderTree run in server mode, we just wait around for file names
355     // to be passed to us and read each in turn, passing the results back to the client
356     char filenameBuffer[2048];
357     while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
358         char *newLineCharacter = strchr(filenameBuffer, '\n');
359         if (newLineCharacter)
360             *newLineCharacter = '\0';
361
362         if (strlen(filenameBuffer) == 0)
363             continue;
364
365         runTest(filenameBuffer);
366     }
367 }
368
369 static void prepareConsistentTestingEnvironment()
370 {
371     class_poseAs(objc_getClass("DumpRenderTreePasteboard"), objc_getClass("NSPasteboard"));
372     class_poseAs(objc_getClass("DumpRenderTreeEvent"), objc_getClass("NSEvent"));
373
374     setDefaultsToConsistentValuesForTesting();
375     activateAhemFont();
376     
377     if (dumpPixels)
378         initializeColorSpaceAndScreeBufferForPixelTests();
379     allocateGlobalControllers();
380     
381     makeLargeMallocFailSilently();
382 }
383
384 void dumpRenderTree(int argc, const char *argv[])
385 {
386     prepareConsistentTestingEnvironment();
387     initializeGlobalsFromCommandLineOptions(argc, argv);
388     addTestPluginsToPluginSearchPath(argv[0]);
389     installSignalHandlers();
390     
391     WebView *webView = createWebViewAndOffscreenWindow();
392     mainFrame = [webView mainFrame];
393
394     [[NSURLCache sharedURLCache] removeAllCachedResponses];
395
396     // <rdar://problem/5222911>
397     testStringByEvaluatingJavaScriptFromString();
398
399     if (threaded)
400         startJavaScriptThreads();
401
402     if (useLongRunningServerMode(argc, argv)) {
403         printSeparators = YES;
404         runTestingServerLoop();
405     } else {
406         printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
407         for (int i = optind; i != argc; ++i)
408             runTest(argv[i]);
409     }
410
411     if (threaded)
412         stopJavaScriptThreads();
413
414     [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts    
415     [webView close];
416     mainFrame = nil;
417
418     // Work around problem where registering drag types leaves an outstanding
419     // "perform selector" on the window, which retains the window. It's a bit
420     // inelegant and perhaps dangerous to just blow them all away, but in practice
421     // it probably won't cause any trouble (and this is just a test tool, after all).
422     NSWindow *window = [webView window];
423     [NSObject cancelPreviousPerformRequestsWithTarget:window];
424     
425     [window close]; // releases when closed
426     [webView release];
427     
428     releaseGlobalControllers();
429     
430     [DumpRenderTreePasteboard releaseLocalPasteboards];
431
432     // FIXME: This should be moved onto LayoutTestController and made into a HashSet
433     if (disallowedURLs) {
434         CFRelease(disallowedURLs);
435         disallowedURLs = 0;
436     }
437
438     if (dumpPixels)
439         restoreColorSpace(0);
440 }
441
442 int main(int argc, const char *argv[])
443 {
444     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
445     [NSApplication sharedApplication]; // Force AppKit to init itself
446     dumpRenderTree(argc, argv);
447     [WebCoreStatistics garbageCollectJavaScriptObjects];
448     [pool release];
449     return 0;
450 }
451
452 static int compareHistoryItems(id item1, id item2, void *context)
453 {
454     return [[item1 target] caseInsensitiveCompare:[item2 target]];
455 }
456
457 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
458 {
459     int start = 0;
460     if (current) {
461         printf("curr->");
462         start = 6;
463     }
464     for (int i = start; i < indent; i++)
465         putchar(' ');
466     printf("%s", [[item URLString] UTF8String]);
467     NSString *target = [item target];
468     if (target && [target length] > 0)
469         printf(" (in frame \"%s\")", [target UTF8String]);
470     if ([item isTargetItem])
471         printf("  **nav target**");
472     putchar('\n');
473     NSArray *kids = [item children];
474     if (kids) {
475         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
476         kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
477         for (unsigned i = 0; i < [kids count]; i++)
478             dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
479     }
480 }
481
482 static void dumpFrameScrollPosition(WebFrame *f)
483 {
484     NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin;
485     if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
486         if ([f parentFrame] != nil)
487             printf("frame '%s' ", [[f name] UTF8String]);
488         printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
489     }
490
491     if (layoutTestController->dumpChildFrameScrollPositions()) {
492         NSArray *kids = [f childFrames];
493         if (kids)
494             for (unsigned i = 0; i < [kids count]; i++)
495                 dumpFrameScrollPosition([kids objectAtIndex:i]);
496     }
497 }
498
499 static NSString *dumpFramesAsText(WebFrame *frame)
500 {
501     DOMDocument *document = [frame DOMDocument];
502     DOMElement *documentElement = [document documentElement];
503
504     if (!documentElement)
505         return @"";
506
507     NSMutableString *result = [[[NSMutableString alloc] init] autorelease];
508
509     // Add header for all but the main frame.
510     if ([frame parentFrame])
511         result = [NSMutableString stringWithFormat:@"\n--------\nFrame: '%@'\n--------\n", [frame name]];
512
513     [result appendFormat:@"%@\n", [documentElement innerText]];
514
515     if (layoutTestController->dumpChildFramesAsText()) {
516         NSArray *kids = [frame childFrames];
517         if (kids) {
518             for (unsigned i = 0; i < [kids count]; i++)
519                 [result appendString:dumpFramesAsText([kids objectAtIndex:i])];
520         }
521     }
522
523     return result;
524 }
525
526 static void convertMIMEType(NSMutableString *mimeType)
527 {
528     if ([mimeType isEqualToString:@"application/x-javascript"])
529         [mimeType setString:@"text/javascript"];
530 }
531
532 static void convertWebResourceDataToString(NSMutableDictionary *resource)
533 {
534     NSMutableString *mimeType = [resource objectForKey:@"WebResourceMIMEType"];
535     convertMIMEType(mimeType);
536     
537     if ([mimeType hasPrefix:@"text/"]) {
538         NSData *data = [resource objectForKey:@"WebResourceData"];
539         NSString *dataAsString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
540         [resource setObject:dataAsString forKey:@"WebResourceData"];
541     }
542 }
543
544 static void normalizeWebResourceURL(NSMutableString *webResourceURL, NSString *oldURLBase)
545 {
546     [webResourceURL replaceOccurrencesOfString:oldURLBase
547                                     withString:@"file://"
548                                        options:NSLiteralSearch
549                                          range:NSMakeRange(0, [webResourceURL length])];
550 }
551
552 static void convertWebResourceResponseToDictionary(NSMutableDictionary *propertyList, NSString *oldURLBase)
553 {
554     NSURLResponse *response = nil;
555     NSData *responseData = [propertyList objectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
556     if ([responseData isKindOfClass:[NSData class]]) {
557         // Decode NSURLResponse
558         NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:responseData];
559         response = [unarchiver decodeObjectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
560         [unarchiver finishDecoding];
561         [unarchiver release];
562     }        
563     
564     NSMutableDictionary *responseDictionary = [[NSMutableDictionary alloc] init];
565     
566     NSMutableString *urlString = [[[response URL] description] mutableCopy];
567     normalizeWebResourceURL(urlString, oldURLBase);
568     [responseDictionary setObject:urlString forKey:@"URL"];
569     [urlString release];
570     
571     NSMutableString *mimeTypeString = [[response MIMEType] mutableCopy];
572     convertMIMEType(mimeTypeString);
573     [responseDictionary setObject:mimeTypeString forKey:@"MIMEType"];
574     [mimeTypeString release];
575
576     NSString *textEncodingName = [response textEncodingName];
577     if (textEncodingName)
578         [responseDictionary setObject:textEncodingName forKey:@"textEncodingName"];
579     [responseDictionary setObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:@"expectedContentLength"];
580     
581     if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
582         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
583         
584         [responseDictionary setObject:[httpResponse allHeaderFields] forKey:@"allHeaderFields"];
585         [responseDictionary setObject:[NSNumber numberWithInt:[httpResponse statusCode]] forKey:@"statusCode"];
586     }
587     
588     [propertyList setObject:responseDictionary forKey:@"WebResourceResponse"];
589     [responseDictionary release];
590 }
591
592 static NSString *serializeWebArchiveToXML(WebArchive *webArchive)
593 {
594     NSString *errorString;
595     NSMutableDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data]
596                                                                          mutabilityOption:NSPropertyListMutableContainersAndLeaves
597                                                                                    format:NULL
598                                                                          errorDescription:&errorString];
599     if (!propertyList)
600         return errorString;
601
602     // Normalize WebResourceResponse and WebResourceURL values in plist for testing
603     NSString *cwdURL = [@"file://" stringByAppendingString:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
604     
605     NSMutableArray *resources = [NSMutableArray arrayWithCapacity:1];
606     [resources addObject:propertyList];
607
608     while ([resources count]) {
609         NSMutableDictionary *resourcePropertyList = [resources objectAtIndex:0];
610         [resources removeObjectAtIndex:0];
611
612         NSMutableDictionary *mainResource = [resourcePropertyList objectForKey:@"WebMainResource"];
613         normalizeWebResourceURL([mainResource objectForKey:@"WebResourceURL"], cwdURL);
614         convertWebResourceDataToString(mainResource);
615
616         // Add subframeArchives to list for processing
617         NSMutableArray *subframeArchives = [resourcePropertyList objectForKey:@"WebSubframeArchives"]; // WebSubframeArchivesKey in WebArchive.m
618         if (subframeArchives)
619             [resources addObjectsFromArray:subframeArchives];
620
621         NSMutableArray *subresources = [resourcePropertyList objectForKey:@"WebSubresources"]; // WebSubresourcesKey in WebArchive.m
622         NSEnumerator *enumerator = [subresources objectEnumerator];
623         NSMutableDictionary *subresourcePropertyList;
624         while ((subresourcePropertyList = [enumerator nextObject])) {
625             normalizeWebResourceURL([subresourcePropertyList objectForKey:@"WebResourceURL"], cwdURL);
626             convertWebResourceResponseToDictionary(subresourcePropertyList, cwdURL);
627             convertWebResourceDataToString(subresourcePropertyList);
628         }
629     }
630
631     NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList
632                                                                  format:NSPropertyListXMLFormat_v1_0
633                                                        errorDescription:&errorString];
634     if (!xmlData)
635         return errorString;
636
637     NSMutableString *string = [[[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] autorelease];
638
639     // Replace "Apple Computer" with "Apple" in the DTD declaration.
640     NSRange range = [string rangeOfString:@"-//Apple Computer//"];
641     if (range.location != NSNotFound)
642         [string replaceCharactersInRange:range withString:@"-//Apple//"];
643     
644     return string;
645 }
646
647 static void dumpBackForwardListForWebView(WebView *view)
648 {
649     printf("\n============== Back Forward List ==============\n");
650     WebBackForwardList *bfList = [view backForwardList];
651
652     // Print out all items in the list after prevTestBFItem, which was from the previous test
653     // Gather items from the end of the list, the print them out from oldest to newest
654     NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
655     for (int i = [bfList forwardListCount]; i > 0; i--) {
656         WebHistoryItem *item = [bfList itemAtIndex:i];
657         // something is wrong if the item from the last test is in the forward part of the b/f list
658         assert(item != prevTestBFItem);
659         [itemsToPrint addObject:item];
660     }
661             
662     assert([bfList currentItem] != prevTestBFItem);
663     [itemsToPrint addObject:[bfList currentItem]];
664     int currentItemIndex = [itemsToPrint count] - 1;
665
666     for (int i = -1; i >= -[bfList backListCount]; i--) {
667         WebHistoryItem *item = [bfList itemAtIndex:i];
668         if (item == prevTestBFItem)
669             break;
670         [itemsToPrint addObject:item];
671     }
672
673     for (int i = [itemsToPrint count]-1; i >= 0; i--)
674         dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
675
676     [itemsToPrint release];
677     printf("===============================================\n");
678 }
679
680 static void sizeWebViewForCurrentTest()
681 {
682     // W3C SVG tests expect to be 480x360
683     bool isSVGW3CTest = ([currentTest rangeOfString:@"svg/W3C-SVG-1.1"].length);
684     if (isSVGW3CTest)
685         [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)];
686     else
687         [[mainFrame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
688 }
689
690 static const char *methodNameStringForFailedTest()
691 {
692     const char *errorMessage;
693     if (layoutTestController->dumpAsText())
694         errorMessage = "[documentElement innerText]";
695     else if (layoutTestController->dumpDOMAsWebArchive())
696         errorMessage = "[[mainFrame DOMDocument] webArchive]";
697     else if (layoutTestController->dumpSourceAsWebArchive())
698         errorMessage = "[[mainFrame dataSource] webArchive]";
699     else
700         errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
701
702     return errorMessage;
703 }
704
705 static void dumpBackForwardListForAllWindows()
706 {
707     CFArrayRef allWindows = (CFArrayRef)[DumpRenderTreeWindow allWindows];
708     unsigned count = CFArrayGetCount(allWindows);
709     for (unsigned i = 0; i < count; i++) {
710         NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(allWindows, i);
711         WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
712         dumpBackForwardListForWebView(webView);
713     }
714 }
715
716 static void invalidateAnyPreviousWaitToDumpWatchdog()
717 {
718     if (waitToDumpWatchdog) {
719         CFRunLoopTimerInvalidate(waitToDumpWatchdog);
720         CFRelease(waitToDumpWatchdog);
721         waitToDumpWatchdog = 0;
722     }
723 }
724
725 void dump()
726 {
727     invalidateAnyPreviousWaitToDumpWatchdog();
728
729     if (dumpTree) {
730         NSString *resultString = nil;
731         NSData *resultData = nil;
732
733         bool dumpAsText = layoutTestController->dumpAsText();
734         dumpAsText |= [[[mainFrame dataSource] _responseMIMEType] isEqualToString:@"text/plain"];
735         layoutTestController->setDumpAsText(dumpAsText);
736         if (layoutTestController->dumpAsText()) {
737             resultString = dumpFramesAsText(mainFrame);
738         } else if (layoutTestController->dumpDOMAsWebArchive()) {
739             WebArchive *webArchive = [[mainFrame DOMDocument] webArchive];
740             resultString = serializeWebArchiveToXML(webArchive);
741         } else if (layoutTestController->dumpSourceAsWebArchive()) {
742             WebArchive *webArchive = [[mainFrame dataSource] webArchive];
743             resultString = serializeWebArchiveToXML(webArchive);
744         } else {
745             sizeWebViewForCurrentTest();
746             resultString = [mainFrame renderTreeAsExternalRepresentation];
747         }
748
749         if (resultString && !resultData)
750             resultData = [resultString dataUsingEncoding:NSUTF8StringEncoding];
751
752         if (resultData) {
753             fwrite([resultData bytes], 1, [resultData length], stdout);
754
755             if (!layoutTestController->dumpAsText() && !layoutTestController->dumpDOMAsWebArchive() && !layoutTestController->dumpSourceAsWebArchive())
756                 dumpFrameScrollPosition(mainFrame);
757
758             if (layoutTestController->dumpBackForwardList())
759                 dumpBackForwardListForAllWindows();
760         } else
761             printf("ERROR: nil result from %s", methodNameStringForFailedTest());
762
763         if (printSeparators)
764             puts("#EOF");
765     }
766     
767     if (dumpPixels)
768         dumpWebViewAsPixelsAndCompareWithExpected(currentTest, dumpAllPixels);
769
770     fflush(stdout);
771
772     done = YES;
773 }
774
775 static bool shouldLogFrameLoadDelegates(const char *pathOrURL)
776 {
777     return strstr(pathOrURL, "loading/");
778 }
779
780 static CFURLRef createCFURLFromPathOrURL(CFStringRef pathOrURLString)
781 {
782     CFURLRef URL;
783     if (CFStringHasPrefix(pathOrURLString, CFSTR("http://")) || CFStringHasPrefix(pathOrURLString, CFSTR("https://")))
784         URL = CFURLCreateWithString(NULL, pathOrURLString, NULL);
785     else
786         URL = CFURLCreateWithFileSystemPath(NULL, pathOrURLString, kCFURLPOSIXPathStyle, FALSE);
787     return URL;
788 }
789
790 static void resetWebViewToConsistentStateBeforeTesting()
791 {
792     [(EditingDelegate *)[[mainFrame webView] editingDelegate] setAcceptsEditing:YES];
793     [[mainFrame webView] makeTextStandardSize:nil];
794     [[mainFrame webView] setTabKeyCyclesThroughElements: YES];
795     [[mainFrame webView] setPolicyDelegate:nil];
796     [[mainFrame webView] _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO];
797     [[[mainFrame webView] preferences] setPrivateBrowsingEnabled:NO];
798     [WebView _setUsesTestModeFocusRingColor:YES];
799 }
800
801 static void runTest(const char *pathOrURL)
802 {
803     CFStringRef pathOrURLString = CFStringCreateWithCString(NULL, pathOrURL, kCFStringEncodingUTF8);
804     if (!pathOrURLString) {
805         fprintf(stderr, "Failed to parse filename as UTF-8: %s\n", pathOrURL);
806         return;
807     }
808
809     CFURLRef URL = createCFURLFromPathOrURL(pathOrURLString);
810     if (!URL) {
811         CFRelease(pathOrURLString);
812         fprintf(stderr, "Can't turn %s into a CFURL\n", pathOrURL);
813         return;
814     }
815
816     resetWebViewToConsistentStateBeforeTesting();
817
818     layoutTestController = new LayoutTestController(testRepaintDefault, repaintSweepHorizontallyDefault);
819     topLoadingFrame = nil;
820     done = NO;
821
822     if (disallowedURLs)
823         CFSetRemoveAllValues(disallowedURLs);
824     if (shouldLogFrameLoadDelegates(pathOrURL))
825         layoutTestController->setDumpFrameLoadCallbacks(true);
826
827     if ([WebHistory optionalSharedHistory])
828         [WebHistory setOptionalSharedHistory:nil];
829     lastMousePosition = NSMakePoint(0, 0);
830
831     if (currentTest != nil)
832         CFRelease(currentTest);
833     currentTest = (NSString *)pathOrURLString;
834     [prevTestBFItem release];
835     prevTestBFItem = [[[[mainFrame webView] backForwardList] currentItem] retain];
836
837     WorkQueue::shared()->clear();
838     WorkQueue::shared()->setFrozen(false);
839
840     BOOL _shouldIgnoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(CFURLGetString(URL));
841     if (_shouldIgnoreWebCoreNodeLeaks)
842         [WebCoreStatistics startIgnoringWebCoreNodeLeaks];
843
844     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
845     [mainFrame loadRequest:[NSURLRequest requestWithURL:(NSURL *)URL]];
846     CFRelease(URL);
847     [pool release];
848     while (!done) {
849         pool = [[NSAutoreleasePool alloc] init];
850         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
851         [pool release];
852     }
853     pool = [[NSAutoreleasePool alloc] init];
854     [EventSendingController clearSavedEvents];
855     [[mainFrame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
856
857     WorkQueue::shared()->clear();
858
859     if (layoutTestController->closeRemainingWindowsWhenComplete()) {
860         NSArray* array = [DumpRenderTreeWindow allWindows];
861         
862         unsigned count = [array count];
863         for (unsigned i = 0; i < count; i++) {
864             NSWindow *window = [array objectAtIndex:i];
865
866             // Don't try to close the main window
867             if (window == [[mainFrame webView] window])
868                 continue;
869             
870             WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
871
872             [webView close];
873             [window close];
874         }
875     }
876     
877     [pool release];
878
879     // We should only have our main window left when we're done
880     ASSERT(CFArrayGetCount(allWindowsRef) == 1);
881     ASSERT(CFArrayGetValueAtIndex(allWindowsRef, 0) == [[mainFrame webView] window]);
882
883     delete layoutTestController;
884     layoutTestController = 0;
885
886     if (_shouldIgnoreWebCoreNodeLeaks)
887         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
888 }
889
890 void displayWebView()
891 {
892     NSView *webView = [mainFrame webView];
893     [webView display];
894     [webView lockFocus];
895     [[[NSColor blackColor] colorWithAlphaComponent:0.66] set];
896     NSRectFillUsingOperation([webView frame], NSCompositeSourceOver);
897     [webView unlockFocus];
898 }
899
900 @implementation DumpRenderTreeEvent
901
902 + (NSPoint)mouseLocation
903 {
904     return [[[mainFrame webView] window] convertBaseToScreen:lastMousePosition];
905 }
906
907 @end