2 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #include "DumpRenderTree.h"
32 #include "EditingDelegate.h"
33 #include "FrameLoadDelegate.h"
34 #include "HistoryDelegate.h"
35 #include "LayoutTestController.h"
36 #include "PixelDumpSupport.h"
37 #include "PolicyDelegate.h"
38 #include "ResourceLoadDelegate.h"
39 #include "UIDelegate.h"
40 #include "WorkQueueItem.h"
41 #include "WorkQueue.h"
51 #include <wtf/RetainPtr.h>
52 #include <wtf/Vector.h>
54 #include <CoreFoundation/CoreFoundation.h>
55 #include <JavaScriptCore/JavaScriptCore.h>
56 #include <WebKit/WebKit.h>
57 #include <WebKit/WebKitCOMAPI.h>
60 #include <CFNetwork/CFURLCachePriv.h>
64 #include <CFNetwork/CFHTTPCookiesPriv.h>
69 #if !defined(NDEBUG) && (!defined(DEBUG_INTERNAL) || defined(DEBUG_ALL))
70 const LPWSTR TestPluginDir = L"TestNetscapePlugin_Debug";
72 const LPWSTR TestPluginDir = L"TestNetscapePlugin";
75 static LPCWSTR fontsEnvironmentVariable = L"WEBKIT_TESTFONTS";
78 const LPCWSTR kDumpRenderTreeClassName = L"DumpRenderTreeWindow";
80 static bool dumpTree = true;
81 static bool dumpPixels;
82 static bool dumpAllPixels;
83 static bool printSeparators;
84 static bool leakChecking = false;
85 static bool threaded = false;
86 static bool forceComplexText = false;
87 static bool printSupportedFeatures = false;
88 static RetainPtr<CFStringRef> persistentUserStyleSheetLocation;
91 // This is the topmost frame that is loading, during a given load, or nil when no load is
92 // in progress. Usually this is the same as the main frame, but not always. In the case
93 // where a frameset is loaded, and then new content is loaded into one of the child frames,
94 // that child frame is the "topmost frame that is loading".
95 IWebFrame* topLoadingFrame; // !nil iff a load is in progress
96 static COMPtr<IWebHistoryItem> prevTestBFItem; // current b/f item at the end of the previous test
97 PolicyDelegate* policyDelegate;
98 COMPtr<FrameLoadDelegate> sharedFrameLoadDelegate;
99 COMPtr<UIDelegate> sharedUIDelegate;
100 COMPtr<EditingDelegate> sharedEditingDelegate;
101 COMPtr<HistoryDelegate> sharedHistoryDelegate;
106 RefPtr<LayoutTestController> gLayoutTestController;
108 UINT_PTR waitToDumpWatchdog = 0;
110 void setPersistentUserStyleSheetLocation(CFStringRef url)
112 persistentUserStyleSheetLocation = url;
115 bool setAlwaysAcceptCookies(bool alwaysAcceptCookies)
118 COMPtr<IWebCookieManager> cookieManager;
119 if (FAILED(WebKitCreateInstance(CLSID_WebCookieManager, 0, IID_IWebCookieManager, reinterpret_cast<void**>(&cookieManager))))
121 CFHTTPCookieStorageRef cookieStorage = 0;
122 if (FAILED(cookieManager->cookieStorage(&cookieStorage)) || !cookieStorage)
125 WebKitCookieStorageAcceptPolicy cookieAcceptPolicy = alwaysAcceptCookies ? WebKitCookieStorageAcceptPolicyAlways : WebKitCookieStorageAcceptPolicyOnlyFromMainDocumentDomain;
126 CFHTTPCookieStorageSetCookieAcceptPolicy(cookieStorage, cookieAcceptPolicy);
134 wstring urlSuitableForTestResult(const wstring& url)
136 if (url.find(L"file://") == wstring::npos)
139 return lastPathComponent(url);
142 wstring lastPathComponent(const wstring& url)
147 return PathFindFileNameW(url.c_str());
150 static string toUTF8(const wchar_t* wideString, size_t length)
152 int result = WideCharToMultiByte(CP_UTF8, 0, wideString, length + 1, 0, 0, 0, 0);
153 Vector<char> utf8Vector(result);
154 result = WideCharToMultiByte(CP_UTF8, 0, wideString, length + 1, utf8Vector.data(), result, 0, 0);
158 return string(utf8Vector.data(), utf8Vector.size() - 1);
161 string toUTF8(BSTR bstr)
163 return toUTF8(bstr, SysStringLen(bstr));
166 string toUTF8(const wstring& wideString)
168 return toUTF8(wideString.c_str(), wideString.length());
171 static LRESULT CALLBACK DumpRenderTreeWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
175 for (unsigned i = openWindows().size() - 1; i >= 0; --i) {
176 if (openWindows()[i] == hWnd) {
177 openWindows().remove(i);
178 windowToWebViewMap().remove(hWnd);
185 return DefWindowProc(hWnd, msg, wParam, lParam);
189 static const wstring& exePath()
192 static bool initialized;
198 TCHAR buffer[MAX_PATH];
199 GetModuleFileName(GetModuleHandle(0), buffer, ARRAYSIZE(buffer));
201 int lastSlash = path.rfind('\\');
202 if (lastSlash != -1 && lastSlash + 1 < path.length())
203 path = path.substr(0, lastSlash + 1);
208 static const wstring& fontsPath()
211 static bool initialized;
217 DWORD size = GetEnvironmentVariable(fontsEnvironmentVariable, 0, 0);
218 Vector<TCHAR> buffer(size);
219 if (GetEnvironmentVariable(fontsEnvironmentVariable, buffer.data(), buffer.size())) {
220 path = buffer.data();
221 if (path[path.length() - 1] != '\\')
226 path = exePath() + TEXT("DumpRenderTree.resources\\");
230 static void addQTDirToPATH()
232 static LPCWSTR pathEnvironmentVariable = L"PATH";
233 static LPCWSTR quickTimeKeyName = L"Software\\Apple Computer, Inc.\\QuickTime";
234 static LPCWSTR quickTimeSysDir = L"QTSysDir";
235 static bool initialized;
241 // Get the QuickTime dll directory from the registry. The key can be in either HKLM or HKCU.
242 WCHAR qtPath[MAX_PATH];
243 DWORD qtPathBufferLen = sizeof(qtPath);
245 HRESULT result = SHGetValue(HKEY_LOCAL_MACHINE, quickTimeKeyName, quickTimeSysDir, &keyType, (LPVOID)qtPath, &qtPathBufferLen);
246 if (result != ERROR_SUCCESS || !qtPathBufferLen || keyType != REG_SZ) {
247 qtPathBufferLen = sizeof(qtPath);
248 result = SHGetValue(HKEY_CURRENT_USER, quickTimeKeyName, quickTimeSysDir, &keyType, (LPVOID)qtPath, &qtPathBufferLen);
249 if (result != ERROR_SUCCESS || !qtPathBufferLen || keyType != REG_SZ)
253 // Read the current PATH.
254 DWORD pathSize = GetEnvironmentVariableW(pathEnvironmentVariable, 0, 0);
255 Vector<WCHAR> oldPath(pathSize);
256 if (!GetEnvironmentVariableW(pathEnvironmentVariable, oldPath.data(), oldPath.size()))
259 // And add the QuickTime dll.
261 newPath.append(qtPath);
262 newPath.append(L";");
263 newPath.append(oldPath.data(), oldPath.size());
264 SetEnvironmentVariableW(pathEnvironmentVariable, newPath.data());
268 #define WEBKITDLL TEXT("WebKit_debug.dll")
270 #define WEBKITDLL TEXT("WebKit.dll")
273 static void initialize()
275 if (HMODULE webKitModule = LoadLibrary(WEBKITDLL))
276 if (FARPROC dllRegisterServer = GetProcAddress(webKitModule, "DllRegisterServer"))
282 static LPCTSTR fontsToInstall[] = {
283 TEXT("AHEM____.ttf"),
284 TEXT("Apple Chancery.ttf"),
285 TEXT("Courier Bold.ttf"),
287 TEXT("Helvetica Bold Oblique.ttf"),
288 TEXT("Helvetica Bold.ttf"),
289 TEXT("Helvetica Oblique.ttf"),
290 TEXT("Helvetica.ttf"),
291 TEXT("Helvetica Neue Bold Italic.ttf"),
292 TEXT("Helvetica Neue Bold.ttf"),
293 TEXT("Helvetica Neue Condensed Black.ttf"),
294 TEXT("Helvetica Neue Condensed Bold.ttf"),
295 TEXT("Helvetica Neue Italic.ttf"),
296 TEXT("Helvetica Neue Light Italic.ttf"),
297 TEXT("Helvetica Neue Light.ttf"),
298 TEXT("Helvetica Neue UltraLight Italic.ttf"),
299 TEXT("Helvetica Neue UltraLight.ttf"),
300 TEXT("Helvetica Neue.ttf"),
301 TEXT("Lucida Grande.ttf"),
302 TEXT("Lucida Grande Bold.ttf"),
305 TEXT("Times Bold Italic.ttf"),
306 TEXT("Times Bold.ttf"),
307 TEXT("Times Italic.ttf"),
308 TEXT("Times Roman.ttf"),
309 TEXT("WebKit Layout Tests 2.ttf"),
310 TEXT("WebKit Layout Tests.ttf"),
311 TEXT("WebKitWeightWatcher100.ttf"),
312 TEXT("WebKitWeightWatcher200.ttf"),
313 TEXT("WebKitWeightWatcher300.ttf"),
314 TEXT("WebKitWeightWatcher400.ttf"),
315 TEXT("WebKitWeightWatcher500.ttf"),
316 TEXT("WebKitWeightWatcher600.ttf"),
317 TEXT("WebKitWeightWatcher700.ttf"),
318 TEXT("WebKitWeightWatcher800.ttf"),
319 TEXT("WebKitWeightWatcher900.ttf")
322 wstring resourcesPath = fontsPath();
324 COMPtr<IWebTextRenderer> textRenderer;
325 if (SUCCEEDED(WebKitCreateInstance(CLSID_WebTextRenderer, 0, IID_IWebTextRenderer, (void**)&textRenderer)))
326 for (int i = 0; i < ARRAYSIZE(fontsToInstall); ++i)
327 textRenderer->registerPrivateFont(wstring(resourcesPath + fontsToInstall[i]).c_str());
329 // Add the QuickTime dll directory to PATH or QT 7.6 will fail to initialize on systems
330 // linked with older versions of qtmlclientlib.dll.
333 // Register a host window
336 wcex.cbSize = sizeof(WNDCLASSEX);
338 wcex.style = CS_HREDRAW | CS_VREDRAW;
339 wcex.lpfnWndProc = DumpRenderTreeWndProc;
342 wcex.hInstance = GetModuleHandle(0);
344 wcex.hCursor = LoadCursor(0, IDC_ARROW);
345 wcex.hbrBackground = 0;
346 wcex.lpszMenuName = 0;
347 wcex.lpszClassName = kDumpRenderTreeClassName;
350 RegisterClassEx(&wcex);
353 void displayWebView()
355 ::InvalidateRect(webViewWindow, 0, TRUE);
356 ::SendMessage(webViewWindow, WM_PAINT, 0, 0);
359 void dumpFrameScrollPosition(IWebFrame* frame)
364 COMPtr<IWebFramePrivate> framePrivate;
365 if (FAILED(frame->QueryInterface(&framePrivate)))
369 if (FAILED(framePrivate->scrollOffset(&scrollPosition)))
372 if (abs(scrollPosition.cx) > 0.00000001 || abs(scrollPosition.cy) > 0.00000001) {
373 COMPtr<IWebFrame> parent;
374 if (FAILED(frame->parentFrame(&parent)))
378 if (FAILED(frame->name(&name)))
380 printf("frame '%S' ", name ? name : L"");
383 printf("scrolled to %.f,%.f\n", (double)scrollPosition.cx, (double)scrollPosition.cy);
386 if (::gLayoutTestController->dumpChildFrameScrollPositions()) {
387 COMPtr<IEnumVARIANT> enumKids;
388 if (FAILED(frame->childFrames(&enumKids)))
392 while (enumKids->Next(1, &var, 0) == S_OK) {
393 ASSERT(V_VT(&var) == VT_UNKNOWN);
394 COMPtr<IWebFrame> framePtr;
395 V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr);
396 dumpFrameScrollPosition(framePtr.get());
402 static wstring dumpFramesAsText(IWebFrame* frame)
407 COMPtr<IDOMDocument> document;
408 if (FAILED(frame->DOMDocument(&document)))
411 COMPtr<IDOMElement> documentElement;
412 if (FAILED(document->documentElement(&documentElement)))
417 // Add header for all but the main frame.
418 COMPtr<IWebFrame> parent;
419 if (FAILED(frame->parentFrame(&parent)))
423 if (FAILED(frame->name(&name)))
426 result.append(L"\n--------\nFrame: '");
427 result.append(name ? name : L"", SysStringLen(name));
428 result.append(L"'\n--------\n");
434 COMPtr<IDOMElementPrivate> docPrivate;
435 if (SUCCEEDED(documentElement->QueryInterface(&docPrivate)))
436 docPrivate->innerText(&innerText);
438 result.append(innerText ? innerText : L"", SysStringLen(innerText));
439 result.append(L"\n");
441 SysFreeString(innerText);
443 if (::gLayoutTestController->dumpChildFramesAsText()) {
444 COMPtr<IEnumVARIANT> enumKids;
445 if (FAILED(frame->childFrames(&enumKids)))
449 while (enumKids->Next(1, &var, 0) == S_OK) {
450 ASSERT(V_VT(&var) == VT_UNKNOWN);
451 COMPtr<IWebFrame> framePtr;
452 V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr);
453 result.append(dumpFramesAsText(framePtr.get()));
461 static int compareHistoryItems(const void* item1, const void* item2)
463 COMPtr<IWebHistoryItemPrivate> itemA;
464 if (FAILED((*(COMPtr<IUnknown>*)item1)->QueryInterface(&itemA)))
467 COMPtr<IWebHistoryItemPrivate> itemB;
468 if (FAILED((*(COMPtr<IUnknown>*)item2)->QueryInterface(&itemB)))
472 if (FAILED(itemA->target(&targetA)))
476 if (FAILED(itemB->target(&targetB))) {
477 SysFreeString(targetA);
481 int result = wcsicmp(wstring(targetA, SysStringLen(targetA)).c_str(), wstring(targetB, SysStringLen(targetB)).c_str());
482 SysFreeString(targetA);
483 SysFreeString(targetB);
487 static void dumpHistoryItem(IWebHistoryItem* item, int indent, bool current)
496 for (int i = start; i < indent; i++)
500 if (FAILED(item->URLString(&url)))
503 if (wcsstr(url, L"file:/") == url) {
504 static wchar_t* layoutTestsString = L"/LayoutTests/";
505 static wchar_t* fileTestString = L"(file test):";
507 wchar_t* result = wcsstr(url, layoutTestsString);
510 wchar_t* start = result + wcslen(layoutTestsString);
512 BSTR newURL = SysAllocStringLen(NULL, SysStringLen(url));
513 wcscpy(newURL, fileTestString);
514 wcscpy(newURL + wcslen(fileTestString), start);
520 printf("%S", url ? url : L"");
523 COMPtr<IWebHistoryItemPrivate> itemPrivate;
524 if (FAILED(item->QueryInterface(&itemPrivate)))
528 if (FAILED(itemPrivate->target(&target)))
530 if (SysStringLen(target))
531 printf(" (in frame \"%S\")", target);
532 SysFreeString(target);
533 BOOL isTargetItem = FALSE;
534 if (FAILED(itemPrivate->isTargetItem(&isTargetItem)))
537 printf(" **nav target**");
542 if (FAILED(itemPrivate->children(&kidsCount, &arrPtr)) || !kidsCount)
545 Vector<COMPtr<IUnknown> > kidsVector;
548 if (FAILED(::SafeArrayGetLBound(arrPtr, 1, &lowerBound)))
552 if (FAILED(::SafeArrayGetUBound(arrPtr, 1, &upperBound)))
555 LONG length = upperBound - lowerBound + 1;
558 ASSERT(length == kidsCount);
560 IUnknown** safeArrayData;
561 if (FAILED(::SafeArrayAccessData(arrPtr, (void**)&safeArrayData)))
564 for (int i = 0; i < length; ++i)
565 kidsVector.append(safeArrayData[i]);
566 ::SafeArrayUnaccessData(arrPtr);
568 // must sort to eliminate arbitrary result ordering which defeats reproducible testing
569 qsort(kidsVector.data(), kidsCount, sizeof(kidsVector[0]), compareHistoryItems);
571 for (unsigned i = 0; i < kidsCount; ++i) {
572 COMPtr<IWebHistoryItem> item;
573 kidsVector[i]->QueryInterface(&item);
574 dumpHistoryItem(item.get(), indent + 4, false);
578 if (arrPtr && SUCCEEDED(::SafeArrayUnlock(arrPtr)))
579 ::SafeArrayDestroy(arrPtr);
582 static void dumpBackForwardList(IWebView* webView)
586 printf("\n============== Back Forward List ==============\n");
588 COMPtr<IWebBackForwardList> bfList;
589 if (FAILED(webView->backForwardList(&bfList)))
592 // Print out all items in the list after prevTestBFItem, which was from the previous test
593 // Gather items from the end of the list, the print them out from oldest to newest
595 Vector<COMPtr<IUnknown> > itemsToPrint;
597 int forwardListCount;
598 if (FAILED(bfList->forwardListCount(&forwardListCount)))
601 for (int i = forwardListCount; i > 0; --i) {
602 COMPtr<IWebHistoryItem> item;
603 if (FAILED(bfList->itemAtIndex(i, &item)))
605 // something is wrong if the item from the last test is in the forward part of the b/f list
606 assert(item != prevTestBFItem);
607 COMPtr<IUnknown> itemUnknown;
608 item->QueryInterface(&itemUnknown);
609 itemsToPrint.append(itemUnknown);
612 COMPtr<IWebHistoryItem> currentItem;
613 if (FAILED(bfList->currentItem(¤tItem)))
616 assert(currentItem != prevTestBFItem);
617 COMPtr<IUnknown> currentItemUnknown;
618 currentItem->QueryInterface(¤tItemUnknown);
619 itemsToPrint.append(currentItemUnknown);
620 int currentItemIndex = itemsToPrint.size() - 1;
623 if (FAILED(bfList->backListCount(&backListCount)))
626 for (int i = -1; i >= -backListCount; --i) {
627 COMPtr<IWebHistoryItem> item;
628 if (FAILED(bfList->itemAtIndex(i, &item)))
630 if (item == prevTestBFItem)
632 COMPtr<IUnknown> itemUnknown;
633 item->QueryInterface(&itemUnknown);
634 itemsToPrint.append(itemUnknown);
637 for (int i = itemsToPrint.size() - 1; i >= 0; --i) {
638 COMPtr<IWebHistoryItem> historyItemToPrint;
639 itemsToPrint[i]->QueryInterface(&historyItemToPrint);
640 dumpHistoryItem(historyItemToPrint.get(), 8, i == currentItemIndex);
643 printf("===============================================\n");
646 static void dumpBackForwardListForAllWindows()
648 unsigned count = openWindows().size();
649 for (unsigned i = 0; i < count; i++) {
650 HWND window = openWindows()[i];
651 IWebView* webView = windowToWebViewMap().get(window).get();
652 dumpBackForwardList(webView);
656 static void invalidateAnyPreviousWaitToDumpWatchdog()
658 if (!waitToDumpWatchdog)
661 KillTimer(0, waitToDumpWatchdog);
662 waitToDumpWatchdog = 0;
667 invalidateAnyPreviousWaitToDumpWatchdog();
669 COMPtr<IWebDataSource> dataSource;
670 if (SUCCEEDED(frame->dataSource(&dataSource))) {
671 COMPtr<IWebURLResponse> response;
672 if (SUCCEEDED(dataSource->response(&response)) && response) {
674 if (SUCCEEDED(response->MIMEType(&mimeType)))
675 ::gLayoutTestController->setDumpAsText(::gLayoutTestController->dumpAsText() | !_tcscmp(mimeType, TEXT("text/plain")));
676 SysFreeString(mimeType);
680 BSTR resultString = 0;
683 if (::gLayoutTestController->dumpAsText()) {
684 ::InvalidateRect(webViewWindow, 0, TRUE);
685 ::SendMessage(webViewWindow, WM_PAINT, 0, 0);
686 wstring result = dumpFramesAsText(frame);
687 resultString = SysAllocStringLen(result.data(), result.size());
689 bool isSVGW3CTest = (gLayoutTestController->testPathOrURL().find("svg\\W3C-SVG-1.1") != string::npos);
696 width = LayoutTestController::maxViewWidth;
697 height = LayoutTestController::maxViewHeight;
700 ::SetWindowPos(webViewWindow, 0, 0, 0, width, height, SWP_NOMOVE);
701 ::InvalidateRect(webViewWindow, 0, TRUE);
702 ::SendMessage(webViewWindow, WM_PAINT, 0, 0);
704 COMPtr<IWebFramePrivate> framePrivate;
705 if (FAILED(frame->QueryInterface(&framePrivate)))
707 framePrivate->renderTreeAsExternalRepresentation(gLayoutTestController->isPrinting(), &resultString);
711 printf("ERROR: nil result from %s", ::gLayoutTestController->dumpAsText() ? "IDOMElement::innerText" : "IFrameViewPrivate::renderTreeAsExternalRepresentation");
713 unsigned stringLength = SysStringLen(resultString);
714 int bufferSize = ::WideCharToMultiByte(CP_UTF8, 0, resultString, stringLength, 0, 0, 0, 0);
715 char* buffer = (char*)malloc(bufferSize + 1);
716 ::WideCharToMultiByte(CP_UTF8, 0, resultString, stringLength, buffer, bufferSize + 1, 0, 0);
717 fwrite(buffer, 1, bufferSize, stdout);
719 if (!::gLayoutTestController->dumpAsText())
720 dumpFrameScrollPosition(frame);
722 if (::gLayoutTestController->dumpBackForwardList())
723 dumpBackForwardListForAllWindows();
726 if (printSeparators) {
727 puts("#EOF"); // terminate the content block
728 fputs("#EOF\n", stderr);
734 && gLayoutTestController->generatePixelResults()
735 && !gLayoutTestController->dumpDOMAsWebArchive()
736 && !gLayoutTestController->dumpSourceAsWebArchive())
737 dumpWebViewAsPixelsAndCompareWithExpected(gLayoutTestController->expectedPixelHash());
739 printf("#EOF\n"); // terminate the (possibly empty) pixels block
743 SysFreeString(resultString);
744 // This will exit from our message loop.
749 static bool shouldLogFrameLoadDelegates(const char* pathOrURL)
751 return strstr(pathOrURL, "/loading/") || strstr(pathOrURL, "\\loading\\");
754 static bool shouldLogHistoryDelegates(const char* pathOrURL)
756 return strstr(pathOrURL, "/globalhistory/") || strstr(pathOrURL, "\\globalhistory\\");
759 static bool shouldOpenWebInspector(const char* pathOrURL)
761 return strstr(pathOrURL, "/inspector/") || strstr(pathOrURL, "\\inspector\\");
764 static bool shouldEnableDeveloperExtras(const char* pathOrURL)
766 return shouldOpenWebInspector(pathOrURL) || strstr(pathOrURL, "/inspector-enabled/") || strstr(pathOrURL, "\\inspector-enabled\\");
769 static void resetDefaultsToConsistentValues(IWebPreferences* preferences)
772 static BSTR standardFamily = SysAllocString(TEXT("Times"));
773 static BSTR fixedFamily = SysAllocString(TEXT("Courier"));
774 static BSTR sansSerifFamily = SysAllocString(TEXT("Helvetica"));
775 static BSTR cursiveFamily = SysAllocString(TEXT("Apple Chancery"));
776 static BSTR fantasyFamily = SysAllocString(TEXT("Papyrus"));
778 static BSTR standardFamily = SysAllocString(TEXT("Times New Roman"));
779 static BSTR fixedFamily = SysAllocString(TEXT("Courier New"));
780 static BSTR sansSerifFamily = SysAllocString(TEXT("Arial"));
781 static BSTR cursiveFamily = SysAllocString(TEXT("Comic Sans MS")); // Not actually cursive, but it's what IE and Firefox use.
782 static BSTR fantasyFamily = SysAllocString(TEXT("Times New Roman"));
785 preferences->setStandardFontFamily(standardFamily);
786 preferences->setFixedFontFamily(fixedFamily);
787 preferences->setSerifFontFamily(standardFamily);
788 preferences->setSansSerifFontFamily(sansSerifFamily);
789 preferences->setCursiveFontFamily(cursiveFamily);
790 preferences->setFantasyFontFamily(fantasyFamily);
792 preferences->setAutosaves(FALSE);
793 preferences->setDefaultFontSize(16);
794 preferences->setDefaultFixedFontSize(13);
795 preferences->setMinimumFontSize(1);
796 preferences->setJavaEnabled(FALSE);
797 preferences->setPlugInsEnabled(TRUE);
798 preferences->setDOMPasteAllowed(TRUE);
799 preferences->setEditableLinkBehavior(WebKitEditableLinkOnlyLiveWithShiftKey);
800 preferences->setFontSmoothing(FontSmoothingTypeStandard);
801 preferences->setUsesPageCache(FALSE);
802 preferences->setPrivateBrowsingEnabled(FALSE);
803 preferences->setJavaScriptCanOpenWindowsAutomatically(TRUE);
804 preferences->setJavaScriptEnabled(TRUE);
805 preferences->setTabsToLinks(FALSE);
806 preferences->setShouldPrintBackgrounds(TRUE);
807 preferences->setLoadsImagesAutomatically(TRUE);
808 preferences->setEditingBehavior(WebKitEditingWinBehavior);
810 if (persistentUserStyleSheetLocation) {
811 Vector<wchar_t> urlCharacters(CFStringGetLength(persistentUserStyleSheetLocation.get()));
812 CFStringGetCharacters(persistentUserStyleSheetLocation.get(), CFRangeMake(0, CFStringGetLength(persistentUserStyleSheetLocation.get())), (UniChar *)urlCharacters.data());
813 BSTR url = SysAllocStringLen(urlCharacters.data(), urlCharacters.size());
814 preferences->setUserStyleSheetLocation(url);
816 preferences->setUserStyleSheetEnabled(TRUE);
818 preferences->setUserStyleSheetEnabled(FALSE);
820 COMPtr<IWebPreferencesPrivate> prefsPrivate(Query, preferences);
822 prefsPrivate->setAllowUniversalAccessFromFileURLs(TRUE);
823 prefsPrivate->setAllowFileAccessFromFileURLs(TRUE);
824 prefsPrivate->setAuthorAndUserStylesEnabled(TRUE);
825 prefsPrivate->setDeveloperExtrasEnabled(FALSE);
826 prefsPrivate->setExperimentalNotificationsEnabled(TRUE);
827 prefsPrivate->setShouldPaintNativeControls(FALSE); // FIXME - need to make DRT pass with Windows native controls <http://bugs.webkit.org/show_bug.cgi?id=25592>
828 prefsPrivate->setJavaScriptCanAccessClipboard(TRUE);
829 prefsPrivate->setXSSAuditorEnabled(FALSE);
830 prefsPrivate->setFrameFlatteningEnabled(FALSE);
831 prefsPrivate->setOfflineWebApplicationCacheEnabled(TRUE);
833 setAlwaysAcceptCookies(false);
835 setlocale(LC_ALL, "");
838 static void resetWebViewToConsistentStateBeforeTesting()
840 COMPtr<IWebView> webView;
841 if (FAILED(frame->webView(&webView)))
844 webView->setPolicyDelegate(0);
845 policyDelegate->setPermissive(false);
846 policyDelegate->setControllerToNotifyDone(0);
848 COMPtr<IWebIBActions> webIBActions(Query, webView);
850 webIBActions->makeTextStandardSize(0);
851 webIBActions->resetPageZoom(0);
855 COMPtr<IWebPreferences> preferences;
856 if (SUCCEEDED(webView->preferences(&preferences)))
857 resetDefaultsToConsistentValues(preferences.get());
859 COMPtr<IWebViewEditing> viewEditing;
860 if (SUCCEEDED(webView->QueryInterface(&viewEditing)))
861 viewEditing->setSmartInsertDeleteEnabled(TRUE);
863 COMPtr<IWebViewPrivate> webViewPrivate(Query, webView);
867 COMPtr<IWebInspector> inspector;
868 if (SUCCEEDED(webViewPrivate->inspector(&inspector)))
869 inspector->setJavaScriptProfilingEnabled(FALSE);
872 if (SUCCEEDED(webViewPrivate->viewWindow(reinterpret_cast<OLE_HANDLE*>(&viewWindow))) && viewWindow)
873 SetFocus(viewWindow);
875 webViewPrivate->clearMainFrameName();
876 webViewPrivate->resetOriginAccessWhitelists();
879 if (SUCCEEDED(webView->groupName(&groupName))) {
880 webViewPrivate->removeAllUserContentFromGroup(groupName);
881 SysFreeString(groupName);
884 sharedUIDelegate->resetUndoManager();
886 sharedFrameLoadDelegate->resetToConsistentState();
889 static void runTest(const string& testPathOrURL)
891 static BSTR methodBStr = SysAllocString(TEXT("GET"));
893 // Look for "'" as a separator between the path or URL, and the pixel dump hash that follows.
894 string pathOrURL(testPathOrURL);
895 string expectedPixelHash;
897 size_t separatorPos = pathOrURL.find("'");
898 if (separatorPos != string::npos) {
899 pathOrURL = string(testPathOrURL, 0, separatorPos);
900 expectedPixelHash = string(testPathOrURL, separatorPos + 1);
905 CFStringRef str = CFStringCreateWithCString(0, pathOrURL.c_str(), kCFStringEncodingWindowsLatin1);
906 CFURLRef url = CFURLCreateWithString(0, str, 0);
909 url = CFURLCreateWithFileSystemPath(0, str, kCFURLWindowsPathStyle, false);
913 str = CFURLGetString(url);
915 CFIndex length = CFStringGetLength(str);
916 UniChar* buffer = new UniChar[length];
918 CFStringGetCharacters(str, CFRangeMake(0, length), buffer);
919 urlBStr = SysAllocStringLen((OLECHAR*)buffer, length);
924 ::gLayoutTestController = LayoutTestController::create(pathOrURL, expectedPixelHash);
928 gLayoutTestController->setIconDatabaseEnabled(false);
930 if (shouldLogFrameLoadDelegates(pathOrURL.c_str()))
931 gLayoutTestController->setDumpFrameLoadCallbacks(true);
933 COMPtr<IWebView> webView;
934 if (SUCCEEDED(frame->webView(&webView))) {
935 COMPtr<IWebViewPrivate> viewPrivate;
936 if (SUCCEEDED(webView->QueryInterface(&viewPrivate))) {
937 if (shouldLogHistoryDelegates(pathOrURL.c_str())) {
938 gLayoutTestController->setDumpHistoryDelegateCallbacks(true);
939 viewPrivate->setHistoryDelegate(sharedHistoryDelegate.get());
941 viewPrivate->setHistoryDelegate(0);
944 COMPtr<IWebHistory> history;
945 if (SUCCEEDED(WebKitCreateInstance(CLSID_WebHistory, 0, __uuidof(history), reinterpret_cast<void**>(&history))))
946 history->setOptionalSharedHistory(0);
948 resetWebViewToConsistentStateBeforeTesting();
950 if (shouldEnableDeveloperExtras(pathOrURL.c_str())) {
951 gLayoutTestController->setDeveloperExtrasEnabled(true);
952 if (shouldOpenWebInspector(pathOrURL.c_str()))
953 gLayoutTestController->showWebInspector();
958 COMPtr<IWebBackForwardList> bfList;
959 if (SUCCEEDED(webView->backForwardList(&bfList)))
960 bfList->currentItem(&prevTestBFItem);
963 WorkQueue::shared()->clear();
964 WorkQueue::shared()->setFrozen(false);
967 webView->hostWindow(reinterpret_cast<OLE_HANDLE*>(&hostWindow));
969 COMPtr<IWebMutableURLRequest> request;
970 HRESULT hr = WebKitCreateInstance(CLSID_WebMutableURLRequest, 0, IID_IWebMutableURLRequest, (void**)&request);
974 request->initWithURL(urlBStr, WebURLRequestUseProtocolCachePolicy, 60);
976 request->setHTTPMethod(methodBStr);
977 frame->loadRequest(request.get());
980 while (GetMessage(&msg, 0, 0, 0)) {
981 // We get spurious WM_MOUSELEAVE events which make event handling machinery think that mouse button
982 // is released during dragging (see e.g. fast\dynamic\layer-hit-test-crash.html).
983 // Mouse can never leave WebView during normal DumpRenderTree operation, so we just ignore all such events.
984 if (msg.message == WM_MOUSELEAVE)
986 TranslateMessage(&msg);
987 DispatchMessage(&msg);
990 if (shouldEnableDeveloperExtras(pathOrURL.c_str())) {
991 gLayoutTestController->closeWebInspector();
992 gLayoutTestController->setDeveloperExtrasEnabled(false);
995 resetWebViewToConsistentStateBeforeTesting();
997 frame->stopLoading();
999 if (::gLayoutTestController->closeRemainingWindowsWhenComplete()) {
1000 Vector<HWND> windows = openWindows();
1001 unsigned size = windows.size();
1002 for (unsigned i = 0; i < size; i++) {
1003 HWND window = windows[i];
1005 // Don't try to close the main window
1006 if (window == hostWindow)
1009 DestroyWindow(window);
1014 SysFreeString(urlBStr);
1015 ::gLayoutTestController.clear();
1020 static Boolean pthreadEqualCallback(const void* value1, const void* value2)
1022 return (Boolean)pthread_equal(*(pthread_t*)value1, *(pthread_t*)value2);
1025 static CFDictionaryKeyCallBacks pthreadKeyCallbacks = { 0, 0, 0, 0, pthreadEqualCallback, 0 };
1027 static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER;
1028 static bool javaScriptThreadsShouldTerminate;
1030 static const int javaScriptThreadsCount = 4;
1031 static CFMutableDictionaryRef javaScriptThreads()
1033 assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY);
1034 static CFMutableDictionaryRef staticJavaScriptThreads;
1035 if (!staticJavaScriptThreads)
1036 staticJavaScriptThreads = CFDictionaryCreateMutable(0, 0, &pthreadKeyCallbacks, 0);
1037 return staticJavaScriptThreads;
1040 // Loops forever, running a script and randomly respawning, until
1041 // javaScriptThreadsShouldTerminate becomes true.
1042 void* runJavaScriptThread(void* arg)
1044 const char* const script =
1047 for (var i = 0; i < 10; i++) { \
1048 array.push(String(i)); \
1053 JSGlobalContextRef ctx = JSGlobalContextCreate(0);
1054 JSStringRef scriptRef = JSStringCreateWithUTF8CString(script);
1056 JSValueRef exception = 0;
1057 JSEvaluateScript(ctx, scriptRef, 0, 0, 1, &exception);
1060 JSGlobalContextRelease(ctx);
1061 JSStringRelease(scriptRef);
1063 JSGarbageCollect(ctx);
1065 pthread_mutex_lock(&javaScriptThreadsMutex);
1067 // Check for cancellation.
1068 if (javaScriptThreadsShouldTerminate) {
1069 pthread_mutex_unlock(&javaScriptThreadsMutex);
1073 // Respawn probabilistically.
1074 if (rand() % 5 == 0) {
1076 pthread_create(&pthread, 0, &runJavaScriptThread, 0);
1077 pthread_detach(pthread);
1079 pthread_t self = pthread_self();
1080 CFDictionaryRemoveValue(javaScriptThreads(), self.p);
1081 CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0);
1083 pthread_mutex_unlock(&javaScriptThreadsMutex);
1087 pthread_mutex_unlock(&javaScriptThreadsMutex);
1091 static void startJavaScriptThreads(void)
1093 pthread_mutex_lock(&javaScriptThreadsMutex);
1095 for (int i = 0; i < javaScriptThreadsCount; i++) {
1097 pthread_create(&pthread, 0, &runJavaScriptThread, 0);
1098 pthread_detach(pthread);
1099 CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0);
1102 pthread_mutex_unlock(&javaScriptThreadsMutex);
1105 static void stopJavaScriptThreads(void)
1107 pthread_mutex_lock(&javaScriptThreadsMutex);
1109 javaScriptThreadsShouldTerminate = true;
1111 pthread_t* pthreads[javaScriptThreadsCount] = {0};
1112 int threadDictCount = CFDictionaryGetCount(javaScriptThreads());
1113 assert(threadDictCount == javaScriptThreadsCount);
1114 CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0);
1116 pthread_mutex_unlock(&javaScriptThreadsMutex);
1118 for (int i = 0; i < javaScriptThreadsCount; i++) {
1119 pthread_t* pthread = pthreads[i];
1120 pthread_join(*pthread, 0);
1125 Vector<HWND>& openWindows()
1127 static Vector<HWND> vector;
1131 WindowToWebViewMap& windowToWebViewMap()
1133 static WindowToWebViewMap map;
1137 IWebView* createWebViewAndOffscreenWindow(HWND* webViewWindow)
1139 unsigned maxViewWidth = LayoutTestController::maxViewWidth;
1140 unsigned maxViewHeight = LayoutTestController::maxViewHeight;
1141 HWND hostWindow = CreateWindowEx(WS_EX_TOOLWINDOW, kDumpRenderTreeClassName, TEXT("DumpRenderTree"), WS_POPUP,
1142 -maxViewWidth, -maxViewHeight, maxViewWidth, maxViewHeight, 0, 0, GetModuleHandle(0), 0);
1146 HRESULT hr = WebKitCreateInstance(CLSID_WebView, 0, IID_IWebView, (void**)&webView);
1148 fprintf(stderr, "Failed to create CLSID_WebView instance, error 0x%x\n", hr);
1152 if (FAILED(webView->setHostWindow((OLE_HANDLE)(ULONG64)hostWindow)))
1156 clientRect.bottom = clientRect.left = clientRect.top = clientRect.right = 0;
1157 BSTR groupName = SysAllocString(L"org.webkit.DumpRenderTree");
1158 bool failed = FAILED(webView->initWithFrame(clientRect, 0, groupName));
1159 SysFreeString(groupName);
1163 COMPtr<IWebViewPrivate> viewPrivate;
1164 if (FAILED(webView->QueryInterface(&viewPrivate)))
1167 viewPrivate->setShouldApplyMacFontAscentHack(TRUE);
1168 viewPrivate->setAlwaysUsesComplexTextCodePath(forceComplexText);
1170 BSTR pluginPath = SysAllocStringLen(0, exePath().length() + _tcslen(TestPluginDir));
1171 _tcscpy(pluginPath, exePath().c_str());
1172 _tcscat(pluginPath, TestPluginDir);
1173 failed = FAILED(viewPrivate->addAdditionalPluginDirectory(pluginPath));
1174 SysFreeString(pluginPath);
1179 if (FAILED(viewPrivate->viewWindow(reinterpret_cast<OLE_HANDLE*>(&viewWindow))))
1182 *webViewWindow = viewWindow;
1184 SetWindowPos(viewWindow, 0, 0, 0, maxViewWidth, maxViewHeight, 0);
1185 ShowWindow(hostWindow, SW_SHOW);
1187 if (FAILED(webView->setFrameLoadDelegate(sharedFrameLoadDelegate.get())))
1190 if (FAILED(viewPrivate->setFrameLoadDelegatePrivate(sharedFrameLoadDelegate.get())))
1193 if (FAILED(webView->setUIDelegate(sharedUIDelegate.get())))
1196 COMPtr<IWebViewEditing> viewEditing;
1197 if (FAILED(webView->QueryInterface(&viewEditing)))
1200 if (FAILED(viewEditing->setEditingDelegate(sharedEditingDelegate.get())))
1203 ResourceLoadDelegate* resourceLoadDelegate = new ResourceLoadDelegate();
1204 HRESULT result = webView->setResourceLoadDelegate(resourceLoadDelegate);
1205 resourceLoadDelegate->Release(); // The delegate is owned by the WebView, so release our reference to it.
1209 openWindows().append(hostWindow);
1210 windowToWebViewMap().set(hostWindow, webView);
1215 RetainPtr<CFURLCacheRef> sharedCFURLCache()
1218 HMODULE module = GetModuleHandle(TEXT("CFNetwork.dll"));
1220 HMODULE module = GetModuleHandle(TEXT("CFNetwork_debug.dll"));
1225 typedef CFURLCacheRef (*CFURLCacheCopySharedURLCacheProcPtr)(void);
1226 if (CFURLCacheCopySharedURLCacheProcPtr copyCache = reinterpret_cast<CFURLCacheCopySharedURLCacheProcPtr>(GetProcAddress(module, "CFURLCacheCopySharedURLCache")))
1227 return RetainPtr<CFURLCacheRef>(AdoptCF, copyCache());
1229 typedef CFURLCacheRef (*CFURLCacheSharedURLCacheProcPtr)(void);
1230 if (CFURLCacheSharedURLCacheProcPtr sharedCache = reinterpret_cast<CFURLCacheSharedURLCacheProcPtr>(GetProcAddress(module, "CFURLCacheSharedURLCache")))
1231 return sharedCache();
1237 static LONG WINAPI exceptionFilter(EXCEPTION_POINTERS*)
1239 fputs("#CRASHED\n", stderr);
1241 return EXCEPTION_CONTINUE_SEARCH;
1244 int main(int argc, char* argv[])
1246 ::SetUnhandledExceptionFilter(exceptionFilter);
1248 leakChecking = false;
1250 _setmode(1, _O_BINARY);
1251 _setmode(2, _O_BINARY);
1255 Vector<const char*> tests;
1257 for (int i = 1; i < argc; ++i) {
1258 if (!stricmp(argv[i], "--threaded")) {
1263 if (!stricmp(argv[i], "--dump-all-pixels")) {
1264 dumpAllPixels = true;
1268 if (!stricmp(argv[i], "--pixel-tests")) {
1273 if (!stricmp(argv[i], "--complex-text")) {
1274 forceComplexText = true;
1278 if (!stricmp(argv[i], "--print-supported-features")) {
1279 printSupportedFeatures = true;
1283 tests.append(argv[i]);
1286 policyDelegate = new PolicyDelegate();
1287 sharedFrameLoadDelegate.adoptRef(new FrameLoadDelegate);
1288 sharedUIDelegate.adoptRef(new UIDelegate);
1289 sharedEditingDelegate.adoptRef(new EditingDelegate);
1290 sharedHistoryDelegate.adoptRef(new HistoryDelegate);
1292 // FIXME - need to make DRT pass with Windows native controls <http://bugs.webkit.org/show_bug.cgi?id=25592>
1293 COMPtr<IWebPreferences> tmpPreferences;
1294 if (FAILED(WebKitCreateInstance(CLSID_WebPreferences, 0, IID_IWebPreferences, reinterpret_cast<void**>(&tmpPreferences))))
1296 COMPtr<IWebPreferences> standardPreferences;
1297 if (FAILED(tmpPreferences->standardPreferences(&standardPreferences)))
1299 COMPtr<IWebPreferencesPrivate> standardPreferencesPrivate;
1300 if (FAILED(standardPreferences->QueryInterface(&standardPreferencesPrivate)))
1302 standardPreferencesPrivate->setShouldPaintNativeControls(FALSE);
1303 standardPreferences->setJavaScriptEnabled(TRUE);
1304 standardPreferences->setDefaultFontSize(16);
1305 standardPreferences->setAcceleratedCompositingEnabled(true);
1306 standardPreferences->setContinuousSpellCheckingEnabled(TRUE);
1308 if (printSupportedFeatures) {
1309 BOOL acceleratedCompositingAvailable;
1310 standardPreferences->acceleratedCompositingEnabled(&acceleratedCompositingAvailable);
1312 #if ENABLE(3D_RENDERING)
1313 // In theory, we could have a software-based 3D rendering implementation that we use when
1314 // hardware-acceleration is not available. But we don't have any such software
1315 // implementation, so 3D rendering is only available when hardware-acceleration is.
1316 BOOL threeDRenderingAvailable = acceleratedCompositingAvailable;
1318 BOOL threeDRenderingAvailable = FALSE;
1321 printf("SupportedFeatures:%s %s\n", acceleratedCompositingAvailable ? "AcceleratedCompositing" : "", threeDRenderingAvailable ? "3DRendering" : "");
1325 COMPtr<IWebView> webView(AdoptCOM, createWebViewAndOffscreenWindow(&webViewWindow));
1329 COMPtr<IWebIconDatabase> iconDatabase;
1330 COMPtr<IWebIconDatabase> tmpIconDatabase;
1331 if (FAILED(WebKitCreateInstance(CLSID_WebIconDatabase, 0, IID_IWebIconDatabase, (void**)&tmpIconDatabase)))
1333 if (FAILED(tmpIconDatabase->sharedIconDatabase(&iconDatabase)))
1336 if (FAILED(webView->mainFrame(&frame)))
1340 RetainPtr<CFURLCacheRef> urlCache = sharedCFURLCache();
1341 CFURLCacheRemoveAllCachedResponses(urlCache.get());
1345 _CrtMemState entryToMainMemCheckpoint;
1347 _CrtMemCheckpoint(&entryToMainMemCheckpoint);
1351 startJavaScriptThreads();
1353 if (tests.size() == 1 && !strcmp(tests[0], "-")) {
1354 char filenameBuffer[2048];
1355 printSeparators = true;
1356 while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) {
1357 char* newLineCharacter = strchr(filenameBuffer, '\n');
1358 if (newLineCharacter)
1359 *newLineCharacter = '\0';
1361 if (strlen(filenameBuffer) == 0)
1364 runTest(filenameBuffer);
1367 printSeparators = tests.size() > 1;
1368 for (int i = 0; i < tests.size(); i++)
1373 stopJavaScriptThreads();
1375 delete policyDelegate;
1380 // dump leaks to stderr
1381 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
1382 _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
1383 _CrtMemDumpAllObjectsSince(&entryToMainMemCheckpoint);