DumpRenderTree.mm 41.4 KB
Newer Older
1
/*
2
 * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved.
gdennis's avatar
gdennis committed
3
 *           (C) 2007 Graham Dennis (graham.dennis@gmail.com)
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
29
30
 
#import "DumpRenderTree.h"
31

bdakin@apple.com's avatar
bdakin@apple.com committed
32
#import "AccessibilityController.h"
33
#import "CheckedMalloc.h"
34
35
#import "DumpRenderTreePasteboard.h"
#import "DumpRenderTreeWindow.h"
ggaren's avatar
ggaren committed
36
37
#import "EditingDelegate.h"
#import "EventSendingController.h"
mjs's avatar
mjs committed
38
#import "FrameLoadDelegate.h"
39
#import "JavaScriptThreading.h"
weinig's avatar
weinig committed
40
#import "LayoutTestController.h"
ggaren's avatar
ggaren committed
41
42
43
#import "NavigationController.h"
#import "ObjCPlugin.h"
#import "ObjCPluginFunction.h"
44
#import "PixelDumpSupport.h"
kmccullo's avatar
kmccullo committed
45
#import "PolicyDelegate.h"
andersca's avatar
andersca committed
46
#import "ResourceLoadDelegate.h"
ggaren's avatar
ggaren committed
47
#import "UIDelegate.h"
weinig's avatar
weinig committed
48
#import "WorkQueue.h"
weinig's avatar
weinig committed
49
#import "WorkQueueItem.h"
50
#import <Carbon/Carbon.h>
ggaren's avatar
ggaren committed
51
#import <CoreFoundation/CoreFoundation.h>
rwlbuis's avatar
rwlbuis committed
52
#import <WebKit/DOMElementPrivate.h>
53
#import <WebKit/DOMExtensions.h>
darin's avatar
darin committed
54
#import <WebKit/DOMRange.h>
justing's avatar
WebKit:    
justing committed
55
#import <WebKit/WebBackForwardList.h>
andersca@apple.com's avatar
andersca@apple.com committed
56
#import <WebKit/WebCache.h>
57
#import <WebKit/WebCoreStatistics.h>
mitz's avatar
mitz committed
58
#import <WebKit/WebDataSourcePrivate.h>
59
#import <WebKit/WebDatabaseManagerPrivate.h>
ggaren's avatar
ggaren committed
60
#import <WebKit/WebDocumentPrivate.h>
darin's avatar
darin committed
61
#import <WebKit/WebEditingDelegate.h>
62
#import <WebKit/WebFrameView.h>
63
#import <WebKit/WebHTMLRepresentationInternal.h>
ddkilzer's avatar
ddkilzer committed
64
#import <WebKit/WebHistory.h>
ggaren's avatar
ggaren committed
65
#import <WebKit/WebHistoryItemPrivate.h>
66
#import <WebKit/WebInspector.h>
ggaren's avatar
ggaren committed
67
#import <WebKit/WebPluginDatabase.h>
68
#import <WebKit/WebPreferences.h>
thatcher's avatar
thatcher committed
69
#import <WebKit/WebPreferencesPrivate.h>
andersca's avatar
andersca committed
70
#import <WebKit/WebResourceLoadDelegate.h>
71
#import <WebKit/WebTypesInternal.h>
adele's avatar
adele committed
72
#import <WebKit/WebViewPrivate.h>
ggaren's avatar
ggaren committed
73
#import <getopt.h>
74
#import <mach-o/getsect.h>
75
76
#import <objc/objc-runtime.h>
#import <wtf/Assertions.h>
mitz@apple.com's avatar
mitz@apple.com committed
77
#import <wtf/RetainPtr.h>
beidson@apple.com's avatar
beidson@apple.com committed
78
#import <wtf/OwnPtr.h>
darin's avatar
darin committed
79

80
81
using namespace std;

darin's avatar
darin committed
82
83
84
@interface DumpRenderTreeEvent : NSEvent
@end

85
static void runTest(const string& testPathOrURL);
86

ggaren's avatar
ggaren committed
87
88
89
90
91
92
93
// Deciding when it's OK to dump out the state is a bit tricky.  All these must be true:
// - There is no load in progress
// - There is no work queued up (see workQueue var, below)
// - waitToDump==NO.  This means either waitUntilDone was never called, or it was called
//       and notifyDone was called subsequently.
// Note that the call to notifyDone and the end of the load can happen in either order.

weinig's avatar
weinig committed
94
95
volatile bool done;

96
97
NavigationController* gNavigationController = 0;
LayoutTestController* gLayoutTestController = 0;
weinig's avatar
weinig committed
98
99

WebFrame *mainFrame = 0;
ggaren's avatar
ggaren committed
100
101
102
103
// This is the topmost frame that is loading, during a given load, or nil when no load is 
// in progress.  Usually this is the same as the main frame, but not always.  In the case
// where a frameset is loaded, and then new content is loaded into one of the child frames,
// that child frame is the "topmost frame that is loading".
mjs's avatar
mjs committed
104
WebFrame *topLoadingFrame = nil;     // !nil iff a load is in progress
ggaren's avatar
ggaren committed
105

weinig's avatar
weinig committed
106
107
108
109
110

CFMutableSetRef disallowedURLs = 0;
CFRunLoopTimerRef waitToDumpWatchdog = 0;

// Delegates
weinig's avatar
weinig committed
111
112
113
114
static FrameLoadDelegate *frameLoadDelegate;
static UIDelegate *uiDelegate;
static EditingDelegate *editingDelegate;
static ResourceLoadDelegate *resourceLoadDelegate;
weinig's avatar
weinig committed
115
PolicyDelegate *policyDelegate;
weinig's avatar
weinig committed
116

ggaren's avatar
ggaren committed
117
118
static int dumpPixels;
static int threaded;
eseidel's avatar
eseidel committed
119
static int dumpTree = YES;
mitz@apple.com's avatar
mitz@apple.com committed
120
static int forceComplexText;
eseidel's avatar
eseidel committed
121
static BOOL printSeparators;
mitz@apple.com's avatar
mitz@apple.com committed
122
static RetainPtr<CFStringRef> persistentUserStyleSheetLocation;
weinig's avatar
weinig committed
123

ggaren's avatar
ggaren committed
124
static WebHistoryItem *prevTestBFItem = nil;  // current b/f item at the end of the previous test
weinig's avatar
weinig committed
125

126
127
const unsigned maxViewHeight = 600;
const unsigned maxViewWidth = 800;
128

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#if __OBJC2__
static void swizzleAllMethods(Class imposter, Class original)
{
    unsigned int imposterMethodCount;
    Method* imposterMethods = class_copyMethodList(imposter, &imposterMethodCount);

    unsigned int originalMethodCount;
    Method* originalMethods = class_copyMethodList(original, &originalMethodCount);

    for (unsigned int i = 0; i < imposterMethodCount; i++) {
        SEL imposterMethodName = method_getName(imposterMethods[i]);

        // Attempt to add the method to the original class.  If it fails, the method already exists and we should
        // instead exchange the implementations.
        if (class_addMethod(original, imposterMethodName, method_getImplementation(originalMethods[i]), method_getTypeEncoding(originalMethods[i])))
            continue;

        unsigned int j = 0;
        for (; j < originalMethodCount; j++) {
            SEL originalMethodName = method_getName(originalMethods[j]);
            if (sel_isEqual(imposterMethodName, originalMethodName))
                break;
        }

        // If class_addMethod failed above then the method must exist on the original class.
        ASSERT(j < originalMethodCount);
        method_exchangeImplementations(imposterMethods[i], originalMethods[j]);
    }

    free(imposterMethods);
    free(originalMethods);
}
#endif

static void poseAsClass(const char* imposter, const char* original)
{
    Class imposterClass = objc_getClass(imposter);
    Class originalClass = objc_getClass(original);

#if !__OBJC2__
    class_poseAs(imposterClass, originalClass);
#else

    // Swizzle instance methods
    swizzleAllMethods(imposterClass, originalClass);
    // and then class methods
    swizzleAllMethods(object_getClass(imposterClass), object_getClass(originalClass));
#endif
}

mitz@apple.com's avatar
mitz@apple.com committed
179
180
181
182
void setPersistentUserStyleSheetLocation(CFStringRef url)
{
    persistentUserStyleSheetLocation = url;
}
ggaren's avatar
ggaren committed
183

184
static bool shouldIgnoreWebCoreNodeLeaks(const string& URLString)
ggaren's avatar
ggaren committed
185
{
186
    static char* const ignoreSet[] = {
ggaren's avatar
ggaren committed
187
        // Keeping this infrastructure around in case we ever need it again.
ggaren's avatar
ggaren committed
188
    };
189
    static const int ignoreSetCount = sizeof(ignoreSet) / sizeof(char*);
ggaren's avatar
ggaren committed
190
191
    
    for (int i = 0; i < ignoreSetCount; i++) {
192
193
194
195
196
        // FIXME: ignore case
        string curIgnore(ignoreSet[i]);
        // Match at the end of the URLString
        if (!URLString.compare(URLString.length() - curIgnore.length(), curIgnore.length(), curIgnore))
            return true;
ggaren's avatar
ggaren committed
197
    }
198
    return false;
ggaren's avatar
ggaren committed
199
200
}

mitz@apple.com's avatar
mitz@apple.com committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
static void activateFonts()
{
    static const char* fontSectionNames[] = {
        "Ahem",
        "WeightWatcher100",
        "WeightWatcher200",
        "WeightWatcher300",
        "WeightWatcher400",
        "WeightWatcher500",
        "WeightWatcher600",
        "WeightWatcher700",
        "WeightWatcher800",
        "WeightWatcher900",
        0
    };
216

mitz@apple.com's avatar
mitz@apple.com committed
217
218
219
220
221
222
223
224
225
226
    for (unsigned i = 0; fontSectionNames[i]; ++i) {
        unsigned long fontDataLength;
        char* fontData = getsectdata("__DATA", fontSectionNames[i], &fontDataLength);
        if (!fontData) {
            fprintf(stderr, "Failed to locate the %s font.\n", fontSectionNames[i]);
            exit(1);
        }

        ATSFontContainerRef fontContainer;
        OSStatus status = ATSFontActivateFromMemory(fontData, fontDataLength, kATSFontContextLocal, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &fontContainer);
227

mitz@apple.com's avatar
mitz@apple.com committed
228
229
230
231
        if (status != noErr) {
            fprintf(stderr, "Failed to activate the %s font.\n", fontSectionNames[i]);
            exit(1);
        }
232
233
234
    }
}

235
WebView *createWebViewAndOffscreenWindow()
andersca's avatar
andersca committed
236
237
{
    NSRect rect = NSMakeRect(0, 0, maxViewWidth, maxViewHeight);
ddkilzer's avatar
ddkilzer committed
238
    WebView *webView = [[WebView alloc] initWithFrame:rect frameName:nil groupName:@"org.webkit.DumpRenderTree"];
andersca's avatar
andersca committed
239
240
        
    [webView setUIDelegate:uiDelegate];
mjs's avatar
mjs committed
241
    [webView setFrameLoadDelegate:frameLoadDelegate];
andersca's avatar
andersca committed
242
243
    [webView setEditingDelegate:editingDelegate];
    [webView setResourceLoadDelegate:resourceLoadDelegate];
kmccullo's avatar
kmccullo committed
244
245
246
247
248

    // Register the same schemes that Safari does
    [WebView registerURLSchemeAsLocal:@"feed"];
    [WebView registerURLSchemeAsLocal:@"feeds"];
    [WebView registerURLSchemeAsLocal:@"feedsearch"];
andersca's avatar
andersca committed
249
250
251
252
253
254
    
    [webView setContinuousSpellCheckingEnabled:YES];
    
    // To make things like certain NSViews, dragging, and plug-ins work, put the WebView a window, but put it off-screen so you don't see it.
    // Put it at -10000, -10000 in "flipped coordinates", since WebCore and the DOM use flipped coordinates.
    NSRect windowRect = NSOffsetRect(rect, -10000, [[[NSScreen screens] objectAtIndex:0] frame].size.height - rect.size.height + 10000);
aliceli1's avatar
aliceli1 committed
255
    DumpRenderTreeWindow *window = [[DumpRenderTreeWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
andersca's avatar
andersca committed
256
257
258
259
260
261
262
263
264
265
266
267
    [[window contentView] addSubview:webView];
    [window orderBack:nil];
    [window setAutodisplay:NO];
    
    // For reasons that are not entirely clear, the following pair of calls makes WebView handle its
    // dynamic scrollbars properly. Without it, every frame will always have scrollbars.
    NSBitmapImageRep *imageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView bounds]];
    [webView cacheDisplayInRect:[webView bounds] toBitmapImageRep:imageRep];
        
    return webView;
}

ggaren's avatar
ggaren committed
268
269
270
271
void testStringByEvaluatingJavaScriptFromString()
{
    // maps expected result <= JavaScript expression
    NSDictionary *expressions = [NSDictionary dictionaryWithObjectsAndKeys:
ggaren's avatar
ggaren committed
272
273
274
        @"0", @"0", 
        @"0", @"'0'", 
        @"", @"",
ggaren's avatar
ggaren committed
275
276
        @"", @"''", 
        @"", @"new String()", 
ggaren's avatar
ggaren committed
277
        @"", @"new String('0')", 
ggaren's avatar
ggaren committed
278
279
280
281
282
283
        @"", @"throw 1", 
        @"", @"{ }", 
        @"", @"[ ]", 
        @"", @"//", 
        @"", @"a.b.c", 
        @"", @"(function() { throw 'error'; })()", 
ggaren's avatar
ggaren committed
284
285
        @"", @"null",
        @"", @"undefined",
ggaren's avatar
ggaren committed
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
        @"true", @"true",
        @"false", @"false",
        nil
    ];

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    WebView *webView = [[WebView alloc] initWithFrame:NSZeroRect frameName:@"" groupName:@""];

    NSEnumerator *enumerator = [expressions keyEnumerator];
    id expression;
    while ((expression = [enumerator nextObject])) {
        NSString *expectedResult = [expressions objectForKey:expression];
        NSString *result = [webView stringByEvaluatingJavaScriptFromString:expression];
        assert([result isEqualToString:expectedResult]);
    }

    [webView close];
    [webView release];
    [pool release];
}

307
308
309
static void setDefaultsToConsistentValuesForTesting()
{
    // Give some clear to undocumented defaults values
310
    static const int NoFontSmoothing = 0;
311
    static const int BlueTintedAppearance = 1;
eseidel's avatar
eseidel committed
312

mjs's avatar
mjs committed
313
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
314
    [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; // smallest font size to CG should perform antialiasing on
315
    [defaults setInteger:NoFontSmoothing forKey:@"AppleFontSmoothing"];
316
    [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"];
mjs's avatar
mjs committed
317
318
    [defaults setObject:@"0.709800 0.835300 1.000000" forKey:@"AppleHighlightColor"];
    [defaults setObject:@"0.500000 0.500000 0.500000" forKey:@"AppleOtherHighlightColor"];
319
    [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
320

321
322
323
324
325
326
327
328
329
    // Scrollbars are drawn either using AppKit (which uses NSUserDefaults) or using HIToolbox (which uses CFPreferences / kCFPreferencesAnyApplication / kCFPreferencesCurrentUser / kCFPreferencesAnyHost)
    [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
    RetainPtr<CFTypeRef> initialValue = CFPreferencesCopyValue(CFSTR("AppleScrollBarVariant"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
    CFPreferencesSetValue(CFSTR("AppleScrollBarVariant"), CFSTR("DoubleMax"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
    ThemeScrollBarArrowStyle style;
    GetThemeScrollBarArrowStyle(&style); // Force HIToolbox to read from CFPreferences
    if (initialValue)
        CFPreferencesSetValue(CFSTR("AppleScrollBarVariant"), initialValue.get(), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);

beidson@apple.com's avatar
beidson@apple.com committed
330
331
332
    NSString *libraryPath = [@"~/Library/Application Support/DumpRenderTree" stringByExpandingTildeInPath];
    [defaults setObject:[libraryPath stringByAppendingPathComponent:@"Databases"] forKey:WebDatabaseDirectoryDefaultsKey];
    
darin's avatar
darin committed
333
    WebPreferences *preferences = [WebPreferences standardPreferences];
334

335
336
337
338
339
340
341
342
    [preferences setStandardFontFamily:@"Times"];
    [preferences setFixedFontFamily:@"Courier"];
    [preferences setSerifFontFamily:@"Times"];
    [preferences setSansSerifFontFamily:@"Helvetica"];
    [preferences setCursiveFontFamily:@"Apple Chancery"];
    [preferences setFantasyFontFamily:@"Papyrus"];
    [preferences setDefaultFontSize:16];
    [preferences setDefaultFixedFontSize:13];
343
    [preferences setMinimumFontSize:1];
darin's avatar
darin committed
344
    [preferences setJavaEnabled:NO];
thatcher's avatar
thatcher committed
345
    [preferences setEditableLinkBehavior:WebKitEditableLinkOnlyLiveWithShiftKey];
darin's avatar
darin committed
346
    [preferences setTabsToLinks:NO];
ggaren's avatar
ggaren committed
347
    [preferences setDOMPasteAllowed:YES];
348
    [preferences setFullDocumentTeardownEnabled:YES];
eric@webkit.org's avatar
eric@webkit.org committed
349
    [preferences setShouldPrintBackgrounds:YES];
weinig@apple.com's avatar
weinig@apple.com committed
350
351
352
353

    // The back/forward cache is causing problems due to layouts during transition from one page to another.
    // So, turn it off for now, but we might want to turn it back on some day.
    [preferences setUsesPageCache:NO];
354
355
}

356
357
static void crashHandler(int sig)
{
358
359
360
    char *signalName = strsignal(sig);
    write(STDERR_FILENO, signalName, strlen(signalName));
    write(STDERR_FILENO, "\n", 1);
361
    restoreMainDisplayColorProfile(0);
362
363
364
    exit(128 + sig);
}

365
static void installSignalHandlers()
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
{
    signal(SIGILL, crashHandler);    /* 4:   illegal instruction (not reset when caught) */
    signal(SIGTRAP, crashHandler);   /* 5:   trace trap (not reset when caught) */
    signal(SIGEMT, crashHandler);    /* 7:   EMT instruction */
    signal(SIGFPE, crashHandler);    /* 8:   floating point exception */
    signal(SIGBUS, crashHandler);    /* 10:  bus error */
    signal(SIGSEGV, crashHandler);   /* 11:  segmentation violation */
    signal(SIGSYS, crashHandler);    /* 12:  bad argument to system call */
    signal(SIGPIPE, crashHandler);   /* 13:  write on a pipe with no reader */
    signal(SIGXCPU, crashHandler);   /* 24:  exceeded CPU time limit */
    signal(SIGXFSZ, crashHandler);   /* 25:  exceeded file size limit */
}

