Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / toolkit / components / remote / nsRemoteService.cpp
blob46860f6768d4caabf26f8d70bd213bbb1ccb87d0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=8:
3 */
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"
9 #ifdef MOZ_WIDGET_GTK
10 # ifdef MOZ_ENABLE_DBUS
11 # include "nsDBusRemoteServer.h"
12 # include "nsDBusRemoteClient.h"
13 # else
14 # include "nsGTKRemoteServer.h"
15 # include "nsXRemoteClient.h"
16 # endif
17 #elif defined(XP_WIN)
18 # include "nsWinRemoteServer.h"
19 # include "nsWinRemoteClient.h"
20 #elif defined(XP_DARWIN)
21 # include "nsMacRemoteServer.h"
22 # include "nsMacRemoteClient.h"
23 #endif
24 #include "nsRemoteService.h"
26 #include "nsIObserverService.h"
27 #include "nsString.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
38 extern int gArgc;
39 extern char** gArgv;
41 using namespace mozilla;
43 nsStartupLock::nsStartupLock(nsIFile* aDir, nsProfileLock& aLock) : mDir(aDir) {
44 mLock = aLock;
47 nsStartupLock::~nsStartupLock() {
48 mLock.Unlock();
49 mLock.Cleanup();
51 mDir->Remove(false);
54 NS_IMPL_ISUPPORTS(nsRemoteService, nsIObserver, nsIRemoteService)
56 nsRemoteService::nsRemoteService() : mProgram("mozilla") {
57 ToLowerCase(mProgram);
60 void nsRemoteService::SetProgram(const char* aProgram) {
61 mProgram = aProgram;
62 ToLowerCase(mProgram);
64 void nsRemoteService::SetProfile(nsACString& aProfile) { mProfile = aProfile; }
66 #ifdef MOZ_WIDGET_GTK
67 void nsRemoteService::SetStartupToken(nsACString& aStartupToken) {
68 mStartupToken = aStartupToken;
70 #endif
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();
76 do {
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.");
83 return rv;
86 rv = aProfileLock.Lock(aMutexDir, nullptr);
87 if (NS_SUCCEEDED(rv)) {
88 return NS_OK;
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(
99 double aTimeout) {
100 // If startup is already locked we can just resolve immediately.
101 RefPtr<nsStartupLock> lock(mStartupLock);
102 if (lock) {
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));
115 if (NS_FAILED(rv)) {
116 return StartupLockPromise::CreateAndReject(rv, __func__);
119 rv = mutexDir->AppendNative(mProgram);
120 if (NS_FAILED(rv)) {
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]() {
130 nsProfileLock lock;
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
141 // `Then`s.
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);
161 if (lock) {
162 return lock.forget();
165 nsCOMPtr<nsIFile> mutexDir;
166 nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
167 getter_AddRefs(mutexDir));
168 if (NS_FAILED(rv)) {
169 return nullptr;
172 rv = mutexDir->AppendNative(mProgram);
173 if (NS_FAILED(rv)) {
174 return nullptr;
177 nsProfileLock profileLock;
178 rv = AcquireLock(mutexDir, START_TIMEOUT_MSEC, profileLock);
180 if (NS_FAILED(rv)) {
181 NS_WARNING("Failed to lock for startup, continuing anyway.");
182 return nullptr;
185 lock = new nsStartupLock(mutexDir, profileLock);
186 mStartupLock = lock;
187 return lock.forget();
190 nsresult nsRemoteService::SendCommandLine(const nsACString& aProfile,
191 size_t aArgc, const char** aArgv,
192 bool aRaise) {
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);
201 # else
202 client = MakeUnique<nsXRemoteClient>(mStartupToken);
203 # endif
204 #elif defined(XP_WIN)
205 client = MakeUnique<nsWinRemoteClient>();
206 #elif defined(XP_DARWIN)
207 client = MakeUnique<nsMacRemoteClient>();
208 #else
209 return NS_ERROR_NOT_AVAILABLE;
210 #endif
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);
220 NS_IMETHODIMP
221 nsRemoteService::SendCommandLine(const nsACString& aProfile,
222 const nsTArray<nsCString>& aArgs,
223 bool aRaise) {
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;
230 #endif
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),
248 true);
251 void nsRemoteService::StartupServer() {
252 if (mRemoteServer) {
253 return;
256 if (mProfile.IsEmpty()) {
257 return;
260 #ifdef MOZ_WIDGET_GTK
261 # if defined(MOZ_ENABLE_DBUS)
262 mRemoteServer = MakeUnique<nsDBusRemoteServer>();
263 # else
264 mRemoteServer = MakeUnique<nsGTKRemoteServer>();
265 # endif
266 #elif defined(XP_WIN)
267 mRemoteServer = MakeUnique<nsWinRemoteServer>();
268 #elif defined(XP_DARWIN)
269 mRemoteServer = MakeUnique<nsMacRemoteServer>();
270 #else
271 return;
272 #endif
274 if (!mRemoteServer) {
275 return;
278 nsresult rv = mRemoteServer->Startup(mProgram.get(), mProfile.get());
280 if (NS_FAILED(rv)) {
281 mRemoteServer = nullptr;
282 return;
285 nsCOMPtr<nsIObserverService> obs(
286 do_GetService("@mozilla.org/observer-service;1"));
287 if (obs) {
288 obs->AddObserver(this, "xpcom-shutdown", false);
289 obs->AddObserver(this, "quit-application", false);
293 void nsRemoteService::ShutdownServer() { mRemoteServer = nullptr; }
295 nsRemoteService::~nsRemoteService() { ShutdownServer(); }
297 NS_IMETHODIMP
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
301 // way.
302 ShutdownServer();
303 return NS_OK;