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