static void allocateGlobalControllers()
{
    // FIXME: We should remove these and move to the ObjC standard [Foo sharedInstance] model
382
    gNavigationController = [[NavigationController alloc] init];
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
    frameLoadDelegate = [[FrameLoadDelegate alloc] init];
    uiDelegate = [[UIDelegate alloc] init];
    editingDelegate = [[EditingDelegate alloc] init];
    resourceLoadDelegate = [[ResourceLoadDelegate alloc] init];
    policyDelegate = [[PolicyDelegate alloc] init];
}

// ObjC++ doens't seem to let me pass NSObject*& sadly.
static inline void releaseAndZero(NSObject** object)
{
    [*object release];
    *object = nil;
}

static void releaseGlobalControllers()
{
399
    releaseAndZero(&gNavigationController);
400
401
402
403
404
405
406
    releaseAndZero(&frameLoadDelegate);
    releaseAndZero(&editingDelegate);
    releaseAndZero(&resourceLoadDelegate);
    releaseAndZero(&uiDelegate);
    releaseAndZero(&policyDelegate);
}

407
408
static void initializeGlobalsFromCommandLineOptions(int argc, const char *argv[])
{
409
410
411
412
413
    struct option options[] = {
        {"notree", no_argument, &dumpTree, NO},
        {"pixel-tests", no_argument, &dumpPixels, YES},
        {"tree", no_argument, &dumpTree, YES},
        {"threaded", no_argument, &threaded, YES},
mitz@apple.com's avatar
mitz@apple.com committed
414
        {"complex-text", no_argument, &forceComplexText, YES},
415
416
        {NULL, 0, NULL, 0}
    };
ggaren's avatar
ggaren committed
417
    
darin's avatar
darin committed
418
    int option;
419
    while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) {
eseidel's avatar
eseidel committed
420
421
422
423
424
425
        switch (option) {
            case '?':   // unknown or ambiguous option
            case ':':   // missing argument
                exit(1);
                break;
        }
426
427
    }
}
428

