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