blob: e71e44fc681f666e9723aabdf2ce8229a7a856bf [file] [log] [blame]
/*
* Copyright (C) 2005-2020 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 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 "DefaultPolicyDelegate.h"
#include "EditingDelegate.h"
#include "FrameLoadDelegate.h"
#include "GCController.h"
#include "HistoryDelegate.h"
#include "JavaScriptThreading.h"
#include "PixelDumpSupport.h"
#include "PolicyDelegate.h"
#include "ResourceLoadDelegate.h"
#include "TestCommand.h"
#include "TestFeatures.h"
#include "TestOptions.h"
#include "TestRunner.h"
#include "UIDelegate.h"
#include "WebCoreTestSupport.h"
#include "WorkQueueItem.h"
#include "WorkQueue.h"
#include <CoreFoundation/CoreFoundation.h>
#include <JavaScriptCore/InitializeThreading.h>
#include <JavaScriptCore/Options.h>
#include <JavaScriptCore/TestRunnerUtils.h>
#include <WebKitLegacy/WebKit.h>
#include <WebKitLegacy/WebKitCOMAPI.h>
#include <comutil.h>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <io.h>
#include <math.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <tchar.h>
#include <windows.h>
#include <wtf/FileSystem.h>
#include <wtf/HashSet.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RetainPtr.h>
#include <wtf/Vector.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringConcatenateNumbers.h>
#include <wtf/text/StringHash.h>
#if USE(CFURLCONNECTION)
#include <CFNetwork/CFHTTPCookiesPriv.h>
#include <CFNetwork/CFURLCachePriv.h>
#endif
using namespace std;
#ifdef DEBUG_ALL
const _bstr_t TestPluginDir = L"TestNetscapePlugin_Debug";
#else
const _bstr_t TestPluginDir = L"TestNetscapePlugin";
#endif
static LPCWSTR fontsEnvironmentVariable = L"WEBKIT_TESTFONTS";
static LPCWSTR dumpRenderTreeTemp = L"DUMPRENDERTREE_TEMP";
#define USE_MAC_FONTS
static CFStringRef WebDatabaseDirectoryDefaultsKey = CFSTR("WebDatabaseDirectory");
static CFStringRef WebKitLocalCacheDefaultsKey = CFSTR("WebKitLocalCache");
static CFStringRef WebStorageDirectoryDefaultsKey = CFSTR("WebKitLocalStorageDatabasePathPreferenceKey");
const LPCWSTR kDumpRenderTreeClassName = L"DumpRenderTreeWindow";
static bool dumpPixelsForAllTests = false;
static bool dumpPixelsForCurrentTest = false;
static bool threaded = false;
static bool dumpTree = true;
static bool forceComplexText = false;
static bool dumpAllPixels;
static bool useAcceleratedDrawing = true; // Not used
// FIXME: gcBetweenTests should initially be false, but we currently set it to true, to make sure
// deallocations are not performed in one of the following tests which might cause flakiness.
static bool gcBetweenTests = true;
static bool printSeparators = false;
static bool leakChecking = false;
static bool showWebView = false;
static RetainPtr<CFStringRef> 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<IWebHistoryItem> prevTestBFItem; // current b/f item at the end of the previous test
PolicyDelegate* policyDelegate;
COMPtr<FrameLoadDelegate> sharedFrameLoadDelegate;
COMPtr<UIDelegate> sharedUIDelegate;
COMPtr<EditingDelegate> sharedEditingDelegate;
COMPtr<ResourceLoadDelegate> resourceLoadDelegate;
COMPtr<HistoryDelegate> sharedHistoryDelegate;
IWebFrame* frame;
HWND webViewWindow;
RefPtr<TestRunner> gTestRunner;
UINT_PTR waitToDumpWatchdog = 0;
bool useTimeoutWatchdog = true;
void setPersistentUserStyleSheetLocation(CFStringRef url)
{
persistentUserStyleSheetLocation = url;
}
bool setAlwaysAcceptCookies(bool)
{
// FIXME: Implement this by making the Windows port use the testing network storage session and
// modify its cookie storage policy.
return false;
}
static RetainPtr<CFStringRef> substringFromIndex(CFStringRef string, CFIndex index)
{
return adoptCF(CFStringCreateWithSubstring(kCFAllocatorDefault, string, CFRangeMake(index, CFStringGetLength(string) - index)));
}
static wstring lastPathComponentAsWString(CFURLRef url)
{
RetainPtr<CFStringRef> lastPathComponent = adoptCF(CFURLCopyLastPathComponent(url));
return cfStringRefToWString(lastPathComponent.get());
}
wstring urlSuitableForTestResult(const wstring& urlString)
{
if (urlString.empty())
return urlString;
RetainPtr<CFURLRef> url = adoptCF(CFURLCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(urlString.c_str()), urlString.length() * sizeof(wstring::value_type), kCFStringEncodingUTF16, 0));
RetainPtr<CFStringRef> scheme = adoptCF(CFURLCopyScheme(url.get()));
if (scheme && CFStringCompare(scheme.get(), CFSTR("file"), kCFCompareCaseInsensitive) != kCFCompareEqualTo)
return urlString;
COMPtr<IWebDataSource> dataSource;
if (FAILED(frame->dataSource(&dataSource))) {
if (FAILED(frame->provisionalDataSource(&dataSource)))
return lastPathComponentAsWString(url.get());
}
COMPtr<IWebMutableURLRequest> request;
if (FAILED(dataSource->request(&request)))
return lastPathComponentAsWString(url.get());
_bstr_t requestURLString;
if (FAILED(request->URL(requestURLString.GetAddress())))
return lastPathComponentAsWString(url.get());
RetainPtr<CFURLRef> requestURL = adoptCF(CFURLCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(requestURLString.GetBSTR()), requestURLString.length() * sizeof(OLECHAR), kCFStringEncodingUTF16, 0));
RetainPtr<CFURLRef> baseURL = adoptCF(CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, requestURL.get()));
RetainPtr<CFStringRef> basePath = adoptCF(CFURLCopyPath(baseURL.get()));
RetainPtr<CFStringRef> path = adoptCF(CFURLCopyPath(url.get()));
if (basePath.get() && CFStringHasPrefix(path.get(), basePath.get()))
return cfStringRefToWString(substringFromIndex(path.get(), CFStringGetLength(basePath.get())).get());
return lastPathComponentAsWString(url.get());
}
wstring lastPathComponent(const wstring& urlString)
{
if (urlString.empty())
return urlString;
RetainPtr<CFURLRef> url = adoptCF(CFURLCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(urlString.c_str()), urlString.length() * sizeof(wstring::value_type), kCFStringEncodingUTF16, 0));
RetainPtr<CFStringRef> lastPathComponent = adoptCF(CFURLCopyLastPathComponent(url.get()));
return cfStringRefToWString(lastPathComponent.get());
}
static string toUTF8(const wchar_t* wideString, size_t length)
{
int result = WideCharToMultiByte(CP_UTF8, 0, wideString, length + 1, 0, 0, 0, 0);
Vector<char> 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);
}
#if USE(CF)
static String libraryPathForDumpRenderTree()
{
DWORD size = ::GetEnvironmentVariable(dumpRenderTreeTemp, 0, 0);
Vector<TCHAR> buffer(size);
if (::GetEnvironmentVariable(dumpRenderTreeTemp, buffer.data(), buffer.size())) {
wstring path = buffer.data();
if (!path.empty() && (path[path.length() - 1] != L'\\'))
path.append(L"\\");
return String (path.data(), path.length());
}
return FileSystem::localUserSpecificStorageDirectory();
}
#endif
std::string toUTF8(BSTR bstr)
{
return toUTF8(bstr, SysStringLen(bstr));
}
std::string toUTF8(const wstring& wideString)
{
return toUTF8(wideString.c_str(), wideString.length());
}
static std::string toUTF8(CFStringRef input)
{
CFIndex maximumURLLengthAsUTF8 = CFStringGetMaximumSizeForEncoding(CFStringGetLength(input), kCFStringEncodingUTF8) + 1;
Vector<char> buffer(maximumURLLengthAsUTF8 + 1, 0);
CFStringGetCString(input, buffer.data(), maximumURLLengthAsUTF8, kCFStringEncodingUTF8);
return buffer.data();
}
wstring cfStringRefToWString(CFStringRef cfStr)
{
Vector<wchar_t> v(CFStringGetLength(cfStr));
CFStringGetCharacters(cfStr, CFRangeMake(0, CFStringGetLength(cfStr)), (UniChar *)v.data());
return wstring(v.data(), v.size());
}
static _bstr_t toBSTR(const std::string& input)
{
return input.c_str();
}
static _bstr_t toBSTR(CFStringRef input)
{
size_t stringLength = CFStringGetLength(input);
Vector<UniChar> buffer(stringLength + 1, 0);
CFStringGetCharacters(input, CFRangeMake(0, stringLength), buffer.data());
return reinterpret_cast<wchar_t*>(buffer.data());
}
static LRESULT CALLBACK DumpRenderTreeWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_DESTROY:
for (long 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<TCHAR> 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;
}
#ifdef DEBUG_ALL
#define WEBKITDLL TEXT("WebKit_debug.dll")
#else
#define WEBKITDLL TEXT("WebKit.dll")
#endif
static void initialize()
{
JSC::Config::configureForTesting();
if (HMODULE webKitModule = LoadLibrary(WEBKITDLL))
if (FARPROC dllRegisterServer = GetProcAddress(webKitModule, "DllRegisterServer"))
dllRegisterServer();
// Init COM
OleInitialize(nullptr);
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<IWebTextRenderer> 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());
// 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<IWebFramePrivate> 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<IWebFrame> parent;
if (FAILED(frame->parentFrame(&parent)))
return;
if (parent) {
_bstr_t name;
if (FAILED(frame->name(&name.GetBSTR())))
return;
fprintf(testResult, "frame '%S' ", static_cast<wchar_t*>(name));
}
fprintf(testResult, "scrolled to %.f,%.f\n", (double)scrollPosition.cx, (double)scrollPosition.cy);
}
if (::gTestRunner->dumpChildFrameScrollPositions()) {
COMPtr<IEnumVARIANT> enumKids;
if (FAILED(frame->childFrames(&enumKids)))
return;
_variant_t var;
while (enumKids->Next(1, &var.GetVARIANT(), nullptr) == S_OK) {
ASSERT(V_VT(&var) == VT_UNKNOWN);
COMPtr<IWebFrame> framePtr;
V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr);
dumpFrameScrollPosition(framePtr.get());
}
}
}
static wstring dumpFramesAsText(IWebFrame* frame)
{
if (!frame)
return L"";
COMPtr<IDOMDocument> document;
if (FAILED(frame->DOMDocument(&document)))
return L"";
COMPtr<IDOMElement> documentElement;
if (FAILED(document->documentElement(&documentElement)))
return L"";
wstring result;
// Add header for all but the main frame.
COMPtr<IWebFrame> parent;
if (FAILED(frame->parentFrame(&parent)))
return L"";
if (parent) {
_bstr_t name;
if (FAILED(frame->name(&name.GetBSTR())))
return L"";
result.append(L"\n--------\nFrame: '");
result.append(static_cast<wchar_t*>(name), name.length());
result.append(L"'\n--------\n");
}
_bstr_t innerText;
COMPtr<IDOMElementPrivate> docPrivate;
if (SUCCEEDED(documentElement->QueryInterface(&docPrivate)))
docPrivate->innerText(&innerText.GetBSTR());
result.append(static_cast<wchar_t*>(innerText), innerText.length());
result.append(L"\n");
if (::gTestRunner->dumpChildFramesAsText()) {
COMPtr<IEnumVARIANT> enumKids;
if (FAILED(frame->childFrames(&enumKids)))
return L"";
_variant_t var;
while (enumKids->Next(1, &var.GetVARIANT(), nullptr) == S_OK) {
ASSERT(V_VT(&var) == VT_UNKNOWN);
COMPtr<IWebFrame> framePtr;
V_UNKNOWN(&var)->QueryInterface(IID_IWebFrame, (void**)&framePtr);
result.append(dumpFramesAsText(framePtr.get()));
}
}
// To keep things tidy, strip all trailing spaces: they are not a meaningful part of dumpAsText test output.
std::wstring::size_type spacePosition;
while ((spacePosition = result.find(L" \n")) != std::wstring::npos)
result.erase(spacePosition, 1);
while (!result.empty() && result.back() == ' ')
result.pop_back();
return result;
}
static int compareHistoryItems(const void* item1, const void* item2)
{
COMPtr<IWebHistoryItemPrivate> itemA;
if (FAILED((*(COMPtr<IUnknown>*)item1)->QueryInterface(&itemA)))
return 0;
COMPtr<IWebHistoryItemPrivate> itemB;
if (FAILED((*(COMPtr<IUnknown>*)item2)->QueryInterface(&itemB)))
return 0;
_bstr_t targetA;
if (FAILED(itemA->target(&targetA.GetBSTR())))
return 0;
_bstr_t targetB;
if (FAILED(itemB->target(&targetB.GetBSTR())))
return 0;
return wcsicmp(static_cast<wchar_t*>(targetA), static_cast<wchar_t*>(targetB));
}
static void dumpHistoryItem(IWebHistoryItem* item, int indent, bool current)
{
ASSERT(item);
int start = 0;
if (current) {
fprintf(testResult, "curr->");
start = 6;
}
for (int i = start; i < indent; i++)
fputc(' ', testResult);
_bstr_t url;
if (FAILED(item->URLString(&url.GetBSTR())))
return;
if (wcsstr(static_cast<wchar_t*>(url), L"file:/") == static_cast<wchar_t*>(url)) {
auto layoutTestsStringUnixPath = L"/LayoutTests/";
auto layoutTestsStringDOSPath = L"\\LayoutTests\\";
wchar_t* result = wcsstr(static_cast<wchar_t*>(url), layoutTestsStringUnixPath);
if (!result)
result = wcsstr(static_cast<wchar_t*>(url), layoutTestsStringDOSPath);
if (!result)
return;
wchar_t* start = result + wcslen(layoutTestsStringUnixPath);
url = _bstr_t(L"(file test):") + _bstr_t(start);
}
fprintf(testResult, "%S", static_cast<wchar_t*>(url));
COMPtr<IWebHistoryItemPrivate> itemPrivate;
if (FAILED(item->QueryInterface(&itemPrivate)))
return;
_bstr_t target;
if (FAILED(itemPrivate->target(&target.GetBSTR())))
return;
if (target.length())
fprintf(testResult, " (in frame \"%S\")", static_cast<wchar_t*>(target));
BOOL isTargetItem = FALSE;
if (FAILED(itemPrivate->isTargetItem(&isTargetItem)))
return;
if (isTargetItem)
fprintf(testResult, " **nav target**");
fputc('\n', testResult);
unsigned kidsCount;
SAFEARRAY* arrayPtr;
if (FAILED(itemPrivate->children(&kidsCount, &arrayPtr)) || !kidsCount)
return;
std::unique_ptr<SAFEARRAY, decltype(&::SafeArrayDestroy)> array(arrayPtr, ::SafeArrayDestroy);
Vector<COMPtr<IUnknown> > kidsVector;
LONG lowerBound;
if (FAILED(::SafeArrayGetLBound(array.get(), 1, &lowerBound)))
return;
LONG upperBound;
if (FAILED(::SafeArrayGetUBound(array.get(), 1, &upperBound)))
return;
LONG length = upperBound - lowerBound + 1;
if (!length)
return;
ASSERT(length == kidsCount);
IUnknown** safeArrayData;
if (FAILED(::SafeArrayAccessData(array.get(), (void**)&safeArrayData)))
return;
for (int i = 0; i < length; ++i)
kidsVector.append(safeArrayData[i]);
::SafeArrayUnaccessData(array.get());
// 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<IWebHistoryItem> item;
kidsVector[i]->QueryInterface(&item);
dumpHistoryItem(item.get(), indent + 4, false);
}
}
static void dumpBackForwardList(IWebView* webView)
{
ASSERT(webView);
fprintf(testResult, "\n============== Back Forward List ==============\n");
COMPtr<IWebBackForwardList> 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<COMPtr<IUnknown> > itemsToPrint;
int forwardListCount;
if (FAILED(bfList->forwardListCount(&forwardListCount)))
return;
for (int i = forwardListCount; i > 0; --i) {
COMPtr<IWebHistoryItem> 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<IUnknown> itemUnknown;
item->QueryInterface(&itemUnknown);
itemsToPrint.append(itemUnknown);
}
COMPtr<IWebHistoryItem> currentItem;
if (FAILED(bfList->currentItem(&currentItem)))
return;
ASSERT(currentItem != prevTestBFItem);
COMPtr<IUnknown> currentItemUnknown;
currentItem->QueryInterface(&currentItemUnknown);
itemsToPrint.append(currentItemUnknown);
int currentItemIndex = itemsToPrint.size() - 1;
int backListCount;
if (FAILED(bfList->backListCount(&backListCount)))
return;
for (int i = -1; i >= -backListCount; --i) {
COMPtr<IWebHistoryItem> item;
if (FAILED(bfList->itemAtIndex(i, &item)))
return;
if (item == prevTestBFItem)
break;
COMPtr<IUnknown> itemUnknown;
item->QueryInterface(&itemUnknown);
itemsToPrint.append(itemUnknown);
}
for (int i = itemsToPrint.size() - 1; i >= 0; --i) {
COMPtr<IWebHistoryItem> historyItemToPrint;
itemsToPrint[i]->QueryInterface(&historyItemToPrint);
dumpHistoryItem(historyItemToPrint.get(), 8, i == currentItemIndex);
}
fprintf(testResult, "===============================================\n");
}
static void dumpBackForwardListForAllWindows()
{
unsigned count = openWindows().size();
for (unsigned i = 0; i < count; i++) {
HWND window = openWindows()[i];
IWebView* webView = windowToWebViewMap().get(window);
dumpBackForwardList(webView);
}
}
static void invalidateAnyPreviousWaitToDumpWatchdog()
{
if (!waitToDumpWatchdog)
return;
KillTimer(0, waitToDumpWatchdog);
waitToDumpWatchdog = 0;
}
void dump()
{
if (done) {
fprintf(stderr, "dump() has already been called!\n");
return;
}
::InvalidateRect(webViewWindow, 0, TRUE);
::SendMessage(webViewWindow, WM_PAINT, 0, 0);
invalidateAnyPreviousWaitToDumpWatchdog();
ASSERT(!::gTestRunner->hasPendingWebNotificationClick());
if (dumpTree) {
_bstr_t resultString;
COMPtr<IWebDataSource> dataSource;
if (SUCCEEDED(frame->dataSource(&dataSource))) {
COMPtr<IWebURLResponse> response;
if (SUCCEEDED(dataSource->response(&response)) && response) {
_bstr_t mimeType;
if (SUCCEEDED(response->MIMEType(&mimeType.GetBSTR())) && !_tcscmp(static_cast<TCHAR*>(mimeType), TEXT("text/plain"))) {
::gTestRunner->setDumpAsText(true);
::gTestRunner->setGeneratePixelResults(false);
}
}
}
if (::gTestRunner->dumpAsText()) {
resultString = dumpFramesAsText(frame).data();
} else {
COMPtr<IWebFramePrivate> framePrivate;
if (FAILED(frame->QueryInterface(&framePrivate)))
goto fail;
if (::gTestRunner->isPrinting())
framePrivate->renderTreeAsExternalRepresentationForPrinting(&resultString.GetBSTR());
else
framePrivate->renderTreeAsExternalRepresentation(::gTestRunner->renderTreeDumpOptions(), &resultString.GetBSTR());
}
if (resultString.length()) {
unsigned stringLength = resultString.length();
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, testResult);
free(buffer);
if (!::gTestRunner->dumpAsText() && !::gTestRunner->dumpDOMAsWebArchive() && !::gTestRunner->dumpSourceAsWebArchive() && !::gTestRunner->dumpAsAudio())
dumpFrameScrollPosition(frame);
if (::gTestRunner->dumpBackForwardList())
dumpBackForwardListForAllWindows();
} else
fprintf(testResult, "ERROR: nil result from %s\n", ::gTestRunner->dumpAsText() ? "IDOMElement::innerText" : "IFrameViewPrivate::renderTreeAsExternalRepresentation");
if (printSeparators)
fputs("#EOF\n", testResult); // terminate the content block
}
if (dumpPixelsForCurrentTest && ::gTestRunner->generatePixelResults()) {
// FIXME: when isPrinting is set, dump the image with page separators.
dumpWebViewAsPixelsAndCompareWithExpected(gTestRunner->expectedPixelHash());
}
fputs("#EOF\n", testResult); // terminate the (possibly empty) pixels block
fflush(testResult);
fail:
// This will exit from our message loop.
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 shouldDumpAsText(const char* pathOrURL)
{
return strstr(pathOrURL, "/dumpAsText/") || strstr(pathOrURL, "\\dumpAsText\\");
}
static void enableExperimentalFeatures(IWebPreferences* preferences)
{
COMPtr<IWebPreferencesPrivate7> prefsPrivate { Query, preferences };
prefsPrivate->setFetchAPIKeepAliveEnabled(TRUE);
// FIXME: CSSGridLayout
// FIXME: SpringTimingFunction
// FIXME: Gamepads
prefsPrivate->setLinkPreloadEnabled(TRUE);
prefsPrivate->setMediaPreloadingEnabled(TRUE);
// FIXME: ModernMediaControls
// FIXME: InputEvents
// FIXME: SubtleCrypto
prefsPrivate->setVisualViewportAPIEnabled(TRUE);
prefsPrivate->setCSSOMViewScrollingAPIEnabled(TRUE);
prefsPrivate->setResizeObserverEnabled(TRUE);
prefsPrivate->setWebAnimationsCompositeOperationsEnabled(TRUE);
prefsPrivate->setWebAnimationsMutableTimelinesEnabled(TRUE);
prefsPrivate->setCSSCustomPropertiesAndValuesEnabled(TRUE);
prefsPrivate->setServerTimingEnabled(TRUE);
prefsPrivate->setAspectRatioOfImgFromWidthAndHeightEnabled(TRUE);
// FIXME: WebGL2
// FIXME: WebRTC
prefsPrivate->setCSSOMViewSmoothScrollingEnabled(TRUE);
}
static void resetWebPreferencesToConsistentValues(IWebPreferences* preferences)
{
ASSERT(preferences);
preferences->setAutosaves(FALSE);
COMPtr<IWebPreferencesPrivate8> prefsPrivate(Query, preferences);
ASSERT(prefsPrivate);
prefsPrivate->resetForTesting();
enableExperimentalFeatures(preferences);
prefsPrivate->setFullScreenEnabled(TRUE);
#ifdef USE_MAC_FONTS
static _bstr_t standardFamily(TEXT("Times"));
static _bstr_t fixedFamily(TEXT("Courier"));
static _bstr_t sansSerifFamily(TEXT("Helvetica"));
static _bstr_t cursiveFamily(TEXT("Apple Chancery"));
static _bstr_t fantasyFamily(TEXT("Papyrus"));
static _bstr_t pictographFamily(TEXT("Apple Color Emoji"));
#else
static _bstr_t standardFamily(TEXT("Times New Roman"));
static _bstr_t fixedFamily(TEXT("Courier New"));
static _bstr_t sansSerifFamily(TEXT("Arial"));
static _bstr_t cursiveFamily(TEXT("Comic Sans MS")); // Not actually cursive, but it's what IE and Firefox use.
static _bstr_t fantasyFamily(TEXT("Times New Roman"));
static _bstr_t pictographFamily(TEXT("Segoe UI Symbol"));
#endif
prefsPrivate->setAllowTopNavigationToDataURLs(TRUE);
prefsPrivate->setModernUnprefixedWebAudioEnabled(TRUE);
prefsPrivate->setAllowUniversalAccessFromFileURLs(TRUE);
prefsPrivate->setAllowFileAccessFromFileURLs(TRUE);
preferences->setStandardFontFamily(standardFamily);
preferences->setFixedFontFamily(fixedFamily);
preferences->setSerifFontFamily(standardFamily);
preferences->setSansSerifFontFamily(sansSerifFamily);
preferences->setCursiveFontFamily(cursiveFamily);
preferences->setFantasyFontFamily(fantasyFamily);
preferences->setPictographFontFamily(pictographFamily);
preferences->setDefaultFontSize(16);
preferences->setDefaultFixedFontSize(13);
preferences->setMinimumFontSize(0);
preferences->setDefaultTextEncodingName(_bstr_t(L"ISO-8859-1"));
preferences->setJavaScriptEnabled(TRUE);
preferences->setEditableLinkBehavior(WebKitEditableLinkOnlyLiveWithShiftKey);
preferences->setDOMPasteAllowed(TRUE);
preferences->setShouldPrintBackgrounds(TRUE);
preferences->setCacheModel(WebCacheModelDocumentBrowser);
prefsPrivate->setXSSAuditorEnabled(FALSE);
preferences->setPlugInsEnabled(TRUE);
preferences->setTextAreasAreResizable(TRUE);
preferences->setUsesPageCache(FALSE);
preferences->setPrivateBrowsingEnabled(FALSE);
prefsPrivate->setAuthorAndUserStylesEnabled(TRUE);
// Shrinks standalone images to fit: YES
preferences->setJavaScriptCanOpenWindowsAutomatically(TRUE);
prefsPrivate->setJavaScriptCanAccessClipboard(TRUE);
prefsPrivate->setOfflineWebApplicationCacheEnabled(TRUE);
prefsPrivate->setJavaScriptRuntimeFlags(WebKitJavaScriptRuntimeFlagsAllEnabled);
// Set JS experiments enabled: YES
preferences->setLoadsImagesAutomatically(TRUE);
prefsPrivate->setLoadsSiteIconsIgnoringImageLoadingPreference(FALSE);
prefsPrivate->setFrameFlatteningEnabled(FALSE);
if (persistentUserStyleSheetLocation) {
preferences->setUserStyleSheetLocation(toBSTR(persistentUserStyleSheetLocation.get()));
preferences->setUserStyleSheetEnabled(TRUE);
} else
preferences->setUserStyleSheetEnabled(FALSE);
#if USE(CG)
prefsPrivate->setAcceleratedCompositingEnabled(TRUE);
#endif
// Set WebGL Enabled: NO
preferences->setCSSRegionsEnabled(TRUE);
// Set uses HTML5 parser quirks: NO
// Async spellcheck: NO
prefsPrivate->setMockScrollbarsEnabled(TRUE);
preferences->setFontSmoothing(FontSmoothingTypeStandard);
prefsPrivate->setWebSQLEnabled(true);
prefsPrivate->setDataTransferItemsEnabled(TRUE);
prefsPrivate->clearNetworkLoaderSession();
prefsPrivate->setSpeechRecognitionEnabled(TRUE);
setAlwaysAcceptCookies(false);
}
template<typename T> T webPreferenceFeatureValue(const std::string& key, const std::unordered_map<std::string, T>& map)
{
auto it = map.find(key);
ASSERT(it != map.end());
return it->second;
}
static void setWebPreferencesForTestOptions(IWebPreferences* preferences, const WTR::TestOptions& options)
{
COMPtr<IWebPreferencesPrivate8> prefsPrivate { Query, preferences };
preferences->setPrivateBrowsingEnabled(options.useEphemeralSession());
// FIXME: Remove this once there is a viable mechanism for reseting WebPreferences between tests,
// at which point, we will not need to manually reset every supported preference for each test.
for (const auto& key : options.supportedBoolWebPreferenceFeatures())
prefsPrivate->setBoolPreferenceForTesting(toBSTR(WTR::TestOptions::toWebKitLegacyPreferenceKey(key)), webPreferenceFeatureValue(key, options.boolWebPreferenceFeatures()));
for (const auto& key : options.supportedUInt32WebPreferenceFeatures())
prefsPrivate->setUInt32PreferenceForTesting(toBSTR(WTR::TestOptions::toWebKitLegacyPreferenceKey(key)), webPreferenceFeatureValue(key, options.uint32WebPreferenceFeatures()));
}
static String applicationId()
{
return makeString("com.apple.DumpRenderTree.", ::GetCurrentProcessId());
}
static void setApplicationId()
{
COMPtr<IWebPreferences> preferences;
if (SUCCEEDED(WebKitCreateInstance(CLSID_WebPreferences, 0, IID_IWebPreferences, (void**)&preferences))) {
COMPtr<IWebPreferencesPrivate4> prefsPrivate4(Query, preferences);
ASSERT(prefsPrivate4);
_bstr_t fileName = applicationId().wideCharacters().data();
prefsPrivate4->setApplicationId(fileName);
}
}
// Called once on DumpRenderTree startup.
static void setDefaultsToConsistentValuesForTesting()
{
#if USE(CF)
// Create separate preferences for each DRT instance
setApplicationId();
RetainPtr<CFStringRef> appId = applicationId().createCFString();
String libraryPath = libraryPathForDumpRenderTree();
// Set up these values before creating the WebView so that the various initializations will see these preferred values.
CFPreferencesSetAppValue(WebDatabaseDirectoryDefaultsKey, FileSystem::pathByAppendingComponent(libraryPath, "Databases"_s).createCFString().get(), appId.get());
CFPreferencesSetAppValue(WebStorageDirectoryDefaultsKey, FileSystem::pathByAppendingComponent(libraryPath, "LocalStorage"_s).createCFString().get(), appId.get());
CFPreferencesSetAppValue(WebKitLocalCacheDefaultsKey, FileSystem::pathByAppendingComponent(libraryPath, "LocalCache"_s).createCFString().get(), appId.get());
#endif
}
static void setJSCOptions(const WTR::TestOptions& options)
{
static WTF::StringBuilder savedOptions;
if (!savedOptions.isEmpty()) {
JSC::Options::setOptions(savedOptions.toString().ascii().data());
savedOptions.clear();
}
if (!options.jscOptions().empty()) {
JSC::Options::dumpAllOptionsInALine(savedOptions);
JSC::Options::setOptions(options.jscOptions().c_str());
}
}
static void resetWebViewToConsistentStateBeforeTesting(const WTR::TestOptions& options)
{
setJSCOptions(options);
COMPtr<IWebView> webView;
if (FAILED(frame->webView(&webView)))
return;
COMPtr<IWebViewEditing> viewEditing;
if (SUCCEEDED(webView->QueryInterface(&viewEditing)) && viewEditing) {
viewEditing->setEditable(FALSE);
COMPtr<IWebEditingDelegate> delegate;
if (SUCCEEDED(viewEditing->editingDelegate(&delegate)) && delegate) {
COMPtr<EditingDelegate> editingDelegate(Query, viewEditing.get());
if (editingDelegate)
editingDelegate->setAcceptsEditing(TRUE);
}
}
COMPtr<IWebIBActions> webIBActions(Query, webView);
if (webIBActions) {
webIBActions->makeTextStandardSize(0);
webIBActions->resetPageZoom(0);
}
COMPtr<IWebViewPrivate2> webViewPrivate(Query, webView);
if (webViewPrivate) {
POINT origin = { 0, 0 };
webViewPrivate->scaleWebView(1.0, origin);
webViewPrivate->setCustomBackingScaleFactor(0);
webViewPrivate->setTabKeyCyclesThroughElements(TRUE);
}
webView->setPolicyDelegate(DefaultPolicyDelegate::sharedInstance());
policyDelegate->setPermissive(false);
policyDelegate->setControllerToNotifyDone(nullptr);
sharedFrameLoadDelegate->resetToConsistentState();
if (webViewPrivate) {
webViewPrivate->clearMainFrameName();
_bstr_t groupName;
if (SUCCEEDED(webView->groupName(&groupName.GetBSTR())))
webViewPrivate->removeAllUserContentFromGroup(groupName);
}
COMPtr<IWebPreferences> preferences;
if (SUCCEEDED(webView->preferences(&preferences))) {
COMPtr<IWebPreferencesPrivate8> prefsPrivate { Query, preferences };
prefsPrivate->startBatchingUpdates();
resetWebPreferencesToConsistentValues(preferences.get());
setWebPreferencesForTestOptions(preferences.get(), options);
prefsPrivate->stopBatchingUpdates();
}
TestRunner::setSerializeHTTPLoads(false);
setlocale(LC_ALL, "");
if (::gTestRunner) {
JSGlobalContextRef context = frame->globalContext();
WebCoreTestSupport::resetInternalsObject(context);
}
if (preferences) {
preferences->setContinuousSpellCheckingEnabled(TRUE);
// Automatic Quote Subs
// Automatic Link Detection
// Autommatic Dash substitution
// Automatic Spell Check
preferences->setGrammarCheckingEnabled(TRUE);
// Use Test Mode Focus Ring
}
HWND viewWindow;
if (webViewPrivate && SUCCEEDED(webViewPrivate->viewWindow(&viewWindow)) && viewWindow)
::SetFocus(viewWindow);
webViewPrivate->resetOriginAccessAllowLists();
sharedUIDelegate->resetUndoManager();
COMPtr<IWebFramePrivate> framePrivate;
if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
framePrivate->clearOpener();
COMPtr<IWebViewPrivate5> webViewPrivate5(Query, webView);
webViewPrivate5->exitFullscreenIfNeeded();
WebCoreTestSupport::clearAllLogChannelsToAccumulate();
WebCoreTestSupport::initializeLogChannelsIfNecessary();
}
static void sizeWebViewForCurrentTest()
{
bool isSVGW3CTest = (::gTestRunner->testURL().find("svg\\W3C-SVG-1.1") != string::npos)
|| (::gTestRunner->testURL().find("svg/W3C-SVG-1.1") != string::npos);
unsigned width = isSVGW3CTest ? TestRunner::w3cSVGViewWidth : TestRunner::viewWidth;
unsigned height = isSVGW3CTest ? TestRunner::w3cSVGViewHeight : TestRunner::viewHeight;
::SetWindowPos(webViewWindow, 0, 0, 0, width, height, SWP_NOMOVE);
}
static String findFontFallback(const char* pathOrUrl)
{
String pathToFontFallback = FileSystem::parentPath(String::fromUTF8(pathOrUrl));
wchar_t fullPath[_MAX_PATH];
if (!_wfullpath(fullPath, pathToFontFallback.wideCharacters().data(), _MAX_PATH))
return emptyString();
if (!::PathIsDirectoryW(fullPath))
return emptyString();
String pathToCheck = fullPath;
StringView pathToCheckView { pathToCheck };
static const String layoutTests = "LayoutTests"_s;
// Find the layout test root on the current path:
size_t location = pathToCheckView.find(layoutTests);
if (WTF::notFound == location)
return emptyString();
StringView pathToTest = pathToCheckView.substring(location + layoutTests.length() + 1);
String possiblePathToLogue = FileSystem::pathByAppendingComponent(pathToCheckView.left(location + layoutTests.length() + 1), "platform\\win"_s);
Vector<String> possiblePaths;
possiblePaths.append(FileSystem::pathByAppendingComponent(possiblePathToLogue, pathToTest));
size_t nextCandidateEnd = pathToTest.reverseFind('\\');
while (nextCandidateEnd && nextCandidateEnd != WTF::notFound) {
pathToTest = pathToTest.left(nextCandidateEnd);
possiblePaths.append(FileSystem::pathByAppendingComponent(possiblePathToLogue, pathToTest));
nextCandidateEnd = pathToTest.reverseFind('\\');
}
for (Vector<String>::iterator pos = possiblePaths.begin(); pos != possiblePaths.end(); ++pos) {
pathToFontFallback = FileSystem::pathByAppendingComponent(*pos, "resources\\"_s);
if (::PathIsDirectoryW(pathToFontFallback.wideCharacters().data()))
return pathToFontFallback;
}
return emptyString();
}
static void addFontFallbackIfPresent(const String& fontFallbackPath)
{
if (fontFallbackPath.isEmpty())
return;
String fontFallback = FileSystem::pathByAppendingComponent(fontFallbackPath, "Mac-compatible-font-fallback.css"_s);
if (!::PathFileExistsW(fontFallback.wideCharacters().data()))
return;
::setPersistentUserStyleSheetLocation(fontFallback.createCFString().get());
}
static void removeFontFallbackIfPresent(const String& fontFallbackPath)
{
if (fontFallbackPath.isEmpty())
return;
String fontFallback = FileSystem::pathByAppendingComponent(fontFallbackPath, "Mac-compatible-font-fallback.css"_s);
if (!::PathFileExistsW(fontFallback.wideCharacters().data()))
return;
::setPersistentUserStyleSheetLocation(nullptr);
}
static bool handleControlCommand(const char* command)
{
if (!strcmp("#CHECK FOR ABANDONED DOCUMENTS", command)) {
// DumpRenderTree does not support checking for abandoned documents.
fputs("Content-Type: text/plain\nContent-Length: 1\n\n#EOF\n", stdout);
fputs("#EOF\n", stderr);
fflush(stdout);
fflush(stderr);
return true;
}
return false;
}
static WTR::TestOptions testOptionsForTest(const WTR::TestCommand& command)
{
WTR::TestFeatures features = WTR::TestOptions::defaults();
WTR::merge(features, WTR::hardcodedFeaturesBasedOnPathForTest(command));
WTR::merge(features, WTR::featureDefaultsFromTestHeaderForTest(command, WTR::TestOptions::keyTypeMapping()));
return WTR::TestOptions { WTFMove(features) };
}
static void runTest(const string& inputLine)
{
ASSERT(!inputLine.empty());
auto command = WTR::parseInputLine(inputLine);
const string& pathOrURL = command.pathOrURL;
dumpPixelsForCurrentTest = command.shouldDumpPixels || dumpPixelsForAllTests;
static _bstr_t methodBStr(TEXT("GET"));
auto str = adoptCF(CFStringCreateWithCString(0, pathOrURL.c_str(), kCFStringEncodingWindowsLatin1));
if (!str) {
fprintf(stderr, "Failed to parse \"%s\" as UTF-8\n", pathOrURL.c_str());
return;
}
auto url = adoptCF(CFURLCreateWithString(0, str.get(), 0));
if (!url)
url = adoptCF(CFURLCreateWithFileSystemPath(0, str.get(), kCFURLWindowsPathStyle, false));
if (!url) {
fprintf(stderr, "Failed to parse \"%s\" as a URL\n", pathOrURL.c_str());
return;
}
String hostName = String(adoptCF(CFURLCopyHostName(url.get())).get());
String fallbackPath = findFontFallback(pathOrURL.c_str());
auto urlCFString = CFURLGetString(url.get());
auto urlBStr = toBSTR(urlCFString);
ASSERT(urlBStr.length() == CFStringGetLength(urlCFString));
auto options = testOptionsForTest(command);
resetWebViewToConsistentStateBeforeTesting(options);
::gTestRunner = TestRunner::create(toUTF8(urlCFString), command.expectedPixelHash);
::gTestRunner->setCustomTimeout(command.timeout.milliseconds());
::gTestRunner->setDumpJSConsoleLogInStdErr(command.dumpJSConsoleLogInStdErr || options.dumpJSConsoleLogInStdErr());
topLoadingFrame = nullptr;
done = false;
addFontFallbackIfPresent(fallbackPath);
sizeWebViewForCurrentTest();
::gTestRunner->setIconDatabaseEnabled(false);
::gTestRunner->clearAllApplicationCaches();
::gTestRunner->clearAllDatabases();
if (shouldLogFrameLoadDelegates(pathOrURL.c_str()))
::gTestRunner->setDumpFrameLoadCallbacks(true);
COMPtr<IWebView> webView;
if (SUCCEEDED(frame->webView(&webView))) {
COMPtr<IWebViewPrivate2> viewPrivate;
if (SUCCEEDED(webView->QueryInterface(&viewPrivate))) {
if (shouldLogHistoryDelegates(pathOrURL.c_str())) {
::gTestRunner->setDumpHistoryDelegateCallbacks(true);
viewPrivate->setHistoryDelegate(sharedHistoryDelegate.get());
} else
viewPrivate->setHistoryDelegate(nullptr);
}
}
if (shouldDumpAsText(pathOrURL.c_str())) {
::gTestRunner->setDumpAsText(true);
::gTestRunner->setGeneratePixelResults(false);
}
COMPtr<IWebHistory> history;
if (SUCCEEDED(WebKitCreateInstance(CLSID_WebHistory, 0, __uuidof(history), reinterpret_cast<void**>(&history))))
history->setOptionalSharedHistory(nullptr);
prevTestBFItem = nullptr;
COMPtr<IWebBackForwardList> bfList;
if (webView && SUCCEEDED(webView->backForwardList(&bfList)))
bfList->currentItem(&prevTestBFItem);
auto& workQueue = DRT::WorkQueue::singleton();
workQueue.clear();
workQueue.setFrozen(false);
MSG msg { };
HWND hostWindow;
webView->hostWindow(&hostWindow);
COMPtr<IWebMutableURLRequest> request, emptyRequest;
HRESULT hr = WebKitCreateInstance(CLSID_WebMutableURLRequest, 0, IID_IWebMutableURLRequest, (void**)&request);
if (FAILED(hr))
goto exit;
request->initWithURL(urlBStr, WebURLRequestUseProtocolCachePolicy, 60);
request->setHTTPMethod(methodBStr);
if (hostName == "localhost"_s || hostName == "127.0.0.1"_s)
request->setAllowsAnyHTTPSCertificate();
frame->loadRequest(request.get());
while (true) {
#if USE(CF)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
#endif
if (done) {
// Post WM_QUIT to ensure that all deferred tasks are dispatched before quitting the run loop.
PostQuitMessage(0);
}
if (!::GetMessage(&msg, 0, 0, 0))
break;
// 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);
}
// EventSendingController clearSavedEvents
workQueue.clear();
// If the test page could have possibly opened the Web Inspector frontend,
// then try to close it in case it was accidentally left open.
::gTestRunner->closeWebInspector();
for (HWND window : Vector(openWindows())) {
// Don't try to close the main window
if (window == hostWindow)
continue;
::DestroyWindow(window);
}
resetWebViewToConsistentStateBeforeTesting(options);
// Loading an empty request synchronously replaces the document with a blank one, which is necessary
// to stop timers, WebSockets and other activity that could otherwise spill output into next test's results.
if (SUCCEEDED(WebKitCreateInstance(CLSID_WebMutableURLRequest, 0, IID_IWebMutableURLRequest, (void**)&emptyRequest))) {
_bstr_t emptyURL(L"");
emptyRequest->initWithURL(emptyURL.GetBSTR(), WebURLRequestUseProtocolCachePolicy, 60);
emptyRequest->setHTTPMethod(methodBStr);
frame->loadRequest(emptyRequest.get());
}
// We should only have our main window left open when we're done
ASSERT(openWindows().size() == 1);
ASSERT(openWindows()[0] == hostWindow);
exit:
removeFontFallbackIfPresent(fallbackPath);
::gTestRunner->cleanup();
::gTestRunner = nullptr;
if (gcBetweenTests) {
GCController gcController;
gcController.collect();
}
JSC::waitForVMDestruction();
fputs("#EOF\n", stderr);
fflush(stderr);
}
Vector<HWND>& openWindows()
{
static NeverDestroyed<Vector<HWND>> vector;
return vector;
}
WindowToWebViewMap& windowToWebViewMap()
{
static NeverDestroyed<WindowToWebViewMap> map;
return map;
}
IWebView* createWebViewAndOffscreenWindow(HWND* webViewWindow)
{
int maxViewWidth = TestRunner::viewWidth;
int maxViewHeight = TestRunner::viewHeight;
HWND hostWindow = (showWebView) ? CreateWindowEx(WS_EX_TOOLWINDOW, kDumpRenderTreeClassName, TEXT("DumpRenderTree"), WS_POPUP, 100, 100, maxViewWidth, maxViewHeight, 0, 0, ::GetModuleHandle(0), nullptr)
: CreateWindowEx(WS_EX_TOOLWINDOW, kDumpRenderTreeClassName, TEXT("DumpRenderTree"), WS_POPUP, -maxViewWidth, -maxViewHeight, maxViewWidth, maxViewHeight, 0, 0, ::GetModuleHandle(0), nullptr);
IWebView* webView = nullptr;
HRESULT hr = WebKitCreateInstance(CLSID_WebView, 0, IID_IWebView, (void**)&webView);
if (FAILED(hr)) {
fprintf(stderr, "Failed to create CLSID_WebView instance, error 0x%lx\n", hr);
return nullptr;
}
if (FAILED(webView->setHostWindow(hostWindow)))
return nullptr;
RECT clientRect;
clientRect.bottom = clientRect.left = clientRect.top = clientRect.right = 0;
_bstr_t groupName(L"org.webkit.DumpRenderTree");
if (FAILED(webView->initWithFrame(clientRect, 0, groupName)))
return nullptr;
COMPtr<IWebViewPrivate2> viewPrivate;
if (FAILED(webView->QueryInterface(&viewPrivate)))
return nullptr;
viewPrivate->setShouldApplyMacFontAscentHack(TRUE);
viewPrivate->setAlwaysUsesComplexTextCodePath(forceComplexText);
viewPrivate->setCustomBackingScaleFactor(1.0);
_bstr_t pluginPath = _bstr_t(exePath().data()) + TestPluginDir;
if (FAILED(viewPrivate->addAdditionalPluginDirectory(pluginPath.GetBSTR())))
return nullptr;
HWND viewWindow;
if (FAILED(viewPrivate->viewWindow(&viewWindow)))
return nullptr;
if (webViewWindow)
*webViewWindow = viewWindow;
::SetWindowPos(viewWindow, 0, 0, 0, maxViewWidth, maxViewHeight, 0);
::ShowWindow(hostWindow, SW_SHOW);
if (FAILED(webView->setUIDelegate(sharedUIDelegate.get())))
return nullptr;
if (FAILED(webView->setFrameLoadDelegate(sharedFrameLoadDelegate.get())))
return nullptr;
if (FAILED(viewPrivate->setFrameLoadDelegatePrivate(sharedFrameLoadDelegate.get())))
return nullptr;
COMPtr<IWebViewEditing> viewEditing;
if (FAILED(webView->QueryInterface(&viewEditing)))
return nullptr;
if (FAILED(viewEditing->setEditingDelegate(sharedEditingDelegate.get())))
return nullptr;
if (FAILED(webView->setResourceLoadDelegate(resourceLoadDelegate.get())))
return nullptr;
viewPrivate->setDefersCallbacks(FALSE);
openWindows().append(hostWindow);
windowToWebViewMap().set(hostWindow, webView);
return webView;
}
#if USE(CFURLCONNECTION)
RetainPtr<CFURLCacheRef> sharedCFURLCache()
{
#ifndef DEBUG_ALL
HMODULE module = GetModuleHandle(TEXT("CFNetwork.dll"));
#else
HMODULE module = GetModuleHandle(TEXT("CFNetwork_debug.dll"));
#endif
if (!module)
return nullptr;
typedef CFURLCacheRef (*CFURLCacheCopySharedURLCacheProcPtr)(void);
if (CFURLCacheCopySharedURLCacheProcPtr copyCache = reinterpret_cast<CFURLCacheCopySharedURLCacheProcPtr>(GetProcAddress(module, "CFURLCacheCopySharedURLCache")))
return adoptCF(copyCache());
typedef CFURLCacheRef (*CFURLCacheSharedURLCacheProcPtr)(void);
if (CFURLCacheSharedURLCacheProcPtr sharedCache = reinterpret_cast<CFURLCacheSharedURLCacheProcPtr>(GetProcAddress(module, "CFURLCacheSharedURLCache")))
return sharedCache();
return nullptr;
}
#endif
static Vector<const char*> initializeGlobalsFromCommandLineOptions(int argc, const char* argv[])
{
Vector<const char*> tests;
for (int i = 1; i < argc; ++i) {
if (!stricmp(argv[i], "--notree")) {
dumpTree = false;
continue;
}
if (!stricmp(argv[i], "--pixel-tests")) {
dumpPixelsForAllTests = true;
continue;
}
if (!stricmp(argv[i], "--tree")) {
dumpTree = true;
continue;
}
if (!stricmp(argv[i], "--threaded")) {
threaded = true;
continue;
}
if (!stricmp(argv[i], "--complex-text")) {
forceComplexText = true;
continue;
}
if (!stricmp(argv[i], "--accelerated-drawing")) {
useAcceleratedDrawing = true;
continue;
}
if (!stricmp(argv[i], "--gc-between-tests")) {
gcBetweenTests = true;
continue;
}
if (!stricmp(argv[i], "--no-timeout")) {
useTimeoutWatchdog = false;
continue;
}
if (!stricmp(argv[i], "--dump-all-pixels")) {
dumpAllPixels = true;
continue;
}
if (!stricmp(argv[i], "--show-webview")) {
showWebView = true;
continue;
}
tests.append(argv[i]);
}
return tests;
}
static void allocateGlobalControllers()
{
sharedFrameLoadDelegate.adoptRef(new FrameLoadDelegate);
sharedUIDelegate.adoptRef(new UIDelegate);
sharedEditingDelegate.adoptRef(new EditingDelegate);
resourceLoadDelegate.adoptRef(new ResourceLoadDelegate);
policyDelegate = new PolicyDelegate();
sharedHistoryDelegate.adoptRef(new HistoryDelegate);
// storage delegate
// policy delegate
}
static void prepareConsistentTestingEnvironment(IWebPreferences* standardPreferences, IWebPreferencesPrivate* standardPreferencesPrivate)
{
ASSERT(standardPreferences);
ASSERT(standardPreferencesPrivate);
standardPreferences->setAutosaves(FALSE);
#if USE(CFURLCONNECTION)
auto newCache = adoptCF(CFURLCacheCreate(kCFAllocatorDefault, 1024 * 1024, 0, nullptr));
CFURLCacheSetSharedURLCache(newCache.get());
#endif
COMPtr<IWebPreferencesPrivate4> prefsPrivate4(Query, standardPreferences);
prefsPrivate4->switchNetworkLoaderToNewTestingSession();
standardPreferences->setJavaScriptEnabled(TRUE);
standardPreferences->setDefaultFontSize(16);
#if USE(CG)
standardPreferences->setAcceleratedCompositingEnabled(TRUE);
standardPreferences->setAVFoundationEnabled(TRUE);
#endif
allocateGlobalControllers();
}
int main(int argc, const char* argv[])
{
// Cygwin calls ::SetErrorMode(SEM_FAILCRITICALERRORS), which we will inherit. This is bad for
// testing/debugging, as it causes the post-mortem debugger not to be invoked. We reset the
// error mode here to work around Cygwin's behavior. See <http://webkit.org/b/55222>.
::SetErrorMode(0);
leakChecking = false;
_setmode(1, _O_BINARY);
_setmode(2, _O_BINARY);
// Some tests are flaky because certain DLLs are writing to stdout, giving incorrect test results.
// We work around that here by duplicating and redirecting stdout.
int fdStdout = _dup(1);
_setmode(fdStdout, _O_BINARY);
testResult = fdopen(fdStdout, "a+b");
// Redirect stdout to stderr.
int result = _dup2(_fileno(stderr), 1);
if (result)
return -5;
// Tests involving the clipboard are flaky when running with multiple DRTs, since the clipboard is global.
// We can fix this by assigning each DRT a separate window station (each window station has its own clipboard).
DWORD processId = ::GetCurrentProcessId();
String windowStationName = makeString("windowStation", processId);
String desktopName = makeString("desktop", processId);
HDESK desktop = nullptr;
auto windowsStation = ::CreateWindowStation(windowStationName.wideCharacters().data(), CWF_CREATE_ONLY, WINSTA_ALL_ACCESS, nullptr);
if (windowsStation) {
if (!::SetProcessWindowStation(windowsStation))
fprintf(stderr, "SetProcessWindowStation failed with error %lu\n", ::GetLastError());
desktop = ::CreateDesktop(desktopName.wideCharacters().data(), nullptr, nullptr, 0, GENERIC_ALL, nullptr);
if (!desktop)
fprintf(stderr, "Failed to create desktop with error %lu\n", ::GetLastError());
} else {
DWORD error = ::GetLastError();
fprintf(stderr, "Failed to create window station with error %lu\n", error);
if (error == ERROR_ACCESS_DENIED)
fprintf(stderr, "DumpRenderTree should be run as Administrator!\n");
}
initialize();
setDefaultsToConsistentValuesForTesting();
Vector<const char*> tests = initializeGlobalsFromCommandLineOptions(argc, argv);
JSC::initialize();
WTF::initializeMainThread();
WebCoreTestSupport::populateJITOperations();
// FIXME - need to make DRT pass with Windows native controls <http://bugs.webkit.org/show_bug.cgi?id=25592>
COMPtr<IWebPreferences> tmpPreferences;
if (FAILED(WebKitCreateInstance(CLSID_WebPreferences, 0, IID_IWebPreferences, reinterpret_cast<void**>(&tmpPreferences))))
return -1;
COMPtr<IWebPreferences> standardPreferences;
if (FAILED(tmpPreferences->standardPreferences(&standardPreferences)))
return -2;
COMPtr<IWebPreferencesPrivate> standardPreferencesPrivate;
if (FAILED(standardPreferences->QueryInterface(&standardPreferencesPrivate)))
return -3;
prepareConsistentTestingEnvironment(standardPreferences.get(), standardPreferencesPrivate.get());
COMPtr<IWebView> webView(AdoptCOM, createWebViewAndOffscreenWindow(&webViewWindow));
if (!webView)
return -4;
if (FAILED(webView->mainFrame(&frame)))
return -7;
#if USE(CFURLCONNECTION)
RetainPtr<CFURLCacheRef> 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;
if (handleControlCommand(filenameBuffer))
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();
#ifdef _CRTDBG_MAP_ALLOC
_CrtDumpMemoryLeaks();
#endif
if (desktop)
::CloseDesktop(desktop);
if (windowsStation)
::CloseWindowStation(windowsStation);
::OleUninitialize();
return 0;
}
extern "C" __declspec(dllexport) int WINAPI dllLauncherEntryPoint(int argc, const char* argv[])
{
return main(argc, argv);
}