429
430
431
432
433
434
static void addTestPluginsToPluginSearchPath(const char* executablePath)
{
    NSString *pwd = [[NSString stringWithUTF8String:executablePath] stringByDeletingLastPathComponent];
    [WebPluginDatabase setAdditionalWebPlugInPaths:[NSArray arrayWithObject:pwd]];
    [[WebPluginDatabase sharedDatabase] refresh];
}
435

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
static bool useLongRunningServerMode(int argc, const char *argv[])
{
    // This assumes you've already called getopt_long
    return (argc == optind+1 && strcmp(argv[optind], "-") == 0);
}

static void runTestingServerLoop()
{
    // When DumpRenderTree run in server mode, we just wait around for file names
    // to be passed to us and read each in turn, passing the results back to the client
    char filenameBuffer[2048];
    while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
        char *newLineCharacter = strchr(filenameBuffer, '\n');
        if (newLineCharacter)
            *newLineCharacter = '\0';

        if (strlen(filenameBuffer) == 0)
            continue;

        runTest(filenameBuffer);
456
    }
457
458
459
460
}

static void prepareConsistentTestingEnvironment()
{
461
462
    poseAsClass("DumpRenderTreePasteboard", "NSPasteboard");
    poseAsClass("DumpRenderTreeEvent", "NSEvent");
463
464

    setDefaultsToConsistentValuesForTesting();
mitz@apple.com's avatar
mitz@apple.com committed
465
    activateFonts();
eseidel's avatar
eseidel committed
466
    
467
    if (dumpPixels)
468
        setupMainDisplayColorProfile();
469
    allocateGlobalControllers();
andersca's avatar
andersca committed
470
    
darin's avatar
darin committed
471
    makeLargeMallocFailSilently();
472
}
ap's avatar
ap committed
473

