/* * 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; #ifdef 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 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)) && !_tcscmp(mimeType, TEXT("text/plain"))) { ::gLayoutTestController->setDumpAsText(true); ::gLayoutTestController->setGeneratePixelResults(false); } 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 true; } 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(0); 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; ResourceLoadDelegate* resourceLoadDelegate = new ResourceLoadDelegate(); HRESULT result = webView->setResourceLoadDelegate(resourceLoadDelegate); resourceLoadDelegate->Release(); // The delegate is owned by the WebView, so release our reference to it. if (FAILED(result)) 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 static LONG WINAPI exceptionFilter(EXCEPTION_POINTERS*) { fputs("#CRASHED\n", stderr); fflush(stderr); return EXCEPTION_CONTINUE_SEARCH; } int main(int argc, char* argv[]) { ::SetUnhandledExceptionFilter(exceptionFilter); 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); 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); standardPreferences->setContinuousSpellCheckingEnabled(TRUE); if (printSupportedFeatures) { BOOL acceleratedCompositingAvailable; standardPreferences->acceleratedCompositingEnabled(&acceleratedCompositingAvailable); #if ENABLE(3D_RENDERING) // In theory, we could have a software-based 3D rendering implementation that we use when // hardware-acceleration is not available. But we don't have any such software // implementation, so 3D rendering is only available when hardware-acceleration is. BOOL threeDRenderingAvailable = acceleratedCompositingAvailable; #else BOOL threeDRenderingAvailable = 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; }