[Mac] REGRESSION: Auto substitution strips new lines
[WebKit-https.git] / Tools / DumpRenderTree / mac / DumpRenderTree.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008, 2009 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 "config.h"
31 #import "DumpRenderTree.h"
32
33 #import "AccessibilityController.h"
34 #import "CheckedMalloc.h"
35 #import "DumpRenderTreeDraggingInfo.h"
36 #import "DumpRenderTreePasteboard.h"
37 #import "DumpRenderTreeWindow.h"
38 #import "EditingDelegate.h"
39 #import "EventSendingController.h"
40 #import "FrameLoadDelegate.h"
41 #import "HistoryDelegate.h"
42 #import "JavaScriptThreading.h"
43 #import "TestRunner.h"
44 #import "MockGeolocationProvider.h"
45 #import "MockWebNotificationProvider.h"
46 #import "NavigationController.h"
47 #import "ObjCPlugin.h"
48 #import "ObjCPluginFunction.h"
49 #import "PixelDumpSupport.h"
50 #import "PolicyDelegate.h"
51 #import "ResourceLoadDelegate.h"
52 #import "StorageTrackerDelegate.h"
53 #import "UIDelegate.h"
54 #import "WebArchiveDumpSupport.h"
55 #import "WebCoreTestSupport.h"
56 #import "WorkQueue.h"
57 #import "WorkQueueItem.h"
58 #import <Carbon/Carbon.h>
59 #import <CoreFoundation/CoreFoundation.h>
60 #import <JavaScriptCore/HeapStatistics.h>
61 #import <JavaScriptCore/Options.h>
62 #import <WebCore/FoundationExtras.h>
63 #import <WebKit/DOMElement.h>
64 #import <WebKit/DOMExtensions.h>
65 #import <WebKit/DOMRange.h>
66 #import <WebKit/WebArchive.h>
67 #import <WebKit/WebBackForwardList.h>
68 #import <WebKit/WebCache.h>
69 #import <WebKit/WebCoreStatistics.h>
70 #import <WebKit/WebDataSourcePrivate.h>
71 #import <WebKit/WebDatabaseManagerPrivate.h>
72 #import <WebKit/WebDocumentPrivate.h>
73 #import <WebKit/WebDeviceOrientationProviderMock.h>
74 #import <WebKit/WebDynamicScrollBarsView.h>
75 #import <WebKit/WebEditingDelegate.h>
76 #import <WebKit/WebFrameView.h>
77 #import <WebKit/WebHistory.h>
78 #import <WebKit/WebHistoryItemPrivate.h>
79 #import <WebKit/WebInspector.h>
80 #import <WebKit/WebKitNSStringExtras.h>
81 #import <WebKit/WebPluginDatabase.h>
82 #import <WebKit/WebPreferences.h>
83 #import <WebKit/WebPreferencesPrivate.h>
84 #import <WebKit/WebPreferenceKeysPrivate.h>
85 #import <WebKit/WebResourceLoadDelegate.h>
86 #import <WebKit/WebStorageManagerPrivate.h>
87 #import <WebKit/WebTypesInternal.h>
88 #import <WebKit/WebViewPrivate.h>
89 #import <getopt.h>
90 #import <wtf/Assertions.h>
91 #import <wtf/FastMalloc.h>
92 #import <wtf/RetainPtr.h>
93 #import <wtf/Threading.h>
94 #import <wtf/ObjcRuntimeExtras.h>
95 #import <wtf/OwnPtr.h>
96
97 extern "C" {
98 #import <mach-o/getsect.h>
99 }
100
101 using namespace std;
102
103 @interface DumpRenderTreeApplication : NSApplication
104 @end
105
106 @interface DumpRenderTreeEvent : NSEvent
107 @end
108
109 @interface NSURLRequest (PrivateThingsWeShouldntReallyUse)
110 +(void)setAllowsAnyHTTPSCertificate:(BOOL)allow forHost:(NSString *)host;
111 @end
112
113 #if USE(APPKIT)
114 @interface NSSound (Details)
115 + (void)_setAlertType:(NSUInteger)alertType;
116 @end
117 #endif
118
119 static void runTest(const string& testPathOrURL);
120
121 // Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
122 // - There is no load in progress
123 // - There is no work queued up (see workQueue var, below)
124 // - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
125 //       and notifyDone was called subsequently.
126 // Note that the call to notifyDone and the end of the load can happen in either order.
127
128 volatile bool done;
129
130 NavigationController* gNavigationController = 0;
131 RefPtr<TestRunner> gTestRunner;
132
133 WebFrame *mainFrame = 0;
134 // This is the topmost frame that is loading, during a given load, or nil when no load is 
135 // in progress.  Usually this is the same as the main frame, but not always.  In the case
136 // where a frameset is loaded, and then new content is loaded into one of the child frames,
137 // that child frame is the "topmost frame that is loading".
138 WebFrame *topLoadingFrame = nil;     // !nil iff a load is in progress
139
140
141 CFMutableSetRef disallowedURLs = 0;
142 static CFRunLoopTimerRef waitToDumpWatchdog = 0;
143
144 // Delegates
145 static FrameLoadDelegate *frameLoadDelegate;
146 static UIDelegate *uiDelegate;
147 static EditingDelegate *editingDelegate;
148 static ResourceLoadDelegate *resourceLoadDelegate;
149 static HistoryDelegate *historyDelegate;
150 PolicyDelegate *policyDelegate;
151 StorageTrackerDelegate *storageDelegate;
152
153 static int dumpPixelsForAllTests = NO;
154 static bool dumpPixelsForCurrentTest = false;
155 static int threaded;
156 static int dumpTree = YES;
157 static int useTimeoutWatchdog = YES;
158 static int forceComplexText;
159 static int gcBetweenTests;
160 static BOOL printSeparators;
161 static RetainPtr<CFStringRef> persistentUserStyleSheetLocation;
162
163 static WebHistoryItem *prevTestBFItem = nil;  // current b/f item at the end of the previous test
164
165 #ifdef __OBJC2__
166 static void swizzleAllMethods(Class imposter, Class original)
167 {
168     unsigned int imposterMethodCount;
169     Method* imposterMethods = class_copyMethodList(imposter, &imposterMethodCount);
170
171     unsigned int originalMethodCount;
172     Method* originalMethods = class_copyMethodList(original, &originalMethodCount);
173
174     for (unsigned int i = 0; i < imposterMethodCount; i++) {
175         SEL imposterMethodName = method_getName(imposterMethods[i]);
176
177         // Attempt to add the method to the original class.  If it fails, the method already exists and we should
178         // instead exchange the implementations.
179         if (class_addMethod(original, imposterMethodName, method_getImplementation(imposterMethods[i]), method_getTypeEncoding(imposterMethods[i])))
180             continue;
181
182         unsigned int j = 0;
183         for (; j < originalMethodCount; j++) {
184             SEL originalMethodName = method_getName(originalMethods[j]);
185             if (sel_isEqual(imposterMethodName, originalMethodName))
186                 break;
187         }
188
189         // If class_addMethod failed above then the method must exist on the original class.
190         ASSERT(j < originalMethodCount);
191         method_exchangeImplementations(imposterMethods[i], originalMethods[j]);
192     }
193
194     free(imposterMethods);
195     free(originalMethods);
196 }
197 #endif
198
199 static void poseAsClass(const char* imposter, const char* original)
200 {
201     Class imposterClass = objc_getClass(imposter);
202     Class originalClass = objc_getClass(original);
203
204 #ifndef __OBJC2__
205     class_poseAs(imposterClass, originalClass);
206 #else
207
208     // Swizzle instance methods
209     swizzleAllMethods(imposterClass, originalClass);
210     // and then class methods
211     swizzleAllMethods(object_getClass(imposterClass), object_getClass(originalClass));
212 #endif
213 }
214
215 void setPersistentUserStyleSheetLocation(CFStringRef url)
216 {
217     persistentUserStyleSheetLocation = url;
218 }
219
220 static bool shouldIgnoreWebCoreNodeLeaks(const string& URLString)
221 {
222     static char* const ignoreSet[] = {
223         // Keeping this infrastructure around in case we ever need it again.
224     };
225     static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(char*);
226     
227     for (int i = 0; i < ignoreSetCount; i++) {
228         // FIXME: ignore case
229         string curIgnore(ignoreSet[i]);
230         // Match at the end of the URLString
231         if (!URLString.compare(URLString.length() - curIgnore.length(), curIgnore.length(), curIgnore))
232             return true;
233     }
234     return false;
235 }
236
237 static NSSet *allowedFontFamilySet()
238 {
239     static NSSet *fontFamiliySet = [[NSSet setWithObjects:
240         @"Ahem",
241         @"Al Bayan",
242         @"American Typewriter",
243         @"Andale Mono",
244         @"Apple Braille",
245         @"Apple Color Emoji",
246         @"Apple Chancery",
247         @"Apple Garamond BT",
248         @"Apple LiGothic",
249         @"Apple LiSung",
250         @"Apple Symbols",
251         @"AppleGothic",
252         @"AppleMyungjo",
253         @"Arial Black",
254         @"Arial Hebrew",
255         @"Arial Narrow",
256         @"Arial Rounded MT Bold",
257         @"Arial Unicode MS",
258         @"Arial",
259         @"Ayuthaya",
260         @"Baghdad",
261         @"Baskerville",
262         @"BiauKai",
263         @"Big Caslon",
264         @"Brush Script MT",
265         @"Chalkboard",
266         @"Chalkduster",
267         @"Charcoal CY",
268         @"Cochin",
269         @"Comic Sans MS",
270         @"Copperplate",
271         @"Corsiva Hebrew",
272         @"Courier New",
273         @"Courier",
274         @"DecoType Naskh",
275         @"Devanagari MT",
276         @"Didot",
277         @"Euphemia UCAS",
278         @"Futura",
279         @"GB18030 Bitmap",
280         @"Geeza Pro",
281         @"Geneva CY",
282         @"Geneva",
283         @"Georgia",
284         @"Gill Sans",
285         @"Gujarati MT",
286         @"GungSeo",
287         @"Gurmukhi MT",
288         @"HeadLineA",
289         @"Hei",
290         @"Heiti SC",
291         @"Heiti TC",
292         @"Helvetica CY",
293         @"Helvetica Neue",
294         @"Helvetica",
295         @"Herculanum",
296         @"Hiragino Kaku Gothic Pro",
297         @"Hiragino Kaku Gothic ProN",
298         @"Hiragino Kaku Gothic Std",
299         @"Hiragino Kaku Gothic StdN",
300         @"Hiragino Maru Gothic Monospaced",
301         @"Hiragino Maru Gothic Pro",
302         @"Hiragino Maru Gothic ProN",
303         @"Hiragino Mincho Pro",
304         @"Hiragino Mincho ProN",
305         @"Hiragino Sans GB",
306         @"Hoefler Text",
307         @"Impact",
308         @"InaiMathi",
309         @"Kai",
310         @"Kailasa",
311         @"Kokonor",
312         @"Krungthep",
313         @"KufiStandardGK",
314         @"LiHei Pro",
315         @"LiSong Pro",
316         @"Lucida Grande",
317         @"Marker Felt",
318         @"Menlo",
319         @"Microsoft Sans Serif",
320         @"Monaco",
321         @"Mshtakan",
322         @"Nadeem",
323         @"New Peninim MT",
324         @"Optima",
325         @"Osaka",
326         @"Papyrus",
327         @"PCMyungjo",
328         @"PilGi",
329         @"Plantagenet Cherokee",
330         @"Raanana",
331         @"Sathu",
332         @"Silom",
333         @"Skia",
334         @"STFangsong",
335         @"STHeiti",
336         @"STIXGeneral",
337         @"STIXSizeOneSym",
338         @"STKaiti",
339         @"STSong",
340         @"Symbol",
341         @"Tahoma",
342         @"Thonburi",
343         @"Times New Roman",
344         @"Times",
345         @"Trebuchet MS",
346         @"Verdana",
347         @"Webdings",
348         @"WebKit WeightWatcher",
349         @"Wingdings 2",
350         @"Wingdings 3",
351         @"Wingdings",
352         @"Zapf Dingbats",
353         @"Zapfino",
354         nil] retain];
355     
356     return fontFamiliySet;
357 }
358
359 static IMP appKitAvailableFontFamiliesIMP;
360 static IMP appKitAvailableFontsIMP;
361
362 static NSArray *drt_NSFontManager_availableFontFamilies(id self, SEL _cmd)
363 {
364     static NSArray *availableFontFamilies;
365     if (availableFontFamilies)
366         return availableFontFamilies;
367     
368     NSArray *availableFamilies = wtfCallIMP<id>(appKitAvailableFontFamiliesIMP, self, _cmd);
369
370     NSMutableSet *prunedFamiliesSet = [NSMutableSet setWithArray:availableFamilies];
371     [prunedFamiliesSet intersectSet:allowedFontFamilySet()];
372
373     availableFontFamilies = [[prunedFamiliesSet allObjects] retain];
374     return availableFontFamilies;
375 }
376
377 static NSArray *drt_NSFontManager_availableFonts(id self, SEL _cmd)
378 {
379     static NSArray *availableFonts;
380     if (availableFonts)
381         return availableFonts;
382     
383     NSSet *allowedFamilies = allowedFontFamilySet();
384     NSMutableArray *availableFontList = [[NSMutableArray alloc] initWithCapacity:[allowedFamilies count] * 2];
385     for (NSString *fontFamily in allowedFontFamilySet()) {
386         NSArray* fontsForFamily = [[NSFontManager sharedFontManager] availableMembersOfFontFamily:fontFamily];
387         for (NSArray* fontInfo in fontsForFamily) {
388             // Font name is the first entry in the array.
389             [availableFontList addObject:[fontInfo objectAtIndex:0]];
390         }
391     }
392     
393     availableFonts = availableFontList;
394     return availableFonts;
395 }
396
397 static void swizzleNSFontManagerMethods()
398 {
399     Method availableFontFamiliesMethod = class_getInstanceMethod(objc_getClass("NSFontManager"), @selector(availableFontFamilies));
400     ASSERT(availableFontFamiliesMethod);
401     if (!availableFontFamiliesMethod) {
402         NSLog(@"Failed to swizzle the \"availableFontFamilies\" method on NSFontManager");
403         return;
404     }
405     
406     appKitAvailableFontFamiliesIMP = method_setImplementation(availableFontFamiliesMethod, (IMP)drt_NSFontManager_availableFontFamilies);
407
408     Method availableFontsMethod = class_getInstanceMethod(objc_getClass("NSFontManager"), @selector(availableFonts));
409     ASSERT(availableFontsMethod);
410     if (!availableFontsMethod) {
411         NSLog(@"Failed to swizzle the \"availableFonts\" method on NSFontManager");
412         return;
413     }
414     
415     appKitAvailableFontsIMP = method_setImplementation(availableFontsMethod, (IMP)drt_NSFontManager_availableFonts);
416 }
417
418 static void activateTestingFonts()
419 {
420     // Work around <rdar://problem/6698023> by activating fonts from disk
421     // FIXME: This code can be removed once <rdar://problem/6698023> is addressed.
422
423     static const char* fontFileNames[] = {
424         "AHEM____.TTF",
425         "WebKitWeightWatcher100.ttf",
426         "WebKitWeightWatcher200.ttf",
427         "WebKitWeightWatcher300.ttf",
428         "WebKitWeightWatcher400.ttf",
429         "WebKitWeightWatcher500.ttf",
430         "WebKitWeightWatcher600.ttf",
431         "WebKitWeightWatcher700.ttf",
432         "WebKitWeightWatcher800.ttf",
433         "WebKitWeightWatcher900.ttf",
434 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
435         "SampleFont.sfont",
436 #endif
437         0
438     };
439
440     NSMutableArray *fontURLs = [NSMutableArray array];
441     NSURL *resourcesDirectory = [NSURL URLWithString:@"DumpRenderTree.resources" relativeToURL:[[NSBundle mainBundle] executableURL]];
442     for (unsigned i = 0; fontFileNames[i]; ++i) {
443         NSURL *fontURL = [resourcesDirectory URLByAppendingPathComponent:[NSString stringWithUTF8String:fontFileNames[i]]];
444         [fontURLs addObject:[fontURL absoluteURL]];
445     }
446
447     CFArrayRef errors = 0;
448     if (!CTFontManagerRegisterFontsForURLs((CFArrayRef)fontURLs, kCTFontManagerScopeProcess, &errors)) {
449         NSLog(@"Failed to activate fonts: %@", errors);
450         CFRelease(errors);
451         exit(1);
452     }
453 }
454
455 static void adjustFonts()
456 {
457     swizzleNSFontManagerMethods();
458     activateTestingFonts();
459 }
460
461 @interface DRTMockScroller : NSScroller
462 @end
463
464 @implementation DRTMockScroller
465
466 - (NSRect)rectForPart:(NSScrollerPart)partCode
467 {
468     switch (partCode) {
469     case NSScrollerKnob: {
470         NSRect frameRect = [self frame];
471         NSRect bounds = [self bounds];
472         BOOL isHorizontal = frameRect.size.width > frameRect.size.height;
473         CGFloat trackLength = isHorizontal ? bounds.size.width : bounds.size.height;
474         CGFloat minKnobSize = isHorizontal ? bounds.size.height : bounds.size.width;
475         CGFloat knobLength = max(minKnobSize, static_cast<CGFloat>(round(trackLength * [self knobProportion])));
476         CGFloat knobPosition = static_cast<CGFloat>((round([self doubleValue] * (trackLength - knobLength))));
477         
478         if (isHorizontal)
479             return NSMakeRect(bounds.origin.x + knobPosition, bounds.origin.y, knobLength, bounds.size.height);
480
481         return NSMakeRect(bounds.origin.x, bounds.origin.y +  + knobPosition, bounds.size.width, knobLength);
482     }
483     }
484     
485     return [super rectForPart:partCode];
486 }
487
488 - (void)drawKnob
489 {
490     if (![self isEnabled])
491         return;
492
493     NSRect knobRect = [self rectForPart:NSScrollerKnob];
494     
495     static NSColor *knobColor = [[NSColor colorWithDeviceRed:0x80 / 255.0 green:0x80 / 255.0 blue:0x80 / 255.0 alpha:1] retain];
496     [knobColor set];
497
498     NSRectFill(knobRect);
499 }
500
501 - (void)drawRect:(NSRect)dirtyRect
502 {
503     static NSColor *trackColor = [[NSColor colorWithDeviceRed:0xC0 / 255.0 green:0xC0 / 255.0 blue:0xC0 / 255.0 alpha:1] retain];
504     static NSColor *disabledTrackColor = [[NSColor colorWithDeviceRed:0xE0 / 255.0 green:0xE0 / 255.0 blue:0xE0 / 255.0 alpha:1] retain];
505
506     if ([self isEnabled])
507         [trackColor set];
508     else
509         [disabledTrackColor set];
510
511     NSRectFill(dirtyRect);
512     
513     [self drawKnob];
514 }
515
516 @end
517
518 static void registerMockScrollbars()
519 {
520     [WebDynamicScrollBarsView setCustomScrollerClass:[DRTMockScroller class]];
521 }
522
523 WebView *createWebViewAndOffscreenWindow()
524 {
525     NSRect rect = NSMakeRect(0, 0, TestRunner::maxViewWidth, TestRunner::maxViewHeight);
526     WebView *webView = [[WebView alloc] initWithFrame:rect frameName:nil groupName:@"org.webkit.DumpRenderTree"];
527         
528     [webView setUIDelegate:uiDelegate];
529     [webView setFrameLoadDelegate:frameLoadDelegate];
530     [webView setEditingDelegate:editingDelegate];
531     [webView setResourceLoadDelegate:resourceLoadDelegate];
532     [webView _setGeolocationProvider:[MockGeolocationProvider shared]];
533     [webView _setDeviceOrientationProvider:[WebDeviceOrientationProviderMock shared]];
534     [webView _setNotificationProvider:[MockWebNotificationProvider shared]];
535
536     // Register the same schemes that Safari does
537     [WebView registerURLSchemeAsLocal:@"feed"];
538     [WebView registerURLSchemeAsLocal:@"feeds"];
539     [WebView registerURLSchemeAsLocal:@"feedsearch"];
540     
541     [webView setContinuousSpellCheckingEnabled:YES];
542     [webView setAutomaticQuoteSubstitutionEnabled:NO];
543     [webView setAutomaticLinkDetectionEnabled:NO];
544     [webView setAutomaticDashSubstitutionEnabled:NO];
545     [webView setAutomaticTextReplacementEnabled:NO];
546     [webView setAutomaticSpellingCorrectionEnabled:YES];
547     [webView setDefersCallbacks:NO];
548     [webView setGrammarCheckingEnabled:YES];
549     [webView setInteractiveFormValidationEnabled:YES];
550     [webView setValidationMessageTimerMagnification:-1];
551     
552     // 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.
553     // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
554     NSRect windowRect = NSOffsetRect(rect, -10000, [(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
555     DumpRenderTreeWindow *window = [[DumpRenderTreeWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
556
557     [window setColorSpace:[[NSScreen mainScreen] colorSpace]];
558     [window setCollectionBehavior:NSWindowCollectionBehaviorStationary];
559     [[window contentView] addSubview:webView];
560     [window orderBack:nil];
561     [window setAutodisplay:NO];
562     [window _setWindowResolution:1 displayIfChanged:YES];
563
564     [window startListeningForAcceleratedCompositingChanges];
565     
566     // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
567     // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
568     NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
569     [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
570         
571     return webView;
572 }
573
574 static NSString *libraryPathForDumpRenderTree()
575 {
576     //FIXME: This may not be sufficient to prevent interactions/crashes
577     //when running more than one copy of DumpRenderTree.
578     //See https://bugs.webkit.org/show_bug.cgi?id=10906
579     char* dumpRenderTreeTemp = getenv("DUMPRENDERTREE_TEMP");
580     if (dumpRenderTreeTemp)
581         return [[NSFileManager defaultManager] stringWithFileSystemRepresentation:dumpRenderTreeTemp length:strlen(dumpRenderTreeTemp)];
582     else
583         return [@"~/Library/Application Support/DumpRenderTree" stringByExpandingTildeInPath];
584 }
585
586 // Called before each test.
587 static void resetDefaultsToConsistentValues()
588 {
589     static const int NoFontSmoothing = 0;
590     static const int BlueTintedAppearance = 1;
591
592     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
593     [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; // smallest font size to CG should perform antialiasing on
594     [defaults setInteger:NoFontSmoothing forKey:@"AppleFontSmoothing"];
595     [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"];
596     [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
597     [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
598     [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
599     [defaults setBool:YES forKey:WebKitEnableFullDocumentTeardownPreferenceKey];
600     [defaults setBool:YES forKey:WebKitFullScreenEnabledPreferenceKey];
601     [defaults setBool:YES forKey:@"UseWebKitWebInspector"];
602
603 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
604     [defaults setObject:[NSDictionary dictionaryWithObjectsAndKeys:
605         @"notational", @"notationl",
606         @"message", @"mesage",
607         @"would", @"wouldn",
608         @"welcome", @"wellcome",
609         @"hello\nworld", @"hellolfworld",
610         nil] forKey:@"NSTestCorrectionDictionary"];
611 #endif
612
613     // Scrollbars are drawn either using AppKit (which uses NSUserDefaults) or using HIToolbox (which uses CFPreferences / kCFPreferencesAnyApplication / kCFPreferencesCurrentUser / kCFPreferencesAnyHost)
614     [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
615     RetainPtr<CFTypeRef> initialValue = CFPreferencesCopyValue(CFSTR("AppleScrollBarVariant"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
616     CFPreferencesSetValue(CFSTR("AppleScrollBarVariant"), CFSTR("DoubleMax"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
617 #ifndef __LP64__
618     // See <rdar://problem/6347388>.
619     ThemeScrollBarArrowStyle style;
620     GetThemeScrollBarArrowStyle(&style); // Force HIToolbox to read from CFPreferences
621 #endif
622
623
624 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
625     [defaults setBool:NO forKey:@"NSScrollAnimationEnabled"];
626 #else
627     [defaults setBool:NO forKey:@"AppleScrollAnimationEnabled"];
628 #endif
629
630     [defaults setBool:NO forKey:@"NSOverlayScrollersEnabled"];
631     [defaults setObject:@"Always" forKey:@"AppleShowScrollBars"];
632
633     if (initialValue)
634         CFPreferencesSetValue(CFSTR("AppleScrollBarVariant"), initialValue.get(), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
635
636     NSString *path = libraryPathForDumpRenderTree();
637     [defaults setObject:[path stringByAppendingPathComponent:@"Databases"] forKey:WebDatabaseDirectoryDefaultsKey];
638     [defaults setObject:[path stringByAppendingPathComponent:@"LocalStorage"] forKey:WebStorageDirectoryDefaultsKey];
639     [defaults setObject:[path stringByAppendingPathComponent:@"LocalCache"] forKey:WebKitLocalCacheDefaultsKey];
640
641     [defaults setBool:NO forKey:@"WebKitKerningAndLigaturesEnabledByDefault"];
642
643     WebPreferences *preferences = [WebPreferences standardPreferences];
644
645     [preferences setAllowUniversalAccessFromFileURLs:YES];
646     [preferences setAllowFileAccessFromFileURLs:YES];
647     [preferences setStandardFontFamily:@"Times"];
648     [preferences setFixedFontFamily:@"Courier"];
649     [preferences setSerifFontFamily:@"Times"];
650     [preferences setSansSerifFontFamily:@"Helvetica"];
651     [preferences setCursiveFontFamily:@"Apple Chancery"];
652     [preferences setFantasyFontFamily:@"Papyrus"];
653     [preferences setPictographFontFamily:@"Apple Color Emoji"];
654     [preferences setDefaultFontSize:16];
655     [preferences setDefaultFixedFontSize:13];
656     [preferences setMinimumFontSize:0];
657     [preferences setDefaultTextEncodingName:@"ISO-8859-1"];
658     [preferences setJavaEnabled:NO];
659     [preferences setJavaScriptEnabled:YES];
660     [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
661     [preferences setTabsToLinks:NO];
662     [preferences setDOMPasteAllowed:YES];
663     [preferences setShouldPrintBackgrounds:YES];
664     [preferences setCacheModel:WebCacheModelDocumentBrowser];
665     [preferences setXSSAuditorEnabled:NO];
666     [preferences setExperimentalNotificationsEnabled:NO];
667     [preferences setPlugInsEnabled:YES];
668     [preferences setTextAreasAreResizable:YES];
669
670     [preferences setPrivateBrowsingEnabled:NO];
671     [preferences setAuthorAndUserStylesEnabled:YES];
672     [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
673     [preferences setJavaScriptCanAccessClipboard:YES];
674     [preferences setOfflineWebApplicationCacheEnabled:YES];
675     [preferences setDeveloperExtrasEnabled:NO];
676     [preferences setJavaScriptExperimentsEnabled:YES];
677     [preferences setLoadsImagesAutomatically:YES];
678     [preferences setLoadsSiteIconsIgnoringImageLoadingPreference:NO];
679     [preferences setFrameFlatteningEnabled:NO];
680     [preferences setSpatialNavigationEnabled:NO];
681     if (persistentUserStyleSheetLocation) {
682         [preferences setUserStyleSheetLocation:[NSURL URLWithString:(NSString *)(persistentUserStyleSheetLocation.get())]];
683         [preferences setUserStyleSheetEnabled:YES];
684     } else
685         [preferences setUserStyleSheetEnabled:NO];
686
687     // The back/forward cache is causing problems due to layouts during transition from one page to another.
688     // So, turn it off for now, but we might want to turn it back on some day.
689     [preferences setUsesPageCache:NO];
690     [preferences setAcceleratedCompositingEnabled:YES];
691 #if USE(CA) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
692     [preferences setCanvasUsesAcceleratedDrawing:YES];
693     [preferences setAcceleratedDrawingEnabled:NO];
694 #endif
695     [preferences setWebGLEnabled:NO];
696     [preferences setCSSRegionsEnabled:YES];
697     [preferences setCSSGridLayoutEnabled:NO];
698     [preferences setUsePreHTML5ParserQuirks:NO];
699     [preferences setAsynchronousSpellCheckingEnabled:NO];
700     [preferences setMockScrollbarsEnabled:YES];
701     [preferences setSeamlessIFramesEnabled:YES];
702
703 #if ENABLE(WEB_AUDIO)
704     [preferences setWebAudioEnabled:YES];
705 #endif
706
707     [preferences setScreenFontSubstitutionEnabled:YES];
708
709     [WebPreferences _setCurrentNetworkLoaderSessionCookieAcceptPolicy:NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain];
710     
711     TestRunner::setSerializeHTTPLoads(false);
712
713     setlocale(LC_ALL, "");
714 }
715
716 // Called once on DumpRenderTree startup.
717 static void setDefaultsToConsistentValuesForTesting()
718 {
719     // FIXME: We'd like to start with a clean state for every test, but this function can't be used more than once yet.
720     [WebPreferences _switchNetworkLoaderToNewTestingSession];
721
722     resetDefaultsToConsistentValues();
723
724     NSString *path = libraryPathForDumpRenderTree();
725     NSURLCache *sharedCache =
726         [[NSURLCache alloc] initWithMemoryCapacity:1024 * 1024
727                                       diskCapacity:0
728                                           diskPath:[path stringByAppendingPathComponent:@"URLCache"]];
729     [NSURLCache setSharedURLCache:sharedCache];
730     [sharedCache release];
731 }
732
733 static void runThread(void* arg)
734 {
735     static ThreadIdentifier previousId = 0;
736     ThreadIdentifier currentId = currentThread();
737     // Verify 2 successive threads do not get the same Id.
738     ASSERT(previousId != currentId);
739     previousId = currentId;
740 }
741
742 static void* runPthread(void* arg)
743 {
744     runThread(arg);
745     return 0;
746 }
747
748 static void testThreadIdentifierMap()
749 {
750     // Imitate 'foreign' threads that are not created by WTF.
751     pthread_t pthread;
752     pthread_create(&pthread, 0, &runPthread, 0);
753     pthread_join(pthread, 0);
754
755     pthread_create(&pthread, 0, &runPthread, 0);
756     pthread_join(pthread, 0);
757
758     // Now create another thread using WTF. On OSX, it will have the same pthread handle
759     // but should get a different ThreadIdentifier.
760     createThread(runThread, 0, "DumpRenderTree: test");
761 }
762
763 static void allocateGlobalControllers()
764 {
765     // FIXME: We should remove these and move to the ObjC standard [Foo sharedInstance] model
766     gNavigationController = [[NavigationController alloc] init];
767     frameLoadDelegate = [[FrameLoadDelegate alloc] init];
768     uiDelegate = [[UIDelegate alloc] init];
769     editingDelegate = [[EditingDelegate alloc] init];
770     resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
771     policyDelegate = [[PolicyDelegate alloc] init];
772     historyDelegate = [[HistoryDelegate alloc] init];
773     storageDelegate = [[StorageTrackerDelegate alloc] init];
774 }
775
776 // ObjC++ doens't seem to let me pass NSObject*& sadly.
777 static inline void releaseAndZero(NSObject** object)
778 {
779     [*object release];
780     *object = nil;
781 }
782
783 static void releaseGlobalControllers()
784 {
785     releaseAndZero(&gNavigationController);
786     releaseAndZero(&frameLoadDelegate);
787     releaseAndZero(&editingDelegate);
788     releaseAndZero(&resourceLoadDelegate);
789     releaseAndZero(&uiDelegate);
790     releaseAndZero(&policyDelegate);
791     releaseAndZero(&storageDelegate);
792 }
793
794 static void initializeGlobalsFromCommandLineOptions(int argc, const char *argv[])
795 {
796     struct option options[] = {
797         {"notree", no_argument, &dumpTree, NO},
798         {"pixel-tests", no_argument, &dumpPixelsForAllTests, YES},
799         {"tree", no_argument, &dumpTree, YES},
800         {"threaded", no_argument, &threaded, YES},
801         {"complex-text", no_argument, &forceComplexText, YES},
802         {"gc-between-tests", no_argument, &gcBetweenTests, YES},
803         {"no-timeout", no_argument, &useTimeoutWatchdog, NO},
804         {NULL, 0, NULL, 0}
805     };
806     
807     int option;
808     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) {
809         switch (option) {
810             case '?':   // unknown or ambiguous option
811             case ':':   // missing argument
812                 exit(1);
813                 break;
814         }
815     }
816 }
817
818 static void addTestPluginsToPluginSearchPath(const char* executablePath)
819 {
820     NSString *pwd = [[NSString stringWithUTF8String:executablePath] stringByDeletingLastPathComponent];
821     [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
822     [[WebPluginDatabase sharedDatabase] refresh];
823 }
824
825 static bool useLongRunningServerMode(int argc, const char *argv[])
826 {
827     // This assumes you've already called getopt_long
828     return (argc == optind+1 && strcmp(argv[optind], "-") == 0);
829 }
830
831 static void runTestingServerLoop()
832 {
833     // When DumpRenderTree run in server mode, we just wait around for file names
834     // to be passed to us and read each in turn, passing the results back to the client
835     char filenameBuffer[2048];
836     while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
837         char *newLineCharacter = strchr(filenameBuffer, '\n');
838         if (newLineCharacter)
839             *newLineCharacter = '\0';
840
841         if (strlen(filenameBuffer) == 0)
842             continue;
843
844         runTest(filenameBuffer);
845     }
846 }
847
848 static void prepareConsistentTestingEnvironment()
849 {
850     poseAsClass("DumpRenderTreePasteboard", "NSPasteboard");
851     poseAsClass("DumpRenderTreeEvent", "NSEvent");
852
853     setDefaultsToConsistentValuesForTesting();
854     adjustFonts();
855     registerMockScrollbars();
856     
857     allocateGlobalControllers();
858     
859     makeLargeMallocFailSilently();
860
861 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
862     static id assertion = [[[NSProcessInfo processInfo] beginSuspensionOfSystemBehaviors:NSSystemBehaviorCommonBehaviors
863         reason:@"DumpRenderTree should not be subject to process suppression"] retain];
864     ASSERT_UNUSED(assertion, assertion);
865 #endif
866 }
867
868 void dumpRenderTree(int argc, const char *argv[])
869 {
870     initializeGlobalsFromCommandLineOptions(argc, argv);
871     prepareConsistentTestingEnvironment();
872     addTestPluginsToPluginSearchPath(argv[0]);
873
874     if (forceComplexText)
875         [WebView _setAlwaysUsesComplexTextCodePath:YES];
876
877 #if USE(APPKIT)
878     [NSSound _setAlertType:0];
879 #endif
880
881     WebView *webView = createWebViewAndOffscreenWindow();
882     mainFrame = [webView mainFrame];
883
884     [[NSURLCache sharedURLCache] removeAllCachedResponses];
885     [WebCache empty];
886
887     [NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:@"localhost"];
888     [NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:@"127.0.0.1"];
889
890     // http://webkit.org/b/32689
891     testThreadIdentifierMap();
892
893     if (threaded)
894         startJavaScriptThreads();
895
896     if (useLongRunningServerMode(argc, argv)) {
897         printSeparators = YES;
898         runTestingServerLoop();
899     } else {
900         printSeparators = optind < argc - 1;
901         for (int i = optind; i != argc; ++i)
902             runTest(argv[i]);
903     }
904
905     if (threaded)
906         stopJavaScriptThreads();
907
908     NSWindow *window = [webView window];
909     [webView close];
910     mainFrame = nil;
911
912     // Work around problem where registering drag types leaves an outstanding
913     // "perform selector" on the window, which retains the window. It's a bit
914     // inelegant and perhaps dangerous to just blow them all away, but in practice
915     // it probably won't cause any trouble (and this is just a test tool, after all).
916     [NSObject cancelPreviousPerformRequestsWithTarget:window];
917     
918     [window close]; // releases when closed
919     [webView release];
920     
921     releaseGlobalControllers();
922     
923     [DumpRenderTreePasteboard releaseLocalPasteboards];
924
925     // FIXME: This should be moved onto TestRunner and made into a HashSet
926     if (disallowedURLs) {
927         CFRelease(disallowedURLs);
928         disallowedURLs = 0;
929     }
930 }
931
932 int main(int argc, const char *argv[])
933 {
934     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
935     [DumpRenderTreeApplication sharedApplication]; // Force AppKit to init itself
936     dumpRenderTree(argc, argv);
937     [WebCoreStatistics garbageCollectJavaScriptObjects];
938     [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts
939     if (JSC::Options::logHeapStatisticsAtExit())
940         JSC::HeapStatistics::reportSuccess();
941     [pool release];
942     return 0;
943 }
944
945 static NSInteger compareHistoryItems(id item1, id item2, void *context)
946 {
947     return [[item1 target] caseInsensitiveCompare:[item2 target]];
948 }
949
950 static NSData *dumpAudio()
951 {
952     const char *encodedAudioData = gTestRunner->encodedAudioData().c_str();
953     
954     NSData *data = [NSData dataWithBytes:encodedAudioData length:gTestRunner->encodedAudioData().length()];
955     return data;
956 }
957
958 static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
959 {
960     int start = 0;
961     if (current) {
962         printf("curr->");
963         start = 6;
964     }
965     for (int i = start; i < indent; i++)
966         putchar(' ');
967     
968     NSString *urlString = [item URLString];
969     if ([[NSURL URLWithString:urlString] isFileURL]) {
970         NSRange range = [urlString rangeOfString:@"/LayoutTests/"];
971         urlString = [@"(file test):" stringByAppendingString:[urlString substringFromIndex:(range.length + range.location)]];
972     }
973     
974     printf("%s", [urlString UTF8String]);
975     NSString *target = [item target];
976     if (target && [target length] > 0)
977         printf(" (in frame \"%s\")", [target UTF8String]);
978     if ([item isTargetItem])
979         printf("  **nav target**");
980     putchar('\n');
981     NSArray *kids = [item children];
982     if (kids) {
983         // must sort to eliminate arbitrary result ordering which defeats reproducible testing
984         kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
985         for (unsigned i = 0; i < [kids count]; i++)
986             dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
987     }
988 }
989
990 static void dumpFrameScrollPosition(WebFrame *f)
991 {
992     WebScriptObject* scriptObject = [f windowObject];
993     NSPoint scrollPosition = NSMakePoint(
994         [[scriptObject valueForKey:@"pageXOffset"] floatValue],
995         [[scriptObject valueForKey:@"pageYOffset"] floatValue]);
996     if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
997         if ([f parentFrame] != nil)
998             printf("frame '%s' ", [[f name] UTF8String]);
999         printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
1000     }
1001
1002     if (gTestRunner->dumpChildFrameScrollPositions()) {
1003         NSArray *kids = [f childFrames];
1004         if (kids)
1005             for (unsigned i = 0; i < [kids count]; i++)
1006                 dumpFrameScrollPosition([kids objectAtIndex:i]);
1007     }
1008 }
1009
1010 static NSString *dumpFramesAsText(WebFrame *frame)
1011 {
1012     DOMDocument *document = [frame DOMDocument];
1013     DOMElement *documentElement = [document documentElement];
1014
1015     if (!documentElement)
1016         return @"";
1017
1018     NSMutableString *result = [[[NSMutableString alloc] init] autorelease];
1019
1020     // Add header for all but the main frame.
1021     if ([frame parentFrame])
1022         result = [NSMutableString stringWithFormat:@"\n--------\nFrame: '%@'\n--------\n", [frame name]];
1023
1024     [result appendFormat:@"%@\n", [documentElement innerText]];
1025
1026     if (gTestRunner->dumpChildFramesAsText()) {
1027         NSArray *kids = [frame childFrames];
1028         if (kids) {
1029             for (unsigned i = 0; i < [kids count]; i++)
1030                 [result appendString:dumpFramesAsText([kids objectAtIndex:i])];
1031         }
1032     }
1033
1034     return result;
1035 }
1036
1037 static NSData *dumpFrameAsPDF(WebFrame *frame)
1038 {
1039     if (!frame)
1040         return nil;
1041
1042     // Sadly we have to dump to a file and then read from that file again
1043     // +[NSPrintOperation PDFOperationWithView:insideRect:] requires a rect and prints to a single page
1044     // likewise +[NSView dataWithPDFInsideRect:] also prints to a single continuous page
1045     // The goal of this function is to test "real" printing across multiple pages.
1046     // FIXME: It's possible there might be printing SPI to let us print a multi-page PDF to an NSData object
1047     NSString *path = [libraryPathForDumpRenderTree() stringByAppendingPathComponent:@"test.pdf"];
1048
1049     NSMutableDictionary *printInfoDict = [NSMutableDictionary dictionaryWithDictionary:[[NSPrintInfo sharedPrintInfo] dictionary]];
1050     [printInfoDict setObject:NSPrintSaveJob forKey:NSPrintJobDisposition];
1051     [printInfoDict setObject:path forKey:NSPrintSavePath];
1052
1053     NSPrintInfo *printInfo = [[NSPrintInfo alloc] initWithDictionary:printInfoDict];
1054     [printInfo setHorizontalPagination:NSAutoPagination];
1055     [printInfo setVerticalPagination:NSAutoPagination];
1056     [printInfo setVerticallyCentered:NO];
1057
1058     NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:[frame frameView] printInfo:printInfo];
1059     [printOperation setShowPanels:NO];
1060     [printOperation runOperation];
1061
1062     [printInfo release];
1063
1064     NSData *pdfData = [NSData dataWithContentsOfFile:path];
1065     [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];
1066
1067     return pdfData;
1068 }
1069
1070 static void dumpBackForwardListForWebView(WebView *view)
1071 {
1072     printf("\n============== Back Forward List ==============\n");
1073     WebBackForwardList *bfList = [view backForwardList];
1074
1075     // Print out all items in the list after prevTestBFItem, which was from the previous test
1076     // Gather items from the end of the list, the print them out from oldest to newest
1077     NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
1078     for (int i = [bfList forwardListCount]; i > 0; i--) {
1079         WebHistoryItem *item = [bfList itemAtIndex:i];
1080         // something is wrong if the item from the last test is in the forward part of the b/f list
1081         assert(item != prevTestBFItem);
1082         [itemsToPrint addObject:item];
1083     }
1084             
1085     assert([bfList currentItem] != prevTestBFItem);
1086     [itemsToPrint addObject:[bfList currentItem]];
1087     int currentItemIndex = [itemsToPrint count] - 1;
1088
1089     for (int i = -1; i >= -[bfList backListCount]; i--) {
1090         WebHistoryItem *item = [bfList itemAtIndex:i];
1091         if (item == prevTestBFItem)
1092             break;
1093         [itemsToPrint addObject:item];
1094     }
1095
1096     for (int i = [itemsToPrint count]-1; i >= 0; i--)
1097         dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
1098
1099     [itemsToPrint release];
1100     printf("===============================================\n");
1101 }
1102
1103 static void sizeWebViewForCurrentTest()
1104 {
1105     // W3C SVG tests expect to be 480x360
1106     bool isSVGW3CTest = (gTestRunner->testPathOrURL().find("svg/W3C-SVG-1.1") != string::npos);
1107     if (isSVGW3CTest)
1108         [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)];
1109     else
1110         [[mainFrame webView] setFrameSize:NSMakeSize(TestRunner::maxViewWidth, TestRunner::maxViewHeight)];
1111 }
1112
1113 static const char *methodNameStringForFailedTest()
1114 {
1115     const char *errorMessage;
1116     if (gTestRunner->dumpAsText())
1117         errorMessage = "[documentElement innerText]";
1118     else if (gTestRunner->dumpDOMAsWebArchive())
1119         errorMessage = "[[mainFrame DOMDocument] webArchive]";
1120     else if (gTestRunner->dumpSourceAsWebArchive())
1121         errorMessage = "[[mainFrame dataSource] webArchive]";
1122     else
1123         errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";
1124
1125     return errorMessage;
1126 }
1127
1128 static void dumpBackForwardListForAllWindows()
1129 {
1130     CFArrayRef openWindows = (CFArrayRef)[DumpRenderTreeWindow openWindows];
1131     unsigned count = CFArrayGetCount(openWindows);
1132     for (unsigned i = 0; i < count; i++) {
1133         NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(openWindows, i);
1134         WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
1135         dumpBackForwardListForWebView(webView);
1136     }
1137 }
1138
1139 static void invalidateAnyPreviousWaitToDumpWatchdog()
1140 {
1141     if (waitToDumpWatchdog) {
1142         CFRunLoopTimerInvalidate(waitToDumpWatchdog);
1143         CFRelease(waitToDumpWatchdog);
1144         waitToDumpWatchdog = 0;
1145     }
1146 }
1147
1148 void setWaitToDumpWatchdog(CFRunLoopTimerRef timer)
1149 {
1150     ASSERT(timer);
1151     ASSERT(shouldSetWaitToDumpWatchdog());
1152     waitToDumpWatchdog = timer;
1153     CFRunLoopAddTimer(CFRunLoopGetCurrent(), waitToDumpWatchdog, kCFRunLoopCommonModes);
1154 }
1155
1156 bool shouldSetWaitToDumpWatchdog()
1157 {
1158     return !waitToDumpWatchdog && useTimeoutWatchdog;
1159 }
1160
1161 void dump()
1162 {
1163     invalidateAnyPreviousWaitToDumpWatchdog();
1164     ASSERT(!gTestRunner->hasPendingWebNotificationClick());
1165
1166     if (dumpTree) {
1167         NSString *resultString = nil;
1168         NSData *resultData = nil;
1169         NSString *resultMimeType = @"text/plain";
1170
1171         if ([[[mainFrame dataSource] _responseMIMEType] isEqualToString:@"text/plain"]) {
1172             gTestRunner->setDumpAsText(true);
1173             gTestRunner->setGeneratePixelResults(false);
1174         }
1175         if (gTestRunner->dumpAsAudio()) {
1176             resultData = dumpAudio();
1177             resultMimeType = @"audio/wav";
1178         } else if (gTestRunner->dumpAsText()) {
1179             resultString = dumpFramesAsText(mainFrame);
1180         } else if (gTestRunner->dumpAsPDF()) {
1181             resultData = dumpFrameAsPDF(mainFrame);
1182             resultMimeType = @"application/pdf";
1183         } else if (gTestRunner->dumpDOMAsWebArchive()) {
1184             WebArchive *webArchive = [[mainFrame DOMDocument] webArchive];
1185             resultString = HardAutorelease(createXMLStringFromWebArchiveData((CFDataRef)[webArchive data]));
1186             resultMimeType = @"application/x-webarchive";
1187         } else if (gTestRunner->dumpSourceAsWebArchive()) {
1188             WebArchive *webArchive = [[mainFrame dataSource] webArchive];
1189             resultString = HardAutorelease(createXMLStringFromWebArchiveData((CFDataRef)[webArchive data]));
1190             resultMimeType = @"application/x-webarchive";
1191         } else
1192             resultString = [mainFrame renderTreeAsExternalRepresentationForPrinting:gTestRunner->isPrinting()];
1193
1194         if (resultString && !resultData)
1195             resultData = [resultString dataUsingEncoding:NSUTF8StringEncoding];
1196
1197         printf("Content-Type: %s\n", [resultMimeType UTF8String]);
1198
1199         if (gTestRunner->dumpAsAudio())
1200             printf("Content-Transfer-Encoding: base64\n");
1201
1202         WTF::FastMallocStatistics mallocStats = WTF::fastMallocStatistics();
1203         printf("DumpMalloc: %li\n", mallocStats.committedVMBytes);
1204
1205         if (resultData) {
1206             fwrite([resultData bytes], 1, [resultData length], stdout);
1207
1208             if (!gTestRunner->dumpAsText() && !gTestRunner->dumpDOMAsWebArchive() && !gTestRunner->dumpSourceAsWebArchive())
1209                 dumpFrameScrollPosition(mainFrame);
1210
1211             if (gTestRunner->dumpBackForwardList())
1212                 dumpBackForwardListForAllWindows();
1213         } else
1214             printf("ERROR: nil result from %s", methodNameStringForFailedTest());
1215
1216         // Stop the watchdog thread before we leave this test to make sure it doesn't
1217         // fire in between tests causing the next test to fail.
1218         // This is a speculative fix for: https://bugs.webkit.org/show_bug.cgi?id=32339
1219         invalidateAnyPreviousWaitToDumpWatchdog();
1220
1221         if (printSeparators) {
1222             puts("#EOF");       // terminate the content block
1223             fputs("#EOF\n", stderr);
1224         }            
1225     }
1226
1227     if (dumpPixelsForCurrentTest && gTestRunner->generatePixelResults())
1228         // FIXME: when isPrinting is set, dump the image with page separators.
1229         dumpWebViewAsPixelsAndCompareWithExpected(gTestRunner->expectedPixelHash());
1230
1231     puts("#EOF");   // terminate the (possibly empty) pixels block
1232
1233     fflush(stdout);
1234     fflush(stderr);
1235
1236     done = YES;
1237 }
1238
1239 static bool shouldLogFrameLoadDelegates(const char* pathOrURL)
1240 {
1241     return strstr(pathOrURL, "loading/");
1242 }
1243
1244 static bool shouldLogHistoryDelegates(const char* pathOrURL)
1245 {
1246     return strstr(pathOrURL, "globalhistory/");
1247 }
1248
1249 static bool shouldOpenWebInspector(const char* pathOrURL)
1250 {
1251     return strstr(pathOrURL, "inspector/");
1252 }
1253
1254 static bool shouldDumpAsText(const char* pathOrURL)
1255 {
1256     return strstr(pathOrURL, "dumpAsText/");
1257 }
1258
1259 static bool shouldEnableDeveloperExtras(const char* pathOrURL)
1260 {
1261     return true;
1262 }
1263
1264 static void resetWebViewToConsistentStateBeforeTesting()
1265 {
1266     WebView *webView = [mainFrame webView];
1267     [webView setEditable:NO];
1268     [(EditingDelegate *)[webView editingDelegate] setAcceptsEditing:YES];
1269     [webView makeTextStandardSize:nil];
1270     [webView resetPageZoom:nil];
1271     [webView _scaleWebView:1.0 atOrigin:NSZeroPoint];
1272     [webView _setCustomBackingScaleFactor:0];
1273     [webView setTabKeyCyclesThroughElements:YES];
1274     [webView setPolicyDelegate:nil];
1275     [policyDelegate setPermissive:NO];
1276     [policyDelegate setControllerToNotifyDone:0];
1277     [frameLoadDelegate resetToConsistentState];
1278     [webView _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO];
1279     [webView _clearMainFrameName];
1280     [[webView undoManager] removeAllActions];
1281     [WebView _removeAllUserContentFromGroup:[webView groupName]];
1282     [[webView window] setAutodisplay:NO];
1283     [webView setTracksRepaints:NO];
1284     
1285     resetDefaultsToConsistentValues();
1286
1287     if (gTestRunner) {
1288         WebCoreTestSupport::resetInternalsObject([mainFrame globalContext]);
1289         // in the case that a test using the chrome input field failed, be sure to clean up for the next test
1290         gTestRunner->removeChromeInputField();
1291     }
1292
1293     [WebView _setUsesTestModeFocusRingColor:YES];
1294     [WebView _resetOriginAccessWhitelists];
1295     [WebView _setAllowsRoundingHacks:NO];
1296
1297     [[MockGeolocationProvider shared] stopTimer];
1298     [[MockWebNotificationProvider shared] reset];
1299     
1300     // Clear the contents of the general pasteboard
1301     [[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
1302
1303     [mainFrame _clearOpener];
1304 }
1305
1306 static void runTest(const string& inputLine)
1307 {
1308     ASSERT(!inputLine.empty());
1309
1310     TestCommand command = parseInputLine(inputLine);
1311     const string& pathOrURL = command.pathOrURL;
1312     dumpPixelsForCurrentTest = command.shouldDumpPixels || dumpPixelsForAllTests;
1313
1314     NSString *pathOrURLString = [NSString stringWithUTF8String:pathOrURL.c_str()];
1315     if (!pathOrURLString) {
1316         fprintf(stderr, "Failed to parse \"%s\" as UTF-8\n", pathOrURL.c_str());
1317         return;
1318     }
1319
1320     NSURL *url;
1321     if ([pathOrURLString hasPrefix:@"http://"] || [pathOrURLString hasPrefix:@"https://"] || [pathOrURLString hasPrefix:@"file://"])
1322         url = [NSURL URLWithString:pathOrURLString];
1323     else
1324         url = [NSURL fileURLWithPath:pathOrURLString];
1325     if (!url) {
1326         fprintf(stderr, "Failed to parse \"%s\" as a URL\n", pathOrURL.c_str());
1327         return;
1328     }
1329
1330     const string testURL([[url absoluteString] UTF8String]);
1331     
1332     resetWebViewToConsistentStateBeforeTesting();
1333
1334     gTestRunner = TestRunner::create(testURL, command.expectedPixelHash);
1335     topLoadingFrame = nil;
1336     ASSERT(!draggingInfo); // the previous test should have called eventSender.mouseUp to drop!
1337     releaseAndZero(&draggingInfo);
1338     done = NO;
1339
1340     sizeWebViewForCurrentTest();
1341     gTestRunner->setIconDatabaseEnabled(false);
1342
1343     if (disallowedURLs)
1344         CFSetRemoveAllValues(disallowedURLs);
1345     if (shouldLogFrameLoadDelegates(pathOrURL.c_str()))
1346         gTestRunner->setDumpFrameLoadCallbacks(true);
1347
1348     if (shouldLogHistoryDelegates(pathOrURL.c_str()))
1349         [[mainFrame webView] setHistoryDelegate:historyDelegate];
1350     else
1351         [[mainFrame webView] setHistoryDelegate:nil];
1352
1353     if (shouldEnableDeveloperExtras(pathOrURL.c_str())) {
1354         gTestRunner->setDeveloperExtrasEnabled(true);
1355         if (shouldOpenWebInspector(pathOrURL.c_str()))
1356             gTestRunner->showWebInspector();
1357         if (shouldDumpAsText(pathOrURL.c_str())) {
1358             gTestRunner->setDumpAsText(true);
1359             gTestRunner->setGeneratePixelResults(false);
1360         }
1361     }
1362
1363     if ([WebHistory optionalSharedHistory])
1364         [WebHistory setOptionalSharedHistory:nil];
1365     lastMousePosition = NSZeroPoint;
1366     lastClickPosition = NSZeroPoint;
1367
1368     [prevTestBFItem release];
1369     prevTestBFItem = [[[[mainFrame webView] backForwardList] currentItem] retain];
1370
1371     WorkQueue::shared()->clear();
1372     WorkQueue::shared()->setFrozen(false);
1373
1374     bool ignoreWebCoreNodeLeaks = shouldIgnoreWebCoreNodeLeaks(testURL);
1375     if (ignoreWebCoreNodeLeaks)
1376         [WebCoreStatistics startIgnoringWebCoreNodeLeaks];
1377
1378     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1379     [mainFrame loadRequest:[NSURLRequest requestWithURL:url]];
1380     [pool release];
1381
1382     while (!done) {
1383         pool = [[NSAutoreleasePool alloc] init];
1384         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]; 
1385         [pool release];
1386     }
1387
1388     pool = [[NSAutoreleasePool alloc] init];
1389     [EventSendingController clearSavedEvents];
1390     [[mainFrame webView] setSelectedDOMRange:nil affinity:NSSelectionAffinityDownstream];
1391
1392     WorkQueue::shared()->clear();
1393
1394     if (gTestRunner->closeRemainingWindowsWhenComplete()) {
1395         NSArray* array = [DumpRenderTreeWindow openWindows];
1396
1397         unsigned count = [array count];
1398         for (unsigned i = 0; i < count; i++) {
1399             NSWindow *window = [array objectAtIndex:i];
1400
1401             // Don't try to close the main window
1402             if (window == [[mainFrame webView] window])
1403                 continue;
1404             
1405             WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
1406
1407             [webView close];
1408             [window close];
1409         }
1410     }
1411
1412     // If developer extras enabled Web Inspector may have been open by the test.
1413     if (shouldEnableDeveloperExtras(pathOrURL.c_str())) {
1414         gTestRunner->closeWebInspector();
1415         gTestRunner->setDeveloperExtrasEnabled(false);
1416     }
1417
1418     resetWebViewToConsistentStateBeforeTesting();
1419
1420     [mainFrame loadHTMLString:@"<html></html>" baseURL:[NSURL URLWithString:@"about:blank"]];
1421     [mainFrame stopLoading];
1422
1423     [pool release];
1424
1425     // We should only have our main window left open when we're done
1426     ASSERT(CFArrayGetCount(openWindowsRef) == 1);
1427     ASSERT(CFArrayGetValueAtIndex(openWindowsRef, 0) == [[mainFrame webView] window]);
1428
1429     gTestRunner.clear();
1430
1431     if (ignoreWebCoreNodeLeaks)
1432         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
1433
1434     if (gcBetweenTests)
1435         [WebCoreStatistics garbageCollectJavaScriptObjects];
1436 }
1437
1438 void displayWebView()
1439 {
1440     WebView *webView = [mainFrame webView];
1441     [webView display];
1442     
1443     [webView setTracksRepaints:YES];
1444     [webView resetTrackedRepaints];
1445 }
1446
1447 @implementation DumpRenderTreeEvent
1448
1449 + (NSPoint)mouseLocation
1450 {
1451     return [[[mainFrame webView] window] convertBaseToScreen:lastMousePosition];
1452 }
1453
1454 @end
1455
1456 @implementation DumpRenderTreeApplication
1457
1458 - (BOOL)isRunning
1459 {
1460     // <rdar://problem/7686123> Java plug-in freezes unless NSApplication is running
1461     return YES;
1462 }
1463
1464 @end