474
475
476
void dumpRenderTree(int argc, const char *argv[])
{
    initializeGlobalsFromCommandLineOptions(argc, argv);
mitz@apple.com's avatar
mitz@apple.com committed
477
    prepareConsistentTestingEnvironment();
478
    addTestPluginsToPluginSearchPath(argv[0]);
479
480
    if (dumpPixels)
        installSignalHandlers();
mitz@apple.com's avatar
mitz@apple.com committed
481
482

    if (forceComplexText)
483
        [WebView _setAlwaysUsesComplexTextCodePath:YES];
mitz@apple.com's avatar
mitz@apple.com committed
484

485
486
487
    WebView *webView = createWebViewAndOffscreenWindow();
    mainFrame = [webView mainFrame];

ap's avatar
ap committed
488
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
489

andersca@apple.com's avatar
andersca@apple.com committed
490
491
    [WebCache empty];
     
ggaren's avatar
ggaren committed
492
493
494
    // <rdar://problem/5222911>
    testStringByEvaluatingJavaScriptFromString();

ggaren's avatar
ggaren committed
495
    if (threaded)
ggaren's avatar
ggaren committed
496
        startJavaScriptThreads();
497
498

    if (useLongRunningServerMode(argc, argv)) {
eseidel's avatar
eseidel committed
499
        printSeparators = YES;
500
        runTestingServerLoop();
501
    } else {
eseidel's avatar
eseidel committed
502
        printSeparators = (optind < argc-1 || (dumpPixels && dumpTree));
darin's avatar
darin committed
503
        for (int i = optind; i != argc; ++i)
ggaren's avatar
ggaren committed
504
            runTest(argv[i]);
505
    }
ggaren's avatar
ggaren committed
506
507

    if (threaded)
ggaren's avatar
ggaren committed
508
        stopJavaScriptThreads();
ggaren's avatar
ggaren committed
509

darin's avatar
WebKit:    
darin committed
510
    [webView close];
mjs's avatar
mjs committed
511
    mainFrame = nil;
darin's avatar
darin committed
512

justing's avatar
WebKit:    
justing committed
513
514
515
516
    // Work around problem where registering drag types leaves an outstanding
    // "perform selector" on the window, which retains the window. It's a bit
    // inelegant and perhaps dangerous to just blow them all away, but in practice
    // it probably won't cause any trouble (and this is just a test tool, after all).
517
    NSWindow *window = [webView window];
justing's avatar
WebKit:    
justing committed
518
519
    [NSObject cancelPreviousPerformRequestsWithTarget:window];
    
thatcher's avatar
thatcher committed
520
    [window close]; // releases when closed
eseidel's avatar
eseidel committed
521
    [webView release];
ggaren's avatar
ggaren committed
522
    
523
    releaseGlobalControllers();
darin's avatar
darin committed
524
    
525
    [DumpRenderTreePasteboard releaseLocalPasteboards];
weinig's avatar
weinig committed
526

527
    // FIXME: This should be moved onto LayoutTestController and made into a HashSet
weinig's avatar
weinig committed
528
529
530
531
532
    if (disallowedURLs) {
        CFRelease(disallowedURLs);
        disallowedURLs = 0;
    }

eseidel's avatar
eseidel committed
533
    if (dumpPixels)
534
        restoreMainDisplayColorProfile(0);
darin's avatar
WebKit:    
darin committed
535
}
darin's avatar
darin committed
536

