49d3bb7db2d0ed22ddf179484871d6a7944a0994
[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     COMPtr<IWebHistory> history(Create, CLSID_WebHistory);
606     if (history)
607         history->setOptionalSharedHistory(0);
608
609     prevTestBFItem = 0;
610     COMPtr<IWebView> webView;
611     if (SUCCEEDED(frame->webView(&webView))) {
612         COMPtr<IWebBackForwardList> bfList;
613         if (SUCCEEDED(webView->backForwardList(&bfList)))
614             bfList->currentItem(&prevTestBFItem);
615
616         webView->setPolicyDelegate(NULL);
617
618         COMPtr<IWebIBActions> webIBActions;
619         if (SUCCEEDED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
620             webIBActions->makeTextStandardSize(0);
621     }
622
623     WorkQueue::shared()->clear();
624     WorkQueue::shared()->setFrozen(false);
625
626     // Set the test timeout timer
627     SetTimer(hostWindow, timeoutId, timeoutValue, 0);
628
629     COMPtr<IWebMutableURLRequest> request;
630     HRESULT hr = CoCreateInstance(CLSID_WebMutableURLRequest, 0, CLSCTX_ALL, IID_IWebMutableURLRequest, (void**)&request);
631     if (FAILED(hr))
632         goto exit;
633
634     request->initWithURL(urlBStr, WebURLRequestUseProtocolCachePolicy, 0);
635
636     request->setHTTPMethod(methodBStr);
637     frame->loadRequest(request.get());
638
639     MSG msg;
640     while (GetMessage(&msg, 0, 0, 0)) {
641         TranslateMessage(&msg);
642         DispatchMessage(&msg);
643     }
644     KillTimer(hostWindow, timeoutId);
645
646     if (timedOut) {
647         fprintf(stderr, "ERROR: Timed out running %s\n", pathOrURL);
648         printf("ERROR: Timed out loading page\n");
649
650         if (printSeparators)
651             puts("#EOF");
652     }
653 exit:
654     SysFreeString(urlBStr);
655     delete ::layoutTestController;
656
657     return;
658 }
659
660 static void initializePreferences(IWebPreferences* preferences)
661 {
662 #ifdef USE_MAC_FONTS
663     BSTR standardFamily = SysAllocString(TEXT("Times"));
664     BSTR fixedFamily = SysAllocString(TEXT("Courier"));
665     BSTR sansSerifFamily = SysAllocString(TEXT("Helvetica"));
666     BSTR cursiveFamily = SysAllocString(TEXT("Apple Chancery"));
667     BSTR fantasyFamily = SysAllocString(TEXT("Papyrus"));
668 #else
669     BSTR standardFamily = SysAllocString(TEXT("Times New Roman"));
670     BSTR fixedFamily = SysAllocString(TEXT("Courier New"));
671     BSTR sansSerifFamily = SysAllocString(TEXT("Arial"));
672     BSTR cursiveFamily = SysAllocString(TEXT("Comic Sans MS")); // Not actually cursive, but it's what IE and Firefox use.
673     BSTR fantasyFamily = SysAllocString(TEXT("Times New Roman"));
674 #endif
675
676     preferences->setStandardFontFamily(standardFamily);
677     preferences->setFixedFontFamily(fixedFamily);
678     preferences->setSerifFontFamily(standardFamily);
679     preferences->setSansSerifFontFamily(sansSerifFamily);
680     preferences->setCursiveFontFamily(cursiveFamily);
681     preferences->setFantasyFontFamily(fantasyFamily);
682
683     preferences->setAutosaves(FALSE);
684     preferences->setJavaEnabled(FALSE);
685     preferences->setPlugInsEnabled(TRUE);
686     preferences->setDOMPasteAllowed(TRUE);
687     preferences->setEditableLinkBehavior(WebKitEditableLinkOnlyLiveWithShiftKey);
688
689     SysFreeString(standardFamily);
690     SysFreeString(fixedFamily);
691     SysFreeString(sansSerifFamily);
692     SysFreeString(cursiveFamily);
693     SysFreeString(fantasyFamily);
694 }
695
696 static Boolean pthreadEqualCallback(const void* value1, const void* value2)
697 {
698     return (Boolean)pthread_equal(*(pthread_t*)value1, *(pthread_t*)value2);
699 }
700
701 static CFDictionaryKeyCallBacks pthreadKeyCallbacks = { 0, 0, 0, 0, pthreadEqualCallback, 0 };
702
703 static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER;
704 static bool javaScriptThreadsShouldTerminate;
705
706 static const int javaScriptThreadsCount = 4;
707 static CFMutableDictionaryRef javaScriptThreads()
708 {
709     assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY);
710     static CFMutableDictionaryRef staticJavaScriptThreads;
711     if (!staticJavaScriptThreads)
712         staticJavaScriptThreads = CFDictionaryCreateMutable(0, 0, &pthreadKeyCallbacks, 0);
713     return staticJavaScriptThreads;
714 }
715
716 // Loops forever, running a script and randomly respawning, until 
717 // javaScriptThreadsShouldTerminate becomes true.
718 void* runJavaScriptThread(void* arg)
719 {
720     const char* const script =
721     " \
722     var array = []; \
723     for (var i = 0; i < 10; i++) { \
724         array.push(String(i)); \
725     } \
726     ";
727
728     while (true) {
729         JSGlobalContextRef ctx = JSGlobalContextCreate(0);
730         JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
731
732         JSValueRef exception = 0;
733         JSEvaluateScript(ctx, scriptRef, 0, 0, 0, &exception);
734         assert(!exception);
735         
736         JSGlobalContextRelease(ctx);
737         JSStringRelease(scriptRef);
738         
739         JSGarbageCollect(ctx);
740
741         pthread_mutex_lock(&javaScriptThreadsMutex);
742
743         // Check for cancellation.
744         if (javaScriptThreadsShouldTerminate) {
745             pthread_mutex_unlock(&javaScriptThreadsMutex);
746             return 0;
747         }
748
749         // Respawn probabilistically.
750         if (rand() % 5 == 0) {
751             pthread_t pthread;
752             pthread_create(&pthread, 0, &runJavaScriptThread, 0);
753             pthread_detach(pthread);
754
755             pthread_t self = pthread_self();
756             CFDictionaryRemoveValue(javaScriptThreads(), self.p);
757             CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0);
758
759             pthread_mutex_unlock(&javaScriptThreadsMutex);
760             return 0;
761         }
762
763         pthread_mutex_unlock(&javaScriptThreadsMutex);
764     }
765 }
766
767 static void startJavaScriptThreads(void)
768 {
769     pthread_mutex_lock(&javaScriptThreadsMutex);
770
771     for (int i = 0; i < javaScriptThreadsCount; i++) {
772         pthread_t pthread;
773         pthread_create(&pthread, 0, &runJavaScriptThread, 0);
774         pthread_detach(pthread);
775         CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0);
776     }
777
778     pthread_mutex_unlock(&javaScriptThreadsMutex);
779 }
780
781 static void stopJavaScriptThreads(void)
782 {
783     pthread_mutex_lock(&javaScriptThreadsMutex);
784
785     javaScriptThreadsShouldTerminate = true;
786
787     pthread_t* pthreads[javaScriptThreadsCount] = {0};
788     int threadDictCount = CFDictionaryGetCount(javaScriptThreads());
789     assert(threadDictCount == javaScriptThreadsCount);
790     CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0);
791
792     pthread_mutex_unlock(&javaScriptThreadsMutex);
793
794     for (int i = 0; i < javaScriptThreadsCount; i++) {
795         pthread_t* pthread = pthreads[i];
796         pthread_join(*pthread, 0);
797         free(pthread);
798     }
799 }
800
801 int main(int argc, char* argv[])
802 {
803     leakChecking = false;
804
805     initialize(GetModuleHandle(0));
806
807     // FIXME: options
808
809     COMPtr<IWebView> webView;
810     HRESULT hr = CoCreateInstance(CLSID_WebView, 0, CLSCTX_ALL, IID_IWebView, (void**)&webView);
811     if (FAILED(hr)) {
812         fprintf(stderr, "Failed to create CLSID_WebView instance, error 0x%x\n", hr);
813         return -1;
814     }
815
816     if (FAILED(webView->setHostWindow((OLE_HANDLE)(ULONG64)hostWindow)))
817         return -1;
818
819     RECT clientRect;
820     clientRect.bottom = clientRect.left = clientRect.top = clientRect.right = 0;
821     BSTR groupName = SysAllocString(L"org.webkit.DumpRenderTree");
822     bool failed = FAILED(webView->initWithFrame(clientRect, 0, groupName));
823     SysFreeString(groupName);
824     if (failed)
825         return -1;
826
827     COMPtr<IWebViewPrivate> viewPrivate;
828     if (FAILED(webView->QueryInterface(&viewPrivate)))
829         return -1;
830     webView->Release();
831
832
833     BSTR pluginPath = SysAllocStringLen(0, exePath().length() + _tcslen(TestPluginDir));
834     _tcscpy(pluginPath, exePath().c_str());
835     _tcscat(pluginPath, TestPluginDir);
836     failed = FAILED(viewPrivate->addAdditionalPluginPath(pluginPath));
837     SysFreeString(pluginPath);
838     if (failed)
839         return -1;
840
841     if (FAILED(viewPrivate->viewWindow((OLE_HANDLE*)&webViewWindow)))
842         return -1;
843
844     SetWindowPos(webViewWindow, 0, 0, 0, maxViewWidth, maxViewHeight, 0);
845     ShowWindow(hostWindow, SW_SHOW);
846
847     COMPtr<FrameLoadDelegate> frameLoadDelegate;
848     frameLoadDelegate.adoptRef(new FrameLoadDelegate);
849     if (FAILED(webView->setFrameLoadDelegate(frameLoadDelegate.get())))
850         return -1;
851
852     policyDelegate = new PolicyDelegate();
853
854     COMPtr<UIDelegate> uiDelegate;
855     uiDelegate.adoptRef(new UIDelegate);
856     if (FAILED(webView->setUIDelegate(uiDelegate.get())))
857         return -1;
858
859     COMPtr<IWebViewEditing> viewEditing;
860     if (FAILED(webView->QueryInterface(&viewEditing)))
861         return -1;
862     webView->Release();
863
864     COMPtr<EditingDelegate> editingDelegate;
865     editingDelegate.adoptRef(new EditingDelegate);
866     if (FAILED(viewEditing->setEditingDelegate(editingDelegate.get())))
867         return -1;
868
869     COMPtr<IWebPreferences> preferences;
870     if (FAILED(webView->preferences(&preferences)))
871         return -1;
872
873     initializePreferences(preferences.get());
874
875     COMPtr<IWebIconDatabase> iconDatabase;
876     COMPtr<IWebIconDatabase> tmpIconDatabase;
877     if (FAILED(CoCreateInstance(CLSID_WebIconDatabase, 0, CLSCTX_ALL, IID_IWebIconDatabase, (void**)&tmpIconDatabase)))
878         return -1;
879     if (FAILED(tmpIconDatabase->sharedIconDatabase(&iconDatabase)))
880         return -1;
881         
882     if (FAILED(webView->mainFrame(&frame)))
883         return -1;
884
885     _CrtMemState entryToMainMemCheckpoint;
886     if (leakChecking)
887         _CrtMemCheckpoint(&entryToMainMemCheckpoint);
888
889     for (int i = 0; i < argc; ++i)
890         if (!stricmp(argv[i], "--threaded")) {
891             argv[i] = argv[argc - 1];
892             argc--;
893             threaded = true;
894             break;
895         }
896
897     if (threaded)
898         startJavaScriptThreads();
899
900     if (argc == 2 && strcmp(argv[1], "-") == 0) {
901         char filenameBuffer[2048];
902         printSeparators = true;
903         while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
904             char* newLineCharacter = strchr(filenameBuffer, '\n');
905             if (newLineCharacter)
906                 *newLineCharacter = '\0';
907             
908             if (strlen(filenameBuffer) == 0)
909                 continue;
910
911             runTest(filenameBuffer);
912             fflush(stdout);
913         }
914     } else {
915         printSeparators = argc > 2;
916         for (int i = 1; i != argc; i++)
917             runTest(argv[i]);
918     }
919
920     if (threaded)
921         stopJavaScriptThreads();
922     
923     delete policyDelegate;
924     frame->Release();
925
926     if (leakChecking) {
927         // dump leaks to stderr
928         _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
929         _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
930         _CrtMemDumpAllObjectsSince(&entryToMainMemCheckpoint);
931     }
932
933     return 0;
934 }