304 lines
8.7 KiB
C++
304 lines
8.7 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:expandtab:shiftwidth=2:tabstop=8:
|
|
*/
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsRemoteClient.h"
|
|
#ifdef MOZ_WIDGET_GTK
|
|
# ifdef MOZ_ENABLE_DBUS
|
|
# include "nsDBusRemoteServer.h"
|
|
# include "nsDBusRemoteClient.h"
|
|
# else
|
|
# include "nsGTKRemoteServer.h"
|
|
# include "nsXRemoteClient.h"
|
|
# endif
|
|
#elif defined(XP_WIN)
|
|
# include "nsWinRemoteServer.h"
|
|
# include "nsWinRemoteClient.h"
|
|
#elif defined(XP_DARWIN)
|
|
# include "nsMacRemoteServer.h"
|
|
# include "nsMacRemoteClient.h"
|
|
#endif
|
|
#include "nsRemoteService.h"
|
|
|
|
#include "nsIObserverService.h"
|
|
#include "nsString.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "SpecialSystemDirectory.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
// Time to wait for the startup lock
|
|
#define START_TIMEOUT_MSEC 5000
|
|
#define START_SLEEP_MSEC 100
|
|
|
|
extern int gArgc;
|
|
extern char** gArgv;
|
|
|
|
using namespace mozilla;
|
|
|
|
nsStartupLock::nsStartupLock(nsIFile* aDir, nsProfileLock& aLock) : mDir(aDir) {
|
|
mLock = aLock;
|
|
}
|
|
|
|
nsStartupLock::~nsStartupLock() {
|
|
mLock.Unlock();
|
|
mLock.Cleanup();
|
|
|
|
mDir->Remove(false);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(nsRemoteService, nsIObserver, nsIRemoteService)
|
|
|
|
nsRemoteService::nsRemoteService() : mProgram("mozilla") {
|
|
ToLowerCase(mProgram);
|
|
}
|
|
|
|
void nsRemoteService::SetProgram(const char* aProgram) {
|
|
mProgram = aProgram;
|
|
ToLowerCase(mProgram);
|
|
}
|
|
void nsRemoteService::SetProfile(nsACString& aProfile) { mProfile = aProfile; }
|
|
|
|
#ifdef MOZ_WIDGET_GTK
|
|
void nsRemoteService::SetStartupToken(nsACString& aStartupToken) {
|
|
mStartupToken = aStartupToken;
|
|
}
|
|
#endif
|
|
|
|
// Attempts to lock the given directory by polling until the timeout is reached.
|
|
static nsresult AcquireLock(nsIFile* aMutexDir, double aTimeout,
|
|
nsProfileLock& aProfileLock) {
|
|
const mozilla::TimeStamp epoch = mozilla::TimeStamp::Now();
|
|
do {
|
|
// If we have been waiting for another instance to release the lock it will
|
|
// have deleted the lock directory when doing so so we have to make sure it
|
|
// exists every time we poll for the lock.
|
|
nsresult rv = aMutexDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
|
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
|
|
NS_WARNING("Unable to create startup lock directory.");
|
|
return rv;
|
|
}
|
|
|
|
rv = aProfileLock.Lock(aMutexDir, nullptr);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
PR_Sleep(START_SLEEP_MSEC);
|
|
} while ((mozilla::TimeStamp::Now() - epoch) <
|
|
mozilla::TimeDuration::FromMilliseconds(aTimeout));
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<nsRemoteService::StartupLockPromise> nsRemoteService::AsyncLockStartup(
|
|
double aTimeout) {
|
|
// If startup is already locked we can just resolve immediately.
|
|
RefPtr<nsStartupLock> lock(mStartupLock);
|
|
if (lock) {
|
|
return StartupLockPromise::CreateAndResolve(lock, __func__);
|
|
}
|
|
|
|
// If there is already an executing promise we can just return that otherwise
|
|
// we have to start a new one.
|
|
if (mStartupLockPromise) {
|
|
return mStartupLockPromise;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> mutexDir;
|
|
nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
|
|
getter_AddRefs(mutexDir));
|
|
if (NS_FAILED(rv)) {
|
|
return StartupLockPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
rv = mutexDir->AppendNative(mProgram);
|
|
if (NS_FAILED(rv)) {
|
|
return StartupLockPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
nsCOMPtr<nsISerialEventTarget> queue;
|
|
MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue("StartupLockTaskQueue",
|
|
getter_AddRefs(queue)));
|
|
|
|
mStartupLockPromise = InvokeAsync(
|
|
queue, __func__, [mutexDir = std::move(mutexDir), aTimeout]() {
|
|
nsProfileLock lock;
|
|
nsresult rv = AcquireLock(mutexDir, aTimeout, lock);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return StartupLockPromise::CreateAndResolve(
|
|
new nsStartupLock(mutexDir, lock), __func__);
|
|
}
|
|
|
|
return StartupLockPromise::CreateAndReject(rv, __func__);
|
|
});
|
|
|
|
// Note this is the guaranteed first `Then` and will run before any other
|
|
// `Then`s.
|
|
mStartupLockPromise->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[this, self = RefPtr{this}](
|
|
const StartupLockPromise::ResolveOrRejectValue& aResult) {
|
|
if (aResult.IsResolve()) {
|
|
mStartupLock = aResult.ResolveValue();
|
|
}
|
|
mStartupLockPromise = nullptr;
|
|
});
|
|
|
|
return mStartupLockPromise;
|
|
}
|
|
|
|
already_AddRefed<nsStartupLock> nsRemoteService::LockStartup() {
|
|
MOZ_RELEASE_ASSERT(!mStartupLockPromise,
|
|
"Should not have started an asynchronous lock attempt");
|
|
|
|
// If we're already locked just return the current lock.
|
|
RefPtr<nsStartupLock> lock(mStartupLock);
|
|
if (lock) {
|
|
return lock.forget();
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> mutexDir;
|
|
nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
|
|
getter_AddRefs(mutexDir));
|
|
if (NS_FAILED(rv)) {
|
|
return nullptr;
|
|
}
|
|
|
|
rv = mutexDir->AppendNative(mProgram);
|
|
if (NS_FAILED(rv)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsProfileLock profileLock;
|
|
rv = AcquireLock(mutexDir, START_TIMEOUT_MSEC, profileLock);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Failed to lock for startup, continuing anyway.");
|
|
return nullptr;
|
|
}
|
|
|
|
lock = new nsStartupLock(mutexDir, profileLock);
|
|
mStartupLock = lock;
|
|
return lock.forget();
|
|
}
|
|
|
|
nsresult nsRemoteService::SendCommandLine(const nsACString& aProfile,
|
|
size_t aArgc, const char** aArgv,
|
|
bool aRaise) {
|
|
if (aProfile.IsEmpty()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
UniquePtr<nsRemoteClient> client;
|
|
#ifdef MOZ_WIDGET_GTK
|
|
# if defined(MOZ_ENABLE_DBUS)
|
|
client = MakeUnique<nsDBusRemoteClient>(mStartupToken);
|
|
# else
|
|
client = MakeUnique<nsXRemoteClient>(mStartupToken);
|
|
# endif
|
|
#elif defined(XP_WIN)
|
|
client = MakeUnique<nsWinRemoteClient>();
|
|
#elif defined(XP_DARWIN)
|
|
client = MakeUnique<nsMacRemoteClient>();
|
|
#else
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
#endif
|
|
|
|
nsresult rv = client ? client->Init() : NS_ERROR_FAILURE;
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return client->SendCommandLine(mProgram.get(),
|
|
PromiseFlatCString(aProfile).get(), aArgc,
|
|
const_cast<const char**>(aArgv), aRaise);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsRemoteService::SendCommandLine(const nsACString& aProfile,
|
|
const nsTArray<nsCString>& aArgs,
|
|
bool aRaise) {
|
|
#ifdef MOZ_WIDGET_GTK
|
|
// Linux clients block until they receive a response so it is impossible to
|
|
// send a remote command to the current profile.
|
|
if (aProfile.Equals(mProfile)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
#endif
|
|
nsAutoCString binaryPath;
|
|
|
|
nsTArray<const char*> args;
|
|
// Note that the command line must include an initial path to the binary but
|
|
// this is generally ignored.
|
|
args.SetCapacity(aArgs.Length() + 1);
|
|
args.AppendElement(binaryPath.get());
|
|
|
|
for (const nsCString& arg : aArgs) {
|
|
args.AppendElement(arg.get());
|
|
}
|
|
|
|
return SendCommandLine(aProfile, args.Length(), args.Elements(), aRaise);
|
|
}
|
|
|
|
nsresult nsRemoteService::StartClient() {
|
|
return SendCommandLine(mProfile, gArgc, const_cast<const char**>(gArgv),
|
|
true);
|
|
}
|
|
|
|
void nsRemoteService::StartupServer() {
|
|
if (mRemoteServer) {
|
|
return;
|
|
}
|
|
|
|
if (mProfile.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_GTK
|
|
# if defined(MOZ_ENABLE_DBUS)
|
|
mRemoteServer = MakeUnique<nsDBusRemoteServer>();
|
|
# else
|
|
mRemoteServer = MakeUnique<nsGTKRemoteServer>();
|
|
# endif
|
|
#elif defined(XP_WIN)
|
|
mRemoteServer = MakeUnique<nsWinRemoteServer>();
|
|
#elif defined(XP_DARWIN)
|
|
mRemoteServer = MakeUnique<nsMacRemoteServer>();
|
|
#else
|
|
return;
|
|
#endif
|
|
|
|
if (!mRemoteServer) {
|
|
return;
|
|
}
|
|
|
|
nsresult rv = mRemoteServer->Startup(mProgram.get(), mProfile.get());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
mRemoteServer = nullptr;
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> obs(
|
|
do_GetService("@mozilla.org/observer-service;1"));
|
|
if (obs) {
|
|
obs->AddObserver(this, "xpcom-shutdown", false);
|
|
obs->AddObserver(this, "quit-application", false);
|
|
}
|
|
}
|
|
|
|
void nsRemoteService::ShutdownServer() { mRemoteServer = nullptr; }
|
|
|
|
nsRemoteService::~nsRemoteService() { ShutdownServer(); }
|
|
|
|
NS_IMETHODIMP
|
|
nsRemoteService::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
// This can be xpcom-shutdown or quit-application, but it's the same either
|
|
// way.
|
|
ShutdownServer();
|
|
return NS_OK;
|
|
}
|