darin's avatar
WebKit:    
darin committed
537
538
539
int main(int argc, const char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
540
    [NSApplication sharedApplication]; // Force AppKit to init itself
darin's avatar
WebKit:    
darin committed
541
542
    dumpRenderTree(argc, argv);
    [WebCoreStatistics garbageCollectJavaScriptObjects];
543
    [WebCoreStatistics emptyCache]; // Otherwise SVGImages trigger false positives for Frame/Node counts    
darin's avatar
WebKit:    
darin committed
544
    [pool release];
545
546
547
    return 0;
}

548
static NSInteger compareHistoryItems(id item1, id item2, void *context)
ggaren's avatar
ggaren committed
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
{
    return [[item1 target] caseInsensitiveCompare:[item2 target]];
}

static void dumpHistoryItem(WebHistoryItem *item, int indent, BOOL current)
{
    int start = 0;
    if (current) {
        printf("curr->");
        start = 6;
    }
    for (int i = start; i < indent; i++)
        putchar(' ');
    printf("%s", [[item URLString] UTF8String]);
    NSString *target = [item target];
    if (target && [target length] > 0)
        printf(" (in frame \"%s\")", [target UTF8String]);
    if ([item isTargetItem])
        printf("  **nav target**");
    putchar('\n');
    NSArray *kids = [item children];
    if (kids) {
        // must sort to eliminate arbitrary result ordering which defeats reproducible testing
        kids = [kids sortedArrayUsingFunction:&compareHistoryItems context:nil];
        for (unsigned i = 0; i < [kids count]; i++)
            dumpHistoryItem([kids objectAtIndex:i], indent+4, NO);
    }
}

static void dumpFrameScrollPosition(WebFrame *f)
{
    NSPoint scrollPosition = [[[[f frameView] documentView] superview] bounds].origin;
    if (ABS(scrollPosition.x) > 0.00000001 || ABS(scrollPosition.y) > 0.00000001) {
        if ([f parentFrame] != nil)
            printf("frame '%s' ", [[f name] UTF8String]);
        printf("scrolled to %.f,%.f\n", scrollPosition.x, scrollPosition.y);
    }
thatcher's avatar
thatcher committed
586

587
    if (gLayoutTestController->dumpChildFrameScrollPositions()) {
thatcher's avatar
thatcher committed
588
589
590
591
592
        NSArray *kids = [f childFrames];
        if (kids)
            for (unsigned i = 0; i < [kids count]; i++)
                dumpFrameScrollPosition([kids objectAtIndex:i]);
    }
ggaren's avatar
ggaren committed
593
594
}

weinig's avatar
weinig committed
595
596
597
598
static NSString *dumpFramesAsText(WebFrame *frame)
{
    DOMDocument *document = [frame DOMDocument];
    DOMElement *documentElement = [document documentElement];
599

weinig's avatar
weinig committed
600
601
602
603
604
605
606
607
608
609
610
    if (!documentElement)
        return @"";

    NSMutableString *result = [[[NSMutableString alloc] init] autorelease];

    // Add header for all but the main frame.
    if ([frame parentFrame])
        result = [NSMutableString stringWithFormat:@"\n--------\nFrame: '%@'\n--------\n", [frame name]];

    [result appendFormat:@"%@\n", [documentElement innerText]];

611
    if (gLayoutTestController->dumpChildFramesAsText()) {
weinig's avatar
weinig committed
612
613
614
615
616
617
618
619
620
621
        NSArray *kids = [frame childFrames];
        if (kids) {
            for (unsigned i = 0; i < [kids count]; i++)
                [result appendString:dumpFramesAsText([kids objectAtIndex:i])];
        }
    }

    return result;
}

eric@webkit.org's avatar
eric@webkit.org committed
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
static NSData *dumpFrameAsPDF(WebFrame *frame)
{
    if (!frame)
        return nil;

    // Sadly we have to dump to a file and then read from that file again
    // +[NSPrintOperation PDFOperationWithView:insideRect:] requires a rect and prints to a single page
    // likewise +[NSView dataWithPDFInsideRect:] also prints to a single continuous page
    // The goal of this function is to test "real" printing across multiple pages.
    // FIXME: It's possible there might be printing SPI to let us print a multi-page PDF to an NSData object
    NSString *path = @"/tmp/test.pdf";

    NSMutableDictionary *printInfoDict = [NSMutableDictionary dictionaryWithDictionary:[[NSPrintInfo sharedPrintInfo] dictionary]];
    [printInfoDict setObject:NSPrintSaveJob forKey:NSPrintJobDisposition];
    [printInfoDict setObject:path forKey:NSPrintSavePath];

    NSPrintInfo *printInfo = [[NSPrintInfo alloc] initWithDictionary:printInfoDict];
    [printInfo setHorizontalPagination:NSAutoPagination];
    [printInfo setVerticalPagination:NSAutoPagination];
    [printInfo setVerticallyCentered:NO];

    NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:[frame frameView] printInfo:printInfo];
    [printOperation setShowPanels:NO];
    [printOperation runOperation];

    [printInfo release];

    NSData *pdfData = [NSData dataWithContentsOfFile:path];
    [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];

    return pdfData;
}

