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