diff -r 000000000000 -r 4f2f89ce4247 WebKitTools/DumpRenderTree/win/DumpRenderTree.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKitTools/DumpRenderTree/win/DumpRenderTree.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,1376 @@ +/* + * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DumpRenderTree.h" + +#include "EditingDelegate.h" +#include "FrameLoadDelegate.h" +#include "HistoryDelegate.h" +#include "LayoutTestController.h" +#include "PixelDumpSupport.h" +#include "PolicyDelegate.h" +#include "ResourceLoadDelegate.h" +#include "UIDelegate.h" +#include "WorkQueueItem.h" +#include "WorkQueue.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE(CFNETWORK) +#include +#endif + +#if USE(CFNETWORK) +#include +#endif + +using namespace std; + +#if !defined(NDEBUG) && (!defined(DEBUG_INTERNAL) || defined(DEBUG_ALL)) +const LPWSTR TestPluginDir = L"TestNetscapePlugin_Debug"; +#else +const LPWSTR TestPluginDir = L"TestNetscapePlugin"; +#endif + +static LPCWSTR fontsEnvironmentVariable = L"WEBKIT_TESTFONTS"; +#define USE_MAC_FONTS + +const LPCWSTR kDumpRenderTreeClassName = L"DumpRenderTreeWindow"; + +static bool dumpTree = true; +static bool dumpPixels; +static bool dumpAllPixels; +static bool printSeparators; +static bool leakChecking = false; +static bool threaded = false; +static bool forceComplexText = false; +static bool printSupportedFeatures = false; +static RetainPtr persistentUserStyleSheetLocation; + +volatile bool done; +// This is the topmost frame that is loading, during a given load, or nil when no load is +// in progress. Usually this is the same as the main frame, but not always. In the case +// where a frameset is loaded, and then new content is loaded into one of the child frames, +// that child frame is the "topmost frame that is loading". +IWebFrame* topLoadingFrame; // !nil iff a load is in progress +static COMPtr prevTestBFItem; // current b/f item at the end of the previous test +PolicyDelegate* policyDelegate; +COMPtr sharedFrameLoadDelegate; +COMPtr sharedUIDelegate; +COMPtr sharedEditingDelegate; +COMPtr sharedResourceLoadDelegate; +COMPtr sharedHistoryDelegate; + +IWebFrame* frame; +HWND webViewWindow; + +RefPtr gLayoutTestController; + +UINT_PTR waitToDumpWatchdog = 0; + +void setPersistentUserStyleSheetLocation(CFStringRef url) +{ + persistentUserStyleSheetLocation = url; +} + +bool setAlwaysAcceptCookies(bool alwaysAcceptCookies) +{ +#if USE(CFNETWORK) + COMPtr cookieManager; + if (FAILED(WebKitCreateInstance(CLSID_WebCookieManager, 0, IID_IWebCookieManager, reinterpret_cast(&cookieManager)))) + return false; + CFHTTPCookieStorageRef cookieStorage = 0; + if (FAILED(cookieManager->cookieStorage(&cookieStorage)) || !cookieStorage) + return false; + + WebKitCookieStorageAcceptPolicy cookieAcceptPolicy = alwaysAcceptCookies ? WebKitCookieStorageAcceptPolicyAlways : WebKitCookieStorageAcceptPolicyOnlyFromMainDocumentDomain; + CFHTTPCookieStorageSetCookieAcceptPolicy(cookieStorage, cookieAcceptPolicy); + return true; +#else + // FIXME: Implement! + return false; +#endif +} + +wstring urlSuitableForTestResult(const wstring& url) +{ + if (url.find(L"file://") == wstring::npos) + return url; + + return lastPathComponent(url); +} + +wstring lastPathComponent(const wstring& url) +{ + if (url.empty()) + return url; + + return PathFindFileNameW(url.c_str()); +} + +static string toUTF8(const wchar_t* wideString, size_t length) +{ + int result = WideCharToMultiByte(CP_UTF8, 0, wideString, length + 1, 0, 0, 0, 0); + Vector utf8Vector(result); + result = WideCharToMultiByte(CP_UTF8, 0, wideString, length + 1, utf8Vector.data(), result, 0, 0); + if (!result) + return string(); + + return string(utf8Vector.data(), utf8Vector.size() - 1); +} + +string toUTF8(BSTR bstr) +{ + return toUTF8(bstr, SysStringLen(bstr)); +} + +string toUTF8(const wstring& wideString) +{ + return toUTF8(wideString.c_str(), wideString.length()); +} + +static LRESULT CALLBACK DumpRenderTreeWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_DESTROY: + for (unsigned i = openWindows().size() - 1; i >= 0; --i) { + if (openWindows()[i] == hWnd) { + openWindows().remove(i); + windowToWebViewMap().remove(hWnd); + break; + } + } + return 0; + break; + default: + return DefWindowProc(hWnd, msg, wParam, lParam); + } +} + +static const wstring& exePath() +{ + static wstring path; + static bool initialized; + + if (initialized) + return path; + initialized = true; + + TCHAR buffer[MAX_PATH]; + GetModuleFileName(GetModuleHandle(0), buffer, ARRAYSIZE(buffer)); + path = buffer; + int lastSlash = path.rfind('\\'); + if (lastSlash != -1 && lastSlash + 1 < path.length()) + path = path.substr(0, lastSlash + 1); + + return path; +} + +static const wstring& fontsPath() +{ + static wstring path; + static bool initialized; + + if (initialized) + return path; + initialized = true; + + DWORD size = GetEnvironmentVariable(fontsEnvironmentVariable, 0, 0); + Vector buffer(size); + if (GetEnvironmentVariable(fontsEnvironmentVariable, buffer.data(), buffer.size())) { + path = buffer.data(); + if (path[path.length() - 1] != '\\') + path.append(L"\\"); + return path; + } + + path = exePath() + TEXT("DumpRenderTree.resources\\"); + return path; +} + +static void addQTDirToPATH() +{ + static LPCWSTR pathEnvironmentVariable = L"PATH"; + static LPCWSTR quickTimeKeyName = L"Software\\Apple Computer, Inc.\\QuickTime"; + static LPCWSTR quickTimeSysDir = L"QTSysDir"; + static bool initialized; + + if (initialized) + return; + initialized = true; + + // Get the QuickTime dll directory from the registry. The key can be in either HKLM or HKCU. + WCHAR qtPath[MAX_PATH]; + DWORD qtPathBufferLen = sizeof(qtPath); + DWORD keyType; + HRESULT result = SHGetValue(HKEY_LOCAL_MACHINE, quickTimeKeyName, quickTimeSysDir, &keyType, (LPVOID)qtPath, &qtPathBufferLen); + if (result != ERROR_SUCCESS || !qtPathBufferLen || keyType != REG_SZ) { + qtPathBufferLen = sizeof(qtPath); + result = SHGetValue(HKEY_CURRENT_USER, quickTimeKeyName, quickTimeSysDir, &keyType, (LPVOID)qtPath, &qtPathBufferLen); + if (result != ERROR_SUCCESS || !qtPathBufferLen || keyType != REG_SZ) + return; + } + + // Read the current PATH. + DWORD pathSize = GetEnvironmentVariableW(pathEnvironmentVariable, 0, 0); + Vector oldPath(pathSize); + if (!GetEnvironmentVariableW(pathEnvironmentVariable, oldPath.data(), oldPath.size())) + return; + + // And add the QuickTime dll. + wstring newPath; + newPath.append(qtPath); + newPath.append(L";"); + newPath.append(oldPath.data(), oldPath.size()); + SetEnvironmentVariableW(pathEnvironmentVariable, newPath.data()); +} + +#ifdef DEBUG_ALL +#define WEBKITDLL TEXT("WebKit_debug.dll") +#else +#define WEBKITDLL TEXT("WebKit.dll") +#endif + +static void initialize() +{ + if (HMODULE webKitModule = LoadLibrary(WEBKITDLL)) + if (FARPROC dllRegisterServer = GetProcAddress(webKitModule, "DllRegisterServer")) + dllRegisterServer(); + + // Init COM + OleInitialize(0); + + static LPCTSTR fontsToInstall[] = { + TEXT("AHEM____.ttf"), + TEXT("Apple Chancery.ttf"), + TEXT("Courier Bold.ttf"), + TEXT("Courier.ttf"), + TEXT("Helvetica Bold Oblique.ttf"), + TEXT("Helvetica Bold.ttf"), + TEXT("Helvetica Oblique.ttf"), + TEXT("Helvetica.ttf"), + TEXT("Helvetica Neue Bold Italic.ttf"), + TEXT("Helvetica Neue Bold.ttf"), + TEXT("Helvetica Neue Condensed Black.ttf"), + TEXT("Helvetica Neue Condensed Bold.ttf"), + TEXT("Helvetica Neue Italic.ttf"), + TEXT("Helvetica Neue Light Italic.ttf"), + TEXT("Helvetica Neue Light.ttf"), + TEXT("Helvetica Neue UltraLight Italic.ttf"), + TEXT("Helvetica Neue UltraLight.ttf"), + TEXT("Helvetica Neue.ttf"), + TEXT("Lucida Grande.ttf"), + TEXT("Lucida Grande Bold.ttf"), + TEXT("Monaco.ttf"), + TEXT("Papyrus.ttf"), + TEXT("Times Bold Italic.ttf"), + TEXT("Times Bold.ttf"), + TEXT("Times Italic.ttf"), + TEXT("Times Roman.ttf"), + TEXT("WebKit Layout Tests 2.ttf"), + TEXT("WebKit Layout Tests.ttf"), + TEXT("WebKitWeightWatcher100.ttf"), + TEXT("WebKitWeightWatcher200.ttf"), + TEXT("WebKitWeightWatcher300.ttf"), + TEXT("WebKitWeightWatcher400.ttf"), + TEXT("WebKitWeightWatcher500.ttf"), + TEXT("WebKitWeightWatcher600.ttf"), + TEXT("WebKitWeightWatcher700.ttf"), + TEXT("WebKitWeightWatcher800.ttf"), + TEXT("WebKitWeightWatcher900.ttf") + }; + + wstring resourcesPath = fontsPath(); + + COMPtr textRenderer; + if (SUCCEEDED(WebKitCreateInstance(CLSID_WebTextRenderer, 0, IID_IWebTextRenderer, (void**)&textRenderer))) + for (int i = 0; i < ARRAYSIZE(fontsToInstall); ++i) + textRenderer->registerPrivateFont(wstring(resourcesPath + fontsToInstall[i]).c_str()); + + // Add the QuickTime dll directory to PATH or QT 7.6 will fail to initialize on systems + // linked with older versions of qtmlclientlib.dll. + addQTDirToPATH(); + + // Register a host window + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = DumpRenderTreeWndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(0); + wcex.hIcon = 0; + wcex.hCursor = LoadCursor(0, IDC_ARROW); + wcex.hbrBackground = 0; + wcex.lpszMenuName = 0; + wcex.lpszClassName = kDumpRenderTreeClassName; + wcex.hIconSm = 0; + + RegisterClassEx(&wcex); +} + +void displayWebView() +{ + ::InvalidateRect(webViewWindow, 0, TRUE); + ::SendMessage(webViewWindow, WM_PAINT, 0, 0); +} + +void dumpFrameScrollPosition(IWebFrame* frame) +{ + if (!frame) + return; + + COMPtr framePrivate; + if (FAILED(frame->QueryInterface(&framePrivate))) + return; + + SIZE scrollPosition; + if (FAILED(framePrivate->scrollOffset(&scrollPosition))) + return; + + if (abs(scrollPosition.cx) > 0.00000001 || abs(scrollPosition.cy) > 0.00000001) { + COMPtr parent; + if (FAILED(frame->parentFrame(&parent))) + return; + if (parent) { + BSTR name; + if (FAILED(frame->name(&name))) + return; + printf("frame '%S' ", name ? name : L""); + SysFreeString(name); + } + printf("scrolled to %.f,%.f\n", (double)scrollPosition.cx, (double)scrollPosition.cy); + } + + if (::gLayoutTestController->dumpChildFrameScrollPositions()) { + COMPtr enumKids; + if (FAILED(frame->childFrames(&enumKids))) + return; + VARIANT var; + VariantInit(&var); + while (enumKids->Next(1, &var, 0) == S_OK) { + ASSERT(V_VT(&var) == VT_UNKNOWN); + COMPtr framePtr; + V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr); + dumpFrameScrollPosition(framePtr.get()); + VariantClear(&var); + } + } +} + +static wstring dumpFramesAsText(IWebFrame* frame) +{ + if (!frame) + return L""; + + COMPtr document; + if (FAILED(frame->DOMDocument(&document))) + return L""; + + COMPtr documentElement; + if (FAILED(document->documentElement(&documentElement))) + return L""; + + wstring result; + + // Add header for all but the main frame. + COMPtr parent; + if (FAILED(frame->parentFrame(&parent))) + return L""; + if (parent) { + BSTR name = L""; + if (FAILED(frame->name(&name))) + return L""; + + result.append(L"\n--------\nFrame: '"); + result.append(name ? name : L"", SysStringLen(name)); + result.append(L"'\n--------\n"); + + SysFreeString(name); + } + + BSTR innerText = 0; + COMPtr docPrivate; + if (SUCCEEDED(documentElement->QueryInterface(&docPrivate))) + docPrivate->innerText(&innerText); + + result.append(innerText ? innerText : L"", SysStringLen(innerText)); + result.append(L"\n"); + + SysFreeString(innerText); + + if (::gLayoutTestController->dumpChildFramesAsText()) { + COMPtr enumKids; + if (FAILED(frame->childFrames(&enumKids))) + return L""; + VARIANT var; + VariantInit(&var); + while (enumKids->Next(1, &var, 0) == S_OK) { + ASSERT(V_VT(&var) == VT_UNKNOWN); + COMPtr framePtr; + V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr); + result.append(dumpFramesAsText(framePtr.get())); + VariantClear(&var); + } + } + + return result; +} + +static int compareHistoryItems(const void* item1, const void* item2) +{ + COMPtr itemA; + if (FAILED((*(COMPtr*)item1)->QueryInterface(&itemA))) + return 0; + + COMPtr itemB; + if (FAILED((*(COMPtr*)item2)->QueryInterface(&itemB))) + return 0; + + BSTR targetA; + if (FAILED(itemA->target(&targetA))) + return 0; + + BSTR targetB; + if (FAILED(itemB->target(&targetB))) { + SysFreeString(targetA); + return 0; + } + + int result = wcsicmp(wstring(targetA, SysStringLen(targetA)).c_str(), wstring(targetB, SysStringLen(targetB)).c_str()); + SysFreeString(targetA); + SysFreeString(targetB); + return result; +} + +static void dumpHistoryItem(IWebHistoryItem* item, int indent, bool current) +{ + assert(item); + + int start = 0; + if (current) { + printf("curr->"); + start = 6; + } + for (int i = start; i < indent; i++) + putchar(' '); + + BSTR url; + if (FAILED(item->URLString(&url))) + return; + + if (wcsstr(url, L"file:/") == url) { + static wchar_t* layoutTestsString = L"/LayoutTests/"; + static wchar_t* fileTestString = L"(file test):"; + + wchar_t* result = wcsstr(url, layoutTestsString); + if (result == NULL) + return; + wchar_t* start = result + wcslen(layoutTestsString); + + BSTR newURL = SysAllocStringLen(NULL, SysStringLen(url)); + wcscpy(newURL, fileTestString); + wcscpy(newURL + wcslen(fileTestString), start); + + SysFreeString(url); + url = newURL; + } + + printf("%S", url ? url : L""); + SysFreeString(url); + + COMPtr itemPrivate; + if (FAILED(item->QueryInterface(&itemPrivate))) + return; + + BSTR target; + if (FAILED(itemPrivate->target(&target))) + return; + if (SysStringLen(target)) + printf(" (in frame \"%S\")", target); + SysFreeString(target); + BOOL isTargetItem = FALSE; + if (FAILED(itemPrivate->isTargetItem(&isTargetItem))) + return; + if (isTargetItem) + printf(" **nav target**"); + putchar('\n'); + + unsigned kidsCount; + SAFEARRAY* arrPtr; + if (FAILED(itemPrivate->children(&kidsCount, &arrPtr)) || !kidsCount) + return; + + Vector > kidsVector; + + LONG lowerBound; + if (FAILED(::SafeArrayGetLBound(arrPtr, 1, &lowerBound))) + goto exit; + + LONG upperBound; + if (FAILED(::SafeArrayGetUBound(arrPtr, 1, &upperBound))) + goto exit; + + LONG length = upperBound - lowerBound + 1; + if (!length) + goto exit; + ASSERT(length == kidsCount); + + IUnknown** safeArrayData; + if (FAILED(::SafeArrayAccessData(arrPtr, (void**)&safeArrayData))) + goto exit; + + for (int i = 0; i < length; ++i) + kidsVector.append(safeArrayData[i]); + ::SafeArrayUnaccessData(arrPtr); + + // must sort to eliminate arbitrary result ordering which defeats reproducible testing + qsort(kidsVector.data(), kidsCount, sizeof(kidsVector[0]), compareHistoryItems); + + for (unsigned i = 0; i < kidsCount; ++i) { + COMPtr item; + kidsVector[i]->QueryInterface(&item); + dumpHistoryItem(item.get(), indent + 4, false); + } + +exit: + if (arrPtr && SUCCEEDED(::SafeArrayUnlock(arrPtr))) + ::SafeArrayDestroy(arrPtr); +} + +static void dumpBackForwardList(IWebView* webView) +{ + ASSERT(webView); + + printf("\n============== Back Forward List ==============\n"); + + COMPtr bfList; + if (FAILED(webView->backForwardList(&bfList))) + return; + + // Print out all items in the list after prevTestBFItem, which was from the previous test + // Gather items from the end of the list, the print them out from oldest to newest + + Vector > itemsToPrint; + + int forwardListCount; + if (FAILED(bfList->forwardListCount(&forwardListCount))) + return; + + for (int i = forwardListCount; i > 0; --i) { + COMPtr item; + if (FAILED(bfList->itemAtIndex(i, &item))) + return; + // something is wrong if the item from the last test is in the forward part of the b/f list + assert(item != prevTestBFItem); + COMPtr itemUnknown; + item->QueryInterface(&itemUnknown); + itemsToPrint.append(itemUnknown); + } + + COMPtr currentItem; + if (FAILED(bfList->currentItem(¤tItem))) + return; + + assert(currentItem != prevTestBFItem); + COMPtr currentItemUnknown; + currentItem->QueryInterface(¤tItemUnknown); + itemsToPrint.append(currentItemUnknown); + int currentItemIndex = itemsToPrint.size() - 1; + + int backListCount; + if (FAILED(bfList->backListCount(&backListCount))) + return; + + for (int i = -1; i >= -backListCount; --i) { + COMPtr item; + if (FAILED(bfList->itemAtIndex(i, &item))) + return; + if (item == prevTestBFItem) + break; + COMPtr itemUnknown; + item->QueryInterface(&itemUnknown); + itemsToPrint.append(itemUnknown); + } + + for (int i = itemsToPrint.size() - 1; i >= 0; --i) { + COMPtr historyItemToPrint; + itemsToPrint[i]->QueryInterface(&historyItemToPrint); + dumpHistoryItem(historyItemToPrint.get(), 8, i == currentItemIndex); + } + + printf("===============================================\n"); +} + +static void dumpBackForwardListForAllWindows() +{ + unsigned count = openWindows().size(); + for (unsigned i = 0; i < count; i++) { + HWND window = openWindows()[i]; + IWebView* webView = windowToWebViewMap().get(window).get(); + dumpBackForwardList(webView); + } +} + +static void invalidateAnyPreviousWaitToDumpWatchdog() +{ + if (!waitToDumpWatchdog) + return; + + KillTimer(0, waitToDumpWatchdog); + waitToDumpWatchdog = 0; +} + +void dump() +{ + invalidateAnyPreviousWaitToDumpWatchdog(); + + COMPtr dataSource; + if (SUCCEEDED(frame->dataSource(&dataSource))) { + COMPtr response; + if (SUCCEEDED(dataSource->response(&response)) && response) { + BSTR mimeType; + if (SUCCEEDED(response->MIMEType(&mimeType))) + ::gLayoutTestController->setDumpAsText(::gLayoutTestController->dumpAsText() | !_tcscmp(mimeType, TEXT("text/plain"))); + SysFreeString(mimeType); + } + } + + BSTR resultString = 0; + + if (dumpTree) { + if (::gLayoutTestController->dumpAsText()) { + ::InvalidateRect(webViewWindow, 0, TRUE); + ::SendMessage(webViewWindow, WM_PAINT, 0, 0); + wstring result = dumpFramesAsText(frame); + resultString = SysAllocStringLen(result.data(), result.size()); + } else { + bool isSVGW3CTest = (gLayoutTestController->testPathOrURL().find("svg\\W3C-SVG-1.1") != string::npos); + unsigned width; + unsigned height; + if (isSVGW3CTest) { + width = 480; + height = 360; + } else { + width = LayoutTestController::maxViewWidth; + height = LayoutTestController::maxViewHeight; + } + + ::SetWindowPos(webViewWindow, 0, 0, 0, width, height, SWP_NOMOVE); + ::InvalidateRect(webViewWindow, 0, TRUE); + ::SendMessage(webViewWindow, WM_PAINT, 0, 0); + + COMPtr framePrivate; + if (FAILED(frame->QueryInterface(&framePrivate))) + goto fail; + framePrivate->renderTreeAsExternalRepresentation(gLayoutTestController->isPrinting(), &resultString); + } + + if (!resultString) + printf("ERROR: nil result from %s", ::gLayoutTestController->dumpAsText() ? "IDOMElement::innerText" : "IFrameViewPrivate::renderTreeAsExternalRepresentation"); + else { + unsigned stringLength = SysStringLen(resultString); + int bufferSize = ::WideCharToMultiByte(CP_UTF8, 0, resultString, stringLength, 0, 0, 0, 0); + char* buffer = (char*)malloc(bufferSize + 1); + ::WideCharToMultiByte(CP_UTF8, 0, resultString, stringLength, buffer, bufferSize + 1, 0, 0); + fwrite(buffer, 1, bufferSize, stdout); + free(buffer); + if (!::gLayoutTestController->dumpAsText()) + dumpFrameScrollPosition(frame); + } + if (::gLayoutTestController->dumpBackForwardList()) + dumpBackForwardListForAllWindows(); + } + + if (printSeparators) { + puts("#EOF"); // terminate the content block + fputs("#EOF\n", stderr); + fflush(stdout); + fflush(stderr); + } + + if (dumpPixels + && gLayoutTestController->generatePixelResults() + && !gLayoutTestController->dumpDOMAsWebArchive() + && !gLayoutTestController->dumpSourceAsWebArchive()) + dumpWebViewAsPixelsAndCompareWithExpected(gLayoutTestController->expectedPixelHash()); + + printf("#EOF\n"); // terminate the (possibly empty) pixels block + fflush(stdout); + +fail: + SysFreeString(resultString); + // This will exit from our message loop. + PostQuitMessage(0); + done = true; +} + +static bool shouldLogFrameLoadDelegates(const char* pathOrURL) +{ + return strstr(pathOrURL, "/loading/") || strstr(pathOrURL, "\\loading\\"); +} + +static bool shouldLogHistoryDelegates(const char* pathOrURL) +{ + return strstr(pathOrURL, "/globalhistory/") || strstr(pathOrURL, "\\globalhistory\\"); +} + +static bool shouldOpenWebInspector(const char* pathOrURL) +{ + return strstr(pathOrURL, "/inspector/") || strstr(pathOrURL, "\\inspector\\"); +} + +static bool shouldEnableDeveloperExtras(const char* pathOrURL) +{ + return shouldOpenWebInspector(pathOrURL) || strstr(pathOrURL, "/inspector-enabled/") || strstr(pathOrURL, "\\inspector-enabled\\"); +} + +static void resetDefaultsToConsistentValues(IWebPreferences* preferences) +{ +#ifdef USE_MAC_FONTS + static BSTR standardFamily = SysAllocString(TEXT("Times")); + static BSTR fixedFamily = SysAllocString(TEXT("Courier")); + static BSTR sansSerifFamily = SysAllocString(TEXT("Helvetica")); + static BSTR cursiveFamily = SysAllocString(TEXT("Apple Chancery")); + static BSTR fantasyFamily = SysAllocString(TEXT("Papyrus")); +#else + static BSTR standardFamily = SysAllocString(TEXT("Times New Roman")); + static BSTR fixedFamily = SysAllocString(TEXT("Courier New")); + static BSTR sansSerifFamily = SysAllocString(TEXT("Arial")); + static BSTR cursiveFamily = SysAllocString(TEXT("Comic Sans MS")); // Not actually cursive, but it's what IE and Firefox use. + static BSTR fantasyFamily = SysAllocString(TEXT("Times New Roman")); +#endif + + preferences->setStandardFontFamily(standardFamily); + preferences->setFixedFontFamily(fixedFamily); + preferences->setSerifFontFamily(standardFamily); + preferences->setSansSerifFontFamily(sansSerifFamily); + preferences->setCursiveFontFamily(cursiveFamily); + preferences->setFantasyFontFamily(fantasyFamily); + + preferences->setAutosaves(FALSE); + preferences->setDefaultFontSize(16); + preferences->setDefaultFixedFontSize(13); + preferences->setMinimumFontSize(1); + preferences->setJavaEnabled(FALSE); + preferences->setPlugInsEnabled(TRUE); + preferences->setDOMPasteAllowed(TRUE); + preferences->setEditableLinkBehavior(WebKitEditableLinkOnlyLiveWithShiftKey); + preferences->setFontSmoothing(FontSmoothingTypeStandard); + preferences->setUsesPageCache(FALSE); + preferences->setPrivateBrowsingEnabled(FALSE); + preferences->setJavaScriptCanOpenWindowsAutomatically(TRUE); + preferences->setJavaScriptEnabled(TRUE); + preferences->setTabsToLinks(FALSE); + preferences->setShouldPrintBackgrounds(TRUE); + preferences->setLoadsImagesAutomatically(TRUE); + preferences->setEditingBehavior(WebKitEditingWinBehavior); + + if (persistentUserStyleSheetLocation) { + Vector urlCharacters(CFStringGetLength(persistentUserStyleSheetLocation.get())); + CFStringGetCharacters(persistentUserStyleSheetLocation.get(), CFRangeMake(0, CFStringGetLength(persistentUserStyleSheetLocation.get())), (UniChar *)urlCharacters.data()); + BSTR url = SysAllocStringLen(urlCharacters.data(), urlCharacters.size()); + preferences->setUserStyleSheetLocation(url); + SysFreeString(url); + preferences->setUserStyleSheetEnabled(TRUE); + } else + preferences->setUserStyleSheetEnabled(FALSE); + + COMPtr prefsPrivate(Query, preferences); + if (prefsPrivate) { + prefsPrivate->setAllowUniversalAccessFromFileURLs(TRUE); + prefsPrivate->setAllowFileAccessFromFileURLs(TRUE); + prefsPrivate->setAuthorAndUserStylesEnabled(TRUE); + prefsPrivate->setDeveloperExtrasEnabled(FALSE); + prefsPrivate->setExperimentalNotificationsEnabled(TRUE); + prefsPrivate->setShouldPaintNativeControls(FALSE); // FIXME - need to make DRT pass with Windows native controls + prefsPrivate->setJavaScriptCanAccessClipboard(TRUE); + prefsPrivate->setXSSAuditorEnabled(FALSE); + prefsPrivate->setFrameFlatteningEnabled(FALSE); + prefsPrivate->setOfflineWebApplicationCacheEnabled(TRUE); + } + setAlwaysAcceptCookies(false); + + setlocale(LC_ALL, ""); +} + +static void resetWebViewToConsistentStateBeforeTesting() +{ + COMPtr webView; + if (FAILED(frame->webView(&webView))) + return; + + webView->setPolicyDelegate(0); + policyDelegate->setPermissive(false); + policyDelegate->setControllerToNotifyDone(0); + + COMPtr webIBActions(Query, webView); + if (webIBActions) { + webIBActions->makeTextStandardSize(0); + webIBActions->resetPageZoom(0); + } + + + COMPtr preferences; + if (SUCCEEDED(webView->preferences(&preferences))) + resetDefaultsToConsistentValues(preferences.get()); + + COMPtr viewEditing; + if (SUCCEEDED(webView->QueryInterface(&viewEditing))) + viewEditing->setSmartInsertDeleteEnabled(TRUE); + + COMPtr webViewPrivate(Query, webView); + if (!webViewPrivate) + return; + + COMPtr inspector; + if (SUCCEEDED(webViewPrivate->inspector(&inspector))) + inspector->setJavaScriptProfilingEnabled(FALSE); + + HWND viewWindow; + if (SUCCEEDED(webViewPrivate->viewWindow(reinterpret_cast(&viewWindow))) && viewWindow) + SetFocus(viewWindow); + + webViewPrivate->clearMainFrameName(); + webViewPrivate->resetOriginAccessWhitelists(); + + BSTR groupName; + if (SUCCEEDED(webView->groupName(&groupName))) { + webViewPrivate->removeAllUserContentFromGroup(groupName); + SysFreeString(groupName); + } + + sharedUIDelegate->resetUndoManager(); + + sharedFrameLoadDelegate->resetToConsistentState(); +} + +static void runTest(const string& testPathOrURL) +{ + static BSTR methodBStr = SysAllocString(TEXT("GET")); + + // Look for "'" as a separator between the path or URL, and the pixel dump hash that follows. + string pathOrURL(testPathOrURL); + string expectedPixelHash; + + size_t separatorPos = pathOrURL.find("'"); + if (separatorPos != string::npos) { + pathOrURL = string(testPathOrURL, 0, separatorPos); + expectedPixelHash = string(testPathOrURL, separatorPos + 1); + } + + BSTR urlBStr; + + CFStringRef str = CFStringCreateWithCString(0, pathOrURL.c_str(), kCFStringEncodingWindowsLatin1); + CFURLRef url = CFURLCreateWithString(0, str, 0); + + if (!url) + url = CFURLCreateWithFileSystemPath(0, str, kCFURLWindowsPathStyle, false); + + CFRelease(str); + + str = CFURLGetString(url); + + CFIndex length = CFStringGetLength(str); + UniChar* buffer = new UniChar[length]; + + CFStringGetCharacters(str, CFRangeMake(0, length), buffer); + urlBStr = SysAllocStringLen((OLECHAR*)buffer, length); + delete[] buffer; + + CFRelease(url); + + ::gLayoutTestController = LayoutTestController::create(pathOrURL, expectedPixelHash); + done = false; + topLoadingFrame = 0; + + gLayoutTestController->setIconDatabaseEnabled(false); + + if (shouldLogFrameLoadDelegates(pathOrURL.c_str())) + gLayoutTestController->setDumpFrameLoadCallbacks(true); + + COMPtr webView; + if (SUCCEEDED(frame->webView(&webView))) { + COMPtr viewPrivate; + if (SUCCEEDED(webView->QueryInterface(&viewPrivate))) { + if (shouldLogHistoryDelegates(pathOrURL.c_str())) { + gLayoutTestController->setDumpHistoryDelegateCallbacks(true); + viewPrivate->setHistoryDelegate(sharedHistoryDelegate.get()); + } else + viewPrivate->setHistoryDelegate(0); + } + } + COMPtr history; + if (SUCCEEDED(WebKitCreateInstance(CLSID_WebHistory, 0, __uuidof(history), reinterpret_cast(&history)))) + history->setOptionalSharedHistory(0); + + resetWebViewToConsistentStateBeforeTesting(); + + if (shouldEnableDeveloperExtras(pathOrURL.c_str())) { + gLayoutTestController->setDeveloperExtrasEnabled(true); + if (shouldOpenWebInspector(pathOrURL.c_str())) + gLayoutTestController->showWebInspector(); + } + + prevTestBFItem = 0; + if (webView) { + COMPtr bfList; + if (SUCCEEDED(webView->backForwardList(&bfList))) + bfList->currentItem(&prevTestBFItem); + } + + WorkQueue::shared()->clear(); + WorkQueue::shared()->setFrozen(false); + + HWND hostWindow; + webView->hostWindow(reinterpret_cast(&hostWindow)); + + COMPtr request; + HRESULT hr = WebKitCreateInstance(CLSID_WebMutableURLRequest, 0, IID_IWebMutableURLRequest, (void**)&request); + if (FAILED(hr)) + goto exit; + + request->initWithURL(urlBStr, WebURLRequestUseProtocolCachePolicy, 60); + + request->setHTTPMethod(methodBStr); + frame->loadRequest(request.get()); + + MSG msg; + while (GetMessage(&msg, 0, 0, 0)) { + // We get spurious WM_MOUSELEAVE events which make event handling machinery think that mouse button + // is released during dragging (see e.g. fast\dynamic\layer-hit-test-crash.html). + // Mouse can never leave WebView during normal DumpRenderTree operation, so we just ignore all such events. + if (msg.message == WM_MOUSELEAVE) + continue; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (shouldEnableDeveloperExtras(pathOrURL.c_str())) { + gLayoutTestController->closeWebInspector(); + gLayoutTestController->setDeveloperExtrasEnabled(false); + } + + resetWebViewToConsistentStateBeforeTesting(); + + frame->stopLoading(); + + if (::gLayoutTestController->closeRemainingWindowsWhenComplete()) { + Vector windows = openWindows(); + unsigned size = windows.size(); + for (unsigned i = 0; i < size; i++) { + HWND window = windows[i]; + + // Don't try to close the main window + if (window == hostWindow) + continue; + + DestroyWindow(window); + } + } + +exit: + SysFreeString(urlBStr); + ::gLayoutTestController.clear(); + + return; +} + +static Boolean pthreadEqualCallback(const void* value1, const void* value2) +{ + return (Boolean)pthread_equal(*(pthread_t*)value1, *(pthread_t*)value2); +} + +static CFDictionaryKeyCallBacks pthreadKeyCallbacks = { 0, 0, 0, 0, pthreadEqualCallback, 0 }; + +static pthread_mutex_t javaScriptThreadsMutex = PTHREAD_MUTEX_INITIALIZER; +static bool javaScriptThreadsShouldTerminate; + +static const int javaScriptThreadsCount = 4; +static CFMutableDictionaryRef javaScriptThreads() +{ + assert(pthread_mutex_trylock(&javaScriptThreadsMutex) == EBUSY); + static CFMutableDictionaryRef staticJavaScriptThreads; + if (!staticJavaScriptThreads) + staticJavaScriptThreads = CFDictionaryCreateMutable(0, 0, &pthreadKeyCallbacks, 0); + return staticJavaScriptThreads; +} + +// Loops forever, running a script and randomly respawning, until +// javaScriptThreadsShouldTerminate becomes true. +void* runJavaScriptThread(void* arg) +{ + const char* const script = + " \ + var array = []; \ + for (var i = 0; i < 10; i++) { \ + array.push(String(i)); \ + } \ + "; + + while (true) { + JSGlobalContextRef ctx = JSGlobalContextCreate(0); + JSStringRef scriptRef = JSStringCreateWithUTF8CString(script); + + JSValueRef exception = 0; + JSEvaluateScript(ctx, scriptRef, 0, 0, 1, &exception); + assert(!exception); + + JSGlobalContextRelease(ctx); + JSStringRelease(scriptRef); + + JSGarbageCollect(ctx); + + pthread_mutex_lock(&javaScriptThreadsMutex); + + // Check for cancellation. + if (javaScriptThreadsShouldTerminate) { + pthread_mutex_unlock(&javaScriptThreadsMutex); + return 0; + } + + // Respawn probabilistically. + if (rand() % 5 == 0) { + pthread_t pthread; + pthread_create(&pthread, 0, &runJavaScriptThread, 0); + pthread_detach(pthread); + + pthread_t self = pthread_self(); + CFDictionaryRemoveValue(javaScriptThreads(), self.p); + CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0); + + pthread_mutex_unlock(&javaScriptThreadsMutex); + return 0; + } + + pthread_mutex_unlock(&javaScriptThreadsMutex); + } +} + +static void startJavaScriptThreads(void) +{ + pthread_mutex_lock(&javaScriptThreadsMutex); + + for (int i = 0; i < javaScriptThreadsCount; i++) { + pthread_t pthread; + pthread_create(&pthread, 0, &runJavaScriptThread, 0); + pthread_detach(pthread); + CFDictionaryAddValue(javaScriptThreads(), pthread.p, 0); + } + + pthread_mutex_unlock(&javaScriptThreadsMutex); +} + +static void stopJavaScriptThreads(void) +{ + pthread_mutex_lock(&javaScriptThreadsMutex); + + javaScriptThreadsShouldTerminate = true; + + pthread_t* pthreads[javaScriptThreadsCount] = {0}; + int threadDictCount = CFDictionaryGetCount(javaScriptThreads()); + assert(threadDictCount == javaScriptThreadsCount); + CFDictionaryGetKeysAndValues(javaScriptThreads(), (const void**)pthreads, 0); + + pthread_mutex_unlock(&javaScriptThreadsMutex); + + for (int i = 0; i < javaScriptThreadsCount; i++) { + pthread_t* pthread = pthreads[i]; + pthread_join(*pthread, 0); + free(pthread); + } +} + +Vector& openWindows() +{ + static Vector vector; + return vector; +} + +WindowToWebViewMap& windowToWebViewMap() +{ + static WindowToWebViewMap map; + return map; +} + +IWebView* createWebViewAndOffscreenWindow(HWND* webViewWindow) +{ + unsigned maxViewWidth = LayoutTestController::maxViewWidth; + unsigned maxViewHeight = LayoutTestController::maxViewHeight; + HWND hostWindow = CreateWindowEx(WS_EX_TOOLWINDOW, kDumpRenderTreeClassName, TEXT("DumpRenderTree"), WS_POPUP, + -maxViewWidth, -maxViewHeight, maxViewWidth, maxViewHeight, 0, 0, GetModuleHandle(0), 0); + + IWebView* webView; + + HRESULT hr = WebKitCreateInstance(CLSID_WebView, 0, IID_IWebView, (void**)&webView); + if (FAILED(hr)) { + fprintf(stderr, "Failed to create CLSID_WebView instance, error 0x%x\n", hr); + return 0; + } + + if (FAILED(webView->setHostWindow((OLE_HANDLE)(ULONG64)hostWindow))) + return 0; + + RECT clientRect; + clientRect.bottom = clientRect.left = clientRect.top = clientRect.right = 0; + BSTR groupName = SysAllocString(L"org.webkit.DumpRenderTree"); + bool failed = FAILED(webView->initWithFrame(clientRect, 0, groupName)); + SysFreeString(groupName); + if (failed) + return 0; + + COMPtr viewPrivate; + if (FAILED(webView->QueryInterface(&viewPrivate))) + return 0; + + viewPrivate->setShouldApplyMacFontAscentHack(TRUE); + viewPrivate->setAlwaysUsesComplexTextCodePath(forceComplexText); + + BSTR pluginPath = SysAllocStringLen(0, exePath().length() + _tcslen(TestPluginDir)); + _tcscpy(pluginPath, exePath().c_str()); + _tcscat(pluginPath, TestPluginDir); + failed = FAILED(viewPrivate->addAdditionalPluginDirectory(pluginPath)); + SysFreeString(pluginPath); + if (failed) + return 0; + + HWND viewWindow; + if (FAILED(viewPrivate->viewWindow(reinterpret_cast(&viewWindow)))) + return 0; + if (webViewWindow) + *webViewWindow = viewWindow; + + SetWindowPos(viewWindow, 0, 0, 0, maxViewWidth, maxViewHeight, 0); + ShowWindow(hostWindow, SW_SHOW); + + if (FAILED(webView->setFrameLoadDelegate(sharedFrameLoadDelegate.get()))) + return 0; + + if (FAILED(viewPrivate->setFrameLoadDelegatePrivate(sharedFrameLoadDelegate.get()))) + return 0; + + if (FAILED(webView->setUIDelegate(sharedUIDelegate.get()))) + return 0; + + COMPtr viewEditing; + if (FAILED(webView->QueryInterface(&viewEditing))) + return 0; + + if (FAILED(viewEditing->setEditingDelegate(sharedEditingDelegate.get()))) + return 0; + + if (FAILED(webView->setResourceLoadDelegate(sharedResourceLoadDelegate.get()))) + return 0; + + openWindows().append(hostWindow); + windowToWebViewMap().set(hostWindow, webView); + return webView; +} + +#if USE(CFNETWORK) +RetainPtr sharedCFURLCache() +{ +#ifndef DEBUG_ALL + HMODULE module = GetModuleHandle(TEXT("CFNetwork.dll")); +#else + HMODULE module = GetModuleHandle(TEXT("CFNetwork_debug.dll")); +#endif + if (!module) + return 0; + + typedef CFURLCacheRef (*CFURLCacheCopySharedURLCacheProcPtr)(void); + if (CFURLCacheCopySharedURLCacheProcPtr copyCache = reinterpret_cast(GetProcAddress(module, "CFURLCacheCopySharedURLCache"))) + return RetainPtr(AdoptCF, copyCache()); + + typedef CFURLCacheRef (*CFURLCacheSharedURLCacheProcPtr)(void); + if (CFURLCacheSharedURLCacheProcPtr sharedCache = reinterpret_cast(GetProcAddress(module, "CFURLCacheSharedURLCache"))) + return sharedCache(); + + return 0; +} +#endif + +int main(int argc, char* argv[]) +{ + leakChecking = false; + + _setmode(1, _O_BINARY); + _setmode(2, _O_BINARY); + + initialize(); + + Vector tests; + + for (int i = 1; i < argc; ++i) { + if (!stricmp(argv[i], "--threaded")) { + threaded = true; + continue; + } + + if (!stricmp(argv[i], "--dump-all-pixels")) { + dumpAllPixels = true; + continue; + } + + if (!stricmp(argv[i], "--pixel-tests")) { + dumpPixels = true; + continue; + } + + if (!stricmp(argv[i], "--complex-text")) { + forceComplexText = true; + continue; + } + + if (!stricmp(argv[i], "--print-supported-features")) { + printSupportedFeatures = true; + continue; + } + + tests.append(argv[i]); + } + + policyDelegate = new PolicyDelegate(); + sharedFrameLoadDelegate.adoptRef(new FrameLoadDelegate); + sharedUIDelegate.adoptRef(new UIDelegate); + sharedEditingDelegate.adoptRef(new EditingDelegate); + sharedResourceLoadDelegate.adoptRef(new ResourceLoadDelegate); + sharedHistoryDelegate.adoptRef(new HistoryDelegate); + + // FIXME - need to make DRT pass with Windows native controls + COMPtr tmpPreferences; + if (FAILED(WebKitCreateInstance(CLSID_WebPreferences, 0, IID_IWebPreferences, reinterpret_cast(&tmpPreferences)))) + return -1; + COMPtr standardPreferences; + if (FAILED(tmpPreferences->standardPreferences(&standardPreferences))) + return -1; + COMPtr standardPreferencesPrivate; + if (FAILED(standardPreferences->QueryInterface(&standardPreferencesPrivate))) + return -1; + standardPreferencesPrivate->setShouldPaintNativeControls(FALSE); + standardPreferences->setJavaScriptEnabled(TRUE); + standardPreferences->setDefaultFontSize(16); + standardPreferences->setAcceleratedCompositingEnabled(true); + + if (printSupportedFeatures) { + BOOL acceleratedCompositingAvailable; + standardPreferences->acceleratedCompositingEnabled(&acceleratedCompositingAvailable); + BOOL threeDRenderingAvailable = +#if ENABLE(3D_RENDERING) + true; +#else + false; +#endif + + printf("SupportedFeatures:%s %s\n", acceleratedCompositingAvailable ? "AcceleratedCompositing" : "", threeDRenderingAvailable ? "3DRendering" : ""); + return 0; + } + + COMPtr webView(AdoptCOM, createWebViewAndOffscreenWindow(&webViewWindow)); + if (!webView) + return -1; + + COMPtr iconDatabase; + COMPtr tmpIconDatabase; + if (FAILED(WebKitCreateInstance(CLSID_WebIconDatabase, 0, IID_IWebIconDatabase, (void**)&tmpIconDatabase))) + return -1; + if (FAILED(tmpIconDatabase->sharedIconDatabase(&iconDatabase))) + return -1; + + if (FAILED(webView->mainFrame(&frame))) + return -1; + +#if USE(CFNETWORK) + RetainPtr urlCache = sharedCFURLCache(); + CFURLCacheRemoveAllCachedResponses(urlCache.get()); +#endif + +#ifdef _DEBUG + _CrtMemState entryToMainMemCheckpoint; + if (leakChecking) + _CrtMemCheckpoint(&entryToMainMemCheckpoint); +#endif + + if (threaded) + startJavaScriptThreads(); + + if (tests.size() == 1 && !strcmp(tests[0], "-")) { + char filenameBuffer[2048]; + printSeparators = true; + while (fgets(filenameBuffer, sizeof(filenameBuffer), stdin)) { + char* newLineCharacter = strchr(filenameBuffer, '\n'); + if (newLineCharacter) + *newLineCharacter = '\0'; + + if (strlen(filenameBuffer) == 0) + continue; + + runTest(filenameBuffer); + } + } else { + printSeparators = tests.size() > 1; + for (int i = 0; i < tests.size(); i++) + runTest(tests[i]); + } + + if (threaded) + stopJavaScriptThreads(); + + delete policyDelegate; + frame->Release(); + +#ifdef _DEBUG + if (leakChecking) { + // dump leaks to stderr + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtMemDumpAllObjectsSince(&entryToMainMemCheckpoint); + } +#endif + + shutDownWebKit(); + + return 0; +}