andersca's avatar
andersca committed
655
656
static void convertMIMEType(NSMutableString *mimeType)
{
657
658
659
660
#ifdef BUILDING_ON_LEOPARD
    // Workaround for <rdar://problem/5539824> on Leopard
    if ([mimeType isEqualToString:@"text/xml"])
        [mimeType setString:@"application/xml"];
661
#endif
662
663
664
    // Workaround for <rdar://problem/6234318> with Dashcode 2.0
    if ([mimeType isEqualToString:@"application/x-javascript"])
        [mimeType setString:@"text/javascript"];
andersca's avatar
andersca committed
665
666
}

ddkilzer's avatar
ddkilzer committed
667
668
static void convertWebResourceDataToString(NSMutableDictionary *resource)
{
andersca's avatar
andersca committed
669
670
671
    NSMutableString *mimeType = [resource objectForKey:@"WebResourceMIMEType"];
    convertMIMEType(mimeType);
    
672
    if ([mimeType hasPrefix:@"text/"] || [[WebHTMLRepresentation supportedNonImageMIMETypes] containsObject:mimeType]) {
ddkilzer's avatar
ddkilzer committed
673
674
675
676
677
678
        NSData *data = [resource objectForKey:@"WebResourceData"];
        NSString *dataAsString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
        [resource setObject:dataAsString forKey:@"WebResourceData"];
    }
}

679
static void normalizeWebResourceURL(NSMutableString *webResourceURL)
ddkilzer's avatar
ddkilzer committed
680
{
681
    static int fileUrlLength = [(NSString *)@"file://" length];
682
683
684
685
686
    NSRange layoutTestsWebArchivePathRange = [webResourceURL rangeOfString:@"/LayoutTests/" options:NSBackwardsSearch];
    if (layoutTestsWebArchivePathRange.location == NSNotFound)
        return;
    NSRange currentWorkingDirectoryRange = NSMakeRange(fileUrlLength, layoutTestsWebArchivePathRange.location - fileUrlLength);
    [webResourceURL replaceCharactersInRange:currentWorkingDirectoryRange withString:@""];
ddkilzer's avatar
ddkilzer committed
687
688
}

689
static void convertWebResourceResponseToDictionary(NSMutableDictionary *propertyList)
ddkilzer's avatar
ddkilzer committed
690
691
692
693
694
695
696
697
698
{
    NSURLResponse *response = nil;
    NSData *responseData = [propertyList objectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
    if ([responseData isKindOfClass:[NSData class]]) {
        // Decode NSURLResponse
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:responseData];
        response = [unarchiver decodeObjectForKey:@"WebResourceResponse"]; // WebResourceResponseKey in WebResource.m
        [unarchiver finishDecoding];
        [unarchiver release];
andersca's avatar
andersca committed
699
700
701
702
703
    }        
    
    NSMutableDictionary *responseDictionary = [[NSMutableDictionary alloc] init];
    
    NSMutableString *urlString = [[[response URL] description] mutableCopy];
704
    normalizeWebResourceURL(urlString);
andersca's avatar
andersca committed
705
    [responseDictionary setObject:urlString forKey:@"URL"];
bdash's avatar
bdash committed
706
    [urlString release];
andersca's avatar
andersca committed
707
708
709
710
    
    NSMutableString *mimeTypeString = [[response MIMEType] mutableCopy];
    convertMIMEType(mimeTypeString);
    [responseDictionary setObject:mimeTypeString forKey:@"MIMEType"];
bdash's avatar
bdash committed
711
712
    [mimeTypeString release];

andersca's avatar
andersca committed
713
714
715
716
717
718
719
720
721
722
    NSString *textEncodingName = [response textEncodingName];
    if (textEncodingName)
        [responseDictionary setObject:textEncodingName forKey:@"textEncodingName"];
    [responseDictionary setObject:[NSNumber numberWithLongLong:[response expectedContentLength]] forKey:@"expectedContentLength"];
    
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        
        [responseDictionary setObject:[httpResponse allHeaderFields] forKey:@"allHeaderFields"];
        [responseDictionary setObject:[NSNumber numberWithInt:[httpResponse statusCode]] forKey:@"statusCode"];
ddkilzer's avatar
ddkilzer committed
723
    }
andersca's avatar
andersca committed
724
725
726
    
    [propertyList setObject:responseDictionary forKey:@"WebResourceResponse"];
    [responseDictionary release];
ddkilzer's avatar
ddkilzer committed
727
728
}

beidson@apple.com's avatar
beidson@apple.com committed
729
730
731
732
733
734
735
736
static NSInteger compareResourceURLs(id resource1, id resource2, void *context)
{
    NSString *url1 = [resource1 objectForKey:@"WebResourceURL"];
    NSString *url2 = [resource2 objectForKey:@"WebResourceURL"];
 
    return [url1 compare:url2];
}

ddkilzer's avatar
ddkilzer committed
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
static NSString *serializeWebArchiveToXML(WebArchive *webArchive)
{
    NSString *errorString;
    NSMutableDictionary *propertyList = [NSPropertyListSerialization propertyListFromData:[webArchive data]
                                                                         mutabilityOption:NSPropertyListMutableContainersAndLeaves
                                                                                   format:NULL
                                                                         errorDescription:&errorString];
    if (!propertyList)
        return errorString;

    NSMutableArray *resources = [NSMutableArray arrayWithCapacity:1];
    [resources addObject:propertyList];

    while ([resources count]) {
        NSMutableDictionary *resourcePropertyList = [resources objectAtIndex:0];
        [resources removeObjectAtIndex:0];

        NSMutableDictionary *mainResource = [resourcePropertyList objectForKey:@"WebMainResource"];
755
        normalizeWebResourceURL([mainResource objectForKey:@"WebResourceURL"]);
ddkilzer's avatar
ddkilzer committed
756
757
758
759
760
761
762
763
764
765
766
        convertWebResourceDataToString(mainResource);

        // Add subframeArchives to list for processing
        NSMutableArray *subframeArchives = [resourcePropertyList objectForKey:@"WebSubframeArchives"]; // WebSubframeArchivesKey in WebArchive.m
        if (subframeArchives)
            [resources addObjectsFromArray:subframeArchives];

        NSMutableArray *subresources = [resourcePropertyList objectForKey:@"WebSubresources"]; // WebSubresourcesKey in WebArchive.m
        NSEnumerator *enumerator = [subresources objectEnumerator];
        NSMutableDictionary *subresourcePropertyList;
        while ((subresourcePropertyList = [enumerator nextObject])) {
767
768
            normalizeWebResourceURL([subresourcePropertyList objectForKey:@"WebResourceURL"]);
            convertWebResourceResponseToDictionary(subresourcePropertyList);
ddkilzer's avatar
ddkilzer committed
769
770
            convertWebResourceDataToString(subresourcePropertyList);
        }
beidson@apple.com's avatar
beidson@apple.com committed
771
772
773
774
        
        // Sort the subresources so they're always in a predictable order for the dump
        if (NSArray *sortedSubresources = [subresources sortedArrayUsingFunction:compareResourceURLs context:nil])
            [resourcePropertyList setObject:sortedSubresources forKey:@"WebSubresources"];
ddkilzer's avatar
ddkilzer committed
775
776
777
778
779
780
781
782
    }

    NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:propertyList
                                                                 format:NSPropertyListXMLFormat_v1_0
                                                       errorDescription:&errorString];
    if (!xmlData)
        return errorString;

