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