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