andersca's avatar
andersca committed
783
784
785
786
787
788
789
790
    NSMutableString *string = [[[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding] autorelease];

    // Replace "Apple Computer" with "Apple" in the DTD declaration.
    NSRange range = [string rangeOfString:@"-//Apple Computer//"];
    if (range.location != NSNotFound)
        [string replaceCharactersInRange:range withString:@"-//Apple//"];
    
    return string;
ddkilzer's avatar
ddkilzer committed
791
792
}

beidson's avatar
beidson committed
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
static void dumpBackForwardListForWebView(WebView *view)
{
    printf("\n============== Back Forward List ==============\n");
    WebBackForwardList *bfList = [view backForwardList];

    // Print out all items in the list after prevTestBFItem, which was from the previous test
    // Gather items from the end of the list, the print them out from oldest to newest
    NSMutableArray *itemsToPrint = [[NSMutableArray alloc] init];
    for (int i = [bfList forwardListCount]; i > 0; i--) {
        WebHistoryItem *item = [bfList itemAtIndex:i];
        // something is wrong if the item from the last test is in the forward part of the b/f list
        assert(item != prevTestBFItem);
        [itemsToPrint addObject:item];
    }
            
    assert([bfList currentItem] != prevTestBFItem);
    [itemsToPrint addObject:[bfList currentItem]];
    int currentItemIndex = [itemsToPrint count] - 1;

    for (int i = -1; i >= -[bfList backListCount]; i--) {
        WebHistoryItem *item = [bfList itemAtIndex:i];
        if (item == prevTestBFItem)
            break;
        [itemsToPrint addObject:item];
    }

819
    for (int i = [itemsToPrint count]-1; i >= 0; i--)
beidson's avatar
beidson committed
820
        dumpHistoryItem([itemsToPrint objectAtIndex:i], 8, i == currentItemIndex);
821

beidson's avatar
beidson committed
822
823
824
825
    [itemsToPrint release];
    printf("===============================================\n");
}

826
827
828
static void sizeWebViewForCurrentTest()
{
    // W3C SVG tests expect to be 480x360
829
    bool isSVGW3CTest = (gLayoutTestController->testPathOrURL().find("svg/W3C-SVG-1.1") != string::npos);
830
831
832
833
834
835
836
837
838
    if (isSVGW3CTest)
        [[mainFrame webView] setFrameSize:NSMakeSize(480, 360)];
    else
        [[mainFrame webView] setFrameSize:NSMakeSize(maxViewWidth, maxViewHeight)];
}

static const char *methodNameStringForFailedTest()
{
    const char *errorMessage;
839
    if (gLayoutTestController->dumpAsText())
840
        errorMessage = "[documentElement innerText]";
841
    else if (gLayoutTestController->dumpDOMAsWebArchive())
842
        errorMessage = "[[mainFrame DOMDocument] webArchive]";
843
    else if (gLayoutTestController->dumpSourceAsWebArchive())
844
845
846
847
848
849
850
851
852
        errorMessage = "[[mainFrame dataSource] webArchive]";
    else
        errorMessage = "[mainFrame renderTreeAsExternalRepresentation]";

    return errorMessage;
}

static void dumpBackForwardListForAllWindows()
{
weinig@apple.com's avatar
weinig@apple.com committed
853
854
    CFArrayRef openWindows = (CFArrayRef)[DumpRenderTreeWindow openWindows];
    unsigned count = CFArrayGetCount(openWindows);
855
    for (unsigned i = 0; i < count; i++) {
weinig@apple.com's avatar
weinig@apple.com committed
856
        NSWindow *window = (NSWindow *)CFArrayGetValueAtIndex(openWindows, i);
857
858
859
860
861
862
        WebView *webView = [[[window contentView] subviews] objectAtIndex:0];
        dumpBackForwardListForWebView(webView);
    }
}

static void invalidateAnyPreviousWaitToDumpWatchdog()
863
{
weinig's avatar
weinig committed
864
865
866
867
868
    if (waitToDumpWatchdog) {
        CFRunLoopTimerInvalidate(waitToDumpWatchdog);
        CFRelease(waitToDumpWatchdog);
        waitToDumpWatchdog = 0;
    }
869
870
871
872
873
}

