1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=8:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsRemoteClient.h"
10 # ifdef MOZ_ENABLE_DBUS
11 # include "nsDBusRemoteServer.h"
12 # include "nsDBusRemoteClient.h"
14 # include "nsGTKRemoteServer.h"
15 # include "nsXRemoteClient.h"
18 # include "nsWinRemoteServer.h"
19 # include "nsWinRemoteClient.h"
20 #elif defined(XP_DARWIN)
21 # include "nsMacRemoteServer.h"
22 # include "nsMacRemoteClient.h"
24 #include "nsRemoteService.h"
26 #include "nsIObserverService.h"
28 #include "nsServiceManagerUtils.h"
29 #include "SpecialSystemDirectory.h"
30 #include "mozilla/StaticPtr.h"
31 #include "mozilla/TimeStamp.h"
32 #include "mozilla/UniquePtr.h"
34 // Time to wait for the startup lock
35 #define START_TIMEOUT_MSEC 5000
36 #define START_SLEEP_MSEC 100
41 using namespace mozilla
;
43 nsStartupLock::nsStartupLock(nsIFile
* aDir
, nsProfileLock
& aLock
) : mDir(aDir
) {
47 nsStartupLock::~nsStartupLock() {
54 NS_IMPL_ISUPPORTS(nsRemoteService
, nsIObserver
, nsIRemoteService
)
56 nsRemoteService::nsRemoteService() : mProgram("mozilla") {
57 ToLowerCase(mProgram
);
60 void nsRemoteService::SetProgram(const char* aProgram
) {
62 ToLowerCase(mProgram
);
64 void nsRemoteService::SetProfile(nsACString
& aProfile
) { mProfile
= aProfile
; }
67 void nsRemoteService::SetStartupToken(nsACString
& aStartupToken
) {
68 mStartupToken
= aStartupToken
;
72 // Attempts to lock the given directory by polling until the timeout is reached.
73 static nsresult
AcquireLock(nsIFile
* aMutexDir
, double aTimeout
,
74 nsProfileLock
& aProfileLock
) {
75 const mozilla::TimeStamp epoch
= mozilla::TimeStamp::Now();
77 // If we have been waiting for another instance to release the lock it will
78 // have deleted the lock directory when doing so so we have to make sure it
79 // exists every time we poll for the lock.
80 nsresult rv
= aMutexDir
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
81 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_ALREADY_EXISTS
) {
82 NS_WARNING("Unable to create startup lock directory.");
86 rv
= aProfileLock
.Lock(aMutexDir
, nullptr);
87 if (NS_SUCCEEDED(rv
)) {
91 PR_Sleep(START_SLEEP_MSEC
);
92 } while ((mozilla::TimeStamp::Now() - epoch
) <
93 mozilla::TimeDuration::FromMilliseconds(aTimeout
));
95 return NS_ERROR_FAILURE
;
98 RefPtr
<nsRemoteService::StartupLockPromise
> nsRemoteService::AsyncLockStartup(
100 // If startup is already locked we can just resolve immediately.
101 RefPtr
<nsStartupLock
> lock(mStartupLock
);
103 return StartupLockPromise::CreateAndResolve(lock
, __func__
);
106 // If there is already an executing promise we can just return that otherwise
107 // we have to start a new one.
108 if (mStartupLockPromise
) {
109 return mStartupLockPromise
;
112 nsCOMPtr
<nsIFile
> mutexDir
;
113 nsresult rv
= GetSpecialSystemDirectory(OS_TemporaryDirectory
,
114 getter_AddRefs(mutexDir
));
116 return StartupLockPromise::CreateAndReject(rv
, __func__
);
119 rv
= mutexDir
->AppendNative(mProgram
);
121 return StartupLockPromise::CreateAndReject(rv
, __func__
);
124 nsCOMPtr
<nsISerialEventTarget
> queue
;
125 MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue("StartupLockTaskQueue",
126 getter_AddRefs(queue
)));
128 mStartupLockPromise
= InvokeAsync(
129 queue
, __func__
, [mutexDir
= std::move(mutexDir
), aTimeout
]() {
131 nsresult rv
= AcquireLock(mutexDir
, aTimeout
, lock
);
132 if (NS_SUCCEEDED(rv
)) {
133 return StartupLockPromise::CreateAndResolve(
134 new nsStartupLock(mutexDir
, lock
), __func__
);
137 return StartupLockPromise::CreateAndReject(rv
, __func__
);
140 // Note this is the guaranteed first `Then` and will run before any other
142 mStartupLockPromise
->Then(
143 GetCurrentSerialEventTarget(), __func__
,
144 [this, self
= RefPtr
{this}](
145 const StartupLockPromise::ResolveOrRejectValue
& aResult
) {
146 if (aResult
.IsResolve()) {
147 mStartupLock
= aResult
.ResolveValue();
149 mStartupLockPromise
= nullptr;
152 return mStartupLockPromise
;
155 already_AddRefed
<nsStartupLock
> nsRemoteService::LockStartup() {
156 MOZ_RELEASE_ASSERT(!mStartupLockPromise
,
157 "Should not have started an asynchronous lock attempt");
159 // If we're already locked just return the current lock.
160 RefPtr
<nsStartupLock
> lock(mStartupLock
);
162 return lock
.forget();
165 nsCOMPtr
<nsIFile
> mutexDir
;
166 nsresult rv
= GetSpecialSystemDirectory(OS_TemporaryDirectory
,
167 getter_AddRefs(mutexDir
));
172 rv
= mutexDir
->AppendNative(mProgram
);
177 nsProfileLock profileLock
;
178 rv
= AcquireLock(mutexDir
, START_TIMEOUT_MSEC
, profileLock
);
181 NS_WARNING("Failed to lock for startup, continuing anyway.");
185 lock
= new nsStartupLock(mutexDir
, profileLock
);
187 return lock
.forget();
190 nsresult
nsRemoteService::SendCommandLine(const nsACString
& aProfile
,
191 size_t aArgc
, const char** aArgv
,
193 if (aProfile
.IsEmpty()) {
194 return NS_ERROR_FAILURE
;
197 UniquePtr
<nsRemoteClient
> client
;
198 #ifdef MOZ_WIDGET_GTK
199 # if defined(MOZ_ENABLE_DBUS)
200 client
= MakeUnique
<nsDBusRemoteClient
>(mStartupToken
);
202 client
= MakeUnique
<nsXRemoteClient
>(mStartupToken
);
204 #elif defined(XP_WIN)
205 client
= MakeUnique
<nsWinRemoteClient
>();
206 #elif defined(XP_DARWIN)
207 client
= MakeUnique
<nsMacRemoteClient
>();
209 return NS_ERROR_NOT_AVAILABLE
;
212 nsresult rv
= client
? client
->Init() : NS_ERROR_FAILURE
;
213 NS_ENSURE_SUCCESS(rv
, rv
);
215 return client
->SendCommandLine(mProgram
.get(),
216 PromiseFlatCString(aProfile
).get(), aArgc
,
217 const_cast<const char**>(aArgv
), aRaise
);
221 nsRemoteService::SendCommandLine(const nsACString
& aProfile
,
222 const nsTArray
<nsCString
>& aArgs
,
224 #ifdef MOZ_WIDGET_GTK
225 // Linux clients block until they receive a response so it is impossible to
226 // send a remote command to the current profile.
227 if (aProfile
.Equals(mProfile
)) {
228 return NS_ERROR_INVALID_ARG
;
231 nsAutoCString binaryPath
;
233 nsTArray
<const char*> args
;
234 // Note that the command line must include an initial path to the binary but
235 // this is generally ignored.
236 args
.SetCapacity(aArgs
.Length() + 1);
237 args
.AppendElement(binaryPath
.get());
239 for (const nsCString
& arg
: aArgs
) {
240 args
.AppendElement(arg
.get());
243 return SendCommandLine(aProfile
, args
.Length(), args
.Elements(), aRaise
);
246 nsresult
nsRemoteService::StartClient() {
247 return SendCommandLine(mProfile
, gArgc
, const_cast<const char**>(gArgv
),
251 void nsRemoteService::StartupServer() {
256 if (mProfile
.IsEmpty()) {
260 #ifdef MOZ_WIDGET_GTK
261 # if defined(MOZ_ENABLE_DBUS)
262 mRemoteServer
= MakeUnique
<nsDBusRemoteServer
>();
264 mRemoteServer
= MakeUnique
<nsGTKRemoteServer
>();
266 #elif defined(XP_WIN)
267 mRemoteServer
= MakeUnique
<nsWinRemoteServer
>();
268 #elif defined(XP_DARWIN)
269 mRemoteServer
= MakeUnique
<nsMacRemoteServer
>();
274 if (!mRemoteServer
) {
278 nsresult rv
= mRemoteServer
->Startup(mProgram
.get(), mProfile
.get());
281 mRemoteServer
= nullptr;
285 nsCOMPtr
<nsIObserverService
> obs(
286 do_GetService("@mozilla.org/observer-service;1"));
288 obs
->AddObserver(this, "xpcom-shutdown", false);
289 obs
->AddObserver(this, "quit-application", false);
293 void nsRemoteService::ShutdownServer() { mRemoteServer
= nullptr; }
295 nsRemoteService::~nsRemoteService() { ShutdownServer(); }
298 nsRemoteService::Observe(nsISupports
* aSubject
, const char* aTopic
,
299 const char16_t
* aData
) {
300 // This can be xpcom-shutdown or quit-application, but it's the same either