mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
399 lines
11 KiB
C++
399 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#undef WINVER
|
|
#undef _WIN32_WINNT
|
|
#define WINVER 0x602
|
|
#define _WIN32_WINNT 0x602
|
|
|
|
#include <windows.h>
|
|
#include <objbase.h>
|
|
#include <combaseapi.h>
|
|
#include <atlcore.h>
|
|
#include <atlstr.h>
|
|
#include <wininet.h>
|
|
#include <shlobj.h>
|
|
#include <shlwapi.h>
|
|
#include <propkey.h>
|
|
#include <propvarutil.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <strsafe.h>
|
|
#include <io.h>
|
|
#include <shellapi.h>
|
|
|
|
static const WCHAR* kFirefoxExe = L"firefox.exe";
|
|
static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
|
|
|
|
// Logging pipe handle
|
|
HANDLE gTestOutputPipe = INVALID_HANDLE_VALUE;
|
|
// Logging pipe read buffer
|
|
#define PIPE_BUFFER_SIZE 4096
|
|
char buffer[PIPE_BUFFER_SIZE + 1];
|
|
|
|
CString sAppParams;
|
|
CString sFirefoxPath;
|
|
|
|
// The tests file we write out for firefox.exe which contains test
|
|
// startup command line paramters.
|
|
#define kMetroTestFile "tests.ini"
|
|
|
|
// Process exit codes for buildbotcustom logic. These are currently ignored, but
|
|
// at some point releng expects to use these.
|
|
#define SUCCESS 0
|
|
#define WARNINGS 1
|
|
#define FAILURE 2
|
|
#define EXCEPTION 3
|
|
#define RETRY 4
|
|
|
|
static void Log(const wchar_t *fmt, ...)
|
|
{
|
|
va_list a = nullptr;
|
|
wchar_t szDebugString[1024];
|
|
if(!lstrlenW(fmt))
|
|
return;
|
|
va_start(a,fmt);
|
|
vswprintf(szDebugString, 1024, fmt, a);
|
|
va_end(a);
|
|
if(!lstrlenW(szDebugString))
|
|
return;
|
|
|
|
wprintf(L"INFO | metrotestharness.exe | %s\n", szDebugString);
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void Fail(bool aRequestRetry, const wchar_t *fmt, ...)
|
|
{
|
|
va_list a = nullptr;
|
|
wchar_t szDebugString[1024];
|
|
if(!lstrlenW(fmt))
|
|
return;
|
|
va_start(a,fmt);
|
|
vswprintf(szDebugString, 1024, fmt, a);
|
|
va_end(a);
|
|
if(!lstrlenW(szDebugString))
|
|
return;
|
|
if (aRequestRetry) {
|
|
wprintf(L"FAIL-SHOULD-RETRY | metrotestharness.exe | %s\n", szDebugString);
|
|
} else {
|
|
wprintf(L"TEST-UNEXPECTED-FAIL | metrotestharness.exe | %s\n", szDebugString);
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
/*
|
|
* Retrieve our module dir path.
|
|
*
|
|
* @aPathBuffer Buffer to fill
|
|
*/
|
|
static bool GetModulePath(CStringW& aPathBuffer)
|
|
{
|
|
WCHAR buffer[MAX_PATH];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
|
|
if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) {
|
|
Fail(false, L"GetModuleFileName failed.");
|
|
return false;
|
|
}
|
|
|
|
WCHAR* slash = wcsrchr(buffer, '\\');
|
|
if (!slash)
|
|
return false;
|
|
*slash = '\0';
|
|
|
|
aPathBuffer = buffer;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Retrieve 'module dir path\firefox.exe'
|
|
*
|
|
* @aPathBuffer Buffer to fill
|
|
*/
|
|
static bool GetDesktopBrowserPath(CStringW& aPathBuffer)
|
|
{
|
|
if (!GetModulePath(aPathBuffer))
|
|
return false;
|
|
|
|
// ceh.exe sits in dist/bin root with the desktop browser. Since this
|
|
// is a firefox only component, this hardcoded filename is ok.
|
|
aPathBuffer.Append(L"\\");
|
|
aPathBuffer.Append(kFirefoxExe);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the app model id of the firefox metro browser.
|
|
*
|
|
* @aPathBuffer Buffer to fill
|
|
* @aCharLength Length of buffer to fill in characters
|
|
*/
|
|
static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer,
|
|
long aCharLength)
|
|
{
|
|
if (!aIDBuffer || aCharLength <= 0)
|
|
return false;
|
|
|
|
memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength));
|
|
|
|
HKEY key;
|
|
if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
|
|
0, KEY_READ, &key) != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
DWORD len = aCharLength * sizeof(WCHAR);
|
|
memset(aIDBuffer, 0, len);
|
|
if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr,
|
|
(LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
|
|
RegCloseKey(key);
|
|
return false;
|
|
}
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
|
|
// Tests.ini file cleanup helper
|
|
class DeleteTestFileHelper
|
|
{
|
|
CStringA mTestFile;
|
|
public:
|
|
DeleteTestFileHelper(CStringA& aTestFile) :
|
|
mTestFile(aTestFile) {}
|
|
~DeleteTestFileHelper() {
|
|
if (mTestFile.GetLength()) {
|
|
Log(L"Deleting %s", CStringW(mTestFile));
|
|
DeleteFileA(mTestFile);
|
|
}
|
|
}
|
|
};
|
|
|
|
static bool SetupTestOutputPipe()
|
|
{
|
|
SECURITY_ATTRIBUTES saAttr;
|
|
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
saAttr.bInheritHandle = TRUE;
|
|
saAttr.lpSecurityDescriptor = nullptr;
|
|
|
|
gTestOutputPipe =
|
|
CreateNamedPipeW(L"\\\\.\\pipe\\metrotestharness",
|
|
PIPE_ACCESS_INBOUND,
|
|
PIPE_TYPE_BYTE|PIPE_WAIT,
|
|
1,
|
|
PIPE_BUFFER_SIZE,
|
|
PIPE_BUFFER_SIZE, 0, nullptr);
|
|
|
|
if (gTestOutputPipe == INVALID_HANDLE_VALUE) {
|
|
Log(L"Failed to create named logging pipe.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void ReadPipe()
|
|
{
|
|
DWORD numBytesRead;
|
|
while (ReadFile(gTestOutputPipe, buffer, PIPE_BUFFER_SIZE,
|
|
&numBytesRead, nullptr) &&
|
|
numBytesRead) {
|
|
buffer[numBytesRead] = '\0';
|
|
printf("%s", buffer);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
static int Launch()
|
|
{
|
|
Log(L"Launching browser...");
|
|
|
|
DWORD processID;
|
|
|
|
// The interface that allows us to activate the browser
|
|
CComPtr<IApplicationActivationManager> activateMgr;
|
|
if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
|
|
CLSCTX_LOCAL_SERVER,
|
|
IID_IApplicationActivationManager,
|
|
(void**)&activateMgr))) {
|
|
Fail(false, L"CoCreateInstance CLSID_ApplicationActivationManager failed.");
|
|
return FAILURE;
|
|
}
|
|
|
|
HRESULT hr;
|
|
WCHAR appModelID[256];
|
|
// Activation is based on the browser's registered app model id
|
|
if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) {
|
|
Fail(false, L"GetDefaultBrowserAppModelID failed.");
|
|
return FAILURE;
|
|
}
|
|
Log(L"App model id='%s'", appModelID);
|
|
|
|
// Hand off focus rights if the terminal has focus to the out-of-process
|
|
// activation server (explorer.exe). Without this the metro interface
|
|
// won't launch.
|
|
hr = CoAllowSetForegroundWindow(activateMgr, nullptr);
|
|
if (FAILED(hr)) {
|
|
// Log but don't fail. This has happened on vms with certain terminals run by
|
|
// QA during mozmill testing.
|
|
Log(L"Windows focus rights hand off failed (HRESULT=0x%X). Ignoring.", hr);
|
|
}
|
|
|
|
Log(L"Harness process id: %d", GetCurrentProcessId());
|
|
|
|
// If provided, validate the firefox path passed in.
|
|
int binLen = wcslen(kFirefoxExe);
|
|
if (sFirefoxPath.GetLength() && sFirefoxPath.Right(binLen) != kFirefoxExe) {
|
|
Log(L"firefoxpath is missing a valid bin name! Assuming '%s'.", kFirefoxExe);
|
|
if (sFirefoxPath.Right(1) != L"\\") {
|
|
sFirefoxPath += L"\\";
|
|
}
|
|
sFirefoxPath += kFirefoxExe;
|
|
}
|
|
|
|
// Because we can't pass command line args, we store params in a
|
|
// tests.ini file in dist/bin which the browser picks up on launch.
|
|
CStringA testFilePath;
|
|
if (sFirefoxPath.GetLength()) {
|
|
// Use the firefoxpath passed to us by the test harness
|
|
int index = sFirefoxPath.ReverseFind('\\');
|
|
if (index == -1) {
|
|
Fail(false, L"Bad firefoxpath path");
|
|
return FAILURE;
|
|
}
|
|
testFilePath = sFirefoxPath.Mid(0, index);
|
|
testFilePath += "\\";
|
|
testFilePath += kMetroTestFile;
|
|
} else {
|
|
// Use the module path
|
|
char path[MAX_PATH];
|
|
if (!GetModuleFileNameA(nullptr, path, MAX_PATH)) {
|
|
Fail(false, L"GetModuleFileNameA errorno=%d", GetLastError());
|
|
return FAILURE;
|
|
}
|
|
char* slash = strrchr(path, '\\');
|
|
if (!slash)
|
|
return FAILURE;
|
|
*slash = '\0'; // no trailing slash
|
|
testFilePath = path;
|
|
testFilePath += "\\";
|
|
sFirefoxPath = testFilePath;
|
|
sFirefoxPath += kFirefoxExe;
|
|
testFilePath += kMetroTestFile;
|
|
}
|
|
|
|
// Make sure the firefox bin exists
|
|
if (GetFileAttributesW(sFirefoxPath) == INVALID_FILE_ATTRIBUTES) {
|
|
Fail(false, L"Invalid bin path: '%s'", sFirefoxPath);
|
|
return FAILURE;
|
|
}
|
|
|
|
Log(L"Using bin path: '%s'", sFirefoxPath);
|
|
|
|
Log(L"Writing out tests.ini to: '%s'", CStringW(testFilePath));
|
|
HANDLE hTestFile = CreateFileA(testFilePath, GENERIC_WRITE,
|
|
0, nullptr, CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
nullptr);
|
|
if (hTestFile == INVALID_HANDLE_VALUE) {
|
|
Fail(false, L"CreateFileA errorno=%d", GetLastError());
|
|
return FAILURE;
|
|
}
|
|
|
|
DeleteTestFileHelper dtf(testFilePath);
|
|
|
|
// nsAppRunner expects the first param to be the bin path, just like a
|
|
// normal startup. So prepend our bin path to our param string we write.
|
|
CStringA asciiParams = sFirefoxPath;
|
|
asciiParams += " ";
|
|
asciiParams += sAppParams;
|
|
asciiParams.Trim();
|
|
Log(L"Browser command line args: '%s'", CString(asciiParams));
|
|
if (!WriteFile(hTestFile, asciiParams, asciiParams.GetLength(),
|
|
nullptr, 0)) {
|
|
CloseHandle(hTestFile);
|
|
Fail(false, L"WriteFile errorno=%d", GetLastError());
|
|
return FAILURE;
|
|
}
|
|
FlushFileBuffers(hTestFile);
|
|
CloseHandle(hTestFile);
|
|
|
|
// Create a named stdout pipe for the browser
|
|
if (!SetupTestOutputPipe()) {
|
|
Fail(false, L"SetupTestOutputPipe failed (errno=%d)", GetLastError());
|
|
return FAILURE;
|
|
}
|
|
|
|
// Launch firefox
|
|
hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI, &processID);
|
|
if (FAILED(hr)) {
|
|
Fail(true, L"ActivateApplication result %X", hr);
|
|
return RETRY;
|
|
}
|
|
|
|
Log(L"Activation succeeded.");
|
|
|
|
// automation.py picks up on this, don't mess with it.
|
|
Log(L"METRO_BROWSER_PROCESS=%d", processID);
|
|
|
|
HANDLE child = OpenProcess(SYNCHRONIZE, FALSE, processID);
|
|
if (!child) {
|
|
Fail(false, L"Couldn't find child process. (%d)", GetLastError());
|
|
return FAILURE;
|
|
}
|
|
|
|
Log(L"Waiting on child process...");
|
|
|
|
MSG msg;
|
|
DWORD waitResult = WAIT_TIMEOUT;
|
|
HANDLE handles[2] = { child, gTestOutputPipe };
|
|
while ((waitResult = MsgWaitForMultipleObjects(2, handles, FALSE, INFINITE, QS_ALLINPUT)) != WAIT_OBJECT_0) {
|
|
if (waitResult == WAIT_FAILED) {
|
|
Log(L"Wait failed (errno=%d)", GetLastError());
|
|
break;
|
|
} else if (waitResult == WAIT_OBJECT_0 + 1) {
|
|
ReadPipe();
|
|
} else if (waitResult == WAIT_OBJECT_0 + 2 &&
|
|
PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
ReadPipe();
|
|
CloseHandle(gTestOutputPipe);
|
|
CloseHandle(child);
|
|
|
|
Log(L"Exiting.");
|
|
return SUCCESS;
|
|
}
|
|
|
|
int wmain(int argc, WCHAR* argv[])
|
|
{
|
|
CoInitialize(nullptr);
|
|
|
|
int idx;
|
|
bool firefoxParam = false;
|
|
for (idx = 1; idx < argc; idx++) {
|
|
CString param = argv[idx];
|
|
param.Trim();
|
|
|
|
// Pickup the firefox path param and store it, we'll need this
|
|
// when we create the tests.ini file.
|
|
if (param == "-firefoxpath") {
|
|
firefoxParam = true;
|
|
continue;
|
|
} else if (firefoxParam) {
|
|
firefoxParam = false;
|
|
sFirefoxPath = param;
|
|
continue;
|
|
}
|
|
|
|
sAppParams.Append(argv[idx]);
|
|
sAppParams.Append(L" ");
|
|
}
|
|
sAppParams.Trim();
|
|
int res = Launch();
|
|
CoUninitialize();
|
|
return res;
|
|
}
|