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