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