4ea37780d503b859674b06a1a62fa784e66041ee
[WebKit-https.git] / WebKitTools / DumpRenderTree / win / DumpRenderTree.cpp
1 /*
2  * Copyright (C) 2005, 2006, 2007 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "DumpRenderTree.h"
30
31 #include "EditingDelegate.h"
32 #include "FrameLoaderDelegate.h"
33 #include "LayoutTestController.h"
34 #include "UIDelegate.h"
35 #include "WorkQueueItem.h"
36 #include "WorkQueue.h"
37 #include <wtf/Vector.h>
38 #include <WebCore/COMPtr.h>
39 #include <CoreFoundation/CoreFoundation.h>
40 #include <JavaScriptCore/JavaScriptCore.h>
41 #include <math.h>
42 #include <pthread.h>
43 #include <string>
44 #include <tchar.h>
45 #include <WebKit/DOMPrivate.h>
46 #include <WebKit/IWebFramePrivate.h>
47 #include <WebKit/IWebHistoryItem.h>
48 #include <WebKit/IWebHistoryItemPrivate.h>
49 #include <WebKit/IWebURLResponse.h>
50 #include <WebKit/IWebViewPrivate.h>
51 #include <WebKit/WebKit.h>
52 #include <windows.h>
53 #include <stdio.h>
54
55 using std::wstring;
56
57 #ifdef _DEBUG
58 const LPWSTR TestPluginDir = L"TestNetscapePlugin_Debug";
59 #else
60 const LPWSTR TestPluginDir = L"TestNetscapePlugin";
61 #endif
62
63 #define USE_MAC_FONTS
64
65 const LPCWSTR kDumpRenderTreeClassName = L"DumpRenderTreeWindow";
66
67 static bool dumpTree = true;
68 static bool printSeparators;
69 static bool leakChecking = false;
70 static bool timedOut = false;
71 static bool threaded = false;
72
73 static const char* currentTest;
74
75 volatile bool done;
76 // This is the topmost frame that is loading, during a given load, or nil when no load is 
77 // in progress.  Usually this is the same as the main frame, but not always.  In the case
78 // where a frameset is loaded, and then new content is loaded into one of the child frames,
79 // that child frame is the "topmost frame that is loading".
80 IWebFrame* topLoadingFrame;     // !nil iff a load is in progress
81 static COMPtr<IWebHistoryItem> prevTestBFItem;  // current b/f item at the end of the previous test
82
83 IWebFrame* frame;
84 HWND webViewWindow;
85 static HWND hostWindow;
86
87 LayoutTestController* layoutTestController = 0;
88 CFRunLoopTimerRef waitToDumpWatchdog = 0; 
89
90 static const unsigned timeoutValue = 60000;
91 static const unsigned timeoutId = 10;
92
93 const unsigned maxViewWidth = 800;
94 const unsigned maxViewHeight = 600;
95
96 static LRESULT CALLBACK DumpRenderTreeWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
97 {
98     switch (msg) {
99         case WM_TIMER:
100             // The test ran long enough to time out
101             timedOut = true;
102             PostQuitMessage(0);
103             return 0;
104             break;
105         default:
106             return DefWindowProc(hWnd, msg, wParam, lParam);
107     }
108 }
109
110 extern "C" BOOL InitializeCoreGraphics();
111
112 static wstring initialize(HMODULE hModule)
113 {
114 #ifdef NDEBUG
115     HMODULE webKitModule = LoadLibraryW(L"WebKit.dll");
116 #else
117     HMODULE webKitModule = LoadLibraryW(L"WebKit_debug.dll");
118 #endif
119     FARPROC dllRegisterServer = ::GetProcAddress(webKitModule, "DllRegisterServer");
120     dllRegisterServer();
121
122     static LPCTSTR fontsToInstall[] = {
123         TEXT("AHEM____.ttf"),
124         TEXT("Apple Chancery.ttf"),
125         TEXT("Courier Bold.ttf"),
126         TEXT("Courier.ttf"),
127         TEXT("Helvetica Bold.ttf"),
128         TEXT("Helvetica.ttf"),
129         TEXT("Helvetica Neue Bold Italic.ttf"),
130         TEXT("Helvetica Neue Bold.ttf"),
131         TEXT("Helvetica Neue Condensed Black.ttf"),
132         TEXT("Helvetica Neue Condensed Bold.ttf"),
133         TEXT("Helvetica Neue Italic.ttf"),
134         TEXT("Helvetica Neue Light Italic.ttf"),
135         TEXT("Helvetica Neue Light.ttf"),
136         TEXT("Helvetica Neue UltraLight Italic.ttf"),
137         TEXT("Helvetica Neue UltraLight.ttf"),
138         TEXT("Helvetica Neue.ttf"),
139         TEXT("Lucida Grande.ttf"),
140         TEXT("Lucida Grande Bold.ttf"),
141         TEXT("Monaco.ttf"),
142         TEXT("Papyrus.ttf"),
143         TEXT("Times Bold Italic.ttf"),
144         TEXT("Times Bold.ttf"),
145         TEXT("Times Italic.ttf"),
146         TEXT("Times Roman.ttf")
147     };
148
149     TCHAR buffer[MAX_PATH];
150     GetModuleFileName(hModule, buffer, ARRAYSIZE(buffer));
151     wstring exePath(buffer);
152     int lastSlash = exePath.rfind('\\');
153     if (lastSlash != -1 && lastSlash + 1< exePath.length())
154         exePath = exePath.substr(0, lastSlash + 1);
155     
156     wstring resourcesPath(exePath + TEXT("DumpRenderTree.resources\\"));
157
158     for (int i = 0; i < ARRAYSIZE(fontsToInstall); ++i)
159         AddFontResourceEx(wstring(resourcesPath + fontsToInstall[i]).c_str(), FR_PRIVATE, 0);
160
161     // Init COM
162     OleInitialize(0);
163
164     // Initialize CG
165     InitializeCoreGraphics();
166
167     // Register a host window
168     WNDCLASSEX wcex;
169
170     wcex.cbSize = sizeof(WNDCLASSEX);
171
172     wcex.style         = CS_HREDRAW | CS_VREDRAW;
173     wcex.lpfnWndProc   = DumpRenderTreeWndProc;
174     wcex.cbClsExtra    = 0;
175     wcex.cbWndExtra    = 0;
176     wcex.hInstance     = hModule;
177     wcex.hIcon         = 0;
178     wcex.hCursor       = LoadCursor(0, IDC_ARROW);
179     wcex.hbrBackground = 0;
180     wcex.lpszMenuName  = 0;
181     wcex.lpszClassName = kDumpRenderTreeClassName;
182     wcex.hIconSm       = 0;
183
184     RegisterClassEx(&wcex);
185
186     hostWindow = CreateWindowEx(WS_EX_TOOLWINDOW, kDumpRenderTreeClassName, TEXT("DumpRenderTree"), WS_POPUP,
187       -maxViewWidth, -maxViewHeight, maxViewWidth, maxViewHeight, 0, 0, hModule, 0);
188
189     return exePath;
190 }
191
192 void displayWebView()
193 {
194     ::InvalidateRect(webViewWindow, 0, TRUE);
195     ::UpdateWindow(webViewWindow);
196 }
197
198 void dumpFrameScrollPosition(IWebFrame* frame)
199 {
200     if (!frame)
201         return;
202
203     COMPtr<IWebFramePrivate> framePrivate;
204     if (FAILED(frame->QueryInterface(&framePrivate)))
205         return;
206
207     SIZE scrollPosition;
208     if (FAILED(framePrivate->scrollOffset(&scrollPosition)))
209         return;
210
211     if (abs(scrollPosition.cx) > 0.00000001 || abs(scrollPosition.cy) > 0.00000001) {
212         COMPtr<IWebFrame> parent;
213         if (FAILED(frame->parentFrame(&parent)))
214             return;
215         if (parent) {
216             BSTR name;
217             if (FAILED(frame->name(&name)))
218                 return;
219             printf("frame '%S' ", name ? name : L"");
220             SysFreeString(name);
221         }
222         printf("scrolled to %.f,%.f\n", (double)scrollPosition.cx, (double)scrollPosition.cy);
223     }
224
225     if (::layoutTestController->dumpChildFrameScrollPositions()) {
226         COMPtr<IEnumVARIANT> enumKids;
227         if (FAILED(frame->childFrames(&enumKids)))
228             return;
229         VARIANT var;
230         VariantInit(&var);
231         while (enumKids->Next(1, &var, 0) == S_OK) {
232             ASSERT(V_VT(&var) == VT_UNKNOWN);
233             COMPtr<IWebFrame> framePtr;
234             V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr);
235             dumpFrameScrollPosition(framePtr.get());
236             VariantClear(&var);
237         }
238     }
239 }
240
241 static wstring dumpFramesAsText(IWebFrame* frame)
242 {
243     if (!frame)
244         return L"";
245
246     COMPtr<IDOMDocument> document;
247     if (FAILED(frame->DOMDocument(&document)))
248         return L"";
249
250     COMPtr<IDOMElement> documentElement;
251     if (FAILED(document->documentElement(&documentElement)))
252         return L"";
253
254     wstring result;
255
256     // Add header for all but the main frame.
257     COMPtr<IWebFrame> parent;
258     if (FAILED(frame->parentFrame(&parent)))
259         return L"";
260     if (parent) {
261         BSTR name = L"";
262         if (FAILED(frame->name(&name)))
263             return L"";
264
265         result.append(L"\n--------\nFrame: '");
266         result.append(name ? name : L"");
267         result.append(L"'\n--------\n");
268
269         SysFreeString(name);
270     }
271
272     BSTR innerText = 0;
273     COMPtr<IDOMElementPrivate> docPrivate;
274     if (SUCCEEDED(documentElement->QueryInterface(&docPrivate)))
275         docPrivate->innerText(&innerText);
276
277     result.append(innerText ? innerText : L"");
278     result.append(L"\n");
279
280     SysFreeString(innerText);
281
282     if (::layoutTestController->dumpChildFramesAsText()) {
283         COMPtr<IEnumVARIANT> enumKids;
284         if (FAILED(frame->childFrames(&enumKids)))
285             return L"";
286         VARIANT var;
287         VariantInit(&var);
288         while (enumKids->Next(1, &var, 0) == S_OK) {
289             ASSERT(V_VT(&var) == VT_UNKNOWN);
290             COMPtr<IWebFrame> framePtr;
291             V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr);
292             result.append(dumpFramesAsText(framePtr.get()));
293             VariantClear(&var);
294         }
295     }
296
297     return result;
298 }
299
300 static int compareHistoryItems(const void* item1, const void* item2)
301 {
302     COMPtr<IWebHistoryItemPrivate> itemA;
303     if (FAILED((*(COMPtr<IUnknown>*)item1)->QueryInterface(&itemA)))
304         return 0;
305
306     COMPtr<IWebHistoryItemPrivate> itemB;
307     if (FAILED((*(COMPtr<IUnknown>*)item2)->QueryInterface(&itemB)))
308         return 0;
309
310     BSTR targetA;
311     if (FAILED(itemA->target(&targetA)))
312         return 0;
313
314     BSTR targetB;
315     if (FAILED(itemB->target(&targetB))) {
316         SysFreeString(targetA);
317         return 0;
318     }
319
320     int result = wcsicmp(wstring(targetA, SysStringLen(targetA)).c_str(), wstring(targetB, SysStringLen(targetB)).c_str());
321     SysFreeString(targetA);
322     SysFreeString(targetB);
323     return result;
324 }
325
326 static void dumpHistoryItem(IWebHistoryItem* item, int indent, bool current)
327 {
328     assert(item);
329
330     int start = 0;
331     if (current) {
332         printf("curr->");
333         start = 6;
334     }
335     for (int i = start; i < indent; i++)
336         putchar(' ');
337
338     BSTR url;
339     if (FAILED(item->URLString(&url)))
340         return;
341     printf("%S", url ? url : L"");
342     SysFreeString(url);
343
344     COMPtr<IWebHistoryItemPrivate> itemPrivate;
345     if (FAILED(item->QueryInterface(&itemPrivate)))
346         return;
347
348     BSTR target;
349     if (FAILED(itemPrivate->target(&target)))
350         return;
351     if (SysStringLen(target))
352         printf(" (in frame \"%S\")", target);
353     SysFreeString(target);
354     BOOL isTargetItem = FALSE;
355     if (FAILED(itemPrivate->isTargetItem(&isTargetItem)))
356         return;
357     if (isTargetItem)
358         printf("  **nav target**");
359     putchar('\n');
360
361     unsigned kidsCount;
362     SAFEARRAY* arrPtr;
363     if (FAILED(itemPrivate->children(&kidsCount, &arrPtr)) || !kidsCount)
364         return;
365
366     Vector<COMPtr<IUnknown> > kidsVector;
367
368     LONG lowerBound;
369     if (FAILED(::SafeArrayGetLBound(arrPtr, 1, &lowerBound)))
370         goto exit;
371
372     LONG upperBound;
373     if (FAILED(::SafeArrayGetUBound(arrPtr, 1, &upperBound)))
374         goto exit;
375
376     LONG length = upperBound - lowerBound + 1;
377     if (!length)
378         goto exit;
379     ASSERT(length == kidsCount);
380
381     IUnknown** safeArrayData;
382     if (FAILED(::SafeArrayAccessData(arrPtr, (void**)&safeArrayData)))
383         goto exit;
384
385     for (int i = 0; i < length; ++i)
386         kidsVector.append(safeArrayData[i]);
387     ::SafeArrayUnaccessData(arrPtr);
388
389     // must sort to eliminate arbitrary result ordering which defeats reproducible testing
390     qsort(kidsVector.data(), kidsCount, sizeof(kidsVector[0]), compareHistoryItems);
391
392     for (unsigned i = 0; i < kidsCount; ++i) {
393         COMPtr<IWebHistoryItem> item;
394         kidsVector[i]->QueryInterface(&item);
395         dumpHistoryItem(item.get(), indent + 4, false);
396     }
397
398 exit:
399     if (arrPtr && SUCCEEDED(::SafeArrayUnlock(arrPtr)))
400         ::SafeArrayDestroy(arrPtr);
401 }
402
403 static void dumpBackForwardList(IWebFrame* frame)
404 {
405     assert(frame);
406
407     printf("\n============== Back Forward List ==============\n");
408     COMPtr<IWebView> webView;
409     if (FAILED(frame->webView(&webView)))
410         return;
411
412     COMPtr<IWebBackForwardList> bfList;
413     if (FAILED(webView->backForwardList(&bfList)))
414         return;
415
416     // Print out all items in the list after prevTestBFItem, which was from the previous test
417     // Gather items from the end of the list, the print them out from oldest to newest
418
419     Vector<COMPtr<IUnknown> > itemsToPrint;
420
421     int forwardListCount;
422     if (FAILED(bfList->forwardListCount(&forwardListCount)))
423         return;
424
425     for (int i = forwardListCount; i > 0; --i) {
426         COMPtr<IWebHistoryItem> item;
427         if (FAILED(bfList->itemAtIndex(i, &item)))
428             return;
429         // something is wrong if the item from the last test is in the forward part of the b/f list
430         assert(item != prevTestBFItem);
431         COMPtr<IUnknown> itemUnknown;
432         item->QueryInterface(&itemUnknown);
433         itemsToPrint.append(itemUnknown);
434     }
435     
436     COMPtr<IWebHistoryItem> currentItem;
437     if (FAILED(bfList->currentItem(&currentItem)))
438         return;
439
440     assert(currentItem != prevTestBFItem);
441     COMPtr<IUnknown> currentItemUnknown;
442     currentItem->QueryInterface(&currentItemUnknown);
443     itemsToPrint.append(currentItemUnknown);
444     int currentItemIndex = itemsToPrint.size() - 1;
445
446     int backListCount;
447     if (FAILED(bfList->backListCount(&backListCount)))
448         return;
449
450     for (int i = -1; i >= -backListCount; --i) {
451         COMPtr<IWebHistoryItem> item;
452         if (FAILED(bfList->itemAtIndex(i, &item)))
453             return;
454         if (item == prevTestBFItem)
455             break;
456         COMPtr<IUnknown> itemUnknown;
457         item->QueryInterface(&itemUnknown);
458         itemsToPrint.append(itemUnknown);
459     }
460
461     for (int i = itemsToPrint.size() - 1; i >= 0; --i) {
462         COMPtr<IWebHistoryItem> historyItemToPrint;
463         itemsToPrint[i]->QueryInterface(&historyItemToPrint);
464         dumpHistoryItem(historyItemToPrint.get(), 8, i == currentItemIndex);
465     }
466
467     printf("===============================================\n");
468 }
469
470 void dump()
471 {
472     COMPtr<IWebDataSource> dataSource;
473     if (SUCCEEDED(frame->dataSource(&dataSource))) {
474         COMPtr<IWebURLResponse> response;
475         if (SUCCEEDED(dataSource->response(&response)) && response) {
476             BSTR mimeType;
477             if (SUCCEEDED(response->MIMEType(&mimeType)))
478                 ::layoutTestController->setDumpAsText(::layoutTestController->dumpAsText() | !_tcscmp(mimeType, TEXT("text/plain")));
479             SysFreeString(mimeType);
480         }
481     }
482
483     BSTR resultString = 0;
484
485     if (dumpTree) {
486         if (::layoutTestController->dumpAsText()) {
487             ::InvalidateRect(webViewWindow, 0, TRUE);
488             ::SendMessage(webViewWindow, WM_PAINT, 0, 0);
489             wstring result = dumpFramesAsText(frame);
490             resultString = SysAllocStringLen(result.data(), result.size());
491         } else {
492             bool isSVGW3CTest = strstr(currentTest, "svg\\W3C-SVG-1.1");
493             unsigned width;
494             unsigned height;
495             if (isSVGW3CTest) {
496                 width = 480;
497                 height = 360;
498             } else {
499                 width = maxViewWidth;
500                 height = maxViewHeight;
501             }
502
503             ::SetWindowPos(webViewWindow, 0, 0, 0, width, height, SWP_NOMOVE);
504             ::InvalidateRect(webViewWindow, 0, TRUE);
505             ::SendMessage(webViewWindow, WM_PAINT, 0, 0);
506
507             COMPtr<IWebFramePrivate> framePrivate;
508             if (FAILED(frame->QueryInterface(&framePrivate)))
509                 goto fail;
510             framePrivate->renderTreeAsExternalRepresentation(&resultString);
511         }
512         
513         if (!resultString)
514             printf("ERROR: nil result from %s", ::layoutTestController->dumpAsText() ? "IDOMElement::innerText" : "IFrameViewPrivate::renderTreeAsExternalRepresentation");
515         else {
516             unsigned stringLength = SysStringLen(resultString);
517             int bufferSize = ::WideCharToMultiByte(CP_UTF8, 0, resultString, stringLength, 0, 0, 0, 0);
518             char* buffer = (char*)malloc(bufferSize + 1);
519             int result = ::WideCharToMultiByte(CP_UTF8, 0, resultString, stringLength, buffer, bufferSize + 1, 0, 0);
520             buffer[bufferSize] = '\0';
521             printf("%s", buffer);
522             free(buffer);
523             if (!::layoutTestController->dumpAsText())
524                 dumpFrameScrollPosition(frame);
525         }
526         if (::layoutTestController->dumpBackForwardList())
527             dumpBackForwardList(frame);
528     }
529
530     if (printSeparators)
531         puts("#EOF");
532 fail:
533     SysFreeString(resultString);
534     // This will exit from our message loop
535     PostQuitMessage(0);
536     done = true;
537 }
538
539 static void runTest(const char* pathOrURL)
540 {
541     static BSTR methodBStr = SysAllocString(TEXT("GET"));
542
543     BSTR urlBStr;
544  
545     CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, pathOrURL, kCFStringEncodingWindowsLatin1);
546     CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, str, 0);
547
548     if (!url)
549         url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLWindowsPathStyle, false);
550
551     CFRelease(str);
552
553     str = CFURLGetString(url);
554
555     CFIndex length = CFStringGetLength(str);
556     UniChar* buffer = new UniChar[length];
557
558     CFStringGetCharacters(str, CFRangeMake(0, length), buffer);
559     urlBStr = SysAllocStringLen((OLECHAR*)buffer, length);
560     delete[] buffer;
561
562     CFRelease(url);
563
564     currentTest = pathOrURL;
565
566     ::layoutTestController = new LayoutTestController(false, false);
567     done = false;
568     topLoadingFrame = 0;
569     timedOut = false;
570
571     prevTestBFItem = 0;
572     COMPtr<IWebView> webView;
573     if (SUCCEEDED(frame->webView(&webView))) {
574         COMPtr<IWebBackForwardList> bfList;
575         if (SUCCEEDED(webView->backForwardList(&bfList)))
576             bfList->currentItem(&prevTestBFItem);
577
578         COMPtr<IWebIBActions> webIBActions;
579         if (SUCCEEDED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
580             webIBActions->makeTextStandardSize(0);
581     }
582
583     WorkQueue::shared()->clear();
584     WorkQueue::shared()->setFrozen(false);
585
586     // Set the test timeout timer
587     SetTimer(hostWindow, timeoutId, timeoutValue, 0);
588
589     COMPtr<IWebMutableURLRequest> request;
590     HRESULT hr = CoCreateInstance(CLSID_WebMutableURLRequest, 0, CLSCTX_ALL, IID_IWebMutableURLRequest, (void**)&request);
591     if (FAILED(hr))
592         goto exit;
593
594     request->initWithURL(urlBStr, WebURLRequestUseProtocolCachePolicy, 0);
595
596     request->setHTTPMethod(methodBStr);
597     frame->loadRequest(request.get());
598
599     MSG msg;
600     while (GetMessage(&msg, 0, 0, 0)) {
601         TranslateMessage(&msg);
602         DispatchMessage(&msg);
603     }
604     KillTimer(hostWindow, timeoutId);
605
606     if (timedOut) {
607         fprintf(stderr, "ERROR: Timed out running %s\n", pathOrURL);
608         printf("ERROR: Timed out loading page\n");
609
610         if (printSeparators)
611             puts("#EOF");
612     }
613 exit:
614     SysFreeString(urlBStr);
615     return;
616 }
617
618 static void initializePreferences(IWebPreferences* preferences)
619 {
620 #ifdef USE_MAC_FONTS
621     BSTR standardFamily = SysAllocString(TEXT("Times"));
622     BSTR fixedFamily = SysAllocString(TEXT("Courier"));
623     BSTR sansSerifFamily = SysAllocString(TEXT("Helvetica"));
624     BSTR cursiveFamily = SysAllocString(TEXT("Apple Chancery"));
625     BSTR fantasyFamily = SysAllocString(TEXT("Papyrus"));
626 #else
627     BSTR standardFamily = SysAllocString(TEXT("Times New Roman"));
628     BSTR fixedFamily = SysAllocString(TEXT("Courier New"));
629     BSTR sansSerifFamily = SysAllocString(TEXT("Arial"));
630     BSTR cursiveFamily = SysAllocString(TEXT("Comic Sans MS")); // Not actually cursive, but it's what IE and Firefox use.
631     BSTR fantasyFamily = SysAllocString(TEXT("Times New Roman"));
632 #endif
633
634     preferences->setStandardFontFamily(standardFamily);
635     preferences->setFixedFontFamily(fixedFamily);
636     preferences->setSerifFontFamily(standardFamily);
637     preferences->setSansSerifFontFamily(sansSerifFamily);
638     preferences->setCursiveFontFamily(cursiveFamily);
639     preferences->setFantasyFontFamily(fantasyFamily);
640
641     preferences->setAutosaves(FALSE);
642     preferences->setJavaEnabled(FALSE);
643     preferences->setPlugInsEnabled(TRUE);
644     preferences->setDOMPasteAllowed(TRUE);
645     preferences->setEditableLinkBehavior(WebKitEditableLinkOnlyLiveWithShiftKey);
646
647     SysFreeString(standardFamily);
648     SysFreeString(fixedFamily);
649     SysFreeString(sansSerifFamily);
650     SysFreeString(cursiveFamily);
651     SysFreeString(fantasyFamily);
652 }
653
654 static Boolean pthreadEqualCallback(const void* value1, const void* value2)
655 {
656     return (Boolean)pthread_equal(*(pthread_t*)value1, *(pthread_t*)value2);
657 }
658
659 static CFDictionaryKeyCallBacks pthreadKeyCallbacks = { 0, 0, 0, 0, pthreadEqualCallback, 0 };
660
661 static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER;
662 static bool javaScriptThreadsShouldTerminate;
663
664 static const int javaScriptThreadsCount = 4;
665 static CFMutableDictionaryRef javaScriptThreads()
666 {
667     assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY);
668     static CFMutableDictionaryRef staticJavaScriptThreads;
669     if (!staticJavaScriptThreads)
670         staticJavaScriptThreads = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &pthreadKeyCallbacks, 0);
671     return staticJavaScriptThreads;
672 }
673
674 // Loops forever, running a script and randomly respawning, until 
675 // javaScriptThreadsShouldTerminate becomes true.
676 void* runJavaScriptThread(void* arg)
677 {
678     const char* const script =
679     " \
680     var array = []; \
681     for (var i = 0; i < 10; i++) { \
682         array.push(String(i)); \
683     } \
684     ";
685
686     while (true) {
687         JSGlobalContextRef ctx = JSGlobalContextCreate(0);
688         JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
689
690         JSValueRef exception = 0;
691         JSEvaluateScript(ctx, scriptRef, 0, 0, 0, &exception);
692         assert(!exception);
693         
694         JSGlobalContextRelease(ctx);
695         JSStringRelease(scriptRef);
696         
697         JSGarbageCollect(ctx);
698
699         pthread_mutex_lock(&javaScriptThreadsMutex);
700
701         // Check for cancellation.
702         if (javaScriptThreadsShouldTerminate) {
703             pthread_mutex_unlock(&javaScriptThreadsMutex);
704             return 0;
705         }
706
707         // Respawn probabilistically.
708         if (rand() % 5 == 0) {
709             pthread_t pthread;
710             pthread_create(&pthread, 0, &runJavaScriptThread, 0);
711             pthread_detach(pthread);
712
713             pthread_t self = pthread_self();
714             CFDictionaryRemoveValue(javaScriptThreads(), self.p);
715             CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0);
716
717             pthread_mutex_unlock(&javaScriptThreadsMutex);
718             return 0;
719         }
720
721         pthread_mutex_unlock(&javaScriptThreadsMutex);
722     }
723 }
724
725 static void startJavaScriptThreads(void)
726 {
727     pthread_mutex_lock(&javaScriptThreadsMutex);
728
729     for (int i = 0; i < javaScriptThreadsCount; i++) {
730         pthread_t pthread;
731         pthread_create(&pthread, 0, &runJavaScriptThread, 0);
732         pthread_detach(pthread);
733         CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0);
734     }
735
736     pthread_mutex_unlock(&javaScriptThreadsMutex);
737 }
738
739 static void stopJavaScriptThreads(void)
740 {
741     pthread_mutex_lock(&javaScriptThreadsMutex);
742
743     javaScriptThreadsShouldTerminate = true;
744
745     pthread_t* pthreads[javaScriptThreadsCount] = {0};
746     int threadDictCount = CFDictionaryGetCount(javaScriptThreads());
747     assert(threadDictCount == javaScriptThreadsCount);
748     CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0);
749
750     pthread_mutex_unlock(&javaScriptThreadsMutex);
751
752     for (int i = 0; i < javaScriptThreadsCount; i++) {
753         pthread_t* pthread = pthreads[i];
754         pthread_join(*pthread, 0);
755         free(pthread);
756     }
757 }
758
759 int main(int argc, char* argv[])
760 {
761     leakChecking = false;
762
763     wstring exePath = initialize(GetModuleHandle(0));
764
765     // FIXME: options
766
767     COMPtr<IWebView> webView;
768     HRESULT hr = CoCreateInstance(CLSID_WebView, 0, CLSCTX_ALL, IID_IWebView, (void**)&webView);
769     if (FAILED(hr)) {
770         fprintf(stderr, "Failed to create CLSID_WebView instance, error 0x%x\n", hr);
771         return -1;
772     }
773
774     if (FAILED(webView->setHostWindow((OLE_HANDLE)(ULONG64)hostWindow)))
775         return -1;
776
777     RECT clientRect;
778     clientRect.bottom = clientRect.left = clientRect.top = clientRect.right = 0;
779     BSTR groupName = SysAllocString(L"org.webkit.DumpRenderTree");
780     bool failed = FAILED(webView->initWithFrame(clientRect, 0, groupName));
781     SysFreeString(groupName);
782     if (failed)
783         return -1;
784
785     COMPtr<IWebViewPrivate> viewPrivate;
786     if (FAILED(webView->QueryInterface(&viewPrivate)))
787         return -1;
788     webView->Release();
789
790
791     BSTR pluginPath = SysAllocStringLen(0, exePath.length() + _tcslen(TestPluginDir));
792     _tcscpy(pluginPath, exePath.c_str());
793     _tcscat(pluginPath, TestPluginDir);
794     failed = FAILED(viewPrivate->addAdditionalPluginPath(pluginPath));
795     SysFreeString(pluginPath);
796     if (failed)
797         return -1;
798
799     if (FAILED(viewPrivate->viewWindow((OLE_HANDLE*)&webViewWindow)))
800         return -1;
801
802     SetWindowPos(webViewWindow, 0, 0, 0, maxViewWidth, maxViewHeight, 0);
803     ShowWindow(hostWindow, SW_SHOW);
804
805     COMPtr<FrameLoadDelegate> frameLoadDelegate;
806     frameLoadDelegate.adoptRef(new FrameLoadDelegate);
807     if (FAILED(webView->setFrameLoadDelegate(frameLoadDelegate.get())))
808         return -1;
809
810     COMPtr<UIDelegate> uiDelegate;
811     uiDelegate.adoptRef(new UIDelegate);
812     if (FAILED(webView->setUIDelegate(uiDelegate.get())))
813         return -1;
814
815     COMPtr<IWebViewEditing> viewEditing;
816     if (FAILED(webView->QueryInterface(&viewEditing)))
817         return -1;
818     webView->Release();
819
820     COMPtr<EditingDelegate> editingDelegate;
821     editingDelegate.adoptRef(new EditingDelegate);
822     if (FAILED(viewEditing->setEditingDelegate(editingDelegate.get())))
823         return -1;
824
825     COMPtr<IWebPreferences> preferences;
826     if (FAILED(webView->preferences(&preferences)))
827         return -1;
828
829     initializePreferences(preferences.get());
830
831     COMPtr<IWebIconDatabase> iconDatabase;
832     COMPtr<IWebIconDatabase> tmpIconDatabase;
833     if (FAILED(CoCreateInstance(CLSID_WebIconDatabase, 0, CLSCTX_ALL, IID_IWebIconDatabase, (void**)&tmpIconDatabase)))
834         return -1;
835     if (FAILED(tmpIconDatabase->sharedIconDatabase(&iconDatabase)))
836         return -1;
837         
838     if (FAILED(webView->mainFrame(&frame)))
839         return -1;
840
841     _CrtMemState entryToMainMemCheckpoint;
842     if (leakChecking)
843         _CrtMemCheckpoint(&entryToMainMemCheckpoint);
844
845     for (int i = 0; i < argc; ++i)
846         if (!stricmp(argv[i], "--threaded")) {
847             argv[i] = argv[argc - 1];
848             argc--;
849             threaded = true;
850             break;
851         }
852
853     if (threaded)
854         startJavaScriptThreads();
855
856     if (argc == 2 && strcmp(argv[1], "-") == 0) {
857         char filenameBuffer[2048];
858         printSeparators = true;
859         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
860             char* newLineCharacter = strchr(filenameBuffer, '\n');
861             if (newLineCharacter)
862                 *newLineCharacter = '\0';
863             
864             if (strlen(filenameBuffer) == 0)
865                 continue;
866
867             runTest(filenameBuffer);
868             fflush(stdout);
869         }
870     } else {
871         printSeparators = argc > 2;
872         for (int i = 1; i != argc; i++)
873             runTest(argv[i]);
874     }
875
876     if (threaded)
877         stopJavaScriptThreads();
878     
879     frame->Release();
880
881     if (leakChecking) {
882         // dump leaks to stderr
883         _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
884         _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
885         _CrtMemDumpAllObjectsSince(&entryToMainMemCheckpoint);
886     }
887
888     return 0;
889 }