void dump()
{
    invalidateAnyPreviousWaitToDumpWatchdog();
weinig's avatar
weinig committed
874

875
    bool dumpAsText = gLayoutTestController->dumpAsText();
eseidel's avatar
eseidel committed
876
    if (dumpTree) {
877
878
        NSString *resultString = nil;
        NSData *resultData = nil;
eric@webkit.org's avatar
eric@webkit.org committed
879
        NSString *resultMimeType = @"text/plain";
880

mitz's avatar
mitz committed
881
        dumpAsText |= [[[mainFrame dataSource] _responseMIMEType] isEqualToString:@"text/plain"];
882
883
        gLayoutTestController->setDumpAsText(dumpAsText);
        if (gLayoutTestController->dumpAsText()) {
884
            resultString = dumpFramesAsText(mainFrame);
885
        } else if (gLayoutTestController->dumpAsPDF()) {
eric@webkit.org's avatar
eric@webkit.org committed
886
887
            resultData = dumpFrameAsPDF(mainFrame);
            resultMimeType = @"application/pdf";
888
        } else if (gLayoutTestController->dumpDOMAsWebArchive()) {
mjs's avatar
mjs committed
889
            WebArchive *webArchive = [[mainFrame DOMDocument] webArchive];
890
            resultString = serializeWebArchiveToXML(webArchive);
eric@webkit.org's avatar
eric@webkit.org committed
891
            resultMimeType = @"application/x-webarchive";
892
        } else if (gLayoutTestController->dumpSourceAsWebArchive()) {
mjs's avatar
mjs committed
893
            WebArchive *webArchive = [[mainFrame dataSource] webArchive];
894
            resultString = serializeWebArchiveToXML(webArchive);
eric@webkit.org's avatar
eric@webkit.org committed
895
            resultMimeType = @"application/x-webarchive";
eseidel's avatar
eseidel committed
896
        } else {
897
898
            sizeWebViewForCurrentTest();
            resultString = [mainFrame renderTreeAsExternalRepresentation];
eseidel's avatar
eseidel committed
899
        }
ddkilzer's avatar
ddkilzer committed
900

901
902
903
        if (resultString && !resultData)
            resultData = [resultString dataUsingEncoding:NSUTF8StringEncoding];

eric@webkit.org's avatar
eric@webkit.org committed
904
905
        printf("Content-Type: %s\n", [resultMimeType UTF8String]);

906
907
908
        if (resultData) {
            fwrite([resultData bytes], 1, [resultData length], stdout);

909
            if (!gLayoutTestController->dumpAsText() && !gLayoutTestController->dumpDOMAsWebArchive() && !gLayoutTestController->dumpSourceAsWebArchive())
mjs's avatar
mjs committed
910
                dumpFrameScrollPosition(mainFrame);
ggaren's avatar
ggaren committed
911

912
            if (gLayoutTestController->dumpBackForwardList())
913
914
915
                dumpBackForwardListForAllWindows();
        } else
            printf("ERROR: nil result from %s", methodNameStringForFailedTest());
darin's avatar
darin committed
916

917
        if (printSeparators) {
918
            puts("#EOF");       // terminate the content block
919
920
            fputs("#EOF\n", stderr);
        }            
921
    }
eseidel's avatar
eseidel committed
922
    
923
924
    if (dumpPixels && !dumpAsText)
        dumpWebViewAsPixelsAndCompareWithExpected(gLayoutTestController->expectedPixelHash());
925
926
    
    puts("#EOF");   // terminate the (possibly empty) pixels block
mitz@apple.com's avatar
mitz@apple.com committed
927

ggaren's avatar
ggaren committed
928
    fflush(stdout);
929
    fflush(stderr);
eseidel's avatar
eseidel committed
930

931
932
933
    done = YES;
}

mjs's avatar
mjs committed
934
935
936
static bool shouldLogFrameLoadDelegates(const char *pathOrURL)
{
    return strstr(pathOrURL, "loading/");
937
}
mjs's avatar
mjs committed
938

939
940
static void resetWebViewToConsistentStateBeforeTesting()
{
timothy@apple.com's avatar
timothy@apple.com committed
941
942
943
    WebView *webView = [mainFrame webView];
    [(EditingDelegate *)[webView editingDelegate] setAcceptsEditing:YES];
    [webView makeTextStandardSize:nil];
944
945
    [webView resetPageZoom:nil];
    [webView setTabKeyCyclesThroughElements:YES];
timothy@apple.com's avatar
timothy@apple.com committed
946
947
    [webView setPolicyDelegate:nil];
    [webView _setDashboardBehavior:WebDashboardBehaviorUseBackwardCompatibilityMode to:NO];
948
    [webView _clearMainFrameName];
timothy@apple.com's avatar
timothy@apple.com committed
949
950
951
952

    WebPreferences *preferences = [webView preferences];
    [preferences setPrivateBrowsingEnabled:NO];
    [preferences setAuthorAndUserStylesEnabled:YES];
kmccullough@apple.com's avatar
kmccullough@apple.com committed
953
    [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
andersca@apple.com's avatar
andersca@apple.com committed
954
    [preferences setOfflineWebApplicationCacheEnabled:YES];
955
    [preferences setFullDocumentTeardownEnabled:YES];
956
957
    [preferences setDeveloperExtrasEnabled:NO];

mitz@apple.com's avatar
mitz@apple.com committed
958
959
960
961
962
    if (persistentUserStyleSheetLocation) {
        [preferences setUserStyleSheetLocation:[NSURL URLWithString:(NSString *)(persistentUserStyleSheetLocation.get())]];
        [preferences setUserStyleSheetEnabled:YES];
    } else
        [preferences setUserStyleSheetEnabled:NO];
963

darin@apple.com's avatar
darin@apple.com committed
964
    [[mainFrame webView] setSmartInsertDeleteEnabled:YES];
965
    [[[mainFrame webView] inspector] setJavaScriptProfilingEnabled:NO];
mitz@apple.com's avatar
mitz@apple.com committed
966

sullivan's avatar
sullivan committed
967
    [WebView _setUsesTestModeFocusRingColor:YES];
968
}
weinig's avatar
weinig committed
969

970
static void runTest(const string& testPathOrURL)
971
{
972
973
974
975
976
977
978
979
980
981
982
983
984
    ASSERT(!testPathOrURL.empty());
    
    // Look for "'" as a separator between the path or URL, and the pixel dump hash that follows.
    string pathOrURL(testPathOrURL);
    string expectedPixelHash;
    
    size_t separatorPos = pathOrURL.find("'");
    if (separatorPos != string::npos) {
        pathOrURL = string(testPathOrURL, 0, separatorPos);
        expectedPixelHash = string(testPathOrURL, separatorPos + 1);
    }

    NSString *pathOrURLString = [NSString stringWithUTF8String:pathOrURL.c_str()];
985
    if (!pathOrURLString) {
986
        fprintf(stderr, "Failed to parse \"%s\" as UTF-8\n", pathOrURL.c_str());
987
988
        return;
    }
weinig's avatar
weinig committed
989

990
991
992
993
994
995
996
    NSURL *url;
    if ([pathOrURLString hasPrefix:@"http://"] || [pathOrURLString hasPrefix:@"https://"])
        url = [NSURL URLWithString:pathOrURLString];
    else
        url = [NSURL fileURLWithPath:pathOrURLString];
    if (!url) {
        fprintf(stderr, "Failed to parse \"%s\" as a URL\n", pathOrURL.c_str());
997
998
999
        return;
    }

1000
    const string testURL([[url absoluteString] UTF8String]);