Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #undef WINVER |
michael@0 | 7 | #undef _WIN32_WINNT |
michael@0 | 8 | #define WINVER 0x602 |
michael@0 | 9 | #define _WIN32_WINNT 0x602 |
michael@0 | 10 | |
michael@0 | 11 | #include <windows.h> |
michael@0 | 12 | #include <objbase.h> |
michael@0 | 13 | #include <combaseapi.h> |
michael@0 | 14 | #include <atlcore.h> |
michael@0 | 15 | #include <atlstr.h> |
michael@0 | 16 | #include <wininet.h> |
michael@0 | 17 | #include <shlobj.h> |
michael@0 | 18 | #include <shlwapi.h> |
michael@0 | 19 | #include <propkey.h> |
michael@0 | 20 | #include <propvarutil.h> |
michael@0 | 21 | #include <stdio.h> |
michael@0 | 22 | #include <stdlib.h> |
michael@0 | 23 | #include <strsafe.h> |
michael@0 | 24 | #include <io.h> |
michael@0 | 25 | #include <shellapi.h> |
michael@0 | 26 | |
michael@0 | 27 | static const WCHAR* kFirefoxExe = L"firefox.exe"; |
michael@0 | 28 | static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; |
michael@0 | 29 | |
michael@0 | 30 | // Logging pipe handle |
michael@0 | 31 | HANDLE gTestOutputPipe = INVALID_HANDLE_VALUE; |
michael@0 | 32 | // Logging pipe read buffer |
michael@0 | 33 | #define PIPE_BUFFER_SIZE 4096 |
michael@0 | 34 | char buffer[PIPE_BUFFER_SIZE + 1]; |
michael@0 | 35 | |
michael@0 | 36 | CString sAppParams; |
michael@0 | 37 | CString sFirefoxPath; |
michael@0 | 38 | |
michael@0 | 39 | // The tests file we write out for firefox.exe which contains test |
michael@0 | 40 | // startup command line paramters. |
michael@0 | 41 | #define kMetroTestFile "tests.ini" |
michael@0 | 42 | |
michael@0 | 43 | // Process exit codes for buildbotcustom logic. These are currently ignored, but |
michael@0 | 44 | // at some point releng expects to use these. |
michael@0 | 45 | #define SUCCESS 0 |
michael@0 | 46 | #define WARNINGS 1 |
michael@0 | 47 | #define FAILURE 2 |
michael@0 | 48 | #define EXCEPTION 3 |
michael@0 | 49 | #define RETRY 4 |
michael@0 | 50 | |
michael@0 | 51 | static void Log(const wchar_t *fmt, ...) |
michael@0 | 52 | { |
michael@0 | 53 | va_list a = nullptr; |
michael@0 | 54 | wchar_t szDebugString[1024]; |
michael@0 | 55 | if(!lstrlenW(fmt)) |
michael@0 | 56 | return; |
michael@0 | 57 | va_start(a,fmt); |
michael@0 | 58 | vswprintf(szDebugString, 1024, fmt, a); |
michael@0 | 59 | va_end(a); |
michael@0 | 60 | if(!lstrlenW(szDebugString)) |
michael@0 | 61 | return; |
michael@0 | 62 | |
michael@0 | 63 | wprintf(L"INFO | metrotestharness.exe | %s\n", szDebugString); |
michael@0 | 64 | fflush(stdout); |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | static void Fail(bool aRequestRetry, const wchar_t *fmt, ...) |
michael@0 | 68 | { |
michael@0 | 69 | va_list a = nullptr; |
michael@0 | 70 | wchar_t szDebugString[1024]; |
michael@0 | 71 | if(!lstrlenW(fmt)) |
michael@0 | 72 | return; |
michael@0 | 73 | va_start(a,fmt); |
michael@0 | 74 | vswprintf(szDebugString, 1024, fmt, a); |
michael@0 | 75 | va_end(a); |
michael@0 | 76 | if(!lstrlenW(szDebugString)) |
michael@0 | 77 | return; |
michael@0 | 78 | if (aRequestRetry) { |
michael@0 | 79 | wprintf(L"FAIL-SHOULD-RETRY | metrotestharness.exe | %s\n", szDebugString); |
michael@0 | 80 | } else { |
michael@0 | 81 | wprintf(L"TEST-UNEXPECTED-FAIL | metrotestharness.exe | %s\n", szDebugString); |
michael@0 | 82 | } |
michael@0 | 83 | fflush(stdout); |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | /* |
michael@0 | 87 | * Retrieve our module dir path. |
michael@0 | 88 | * |
michael@0 | 89 | * @aPathBuffer Buffer to fill |
michael@0 | 90 | */ |
michael@0 | 91 | static bool GetModulePath(CStringW& aPathBuffer) |
michael@0 | 92 | { |
michael@0 | 93 | WCHAR buffer[MAX_PATH]; |
michael@0 | 94 | memset(buffer, 0, sizeof(buffer)); |
michael@0 | 95 | |
michael@0 | 96 | if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) { |
michael@0 | 97 | Fail(false, L"GetModuleFileName failed."); |
michael@0 | 98 | return false; |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | WCHAR* slash = wcsrchr(buffer, '\\'); |
michael@0 | 102 | if (!slash) |
michael@0 | 103 | return false; |
michael@0 | 104 | *slash = '\0'; |
michael@0 | 105 | |
michael@0 | 106 | aPathBuffer = buffer; |
michael@0 | 107 | return true; |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | /* |
michael@0 | 111 | * Retrieve 'module dir path\firefox.exe' |
michael@0 | 112 | * |
michael@0 | 113 | * @aPathBuffer Buffer to fill |
michael@0 | 114 | */ |
michael@0 | 115 | static bool GetDesktopBrowserPath(CStringW& aPathBuffer) |
michael@0 | 116 | { |
michael@0 | 117 | if (!GetModulePath(aPathBuffer)) |
michael@0 | 118 | return false; |
michael@0 | 119 | |
michael@0 | 120 | // ceh.exe sits in dist/bin root with the desktop browser. Since this |
michael@0 | 121 | // is a firefox only component, this hardcoded filename is ok. |
michael@0 | 122 | aPathBuffer.Append(L"\\"); |
michael@0 | 123 | aPathBuffer.Append(kFirefoxExe); |
michael@0 | 124 | return true; |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | /* |
michael@0 | 128 | * Retrieve the app model id of the firefox metro browser. |
michael@0 | 129 | * |
michael@0 | 130 | * @aPathBuffer Buffer to fill |
michael@0 | 131 | * @aCharLength Length of buffer to fill in characters |
michael@0 | 132 | */ |
michael@0 | 133 | static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer, |
michael@0 | 134 | long aCharLength) |
michael@0 | 135 | { |
michael@0 | 136 | if (!aIDBuffer || aCharLength <= 0) |
michael@0 | 137 | return false; |
michael@0 | 138 | |
michael@0 | 139 | memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength)); |
michael@0 | 140 | |
michael@0 | 141 | HKEY key; |
michael@0 | 142 | if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey, |
michael@0 | 143 | 0, KEY_READ, &key) != ERROR_SUCCESS) { |
michael@0 | 144 | return false; |
michael@0 | 145 | } |
michael@0 | 146 | DWORD len = aCharLength * sizeof(WCHAR); |
michael@0 | 147 | memset(aIDBuffer, 0, len); |
michael@0 | 148 | if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr, |
michael@0 | 149 | (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) { |
michael@0 | 150 | RegCloseKey(key); |
michael@0 | 151 | return false; |
michael@0 | 152 | } |
michael@0 | 153 | RegCloseKey(key); |
michael@0 | 154 | return true; |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | // Tests.ini file cleanup helper |
michael@0 | 158 | class DeleteTestFileHelper |
michael@0 | 159 | { |
michael@0 | 160 | CStringA mTestFile; |
michael@0 | 161 | public: |
michael@0 | 162 | DeleteTestFileHelper(CStringA& aTestFile) : |
michael@0 | 163 | mTestFile(aTestFile) {} |
michael@0 | 164 | ~DeleteTestFileHelper() { |
michael@0 | 165 | if (mTestFile.GetLength()) { |
michael@0 | 166 | Log(L"Deleting %s", CStringW(mTestFile)); |
michael@0 | 167 | DeleteFileA(mTestFile); |
michael@0 | 168 | } |
michael@0 | 169 | } |
michael@0 | 170 | }; |
michael@0 | 171 | |
michael@0 | 172 | static bool SetupTestOutputPipe() |
michael@0 | 173 | { |
michael@0 | 174 | SECURITY_ATTRIBUTES saAttr; |
michael@0 | 175 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); |
michael@0 | 176 | saAttr.bInheritHandle = TRUE; |
michael@0 | 177 | saAttr.lpSecurityDescriptor = nullptr; |
michael@0 | 178 | |
michael@0 | 179 | gTestOutputPipe = |
michael@0 | 180 | CreateNamedPipeW(L"\\\\.\\pipe\\metrotestharness", |
michael@0 | 181 | PIPE_ACCESS_INBOUND, |
michael@0 | 182 | PIPE_TYPE_BYTE|PIPE_WAIT, |
michael@0 | 183 | 1, |
michael@0 | 184 | PIPE_BUFFER_SIZE, |
michael@0 | 185 | PIPE_BUFFER_SIZE, 0, nullptr); |
michael@0 | 186 | |
michael@0 | 187 | if (gTestOutputPipe == INVALID_HANDLE_VALUE) { |
michael@0 | 188 | Log(L"Failed to create named logging pipe."); |
michael@0 | 189 | return false; |
michael@0 | 190 | } |
michael@0 | 191 | return true; |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | static void ReadPipe() |
michael@0 | 195 | { |
michael@0 | 196 | DWORD numBytesRead; |
michael@0 | 197 | while (ReadFile(gTestOutputPipe, buffer, PIPE_BUFFER_SIZE, |
michael@0 | 198 | &numBytesRead, nullptr) && |
michael@0 | 199 | numBytesRead) { |
michael@0 | 200 | buffer[numBytesRead] = '\0'; |
michael@0 | 201 | printf("%s", buffer); |
michael@0 | 202 | fflush(stdout); |
michael@0 | 203 | } |
michael@0 | 204 | } |
michael@0 | 205 | |
michael@0 | 206 | static int Launch() |
michael@0 | 207 | { |
michael@0 | 208 | Log(L"Launching browser..."); |
michael@0 | 209 | |
michael@0 | 210 | DWORD processID; |
michael@0 | 211 | |
michael@0 | 212 | // The interface that allows us to activate the browser |
michael@0 | 213 | CComPtr<IApplicationActivationManager> activateMgr; |
michael@0 | 214 | if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, |
michael@0 | 215 | CLSCTX_LOCAL_SERVER, |
michael@0 | 216 | IID_IApplicationActivationManager, |
michael@0 | 217 | (void**)&activateMgr))) { |
michael@0 | 218 | Fail(false, L"CoCreateInstance CLSID_ApplicationActivationManager failed."); |
michael@0 | 219 | return FAILURE; |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | HRESULT hr; |
michael@0 | 223 | WCHAR appModelID[256]; |
michael@0 | 224 | // Activation is based on the browser's registered app model id |
michael@0 | 225 | if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) { |
michael@0 | 226 | Fail(false, L"GetDefaultBrowserAppModelID failed."); |
michael@0 | 227 | return FAILURE; |
michael@0 | 228 | } |
michael@0 | 229 | Log(L"App model id='%s'", appModelID); |
michael@0 | 230 | |
michael@0 | 231 | // Hand off focus rights if the terminal has focus to the out-of-process |
michael@0 | 232 | // activation server (explorer.exe). Without this the metro interface |
michael@0 | 233 | // won't launch. |
michael@0 | 234 | hr = CoAllowSetForegroundWindow(activateMgr, nullptr); |
michael@0 | 235 | if (FAILED(hr)) { |
michael@0 | 236 | // Log but don't fail. This has happened on vms with certain terminals run by |
michael@0 | 237 | // QA during mozmill testing. |
michael@0 | 238 | Log(L"Windows focus rights hand off failed (HRESULT=0x%X). Ignoring.", hr); |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | Log(L"Harness process id: %d", GetCurrentProcessId()); |
michael@0 | 242 | |
michael@0 | 243 | // If provided, validate the firefox path passed in. |
michael@0 | 244 | int binLen = wcslen(kFirefoxExe); |
michael@0 | 245 | if (sFirefoxPath.GetLength() && sFirefoxPath.Right(binLen) != kFirefoxExe) { |
michael@0 | 246 | Log(L"firefoxpath is missing a valid bin name! Assuming '%s'.", kFirefoxExe); |
michael@0 | 247 | if (sFirefoxPath.Right(1) != L"\\") { |
michael@0 | 248 | sFirefoxPath += L"\\"; |
michael@0 | 249 | } |
michael@0 | 250 | sFirefoxPath += kFirefoxExe; |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | // Because we can't pass command line args, we store params in a |
michael@0 | 254 | // tests.ini file in dist/bin which the browser picks up on launch. |
michael@0 | 255 | CStringA testFilePath; |
michael@0 | 256 | if (sFirefoxPath.GetLength()) { |
michael@0 | 257 | // Use the firefoxpath passed to us by the test harness |
michael@0 | 258 | int index = sFirefoxPath.ReverseFind('\\'); |
michael@0 | 259 | if (index == -1) { |
michael@0 | 260 | Fail(false, L"Bad firefoxpath path"); |
michael@0 | 261 | return FAILURE; |
michael@0 | 262 | } |
michael@0 | 263 | testFilePath = sFirefoxPath.Mid(0, index); |
michael@0 | 264 | testFilePath += "\\"; |
michael@0 | 265 | testFilePath += kMetroTestFile; |
michael@0 | 266 | } else { |
michael@0 | 267 | // Use the module path |
michael@0 | 268 | char path[MAX_PATH]; |
michael@0 | 269 | if (!GetModuleFileNameA(nullptr, path, MAX_PATH)) { |
michael@0 | 270 | Fail(false, L"GetModuleFileNameA errorno=%d", GetLastError()); |
michael@0 | 271 | return FAILURE; |
michael@0 | 272 | } |
michael@0 | 273 | char* slash = strrchr(path, '\\'); |
michael@0 | 274 | if (!slash) |
michael@0 | 275 | return FAILURE; |
michael@0 | 276 | *slash = '\0'; // no trailing slash |
michael@0 | 277 | testFilePath = path; |
michael@0 | 278 | testFilePath += "\\"; |
michael@0 | 279 | sFirefoxPath = testFilePath; |
michael@0 | 280 | sFirefoxPath += kFirefoxExe; |
michael@0 | 281 | testFilePath += kMetroTestFile; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | // Make sure the firefox bin exists |
michael@0 | 285 | if (GetFileAttributesW(sFirefoxPath) == INVALID_FILE_ATTRIBUTES) { |
michael@0 | 286 | Fail(false, L"Invalid bin path: '%s'", sFirefoxPath); |
michael@0 | 287 | return FAILURE; |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | Log(L"Using bin path: '%s'", sFirefoxPath); |
michael@0 | 291 | |
michael@0 | 292 | Log(L"Writing out tests.ini to: '%s'", CStringW(testFilePath)); |
michael@0 | 293 | HANDLE hTestFile = CreateFileA(testFilePath, GENERIC_WRITE, |
michael@0 | 294 | 0, nullptr, CREATE_ALWAYS, |
michael@0 | 295 | FILE_ATTRIBUTE_NORMAL, |
michael@0 | 296 | nullptr); |
michael@0 | 297 | if (hTestFile == INVALID_HANDLE_VALUE) { |
michael@0 | 298 | Fail(false, L"CreateFileA errorno=%d", GetLastError()); |
michael@0 | 299 | return FAILURE; |
michael@0 | 300 | } |
michael@0 | 301 | |
michael@0 | 302 | DeleteTestFileHelper dtf(testFilePath); |
michael@0 | 303 | |
michael@0 | 304 | // nsAppRunner expects the first param to be the bin path, just like a |
michael@0 | 305 | // normal startup. So prepend our bin path to our param string we write. |
michael@0 | 306 | CStringA asciiParams = sFirefoxPath; |
michael@0 | 307 | asciiParams += " "; |
michael@0 | 308 | asciiParams += sAppParams; |
michael@0 | 309 | asciiParams.Trim(); |
michael@0 | 310 | Log(L"Browser command line args: '%s'", CString(asciiParams)); |
michael@0 | 311 | if (!WriteFile(hTestFile, asciiParams, asciiParams.GetLength(), |
michael@0 | 312 | nullptr, 0)) { |
michael@0 | 313 | CloseHandle(hTestFile); |
michael@0 | 314 | Fail(false, L"WriteFile errorno=%d", GetLastError()); |
michael@0 | 315 | return FAILURE; |
michael@0 | 316 | } |
michael@0 | 317 | FlushFileBuffers(hTestFile); |
michael@0 | 318 | CloseHandle(hTestFile); |
michael@0 | 319 | |
michael@0 | 320 | // Create a named stdout pipe for the browser |
michael@0 | 321 | if (!SetupTestOutputPipe()) { |
michael@0 | 322 | Fail(false, L"SetupTestOutputPipe failed (errno=%d)", GetLastError()); |
michael@0 | 323 | return FAILURE; |
michael@0 | 324 | } |
michael@0 | 325 | |
michael@0 | 326 | // Launch firefox |
michael@0 | 327 | hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI, &processID); |
michael@0 | 328 | if (FAILED(hr)) { |
michael@0 | 329 | Fail(true, L"ActivateApplication result %X", hr); |
michael@0 | 330 | return RETRY; |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | Log(L"Activation succeeded."); |
michael@0 | 334 | |
michael@0 | 335 | // automation.py picks up on this, don't mess with it. |
michael@0 | 336 | Log(L"METRO_BROWSER_PROCESS=%d", processID); |
michael@0 | 337 | |
michael@0 | 338 | HANDLE child = OpenProcess(SYNCHRONIZE, FALSE, processID); |
michael@0 | 339 | if (!child) { |
michael@0 | 340 | Fail(false, L"Couldn't find child process. (%d)", GetLastError()); |
michael@0 | 341 | return FAILURE; |
michael@0 | 342 | } |
michael@0 | 343 | |
michael@0 | 344 | Log(L"Waiting on child process..."); |
michael@0 | 345 | |
michael@0 | 346 | MSG msg; |
michael@0 | 347 | DWORD waitResult = WAIT_TIMEOUT; |
michael@0 | 348 | HANDLE handles[2] = { child, gTestOutputPipe }; |
michael@0 | 349 | while ((waitResult = MsgWaitForMultipleObjects(2, handles, FALSE, INFINITE, QS_ALLINPUT)) != WAIT_OBJECT_0) { |
michael@0 | 350 | if (waitResult == WAIT_FAILED) { |
michael@0 | 351 | Log(L"Wait failed (errno=%d)", GetLastError()); |
michael@0 | 352 | break; |
michael@0 | 353 | } else if (waitResult == WAIT_OBJECT_0 + 1) { |
michael@0 | 354 | ReadPipe(); |
michael@0 | 355 | } else if (waitResult == WAIT_OBJECT_0 + 2 && |
michael@0 | 356 | PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { |
michael@0 | 357 | TranslateMessage(&msg); |
michael@0 | 358 | DispatchMessage(&msg); |
michael@0 | 359 | } |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | ReadPipe(); |
michael@0 | 363 | CloseHandle(gTestOutputPipe); |
michael@0 | 364 | CloseHandle(child); |
michael@0 | 365 | |
michael@0 | 366 | Log(L"Exiting."); |
michael@0 | 367 | return SUCCESS; |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | int wmain(int argc, WCHAR* argv[]) |
michael@0 | 371 | { |
michael@0 | 372 | CoInitialize(nullptr); |
michael@0 | 373 | |
michael@0 | 374 | int idx; |
michael@0 | 375 | bool firefoxParam = false; |
michael@0 | 376 | for (idx = 1; idx < argc; idx++) { |
michael@0 | 377 | CString param = argv[idx]; |
michael@0 | 378 | param.Trim(); |
michael@0 | 379 | |
michael@0 | 380 | // Pickup the firefox path param and store it, we'll need this |
michael@0 | 381 | // when we create the tests.ini file. |
michael@0 | 382 | if (param == "-firefoxpath") { |
michael@0 | 383 | firefoxParam = true; |
michael@0 | 384 | continue; |
michael@0 | 385 | } else if (firefoxParam) { |
michael@0 | 386 | firefoxParam = false; |
michael@0 | 387 | sFirefoxPath = param; |
michael@0 | 388 | continue; |
michael@0 | 389 | } |
michael@0 | 390 | |
michael@0 | 391 | sAppParams.Append(argv[idx]); |
michael@0 | 392 | sAppParams.Append(L" "); |
michael@0 | 393 | } |
michael@0 | 394 | sAppParams.Trim(); |
michael@0 | 395 | int res = Launch(); |
michael@0 | 396 | CoUninitialize(); |
michael@0 | 397 | return res; |
michael@0 | 398 | } |