Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#if defined(ENABLE_TESTS)
# include "mozilla/ipc/UtilityProcessManager.h"
# include "mozilla/ipc/UtilityProcessTest.h"
# include "mozilla/dom/ChromeUtilsBinding.h"
# include "mozilla/dom/Promise.h"
# include "mozilla/ProcInfo.h"
# include "mozilla/IntentionalCrash.h"
# ifdef XP_WIN
# include <handleapi.h>
# include <processthreadsapi.h>
# include <tlhelp32.h>
# include "mozilla/WinHandleWatcher.h"
# include "nsISupports.h"
# include "nsWindowsHelpers.h"
# endif
namespace mozilla::ipc {
static UtilityActorName UtilityActorNameFromString(
const nsACString& aStringName) {
using namespace mozilla::dom;
auto idlName = StringToEnum<UtilityActorName>(aStringName);
if (idlName.isSome()) {
return idlName.value();
}
MOZ_CRASH("Unknown utility actor name");
}
// Find the utility process with the given actor or any utility process if
// aActorName is Nothing().
static SandboxingKind FindUtilityProcessWithActor(
const Maybe<UtilityActorName>& aActorName) {
RefPtr<UtilityProcessManager> utilityProc =
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
for (size_t i = 0; i < SandboxingKind::COUNT; ++i) {
auto sbKind = static_cast<SandboxingKind>(i);
if (!utilityProc->Process(sbKind)) {
continue;
}
if (aActorName.isNothing()) {
return sbKind;
}
for (auto actor : utilityProc->GetActors(sbKind)) {
if (actor == aActorName.ref()) {
return sbKind;
}
}
}
return SandboxingKind::COUNT;
}
# ifdef XP_WIN
namespace {
// Promise implementation for `UntilChildProcessDead`.
//
// Resolves the provided JS promise when the provided Windows HANDLE becomes
// signaled.
class WinHandlePromiseImpl final {
public:
NS_INLINE_DECL_REFCOUNTING(WinHandlePromiseImpl)
using HandlePtr = mozilla::UniqueFileHandle;
// Takes ownership of aHandle.
static void Create(mozilla::UniqueFileHandle handle,
RefPtr<mozilla::dom::Promise> promise) {
MOZ_ASSERT(handle);
MOZ_ASSERT(promise);
RefPtr obj{new WinHandlePromiseImpl(std::move(handle), std::move(promise))};
// WARNING: This creates an owning-reference cycle: (self -> HandleWatcher
// -> Runnable -> self). `obj` will therefore only be destroyed when and
// if the HANDLE is signaled.
obj->watcher.Watch(obj->handle.get(), GetCurrentSerialEventTarget(),
NewRunnableMethod("WinHandlePromiseImpl::Resolve", obj,
&WinHandlePromiseImpl::Resolve));
}
private:
WinHandlePromiseImpl(mozilla::UniqueFileHandle handle,
RefPtr<mozilla::dom::Promise> promise)
: handle(std::move(handle)), promise(std::move(promise)) {}
~WinHandlePromiseImpl() { watcher.Stop(); }
void Resolve() { promise->MaybeResolveWithUndefined(); }
mozilla::UniqueFileHandle handle;
HandleWatcher watcher;
RefPtr<mozilla::dom::Promise> promise;
};
} // namespace
# endif
NS_IMETHODIMP
UtilityProcessTest::StartProcess(const nsTArray<nsCString>& aActorsToRegister,
JSContext* aCx,
mozilla::dom::Promise** aOutPromise) {
NS_ENSURE_ARG(aOutPromise);
*aOutPromise = nullptr;
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
ErrorResult erv;
RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv);
if (NS_WARN_IF(erv.Failed())) {
return erv.StealNSResult();
}
RefPtr<UtilityProcessManager> utilityProc =
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
auto actors = aActorsToRegister.Clone();
utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, utilityProc, actors = std::move(actors)] {
RefPtr<UtilityProcessParent> utilityParent =
utilityProc->GetProcessParent(SandboxingKind::GENERIC_UTILITY);
Maybe<int32_t> utilityPid =
utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
for (size_t i = 0; i < actors.Length(); ++i) {
auto uan = UtilityActorNameFromString(actors[i]);
utilityProc->RegisterActor(utilityParent, uan);
}
if (utilityPid.isSome()) {
promise->MaybeResolve(*utilityPid);
} else {
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
}
},
[promise](LaunchError aError) {
MOZ_ASSERT_UNREACHABLE(
"UtilityProcessTest; failure to get Utility process");
promise->MaybeReject(NS_ERROR_FAILURE);
});
promise.forget(aOutPromise);
return NS_OK;
}
NS_IMETHODIMP
UtilityProcessTest::NoteIntentionalCrash(uint32_t aPid) {
mozilla::NoteIntentionalCrash("utility", aPid);
return NS_OK;
}
NS_IMETHODIMP
UtilityProcessTest::UntilChildProcessDead(
uint32_t pid, JSContext* cx, ::mozilla::dom::Promise** aOutPromise) {
NS_ENSURE_ARG(aOutPromise);
*aOutPromise = nullptr;
# ifdef XP_WIN
if (pid == 0) {
return NS_ERROR_INVALID_ARG;
}
nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx);
if (NS_WARN_IF(!global)) {
return NS_ERROR_FAILURE;
}
ErrorResult erv;
RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv);
if (NS_WARN_IF(erv.Failed())) {
return erv.StealNSResult();
}
// Get a fresh handle to the child process with the specified PID.
mozilla::UniqueFileHandle handle;
{
bool failed = false;
GeckoChildProcessHost::GetAll([&](GeckoChildProcessHost* aProc) {
if (handle || failed) {
return;
}
if (aProc->GetChildProcessId() != pid) {
return;
}
HANDLE handle_ = nullptr;
if (!::DuplicateHandle(
::GetCurrentProcess(), aProc->GetChildProcessHandle(),
::GetCurrentProcess(), &handle_, SYNCHRONIZE, FALSE, 0)) {
failed = true;
} else {
handle.reset(handle_);
}
});
if (failed || !handle) {
return NS_ERROR_FAILURE;
}
}
// Create and attach the resolver for the promise, giving the handle over to
// it.
WinHandlePromiseImpl::Create(std::move(handle), promise);
promise.forget(aOutPromise);
return NS_OK;
# else // !defined(XP_WIN)
return NS_ERROR_NOT_IMPLEMENTED;
# endif
}
NS_IMETHODIMP
UtilityProcessTest::StopProcess(const char* aActorName) {
using namespace mozilla::dom;
SandboxingKind sbKind;
if (aActorName) {
const nsDependentCString actorStringName(aActorName);
UtilityActorName actorName = UtilityActorNameFromString(actorStringName);
sbKind = FindUtilityProcessWithActor(Some(actorName));
} else {
sbKind = FindUtilityProcessWithActor(Nothing());
}
if (sbKind == SandboxingKind::COUNT) {
MOZ_ASSERT_UNREACHABLE(
"Attempted to stop process for actor when no "
"such process exists");
return NS_ERROR_FAILURE;
}
RefPtr<UtilityProcessManager> utilityProc =
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
utilityProc->CleanShutdown(sbKind);
Maybe<int32_t> utilityPid = utilityProc->ProcessPid(sbKind);
MOZ_RELEASE_ASSERT(utilityPid.isNothing(),
"Should not have a utility process PID anymore");
return NS_OK;
}
NS_IMETHODIMP
UtilityProcessTest::TestTelemetryProbes() {
RefPtr<UtilityProcessManager> utilityProc =
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
for (RefPtr<UtilityProcessParent>& parent :
utilityProc->GetAllProcessesProcessParent()) {
Unused << parent->SendTestTelemetryProbes();
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(UtilityProcessTest, nsIUtilityProcessTest)
} // namespace mozilla::ipc
#endif // defined(ENABLE_TESTS)