|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
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 |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /***************************************************************************** |
|
7 * |
|
8 * nsProcess is used to execute new processes and specify if you want to |
|
9 * wait (blocking) or continue (non-blocking). |
|
10 * |
|
11 ***************************************************************************** |
|
12 */ |
|
13 |
|
14 #include "mozilla/ArrayUtils.h" |
|
15 |
|
16 #include "nsCOMPtr.h" |
|
17 #include "nsAutoPtr.h" |
|
18 #include "nsMemory.h" |
|
19 #include "nsProcess.h" |
|
20 #include "prio.h" |
|
21 #include "prenv.h" |
|
22 #include "nsCRT.h" |
|
23 #include "nsThreadUtils.h" |
|
24 #include "nsIObserverService.h" |
|
25 #include "mozilla/Services.h" |
|
26 |
|
27 #include <stdlib.h> |
|
28 |
|
29 #if defined(PROCESSMODEL_WINAPI) |
|
30 #include "prmem.h" |
|
31 #include "nsString.h" |
|
32 #include "nsLiteralString.h" |
|
33 #include "nsReadableUtils.h" |
|
34 #else |
|
35 #ifdef XP_MACOSX |
|
36 #include <crt_externs.h> |
|
37 #include <spawn.h> |
|
38 #include <sys/wait.h> |
|
39 #endif |
|
40 #include <sys/types.h> |
|
41 #include <signal.h> |
|
42 #endif |
|
43 |
|
44 using namespace mozilla; |
|
45 |
|
46 #ifdef XP_MACOSX |
|
47 cpu_type_t pref_cpu_types[2] = { |
|
48 #if defined(__i386__) |
|
49 CPU_TYPE_X86, |
|
50 #elif defined(__x86_64__) |
|
51 CPU_TYPE_X86_64, |
|
52 #elif defined(__ppc__) |
|
53 CPU_TYPE_POWERPC, |
|
54 #endif |
|
55 CPU_TYPE_ANY }; |
|
56 #endif |
|
57 |
|
58 //-------------------------------------------------------------------// |
|
59 // nsIProcess implementation |
|
60 //-------------------------------------------------------------------// |
|
61 NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, |
|
62 nsIObserver) |
|
63 |
|
64 //Constructor |
|
65 nsProcess::nsProcess() |
|
66 : mThread(nullptr) |
|
67 , mLock("nsProcess.mLock") |
|
68 , mShutdown(false) |
|
69 , mBlocking(false) |
|
70 , mPid(-1) |
|
71 , mObserver(nullptr) |
|
72 , mWeakObserver(nullptr) |
|
73 , mExitValue(-1) |
|
74 #if !defined(XP_MACOSX) |
|
75 , mProcess(nullptr) |
|
76 #endif |
|
77 { |
|
78 } |
|
79 |
|
80 //Destructor |
|
81 nsProcess::~nsProcess() |
|
82 { |
|
83 } |
|
84 |
|
85 NS_IMETHODIMP |
|
86 nsProcess::Init(nsIFile* executable) |
|
87 { |
|
88 if (mExecutable) |
|
89 return NS_ERROR_ALREADY_INITIALIZED; |
|
90 |
|
91 if (NS_WARN_IF(!executable)) |
|
92 return NS_ERROR_INVALID_ARG; |
|
93 bool isFile; |
|
94 |
|
95 //First make sure the file exists |
|
96 nsresult rv = executable->IsFile(&isFile); |
|
97 if (NS_FAILED(rv)) return rv; |
|
98 if (!isFile) |
|
99 return NS_ERROR_FAILURE; |
|
100 |
|
101 //Store the nsIFile in mExecutable |
|
102 mExecutable = executable; |
|
103 //Get the path because it is needed by the NSPR process creation |
|
104 #ifdef XP_WIN |
|
105 rv = mExecutable->GetTarget(mTargetPath); |
|
106 if (NS_FAILED(rv) || mTargetPath.IsEmpty() ) |
|
107 #endif |
|
108 rv = mExecutable->GetPath(mTargetPath); |
|
109 |
|
110 return rv; |
|
111 } |
|
112 |
|
113 |
|
114 #if defined(XP_WIN) |
|
115 // Out param `wideCmdLine` must be PR_Freed by the caller. |
|
116 static int assembleCmdLine(char *const *argv, wchar_t **wideCmdLine, |
|
117 UINT codePage) |
|
118 { |
|
119 char *const *arg; |
|
120 char *p, *q, *cmdLine; |
|
121 int cmdLineSize; |
|
122 int numBackslashes; |
|
123 int i; |
|
124 int argNeedQuotes; |
|
125 |
|
126 /* |
|
127 * Find out how large the command line buffer should be. |
|
128 */ |
|
129 cmdLineSize = 0; |
|
130 for (arg = argv; *arg; arg++) { |
|
131 /* |
|
132 * \ and " need to be escaped by a \. In the worst case, |
|
133 * every character is a \ or ", so the string of length |
|
134 * may double. If we quote an argument, that needs two ". |
|
135 * Finally, we need a space between arguments, and |
|
136 * a null byte at the end of command line. |
|
137 */ |
|
138 cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */ |
|
139 + 2 /* we quote every argument */ |
|
140 + 1; /* space in between, or final null */ |
|
141 } |
|
142 p = cmdLine = (char *) PR_MALLOC(cmdLineSize*sizeof(char)); |
|
143 if (p == nullptr) { |
|
144 return -1; |
|
145 } |
|
146 |
|
147 for (arg = argv; *arg; arg++) { |
|
148 /* Add a space to separates the arguments */ |
|
149 if (arg != argv) { |
|
150 *p++ = ' '; |
|
151 } |
|
152 q = *arg; |
|
153 numBackslashes = 0; |
|
154 argNeedQuotes = 0; |
|
155 |
|
156 /* If the argument contains white space, it needs to be quoted. */ |
|
157 if (strpbrk(*arg, " \f\n\r\t\v")) { |
|
158 argNeedQuotes = 1; |
|
159 } |
|
160 |
|
161 if (argNeedQuotes) { |
|
162 *p++ = '"'; |
|
163 } |
|
164 while (*q) { |
|
165 if (*q == '\\') { |
|
166 numBackslashes++; |
|
167 q++; |
|
168 } else if (*q == '"') { |
|
169 if (numBackslashes) { |
|
170 /* |
|
171 * Double the backslashes since they are followed |
|
172 * by a quote |
|
173 */ |
|
174 for (i = 0; i < 2 * numBackslashes; i++) { |
|
175 *p++ = '\\'; |
|
176 } |
|
177 numBackslashes = 0; |
|
178 } |
|
179 /* To escape the quote */ |
|
180 *p++ = '\\'; |
|
181 *p++ = *q++; |
|
182 } else { |
|
183 if (numBackslashes) { |
|
184 /* |
|
185 * Backslashes are not followed by a quote, so |
|
186 * don't need to double the backslashes. |
|
187 */ |
|
188 for (i = 0; i < numBackslashes; i++) { |
|
189 *p++ = '\\'; |
|
190 } |
|
191 numBackslashes = 0; |
|
192 } |
|
193 *p++ = *q++; |
|
194 } |
|
195 } |
|
196 |
|
197 /* Now we are at the end of this argument */ |
|
198 if (numBackslashes) { |
|
199 /* |
|
200 * Double the backslashes if we have a quote string |
|
201 * delimiter at the end. |
|
202 */ |
|
203 if (argNeedQuotes) { |
|
204 numBackslashes *= 2; |
|
205 } |
|
206 for (i = 0; i < numBackslashes; i++) { |
|
207 *p++ = '\\'; |
|
208 } |
|
209 } |
|
210 if (argNeedQuotes) { |
|
211 *p++ = '"'; |
|
212 } |
|
213 } |
|
214 |
|
215 *p = '\0'; |
|
216 int32_t numChars = MultiByteToWideChar(codePage, 0, cmdLine, -1, nullptr, 0); |
|
217 *wideCmdLine = (wchar_t *) PR_MALLOC(numChars*sizeof(wchar_t)); |
|
218 MultiByteToWideChar(codePage, 0, cmdLine, -1, *wideCmdLine, numChars); |
|
219 PR_Free(cmdLine); |
|
220 return 0; |
|
221 } |
|
222 #endif |
|
223 |
|
224 void nsProcess::Monitor(void *arg) |
|
225 { |
|
226 nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(arg)); |
|
227 |
|
228 if (!process->mBlocking) |
|
229 PR_SetCurrentThreadName("RunProcess"); |
|
230 |
|
231 #if defined(PROCESSMODEL_WINAPI) |
|
232 DWORD dwRetVal; |
|
233 unsigned long exitCode = -1; |
|
234 |
|
235 dwRetVal = WaitForSingleObject(process->mProcess, INFINITE); |
|
236 if (dwRetVal != WAIT_FAILED) { |
|
237 if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) |
|
238 exitCode = -1; |
|
239 } |
|
240 |
|
241 // Lock in case Kill or GetExitCode are called during this |
|
242 { |
|
243 MutexAutoLock lock(process->mLock); |
|
244 CloseHandle(process->mProcess); |
|
245 process->mProcess = nullptr; |
|
246 process->mExitValue = exitCode; |
|
247 if (process->mShutdown) |
|
248 return; |
|
249 } |
|
250 #else |
|
251 #ifdef XP_MACOSX |
|
252 int exitCode = -1; |
|
253 int status = 0; |
|
254 if (waitpid(process->mPid, &status, 0) == process->mPid) { |
|
255 if (WIFEXITED(status)) { |
|
256 exitCode = WEXITSTATUS(status); |
|
257 } |
|
258 else if(WIFSIGNALED(status)) { |
|
259 exitCode = 256; // match NSPR's signal exit status |
|
260 } |
|
261 } |
|
262 #else |
|
263 int32_t exitCode = -1; |
|
264 if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) |
|
265 exitCode = -1; |
|
266 #endif |
|
267 |
|
268 // Lock in case Kill or GetExitCode are called during this |
|
269 { |
|
270 MutexAutoLock lock(process->mLock); |
|
271 #if !defined(XP_MACOSX) |
|
272 process->mProcess = nullptr; |
|
273 #endif |
|
274 process->mExitValue = exitCode; |
|
275 if (process->mShutdown) |
|
276 return; |
|
277 } |
|
278 #endif |
|
279 |
|
280 // If we ran a background thread for the monitor then notify on the main |
|
281 // thread |
|
282 if (NS_IsMainThread()) { |
|
283 process->ProcessComplete(); |
|
284 } |
|
285 else { |
|
286 nsCOMPtr<nsIRunnable> event = |
|
287 NS_NewRunnableMethod(process, &nsProcess::ProcessComplete); |
|
288 NS_DispatchToMainThread(event); |
|
289 } |
|
290 } |
|
291 |
|
292 void nsProcess::ProcessComplete() |
|
293 { |
|
294 if (mThread) { |
|
295 nsCOMPtr<nsIObserverService> os = |
|
296 mozilla::services::GetObserverService(); |
|
297 if (os) |
|
298 os->RemoveObserver(this, "xpcom-shutdown"); |
|
299 PR_JoinThread(mThread); |
|
300 mThread = nullptr; |
|
301 } |
|
302 |
|
303 const char* topic; |
|
304 if (mExitValue < 0) |
|
305 topic = "process-failed"; |
|
306 else |
|
307 topic = "process-finished"; |
|
308 |
|
309 mPid = -1; |
|
310 nsCOMPtr<nsIObserver> observer; |
|
311 if (mWeakObserver) |
|
312 observer = do_QueryReferent(mWeakObserver); |
|
313 else if (mObserver) |
|
314 observer = mObserver; |
|
315 mObserver = nullptr; |
|
316 mWeakObserver = nullptr; |
|
317 |
|
318 if (observer) |
|
319 observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); |
|
320 } |
|
321 |
|
322 // XXXldb |args| has the wrong const-ness |
|
323 NS_IMETHODIMP |
|
324 nsProcess::Run(bool blocking, const char **args, uint32_t count) |
|
325 { |
|
326 return CopyArgsAndRunProcess(blocking, args, count, nullptr, false); |
|
327 } |
|
328 |
|
329 // XXXldb |args| has the wrong const-ness |
|
330 NS_IMETHODIMP |
|
331 nsProcess::RunAsync(const char **args, uint32_t count, |
|
332 nsIObserver* observer, bool holdWeak) |
|
333 { |
|
334 return CopyArgsAndRunProcess(false, args, count, observer, holdWeak); |
|
335 } |
|
336 |
|
337 nsresult |
|
338 nsProcess::CopyArgsAndRunProcess(bool blocking, const char** args, |
|
339 uint32_t count, nsIObserver* observer, |
|
340 bool holdWeak) |
|
341 { |
|
342 // Add one to the count for the program name and one for null termination. |
|
343 char **my_argv = nullptr; |
|
344 my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2)); |
|
345 if (!my_argv) { |
|
346 return NS_ERROR_OUT_OF_MEMORY; |
|
347 } |
|
348 |
|
349 my_argv[0] = ToNewUTF8String(mTargetPath); |
|
350 |
|
351 for (uint32_t i = 0; i < count; i++) { |
|
352 my_argv[i + 1] = const_cast<char*>(args[i]); |
|
353 } |
|
354 |
|
355 my_argv[count + 1] = nullptr; |
|
356 |
|
357 nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, false); |
|
358 |
|
359 NS_Free(my_argv[0]); |
|
360 NS_Free(my_argv); |
|
361 return rv; |
|
362 } |
|
363 |
|
364 // XXXldb |args| has the wrong const-ness |
|
365 NS_IMETHODIMP |
|
366 nsProcess::Runw(bool blocking, const char16_t **args, uint32_t count) |
|
367 { |
|
368 return CopyArgsAndRunProcessw(blocking, args, count, nullptr, false); |
|
369 } |
|
370 |
|
371 // XXXldb |args| has the wrong const-ness |
|
372 NS_IMETHODIMP |
|
373 nsProcess::RunwAsync(const char16_t **args, uint32_t count, |
|
374 nsIObserver* observer, bool holdWeak) |
|
375 { |
|
376 return CopyArgsAndRunProcessw(false, args, count, observer, holdWeak); |
|
377 } |
|
378 |
|
379 nsresult |
|
380 nsProcess::CopyArgsAndRunProcessw(bool blocking, const char16_t** args, |
|
381 uint32_t count, nsIObserver* observer, |
|
382 bool holdWeak) |
|
383 { |
|
384 // Add one to the count for the program name and one for null termination. |
|
385 char **my_argv = nullptr; |
|
386 my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2)); |
|
387 if (!my_argv) { |
|
388 return NS_ERROR_OUT_OF_MEMORY; |
|
389 } |
|
390 |
|
391 my_argv[0] = ToNewUTF8String(mTargetPath); |
|
392 |
|
393 for (uint32_t i = 0; i < count; i++) { |
|
394 my_argv[i + 1] = ToNewUTF8String(nsDependentString(args[i])); |
|
395 } |
|
396 |
|
397 my_argv[count + 1] = nullptr; |
|
398 |
|
399 nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, true); |
|
400 |
|
401 for (uint32_t i = 0; i <= count; i++) { |
|
402 NS_Free(my_argv[i]); |
|
403 } |
|
404 NS_Free(my_argv); |
|
405 return rv; |
|
406 } |
|
407 |
|
408 nsresult |
|
409 nsProcess::RunProcess(bool blocking, char **my_argv, nsIObserver* observer, |
|
410 bool holdWeak, bool argsUTF8) |
|
411 { |
|
412 if (NS_WARN_IF(!mExecutable)) |
|
413 return NS_ERROR_NOT_INITIALIZED; |
|
414 if (NS_WARN_IF(mThread)) |
|
415 return NS_ERROR_ALREADY_INITIALIZED; |
|
416 |
|
417 if (observer) { |
|
418 if (holdWeak) { |
|
419 mWeakObserver = do_GetWeakReference(observer); |
|
420 if (!mWeakObserver) |
|
421 return NS_NOINTERFACE; |
|
422 } |
|
423 else { |
|
424 mObserver = observer; |
|
425 } |
|
426 } |
|
427 |
|
428 mExitValue = -1; |
|
429 mPid = -1; |
|
430 |
|
431 #if defined(PROCESSMODEL_WINAPI) |
|
432 BOOL retVal; |
|
433 wchar_t *cmdLine = nullptr; |
|
434 |
|
435 // The 'argv' array is null-terminated and always starts with the program path. |
|
436 // If the second slot is non-null then arguments are being passed. |
|
437 if (my_argv[1] != nullptr && |
|
438 assembleCmdLine(my_argv + 1, &cmdLine, argsUTF8 ? CP_UTF8 : CP_ACP) == -1) { |
|
439 return NS_ERROR_FILE_EXECUTION_FAILED; |
|
440 } |
|
441 |
|
442 /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows |
|
443 * from appearing. This makes behavior the same on all platforms. The flag |
|
444 * will not have any effect on non-console applications. |
|
445 */ |
|
446 |
|
447 // The program name in my_argv[0] is always UTF-8 |
|
448 NS_ConvertUTF8toUTF16 wideFile(my_argv[0]); |
|
449 |
|
450 SHELLEXECUTEINFOW sinfo; |
|
451 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); |
|
452 sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); |
|
453 sinfo.hwnd = nullptr; |
|
454 sinfo.lpFile = wideFile.get(); |
|
455 sinfo.nShow = SW_SHOWNORMAL; |
|
456 sinfo.fMask = SEE_MASK_FLAG_DDEWAIT | |
|
457 SEE_MASK_NO_CONSOLE | |
|
458 SEE_MASK_NOCLOSEPROCESS; |
|
459 |
|
460 if (cmdLine) |
|
461 sinfo.lpParameters = cmdLine; |
|
462 |
|
463 retVal = ShellExecuteExW(&sinfo); |
|
464 if (!retVal) { |
|
465 return NS_ERROR_FILE_EXECUTION_FAILED; |
|
466 } |
|
467 |
|
468 mProcess = sinfo.hProcess; |
|
469 |
|
470 if (cmdLine) |
|
471 PR_Free(cmdLine); |
|
472 |
|
473 mPid = GetProcessId(mProcess); |
|
474 #elif defined(XP_MACOSX) |
|
475 // Initialize spawn attributes. |
|
476 posix_spawnattr_t spawnattr; |
|
477 if (posix_spawnattr_init(&spawnattr) != 0) { |
|
478 return NS_ERROR_FAILURE; |
|
479 } |
|
480 |
|
481 // Set spawn attributes. |
|
482 size_t attr_count = ArrayLength(pref_cpu_types); |
|
483 size_t attr_ocount = 0; |
|
484 if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 || |
|
485 attr_ocount != attr_count) { |
|
486 posix_spawnattr_destroy(&spawnattr); |
|
487 return NS_ERROR_FAILURE; |
|
488 } |
|
489 |
|
490 // Note that the 'argv' array is already null-terminated, which 'posix_spawnp' requires. |
|
491 pid_t newPid = 0; |
|
492 int result = posix_spawnp(&newPid, my_argv[0], nullptr, &spawnattr, my_argv, *_NSGetEnviron()); |
|
493 mPid = static_cast<int32_t>(newPid); |
|
494 |
|
495 posix_spawnattr_destroy(&spawnattr); |
|
496 |
|
497 if (result != 0) { |
|
498 return NS_ERROR_FAILURE; |
|
499 } |
|
500 #else |
|
501 mProcess = PR_CreateProcess(my_argv[0], my_argv, nullptr, nullptr); |
|
502 if (!mProcess) |
|
503 return NS_ERROR_FAILURE; |
|
504 struct MYProcess { |
|
505 uint32_t pid; |
|
506 }; |
|
507 MYProcess* ptrProc = (MYProcess *) mProcess; |
|
508 mPid = ptrProc->pid; |
|
509 #endif |
|
510 |
|
511 NS_ADDREF_THIS(); |
|
512 mBlocking = blocking; |
|
513 if (blocking) { |
|
514 Monitor(this); |
|
515 if (mExitValue < 0) |
|
516 return NS_ERROR_FILE_EXECUTION_FAILED; |
|
517 } |
|
518 else { |
|
519 mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, |
|
520 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, |
|
521 PR_JOINABLE_THREAD, 0); |
|
522 if (!mThread) { |
|
523 NS_RELEASE_THIS(); |
|
524 return NS_ERROR_FAILURE; |
|
525 } |
|
526 |
|
527 // It isn't a failure if we just can't watch for shutdown |
|
528 nsCOMPtr<nsIObserverService> os = |
|
529 mozilla::services::GetObserverService(); |
|
530 if (os) |
|
531 os->AddObserver(this, "xpcom-shutdown", false); |
|
532 } |
|
533 |
|
534 return NS_OK; |
|
535 } |
|
536 |
|
537 NS_IMETHODIMP nsProcess::GetIsRunning(bool *aIsRunning) |
|
538 { |
|
539 if (mThread) |
|
540 *aIsRunning = true; |
|
541 else |
|
542 *aIsRunning = false; |
|
543 |
|
544 return NS_OK; |
|
545 } |
|
546 |
|
547 NS_IMETHODIMP |
|
548 nsProcess::GetPid(uint32_t *aPid) |
|
549 { |
|
550 if (!mThread) |
|
551 return NS_ERROR_FAILURE; |
|
552 if (mPid < 0) |
|
553 return NS_ERROR_NOT_IMPLEMENTED; |
|
554 *aPid = mPid; |
|
555 return NS_OK; |
|
556 } |
|
557 |
|
558 NS_IMETHODIMP |
|
559 nsProcess::Kill() |
|
560 { |
|
561 if (!mThread) |
|
562 return NS_ERROR_FAILURE; |
|
563 |
|
564 { |
|
565 MutexAutoLock lock(mLock); |
|
566 #if defined(PROCESSMODEL_WINAPI) |
|
567 if (TerminateProcess(mProcess, 0) == 0) |
|
568 return NS_ERROR_FAILURE; |
|
569 #elif defined(XP_MACOSX) |
|
570 if (kill(mPid, SIGKILL) != 0) |
|
571 return NS_ERROR_FAILURE; |
|
572 #else |
|
573 if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) |
|
574 return NS_ERROR_FAILURE; |
|
575 #endif |
|
576 } |
|
577 |
|
578 // We must null out mThread if we want IsRunning to return false immediately |
|
579 // after this call. |
|
580 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
|
581 if (os) |
|
582 os->RemoveObserver(this, "xpcom-shutdown"); |
|
583 PR_JoinThread(mThread); |
|
584 mThread = nullptr; |
|
585 |
|
586 return NS_OK; |
|
587 } |
|
588 |
|
589 NS_IMETHODIMP |
|
590 nsProcess::GetExitValue(int32_t *aExitValue) |
|
591 { |
|
592 MutexAutoLock lock(mLock); |
|
593 |
|
594 *aExitValue = mExitValue; |
|
595 |
|
596 return NS_OK; |
|
597 } |
|
598 |
|
599 NS_IMETHODIMP |
|
600 nsProcess::Observe(nsISupports* subject, const char* topic, const char16_t* data) |
|
601 { |
|
602 // Shutting down, drop all references |
|
603 if (mThread) { |
|
604 nsCOMPtr<nsIObserverService> os = |
|
605 mozilla::services::GetObserverService(); |
|
606 if (os) |
|
607 os->RemoveObserver(this, "xpcom-shutdown"); |
|
608 mThread = nullptr; |
|
609 } |
|
610 |
|
611 mObserver = nullptr; |
|
612 mWeakObserver = nullptr; |
|
613 |
|
614 MutexAutoLock lock(mLock); |
|
615 mShutdown = true; |
|
616 |
|
617 return NS_OK; |
|
618 } |