Backed out 8 changesets (bug 1446161, bug 1487287, bug 1488993, bug 1474991, bug 1496608) for very frequent automation.py crashes on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Tue, 27 Nov 2018 08:53:18 +0200
changeset 504619 b0e69b1368832a3846c0940a6fb8bb834bdc62a1
parent 504618 e321cef882b8caf0063cf9d17eec89a8b3170606
child 504630 ce39a152428a7f8ba5a4c82455dcf501c76c031b
child 504631 ff40e8ca1e42bc69b512e1e3e063ebc2ed394eb3
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1446161, 1487287, 1488993, 1474991, 1496608
milestone65.0a1
backs out8b1f88d7bfeb1949060170ba51b6530db5e93c8e
8fa5e81ad8015f9bd5ba2b7497f840e4cc50b8bd
7a480161fa0fc82f11d2ed492f9f8938a1cdf1fc
80116391b7fe48cb2ecc1960721d74dc2c185c6a
1bdf64b29121762bbf6b1e13df1828034b149bac
37bf52f0e9cf2ccd4427197a2e99f40c239f01a7
8ede2ebe6b7ab639e729614cb3609776955a6bc2
cea43bc88c7a564f0ca72995f6eac6b0d118f3f1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 8 changesets (bug 1446161, bug 1487287, bug 1488993, bug 1474991, bug 1496608) for very frequent automation.py crashes on a CLOSED TREE Backed out changeset 8b1f88d7bfeb (bug 1487287) Backed out changeset 8fa5e81ad801 (bug 1487287) Backed out changeset 7a480161fa0f (bug 1474991) Backed out changeset 80116391b7fe (bug 1446161) Backed out changeset 1bdf64b29121 (bug 1446161) Backed out changeset 37bf52f0e9cf (bug 1446161) Backed out changeset 8ede2ebe6b7a (bug 1496608) Backed out changeset cea43bc88c7a (bug 1488993)
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/ContentProcessHost.cpp
dom/ipc/ContentProcessHost.h
dom/ipc/PreallocatedProcessManager.cpp
dom/ipc/moz.build
ipc/chromium/src/base/shared_memory.h
ipc/chromium/src/base/shared_memory_posix.cc
ipc/chromium/src/base/shared_memory_win.cc
ipc/glue/EnvironmentMap.h
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/GeckoChildProcessHost.h
ipc/glue/ProtocolUtils.cpp
ipc/glue/ProtocolUtils.h
toolkit/components/telemetry/Histograms.json
tools/profiler/core/platform.cpp
tools/profiler/public/GeckoProfiler.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -617,26 +617,32 @@ static const char* sObserverTopics[] = {
 };
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 bool ContentParent::sEarlySandboxInit = false;
 #endif
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
-/*static*/ RefPtr<ContentParent::LaunchPromise>
+/*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateProcess()
 {
   RefPtr<ContentParent> process =
     new ContentParent(/* aOpener = */ nullptr,
                       NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
                       eNotRecordingOrReplaying,
                       /* aRecordingFile = */ EmptyString());
 
-  return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC);
+  PreallocatedProcessManager::AddBlocker(process);
+
+  if (!process->LaunchSubprocess(PROCESS_PRIORITY_PREALLOC)) {
+    return nullptr;
+  }
+
+  return process.forget();
 }
 
 /*static*/ void
 ContentParent::StartUp()
 {
   // We could launch sub processes from content process
   // FIXME Bug 1023701 - Stop using ContentParent static methods in
   // child process
@@ -891,23 +897,23 @@ ContentParent::GetNewOrUsedBrowserProces
       p->mActivateTS = TimeStamp::Now();
       return p.forget();
     }
   }
 
   // Create a new process from scratch.
   RefPtr<ContentParent> p = new ContentParent(aOpener, aRemoteType, recordReplayState, recordingFile);
 
-  if (!p->LaunchSubprocessSync(aPriority)) {
-    return nullptr;
-  }
-
   // Until the new process is ready let's not allow to start up any preallocated processes.
   PreallocatedProcessManager::AddBlocker(p);
 
+  if (!p->LaunchSubprocess(aPriority)) {
+    return nullptr;
+  }
+
   if (recordReplayState == eNotRecordingOrReplaying) {
     contentParents.AppendElement(p);
   }
 
   p->mActivateTS = TimeStamp::Now();
   return p.forget();
 }
 
@@ -924,17 +930,17 @@ ContentParent::GetNewOrUsedJSPluginProce
   }
 
   if (p) {
     return p.forget();
   }
 
   p = new ContentParent(aPluginID);
 
-  if (!p->LaunchSubprocessSync(aPriority)) {
+  if (!p->LaunchSubprocess(aPriority)) {
     return nullptr;
   }
 
   sJSPluginContentParents->Put(aPluginID, p);
 
   return p.forget();
 }
 
@@ -1608,17 +1614,19 @@ ContentParent::OnChannelError()
   PContentParent::OnChannelError();
 }
 
 void
 ContentParent::OnChannelConnected(int32_t pid)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+#ifndef ASYNC_CONTENTPROC_LAUNCH
   SetOtherProcessId(pid);
+#endif
 
 #if defined(ANDROID) || defined(LINUX)
   // Check nice preference
   int32_t nice = Preferences::GetInt("dom.ipc.content.nice", 0);
 
   // Environment variable overrides preference
   char* relativeNicenessStr = getenv("MOZ_CHILD_PROCESS_RELATIVE_NICENESS");
   if (relativeNicenessStr) {
@@ -1633,16 +1641,21 @@ ContentParent::OnChannelConnected(int32_
     if (NS_FAILED(rv)) {
       cpus = 1;
     }
     if (nice != 0 && cpus == 1) {
       setpriority(PRIO_PROCESS, pid, getpriority(PRIO_PROCESS, pid) + nice);
     }
   }
 #endif
+
+#if defined(MOZ_CODE_COVERAGE) && defined(ASYNC_CONTENTPROC_LAUNCH)
+  Unused << SendShareCodeCoverageMutex(
+              CodeCoverageHandler::Get()->GetMutexHandle(pid));
+#endif
 }
 
 void
 ContentParent::ProcessingError(Result aCode, const char* aReason)
 {
   if (MsgDropped == aCode) {
     return;
   }
@@ -2190,40 +2203,24 @@ ContentParent::AppendSandboxParams(std::
                sMacSandboxParams->begin(),
                sMacSandboxParams->end());
 
   // Append remaining arguments.
   AppendDynamicSandboxParams(aArgs);
 }
 #endif // XP_MACOSX && MOZ_CONTENT_SANDBOX
 
-void
-ContentParent::LaunchSubprocessInternal(
-  ProcessPriority aInitialPriority,
-  mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval)
+bool
+ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
 {
   AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess", OTHER);
-  const bool isSync = aRetval.is<bool*>();
-
-  Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC,
-                        static_cast<uint32_t>(isSync));
-
-  auto earlyReject = [aRetval, isSync]() {
-    if (isSync) {
-      *aRetval.as<bool*>() = false;
-    } else {
-      *aRetval.as<RefPtr<LaunchPromise>*>() = LaunchPromise::CreateAndReject(
-        GeckoChildProcessHost::LaunchError(), __func__);
-    }
-  };
 
   if (!ContentProcessManager::GetSingleton()) {
     // Shutdown has begun, we shouldn't spawn any more child processes.
-    earlyReject();
-    return;
+    return false;
   }
 
   std::vector<std::string> extraArgs;
   extraArgs.push_back("-childID");
   char idStr[21];
   SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID));
   extraArgs.push_back(idStr);
   extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser");
@@ -2238,24 +2235,22 @@ ContentParent::LaunchSubprocessInternal(
   nsAutoCStringN<1024> prefs;
   Preferences::SerializePreferences(prefs);
 
   // Set up the shared memory.
   base::SharedMemory shm;
   if (!shm.Create(prefs.Length())) {
     NS_ERROR("failed to create shared memory in the parent");
     MarkAsDead();
-    earlyReject();
-    return;
+    return false;
   }
   if (!shm.Map(prefs.Length())) {
     NS_ERROR("failed to map shared memory in the parent");
     MarkAsDead();
-    earlyReject();
-    return;
+    return false;
   }
 
   // Copy the serialized prefs into the shared memory.
   memcpy(static_cast<char*>(shm.memory()), prefs.get(), prefs.Length());
 
   // Formats a pointer or pointer-sized-integer as a string suitable for passing
   // in an arguments list.
   auto formatPtrArg = [] (auto arg) {
@@ -2321,145 +2316,89 @@ ContentParent::LaunchSubprocessInternal(
                               : (int) recordreplay::ProcessKind::MiddlemanReplaying);
     extraArgs.push_back(recordreplay::gProcessKindOption);
     extraArgs.push_back(buf.get());
 
     extraArgs.push_back(recordreplay::gRecordingFileOption);
     extraArgs.push_back(NS_ConvertUTF16toUTF8(mRecordingFile).get());
   }
 
-  RefPtr<ContentParent> self(this);
-
-  auto reject = [self, this](GeckoChildProcessHost::LaunchError err) {
+  SetOtherProcessId(kInvalidProcessId, ProcessIdState::ePending);
+#ifdef ASYNC_CONTENTPROC_LAUNCH
+  if (!mSubprocess->Launch(extraArgs)) {
+#else
+  if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
+#endif
     NS_ERROR("failed to launch child in the parent");
     MarkAsDead();
-    return LaunchPromise::CreateAndReject(err, __func__);
-  };
+    return false;
+  }
 
   // See also ActorDestroy.
   mSelfRef = this;
 
-  // Lifetime note: the GeckoChildProcessHost holds a strong reference
-  // to the launch promise, which takes ownership of these closures,
-  // which hold strong references to this ContentParent; the
-  // ContentParent then owns the GeckoChildProcessHost (and that
-  // ownership is not exposed to the cycle collector).  Therefore,
-  // this all stays alive until the promise is resolved or rejected.
-
-  auto resolve = [self, this, aInitialPriority, isSync,
-                  // Transfer ownership of RAII file descriptor/handle
-                  // holders so that they won't be closed before the
-                  // child can inherit them.
-                  shm = std::move(shm),
-                  prefMapHandle = std::move(prefMapHandle)
-                 ](base::ProcessHandle handle) {
-    AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER);
-    const auto launchResumeTS = TimeStamp::Now();
-
-    base::ProcessId procId = base::GetProcId(handle);
-    Open(mSubprocess->GetChannel(), procId);
+#ifdef ASYNC_CONTENTPROC_LAUNCH
+  OpenWithAsyncPid(mSubprocess->GetChannel());
+#else
+  base::ProcessId procId =
+    base::GetProcId(mSubprocess->GetChildProcessHandle());
+  Open(mSubprocess->GetChannel(), procId);
 #ifdef MOZ_CODE_COVERAGE
-    Unused << SendShareCodeCoverageMutex(
-      CodeCoverageHandler::Get()->GetMutexHandle(procId));
+  Unused << SendShareCodeCoverageMutex(
+              CodeCoverageHandler::Get()->GetMutexHandle(procId));
 #endif
-
-    mIsAlive = true;
-    InitInternal(aInitialPriority);
-
-    ContentProcessManager::GetSingleton()->AddContentProcess(this);
-
-    mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
-
-    // Set a reply timeout for CPOWs.
-    SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
-
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-      nsAutoString cpId;
-      cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
-      obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
-    }
-
-    Init();
-
-    if (isSync) {
-      Telemetry::AccumulateTimeDelta(
-        Telemetry::CONTENT_PROCESS_SYNC_LAUNCH_MS, mLaunchTS);
-    } else {
-      Telemetry::AccumulateTimeDelta(
-        Telemetry::CONTENT_PROCESS_LAUNCH_TOTAL_MS, mLaunchTS);
-
-      Telemetry::Accumulate(
-        Telemetry::CONTENT_PROCESS_LAUNCH_MAINTHREAD_MS,
-        static_cast<uint32_t>(((mLaunchYieldTS - mLaunchTS) +
-                               (TimeStamp::Now() - launchResumeTS))
-                              .ToMilliseconds()));
-    }
-
-    return LaunchPromise::CreateAndResolve(self, __func__);
-  };
-
-  if (isSync) {
-    bool ok = mSubprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
-    if (ok) {
-      Unused << resolve(mSubprocess->GetChildProcessHandle());
-    } else {
-      Unused << reject(GeckoChildProcessHost::LaunchError{});
-    }
-    *aRetval.as<bool*>() = ok;
-  } else {
-    auto* retptr = aRetval.as<RefPtr<LaunchPromise>*>();
-    if (mSubprocess->AsyncLaunch(std::move(extraArgs))) {
-      RefPtr<GeckoChildProcessHost::HandlePromise> ready =
-        mSubprocess->WhenProcessHandleReady();
-      mLaunchYieldTS = TimeStamp::Now();
-      *retptr = ready->Then(GetCurrentThreadSerialEventTarget(), __func__,
-                            std::move(resolve), std::move(reject));
-    } else {
-      *retptr = reject(GeckoChildProcessHost::LaunchError{});
-    }
-  }
-}
-
-/* static */ bool
-ContentParent::LaunchSubprocessSync(hal::ProcessPriority aInitialPriority)
-{
-  bool retval;
-  LaunchSubprocessInternal(aInitialPriority, mozilla::AsVariant(&retval));
-  return retval;
-}
-
-/* static */ RefPtr<ContentParent::LaunchPromise>
-ContentParent::LaunchSubprocessAsync(hal::ProcessPriority aInitialPriority)
-{
-  RefPtr<LaunchPromise> retval;
-  LaunchSubprocessInternal(aInitialPriority, mozilla::AsVariant(&retval));
-  return retval;
+#endif // ASYNC_CONTENTPROC_LAUNCH
+
+  InitInternal(aInitialPriority);
+
+  ContentProcessManager::GetSingleton()->AddContentProcess(this);
+
+  mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
+
+  // Set a reply timeout for CPOWs.
+  SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
+
+  // TODO: In ASYNC_CONTENTPROC_LAUNCH, if OtherPid() is not called between
+  // mSubprocess->Launch() and this, then we're not really measuring how long it
+  // took to spawn the process.
+  Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
+                        static_cast<uint32_t>((TimeStamp::Now() - mLaunchTS)
+                                              .ToMilliseconds()));
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    nsAutoString cpId;
+    cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
+    obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
+  }
+
+  Init();
+
+  return true;
 }
 
 ContentParent::ContentParent(ContentParent* aOpener,
                              const nsAString& aRemoteType,
                              RecordReplayState aRecordReplayState,
                              const nsAString& aRecordingFile,
                              int32_t aJSPluginID)
   : nsIContentParent()
   , mSelfRef(nullptr)
   , mSubprocess(nullptr)
   , mLaunchTS(TimeStamp::Now())
-  , mLaunchYieldTS(mLaunchTS)
-  , mActivateTS(mLaunchTS)
+  , mActivateTS(TimeStamp::Now())
   , mOpener(aOpener)
   , mRemoteType(aRemoteType)
   , mChildID(gContentChildID++)
   , mGeolocationWatchID(-1)
   , mJSPluginID(aJSPluginID)
   , mRemoteWorkerActors(0)
   , mNumDestroyingTabs(0)
   , mIsAvailable(true)
-  , mIsAlive(false)
+  , mIsAlive(true)
   , mIsForBrowser(!mRemoteType.IsEmpty())
   , mRecordReplayState(aRecordReplayState)
   , mRecordingFile(aRecordingFile)
   , mCalledClose(false)
   , mCalledKillHard(false)
   , mCreatedPairedMinidumps(false)
   , mShutdownPending(false)
   , mIPCOpen(true)
@@ -2484,17 +2423,17 @@ ContentParent::ContentParent(ContentPare
   // Request Windows message deferral behavior on our side of the PContent
   // channel. Generally only applies to the situation where we get caught in
   // a deadlock with the plugin process when sending CPOWs.
   GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   bool isFile = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
-  mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, isFile);
+  mSubprocess = new ContentProcessHost(this, isFile);
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
   // sEarlySandboxInit is statically initialized to false.
   // Once we've set it to true due to the pref, avoid checking the
   // pref on subsequent calls. As a result, changing the earlyinit
   // pref requires restarting the browser to take effect.
   if (!ContentParent::sEarlySandboxInit) {
     ContentParent::sEarlySandboxInit =
@@ -2516,27 +2455,25 @@ ContentParent::~ContentParent()
   if (IsForJSPlugin()) {
     MOZ_ASSERT(!sJSPluginContentParents ||
                !sJSPluginContentParents->Get(mJSPluginID));
   } else {
     MOZ_ASSERT(!sBrowserContentParents ||
                !sBrowserContentParents->Contains(mRemoteType) ||
                !sBrowserContentParents->Get(mRemoteType)->Contains(this));
   }
-
-  // Normally mSubprocess is destroyed in ActorDestroy, but that won't
-  // happen if the process wasn't launched or if it failed to launch.
-  if (mSubprocess) {
-    DelayedDeleteSubprocess(mSubprocess);
-  }
 }
 
 void
 ContentParent::InitInternal(ProcessPriority aInitialPriority)
 {
+  Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
+                        static_cast<uint32_t>((TimeStamp::Now() - mLaunchTS)
+                                              .ToMilliseconds()));
+
   XPCOMInitData xpcomInit;
 
   nsCOMPtr<nsIIOService> io(do_GetIOService());
   MOZ_ASSERT(io, "No IO service?");
   DebugOnly<nsresult> rv = io->GetOffline(&xpcomInit.isOffline());
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
 
   rv = io->GetConnectivity(&xpcomInit.isConnected());
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -6,27 +6,26 @@
 
 #ifndef mozilla_dom_ContentParent_h
 #define mozilla_dom_ContentParent_h
 
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/nsIContentParent.h"
 #include "mozilla/gfx/gfxVarReceiver.h"
 #include "mozilla/gfx/GPUProcessListener.h"
-#include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReportingProcess.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
-#include "mozilla/Variant.h"
 #include "mozilla/UniquePtr.h"
 
+#include "ContentProcessHost.h"
 #include "nsDataHashtable.h"
 #include "nsPluginTags.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
@@ -110,40 +109,37 @@ class ContentParent final : public PCont
                           , public nsIDOMGeoPositionCallback
                           , public nsIDOMGeoPositionErrorCallback
                           , public nsIInterfaceRequestor
                           , public gfx::gfxVarReceiver
                           , public mozilla::LinkedListElement<ContentParent>
                           , public gfx::GPUProcessListener
                           , public mozilla::MemoryReportingProcess
 {
-  typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
   typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
   typedef mozilla::ipc::PFileDescriptorSetParent PFileDescriptorSetParent;
   typedef mozilla::ipc::TestShellParent TestShellParent;
   typedef mozilla::ipc::URIParams URIParams;
   typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
+  friend class ContentProcessHost;
   friend class mozilla::PreallocatedProcessManagerImpl;
 #ifdef FUZZING
   friend class mozilla::ipc::ProtocolFuzzerHelper;
 #endif
 
 public:
 
   virtual bool IsContentParent() const override { return true; }
 
-  using LaunchError = GeckoChildProcessHost::LaunchError;
-  using LaunchPromise = GeckoChildProcessHost::LaunchPromise<RefPtr<ContentParent>>;
-
   /**
    * Create a subprocess suitable for use later as a content process.
    */
-  static RefPtr<LaunchPromise> PreallocateProcess();
+  static already_AddRefed<ContentParent> PreallocateProcess();
 
   /**
    * Start up the content-process machinery.  This might include
    * scheduling pre-launch tasks.
    */
   static void StartUp();
 
   /** Shut down the content-process machinery. */
@@ -392,17 +388,17 @@ public:
   {
     return mIsForBrowser;
   }
   virtual bool IsForJSPlugin() const override
   {
     return mJSPluginID != nsFakePluginTag::NOT_JSPLUGIN;
   }
 
-  GeckoChildProcessHost* Process() const
+  ContentProcessHost* Process() const
   {
     return mSubprocess;
   }
 
   ContentParent* Opener() const
   {
     return mOpener;
   }
@@ -796,30 +792,17 @@ private:
   ContentParent(ContentParent* aOpener,
                 const nsAString& aRemoteType,
                 RecordReplayState aRecordReplayState,
                 const nsAString& aRecordingFile,
                 int32_t aPluginID);
 
   // Launch the subprocess and associated initialization.
   // Returns false if the process fails to start.
-  // Deprecated in favor of LaunchSubprocessAsync.
-  bool LaunchSubprocessSync(hal::ProcessPriority aInitialPriority);
-
-  // Launch the subprocess and associated initialization;
-  // returns a promise and signals failure by rejecting.
-  // OS-level launching work is dispatched to another thread, but some
-  // initialization (creating IPDL actors, etc.; see Init()) is run on
-  // the main thread.
-  RefPtr<LaunchPromise> LaunchSubprocessAsync(hal::ProcessPriority aInitialPriority);
-
-  // Common implementation of LaunchSubprocess{Sync,Async}.
-  void LaunchSubprocessInternal(
-    hal::ProcessPriority aInitialPriority,
-    mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval);
+  bool LaunchSubprocess(hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
 
   // Common initialization after sub process launch.
   void InitInternal(ProcessPriority aPriority);
 
   // Generate a minidump for the child process and one for the main process
   void GeneratePairedMinidump(const char* aReason);
 
   virtual ~ContentParent();
@@ -1303,19 +1286,18 @@ public:
 private:
   // Released in ActorDestroy; deliberately not exposed to the CC.
   RefPtr<ContentParent> mSelfRef;
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
   // details.
 
-  GeckoChildProcessHost* mSubprocess;
+  ContentProcessHost* mSubprocess;
   const TimeStamp mLaunchTS; // used to calculate time to start content process
-  TimeStamp mLaunchYieldTS; // used to calculate async launch main thread time
   TimeStamp mActivateTS;
   ContentParent* mOpener;
 
   nsString mRemoteType;
 
   ContentParentId mChildID;
   int32_t mGeolocationWatchID;
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ContentProcessHost.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sts=8 sw=2 ts=2 tw=99 et :
+ * 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 "ContentProcessHost.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace ipc;
+
+ContentProcessHost::ContentProcessHost(ContentParent* aContentParent,
+                                       bool aIsFileContent)
+ : GeckoChildProcessHost(GeckoProcessType_Content, aIsFileContent),
+   mHasLaunched(false),
+   mContentParent(aContentParent)
+{
+  MOZ_COUNT_CTOR(ContentProcessHost);
+}
+
+ContentProcessHost::~ContentProcessHost()
+{
+  MOZ_COUNT_DTOR(ContentProcessHost);
+}
+
+bool
+ContentProcessHost::Launch(StringVector aExtraOpts)
+{
+  MOZ_ASSERT(!mHasLaunched);
+  MOZ_ASSERT(mContentParent);
+
+  bool res = GeckoChildProcessHost::AsyncLaunch(aExtraOpts);
+  MOZ_RELEASE_ASSERT(res);
+  return true;
+}
+
+void
+ContentProcessHost::OnProcessHandleReady(ProcessHandle aProcessHandle)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  // This will wake up the main thread if it is waiting for the process to
+  // launch.
+  mContentParent->SetOtherProcessId(base::GetProcId(aProcessHandle));
+
+  mHasLaunched = true;
+  GeckoChildProcessHost::OnProcessHandleReady(aProcessHandle);
+}
+
+void
+ContentProcessHost::OnProcessLaunchError()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  mContentParent->SetOtherProcessId(mozilla::ipc::kInvalidProcessId,
+                                    ContentParent::ProcessIdState::eError);
+  mHasLaunched = true;
+  GeckoChildProcessHost::OnProcessLaunchError();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ContentProcessHost.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sts=8 sw=2 ts=2 tw=99 et :
+ * 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/. */
+
+#ifndef _include_mozilla_dom_ipc_ContentProcessHost_h_
+#define _include_mozilla_dom_ipc_ContentProcessHost_h_
+
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+namespace mozilla {
+namespace dom {
+
+class ContentParent;
+
+// ContentProcessHost is the "parent process" container for a subprocess handle
+// and IPC connection. It owns the parent process IPDL actor, which in this
+// case, is a ContentParent.
+class ContentProcessHost final : public ::mozilla::ipc::GeckoChildProcessHost
+{
+  friend class ContentParent;
+
+public:
+  explicit
+  ContentProcessHost(ContentParent* aContentParent,
+                     bool aIsFileContent = false);
+  ~ContentProcessHost();
+
+  // Launch the subprocess asynchronously. On failure, false is returned.
+  // Otherwise, true is returned, and either the OnProcessHandleReady method is
+  // called when the process is created, or OnProcessLaunchError will be called
+  // if the process could not be spawned due to an asynchronous error.
+  bool Launch(StringVector aExtraOpts);
+
+  // Called on the IO thread.
+  void OnProcessHandleReady(ProcessHandle aProcessHandle) override;
+  void OnProcessLaunchError() override;
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(ContentProcessHost);
+
+  bool mHasLaunched;
+
+  ContentParent* mContentParent; // weak
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _include_mozilla_dom_ipc_ContentProcessHost_h_
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -43,17 +43,17 @@ public:
   void RemoveBlocker(ContentParent* aParent);
   already_AddRefed<ContentParent> Take();
   bool Provide(ContentParent* aParent);
 
 private:
   static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
 
   PreallocatedProcessManagerImpl();
-  ~PreallocatedProcessManagerImpl();
+  ~PreallocatedProcessManagerImpl() {}
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
 
   void Init();
 
   bool CanAllocate();
   void AllocateAfterDelay();
   void AllocateOnIdle();
   void AllocateNow();
@@ -62,23 +62,18 @@ private:
   void Enable();
   void Disable();
   void CloseProcess();
 
   void ObserveProcessShutdown(nsISupports* aSubject);
 
   bool mEnabled;
   bool mShutdown;
-  bool mLaunchInProgress;
   RefPtr<ContentParent> mPreallocatedProcess;
   nsTHashtable<nsUint64HashKey> mBlockers;
-
-  bool IsEmpty() const {
-    return !mPreallocatedProcess && !mLaunchInProgress;
-  }
 };
 
 /* static */ StaticRefPtr<PreallocatedProcessManagerImpl>
 PreallocatedProcessManagerImpl::sSingleton;
 
 /* static */ PreallocatedProcessManagerImpl*
 PreallocatedProcessManagerImpl::Singleton()
 {
@@ -92,26 +87,18 @@ PreallocatedProcessManagerImpl::Singleto
   return sSingleton;
 }
 
 NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
 
 PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
   : mEnabled(false)
   , mShutdown(false)
-  , mLaunchInProgress(false)
 {}
 
-PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl()
-{
-  // This shouldn't happen, because the promise callbacks should
-  // hold strong references, but let't make absolutely sure:
-  MOZ_RELEASE_ASSERT(!mLaunchInProgress);
-}
-
 void
 PreallocatedProcessManagerImpl::Init()
 {
   Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
   // We have to respect processCount at all time. This is especially important
   // for testing.
   Preferences::AddStrongObserver(this, "dom.ipc.processCount");
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
@@ -187,19 +174,16 @@ PreallocatedProcessManagerImpl::Take()
   }
 
   return mPreallocatedProcess.forget();
 }
 
 bool
 PreallocatedProcessManagerImpl::Provide(ContentParent* aParent)
 {
-  // This will take the already-running process even if there's a
-  // launch in progress; if that process hasn't been taken by the
-  // time the launch completes, the new process will be shut down.
   if (mEnabled && !mShutdown && !mPreallocatedProcess) {
     mPreallocatedProcess = aParent;
   }
 
   // We might get a call from both NotifyTabDestroying and NotifyTabDestroyed with the same
   // ContentParent. Returning true here for both calls is important to avoid the cached process
   // to be destroyed.
   return aParent == mPreallocatedProcess;
@@ -223,35 +207,29 @@ PreallocatedProcessManagerImpl::AddBlock
   MOZ_ASSERT(!mBlockers.Contains(childID));
   mBlockers.PutEntry(childID);
 }
 
 void
 PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent)
 {
   uint64_t childID = aParent->ChildID();
-  // This used to assert that the blocker existed, but preallocated
-  // processes aren't blockers anymore because it's not useful and
-  // interferes with async launch, and it's simpler if content
-  // processes don't need to remember whether they were preallocated.
-  // (And preallocated processes can't AddBlocker when taken, because
-  // it's possible for a short-lived process to be recycled through
-  // Provide() and Take() before reaching RecvFirstIdle.)
+  MOZ_ASSERT(mBlockers.Contains(childID));
   mBlockers.RemoveEntry(childID);
-  if (IsEmpty() && mBlockers.IsEmpty()) {
+  if (!mPreallocatedProcess && mBlockers.IsEmpty()) {
     AllocateAfterDelay();
   }
 }
 
 bool
 PreallocatedProcessManagerImpl::CanAllocate()
 {
   return mEnabled &&
          mBlockers.IsEmpty() &&
-         IsEmpty() &&
+         !mPreallocatedProcess &&
          !mShutdown &&
          !ContentParent::IsMaxProcessCountReached(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
 }
 
 void
 PreallocatedProcessManagerImpl::AllocateAfterDelay()
 {
   if (!mEnabled) {
@@ -278,41 +256,24 @@ PreallocatedProcessManagerImpl::Allocate
                       this,
                       &PreallocatedProcessManagerImpl::AllocateNow));
 }
 
 void
 PreallocatedProcessManagerImpl::AllocateNow()
 {
   if (!CanAllocate()) {
-    if (mEnabled && !mShutdown && IsEmpty() && !mBlockers.IsEmpty()) {
+    if (mEnabled && !mShutdown && !mPreallocatedProcess && !mBlockers.IsEmpty()) {
       // If it's too early to allocate a process let's retry later.
       AllocateAfterDelay();
     }
     return;
   }
 
-  RefPtr<PreallocatedProcessManagerImpl> self(this);
-  mLaunchInProgress = true;
-
-  ContentParent::PreallocateProcess()
-    ->Then(GetCurrentThreadSerialEventTarget(), __func__,
-
-           [self, this](const RefPtr<ContentParent>& process) {
-             mLaunchInProgress = false;
-             if (CanAllocate()) {
-               mPreallocatedProcess = process;
-             } else {
-               process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
-             }
-           },
-
-           [self, this](ContentParent::LaunchError err) {
-             mLaunchInProgress = false;
-           });
+  mPreallocatedProcess = ContentParent::PreallocateProcess();
 }
 
 void
 PreallocatedProcessManagerImpl::Disable()
 {
   if (!mEnabled) {
     return;
   }
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -31,16 +31,17 @@ EXPORTS.mozilla.dom += [
     'CoalescedInputData.h',
     'CoalescedMouseData.h',
     'CoalescedWheelData.h',
     'ContentBridgeChild.h',
     'ContentBridgeParent.h',
     'ContentChild.h',
     'ContentParent.h',
     'ContentProcess.h',
+    'ContentProcessHost.h',
     'ContentProcessManager.h',
     'CPOWManagerGetter.h',
     'FilePickerParent.h',
     'MemoryReportRequest.h',
     'nsIContentChild.h',
     'nsIContentParent.h',
     'PermissionMessageUtils.h',
     'TabChild.h',
@@ -61,16 +62,17 @@ EXPORTS.mozilla += [
 UNIFIED_SOURCES += [
     'CoalescedMouseData.cpp',
     'CoalescedWheelData.cpp',
     'ColorPickerParent.cpp',
     'ContentBridgeChild.cpp',
     'ContentBridgeParent.cpp',
     'ContentParent.cpp',
     'ContentProcess.cpp',
+    'ContentProcessHost.cpp',
     'ContentProcessManager.cpp',
     'FilePickerParent.cpp',
     'MemMapSnapshot.cpp',
     'MemoryReportRequest.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
--- a/ipc/chromium/src/base/shared_memory.h
+++ b/ipc/chromium/src/base/shared_memory.h
@@ -38,19 +38,16 @@ class SharedMemory {
 
   // Create a new SharedMemory object from an existing, open
   // shared memory file.
   SharedMemory(SharedMemoryHandle init_handle, bool read_only)
     : SharedMemory() {
     SetHandle(init_handle, read_only);
   }
 
-  // Move constructor; transfers ownership.
-  SharedMemory(SharedMemory&& other);
-
   // Destructor.  Will close any open files.
   ~SharedMemory();
 
   // Initialize a new SharedMemory object from an existing, open
   // shared memory file.
   bool SetHandle(SharedMemoryHandle handle, bool read_only);
 
   // Return true iff the given handle is valid (i.e. not the distingished
--- a/ipc/chromium/src/base/shared_memory_posix.cc
+++ b/ipc/chromium/src/base/shared_memory_posix.cc
@@ -26,30 +26,16 @@ namespace base {
 
 SharedMemory::SharedMemory()
     : mapped_file_(-1),
       memory_(NULL),
       read_only_(false),
       max_size_(0) {
 }
 
-SharedMemory::SharedMemory(SharedMemory&& other) {
-  if (this == &other) {
-    return;
-  }
-
-  mapped_file_ = other.mapped_file_;
-  memory_ = other.memory_;
-  read_only_ = other.read_only_;
-  max_size_ = other.max_size_;
-
-  other.mapped_file_ = -1;
-  other.memory_ = nullptr;
-}
-
 SharedMemory::~SharedMemory() {
   Close();
 }
 
 bool SharedMemory::SetHandle(SharedMemoryHandle handle, bool read_only) {
   DCHECK(mapped_file_ == -1);
 
   mapped_file_ = handle.fd;
--- a/ipc/chromium/src/base/shared_memory_win.cc
+++ b/ipc/chromium/src/base/shared_memory_win.cc
@@ -59,31 +59,16 @@ namespace base {
 SharedMemory::SharedMemory()
     : external_section_(false),
       mapped_file_(NULL),
       memory_(NULL),
       read_only_(false),
       max_size_(0) {
 }
 
-SharedMemory::SharedMemory(SharedMemory&& other) {
-  if (this == &other) {
-    return;
-  }
-
-  mapped_file_ = other.mapped_file_;
-  memory_ = other.memory_;
-  read_only_ = other.read_only_;
-  max_size_ = other.max_size_;
-  external_section_ = other.external_section_;
-
-  other.mapped_file_ = nullptr;
-  other.memory_ = nullptr;
-}
-
 SharedMemory::~SharedMemory() {
   external_section_ = true;
   Close();
 }
 
 bool SharedMemory::SetHandle(SharedMemoryHandle handle, bool read_only) {
   DCHECK(mapped_file_ == NULL);
 
--- a/ipc/glue/EnvironmentMap.h
+++ b/ipc/glue/EnvironmentMap.h
@@ -1,10 +1,8 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef SANDBOXING_COMMON_ENVIRONMENTMAP_H_
 #define SANDBOXING_COMMON_ENVIRONMENTMAP_H_
 
 #include <map>
@@ -15,17 +13,17 @@ namespace base {
 
 #if defined(OS_WIN)
 
 typedef std::wstring NativeEnvironmentString;
 typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
     EnvironmentMap;
 
 #define ENVIRONMENT_LITERAL(x) L##x
-#define ENVIRONMENT_STRING(x) ((std::wstring)(NS_ConvertUTF8toUTF16((x)).get()))
+#define ENVIRONMENT_STRING(x) (std::wstring)(NS_ConvertUTF8toUTF16((x)).get())
 
 // Returns a modified environment vector constructed from the given environment
 // and the list of changes given in |changes|. Each key in the environment is
 // matched against the first element of the pairs. In the event of a match, the
 // value is replaced by the second of the pair, unless the second is empty, in
 // which case the key-value is removed.
 //
 // This Windows version takes and returns a Windows-style environment block
@@ -37,17 +35,17 @@ NativeEnvironmentString AlterEnvironment
 
 #elif defined(OS_POSIX)
 
 typedef std::string NativeEnvironmentString;
 typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
     EnvironmentMap;
 
 #define ENVIRONMENT_LITERAL(x) x
-#define ENVIRONMENT_STRING(x) x
+#define ENVIRONMENT_STRING(x) ((x)).get()
 
 // See general comments for the Windows version above.
 //
 // This Posix version takes and returns a Posix-style environment block, which
 // is a null-terminated list of pointers to null-terminated strings. The
 // returned array will have appended to it the storage for the array itself so
 // there is only one pointer to manage, but this means that you can't copy the
 // array without keeping the original around.
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -28,24 +28,21 @@
 #include "nsAppDirectoryServiceDefs.h"
 #endif
 
 #include "nsExceptionHandler.h"
 
 #include "nsDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsPrintfCString.h"
-#include "nsIObserverService.h"
 
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/EnvironmentMap.h"
 #include "mozilla/Omnijar.h"
-#include "mozilla/Services.h"
-#include "mozilla/SharedThreadPool.h"
-#include "mozilla/StaticMutex.h"
 #include "mozilla/Telemetry.h"
 #include "ProtocolUtils.h"
 #include <sys/stat.h>
 
 #ifdef XP_WIN
 #include "nsIWinTaskbar.h"
 #include <stdlib.h>
 #define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
@@ -64,17 +61,16 @@
 #include "nsTArray.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsNativeCharsetUtils.h"
 #include "nscore.h" // for NS_FREE_PERMANENT_DATA
 #include "private/pprio.h"
 
 using mozilla::MonitorAutoLock;
-using mozilla::StaticMutexAutoLock;
 using mozilla::ipc::GeckoChildProcessHost;
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #include "GeneratedJNIWrappers.h"
 #include "mozilla/jni/Refs.h"
 #include "mozilla/jni/Utils.h"
 #endif
@@ -345,19 +341,16 @@ GeckoChildProcessHost::SyncLaunch(std::v
 
 bool
 GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts)
 {
   PrepareLaunch();
 
   MessageLoop* ioLoop = XRE_GetIOMessageLoop();
 
-  MOZ_ASSERT(mHandlePromise == nullptr);
-  mHandlePromise = new HandlePromise::Private(__func__);
-
   // Currently this can't fail (see the MOZ_ALWAYS_SUCCEEDS in
   // MessageLoop::PostTask_Helper), but in the future it possibly
   // could, in which case this method could return false.
   ioLoop->PostTask(NewNonOwningRunnableMethod<std::vector<std::string>>(
     "ipc::GeckoChildProcessHost::RunPerformAsyncLaunch",
     this,
     &GeckoChildProcessHost::RunPerformAsyncLaunch,
     aExtraOpts));
@@ -485,126 +478,41 @@ GeckoChildProcessHost::GetChildLogName(c
     buffer.Append(origLogName);
   }
 
   // Append child-specific postfix to name
   buffer.AppendLiteral(".child-");
   buffer.AppendInt(mChildCounter);
 }
 
-namespace {
-// Windows needs a single dedicated thread for process launching,
-// because of thread-safety restrictions/assertions in the sandbox
-// code.  (This implementation isn't itself Windows-specific, so
-// the ifdef can be changed to test on other platforms.)
-#ifdef XP_WIN
-
-static mozilla::StaticMutex gIPCLaunchThreadMutex;
-static mozilla::StaticRefPtr<nsIThread> gIPCLaunchThread;
-
-class IPCLaunchThreadObserver final : public nsIObserver
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-protected:
-  virtual ~IPCLaunchThreadObserver() = default;
-};
-
-NS_IMPL_ISUPPORTS(IPCLaunchThreadObserver, nsIObserver, nsISupports)
-
-NS_IMETHODIMP
-IPCLaunchThreadObserver::Observe(nsISupports* aSubject,
-                                 const char* aTopic,
-                                 const char16_t* aData)
-{
-  MOZ_RELEASE_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0);
-  StaticMutexAutoLock lock(gIPCLaunchThreadMutex);
-
-  nsresult rv = NS_OK;
-  if (gIPCLaunchThread) {
-    rv = gIPCLaunchThread->Shutdown();
-    gIPCLaunchThread = nullptr;
-  }
-  mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
-  return rv;
-}
-
-static nsCOMPtr<nsIEventTarget>
-GetIPCLauncher()
-{
-  StaticMutexAutoLock lock(gIPCLaunchThreadMutex);
-  if (!gIPCLaunchThread) {
-    nsCOMPtr<nsIThread> thread;
-    nsresult rv =
-      NS_NewNamedThread(NS_LITERAL_CSTRING("IPC Launch"),
-                        getter_AddRefs(thread));
-    if (!NS_WARN_IF(NS_FAILED(rv))) {
-      NS_DispatchToMainThread(NS_NewRunnableFunction(
-        "GeckoChildProcessHost::GetIPCLauncher",
-        [] {
-          nsCOMPtr<nsIObserverService> obsService =
-            mozilla::services::GetObserverService();
-          nsCOMPtr<nsIObserver> obs = new IPCLaunchThreadObserver();
-          obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
-        }));
-      gIPCLaunchThread = thread.forget();
-    }
-  }
-
-  nsCOMPtr<nsIEventTarget> thread = gIPCLaunchThread.get();
-  return thread;
-}
-
-#else // XP_WIN
-
-// Non-Windows platforms can use an on-demand thread pool.
-
-static nsCOMPtr<nsIEventTarget>
-GetIPCLauncher()
-{
-  nsCOMPtr<nsIEventTarget> pool =
-    mozilla::SharedThreadPool::Get(NS_LITERAL_CSTRING("IPC Launch"));
-  return pool;
-}
-
-#endif // XP_WIN
-} // anonymous namespace
-
 bool
 GeckoChildProcessHost::RunPerformAsyncLaunch(std::vector<std::string> aExtraOpts)
 {
-  // This (probably?) needs to happen on the I/O thread.
   InitializeChannel();
 
-  auto launcher = GetIPCLauncher();
-  if (NS_WARN_IF(!launcher)) {
-    return false;
+  bool ok = PerformAsyncLaunch(aExtraOpts);
+  if (!ok) {
+    // WaitUntilConnected might be waiting for us to signal.
+    // If something failed let's set the error state and notify.
+    MonitorAutoLock lock(mMonitor);
+    mProcessState = PROCESS_ERROR;
+    lock.Notify();
+#ifdef ASYNC_CONTENTPROC_LAUNCH
+    OnProcessLaunchError();
+#endif
+    CHROMIUM_LOG(ERROR) << "Failed to launch " <<
+      XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
+    Telemetry::Accumulate(Telemetry::SUBPROCESS_LAUNCH_FAILURE,
+      nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
+#ifdef ASYNC_CONTENTPROC_LAUNCH
+  } else {
+    OnProcessHandleReady(mChildProcessHandle);
+#endif
   }
-
-  // But the rest of this doesn't, and shouldn't block IPC messages:
-  nsresult rv = launcher->Dispatch(NS_NewRunnableFunction(
-    "ipc::GeckoChildProcessHost::PerformAsyncLaunch",
-    [this, aExtraOpts = std::move(aExtraOpts)]() {
-      bool ok = PerformAsyncLaunch(aExtraOpts);
-
-      if (!ok) {
-        // WaitUntilConnected might be waiting for us to signal.
-        // If something failed let's set the error state and notify.
-        MonitorAutoLock lock(mMonitor);
-        mProcessState = PROCESS_ERROR;
-        mHandlePromise->Reject(LaunchError{}, __func__);
-        lock.Notify();
-        CHROMIUM_LOG(ERROR) << "Failed to launch " <<
-          XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
-        Telemetry::Accumulate(Telemetry::SUBPROCESS_LAUNCH_FAILURE,
-          nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
-       }
-    }), NS_DISPATCH_NORMAL);
-  return !NS_WARN_IF(NS_FAILED(rv));
+  return ok;
 }
 
 void
 #if defined(XP_WIN)
 AddAppDirToCommandLine(CommandLine& aCmdLine)
 #else
 AddAppDirToCommandLine(std::vector<std::string>& aCmdLine)
 #endif
@@ -658,60 +566,55 @@ AddAppDirToCommandLine(std::vector<std::
     }
   }
 }
 
 bool
 GeckoChildProcessHost::PerformAsyncLaunch(std::vector<std::string> aExtraOpts)
 {
 #ifdef MOZ_GECKO_PROFILER
-  GetProfilerEnvVarsForChildProcess([this](const char* key, const char* value) {
-    mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
-      ENVIRONMENT_STRING(value);
-  });
+  AutoSetProfilerEnvVarsForChildProcess profilerEnvironment;
 #endif
 
-  const auto startTS = TimeStamp::Now();
-
   // - Note: this code is not called re-entrantly, nor are restoreOrig*LogName
   //   or mChildCounter touched by any other thread, so this is safe.
   ++mChildCounter;
 
   const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE");
   const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE");
 
   if (origNSPRLogName) {
     nsAutoCString nsprLogName;
     GetChildLogName(origNSPRLogName, nsprLogName);
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("NSPR_LOG_FILE")]
-      = ENVIRONMENT_STRING(nsprLogName.get());
+        = ENVIRONMENT_STRING(nsprLogName);
   }
   if (origMozLogName) {
     nsAutoCString mozLogName;
     GetChildLogName(origMozLogName, mozLogName);
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MOZ_LOG_FILE")]
-      = ENVIRONMENT_STRING(mozLogName.get());
+        = ENVIRONMENT_STRING(mozLogName);
   }
 
   // `RUST_LOG_CHILD` is meant for logging child processes only.
   nsAutoCString childRustLog(PR_GetEnv("RUST_LOG_CHILD"));
   if (!childRustLog.IsEmpty()) {
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("RUST_LOG")]
-      = ENVIRONMENT_STRING(childRustLog.get());
+        = ENVIRONMENT_STRING(childRustLog);
   }
 
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
   if (!mTmpDirName.IsEmpty()) {
     // Point a bunch of things that might want to write from content to our
     // shiny new content-process specific tmpdir
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("TMPDIR")] =
-      ENVIRONMENT_STRING(mTmpDirName.get());
+      ENVIRONMENT_STRING(mTmpDirName);
     // Partial fix for bug 1380051 (not persistent - should be)
     mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MESA_GLSL_CACHE_DIR")] =
-      ENVIRONMENT_STRING(mTmpDirName.get());
+      ENVIRONMENT_STRING(mTmpDirName);
   }
 #endif
 
   // We rely on the fact that InitializeChannel() has already been processed
   // on the IO thread before this point is reached.
   if (!GetChannel()) {
     return false;
   }
@@ -1224,45 +1127,48 @@ GeckoChildProcessHost::PerformAsyncLaunc
 #else
   CrashReporter::RegisterChildCrashAnnotationFileDescriptor(process,
                                                             crashAnnotationReadPipe);
 #endif
   PR_Close(crashAnnotationWritePipe);
 
   MonitorAutoLock lock(mMonitor);
   mProcessState = PROCESS_CREATED;
-  mHandlePromise->Resolve(process, __func__);
   lock.Notify();
 
   mLaunchOptions = nullptr;
-
-  Telemetry::AccumulateTimeDelta(Telemetry::CHILD_PROCESS_LAUNCH_MS, startTS);
-
   return true;
 }
 
 bool
 GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid)
 {
   if (mChildProcessHandle) {
     MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle));
     return true;
   }
 
   return base::OpenPrivilegedProcessHandle(aPid, &mChildProcessHandle);
 }
 
 void
+GeckoChildProcessHost::OnProcessHandleReady(ProcessHandle aProcessHandle)
+{}
+
+void
+GeckoChildProcessHost::OnProcessLaunchError()
+{}
+
+void
 GeckoChildProcessHost::OnChannelConnected(int32_t peer_pid)
 {
   if (!OpenPrivilegedHandle(peer_pid)) {
     MOZ_CRASH("can't open handle to child process");
   }
   MonitorAutoLock lock(mMonitor);
-  MOZ_DIAGNOSTIC_ASSERT(mProcessState == PROCESS_CREATED);
   mProcessState = PROCESS_CONNECTED;
   lock.Notify();
 }
 
 void
 GeckoChildProcessHost::OnMessageReceived(IPC::Message&& aMsg)
 {
   // We never process messages ourself, just save them up for the next
@@ -1274,30 +1180,22 @@ void
 GeckoChildProcessHost::OnChannelError()
 {
   // Update the process state to an error state if we have a channel
   // error before we're connected. This fixes certain failures,
   // but does not address the full range of possible issues described
   // in the FIXME comment below.
   MonitorAutoLock lock(mMonitor);
   if (mProcessState < PROCESS_CONNECTED) {
-    MOZ_DIAGNOSTIC_ASSERT(mProcessState == PROCESS_CREATED);
     mProcessState = PROCESS_ERROR;
     lock.Notify();
   }
   // FIXME/bug 773925: save up this error for the next listener.
 }
 
-RefPtr<GeckoChildProcessHost::HandlePromise>
-GeckoChildProcessHost::WhenProcessHandleReady()
-{
-  MOZ_ASSERT(mHandlePromise != nullptr);
-  return mHandlePromise;
-}
-
 void
 GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue)
 {
   // If this is called off the IO thread, bad things will happen.
   DCHECK(MessageLoopForIO::current());
   swap(queue, mQueue);
   // We expect the next listener to take over processing of our queue.
 }
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -10,17 +10,16 @@
 #include "base/file_path.h"
 #include "base/process_util.h"
 #include "base/waitable_event.h"
 #include "chrome/common/child_process_host.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ipc/FileDescriptor.h"
 #include "mozilla/Monitor.h"
-#include "mozilla/MozPromise.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsCOMPtr.h"
 #include "nsXULAppAPI.h"        // for GeckoProcessType
 #include "nsString.h"
 
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
@@ -43,18 +42,17 @@ public:
                                  bool aIsFileContent = false);
 
   ~GeckoChildProcessHost();
 
   static uint32_t GetUniqueID();
 
   // Does not block.  The IPC channel may not be initialized yet, and
   // the child process may or may not have been created when this
-  // method returns.  This GeckoChildProcessHost must not be destroyed
-  // while the launch is in progress.
+  // method returns.
   bool AsyncLaunch(StringVector aExtraOpts=StringVector());
 
   virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0);
 
   // Block until the IPC channel for our subprocess is initialized and
   // the OS process is created.  The subprocess may or may not have
   // connected back to us when this method returns.
   //
@@ -69,30 +67,23 @@ public:
   bool LaunchAndWaitForProcessHandle(StringVector aExtraOpts=StringVector());
 
   // Block until the child process has been created and it connects to
   // the IPC channel, meaning it's fully initialized.  (Or until an
   // error occurs.)
   bool SyncLaunch(StringVector aExtraOpts=StringVector(),
                   int32_t timeoutMs=0);
 
+  virtual void OnProcessHandleReady(ProcessHandle aProcessHandle);
+  virtual void OnProcessLaunchError();
   virtual void OnChannelConnected(int32_t peer_pid) override;
   virtual void OnMessageReceived(IPC::Message&& aMsg) override;
   virtual void OnChannelError() override;
   virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
 
-  struct LaunchError {};
-  template <typename T>
-  using LaunchPromise = mozilla::MozPromise<T, LaunchError, /* excl: */ false>;
-  using HandlePromise = LaunchPromise<base::ProcessHandle>;
-
-  // Resolves to the process handle when it's available (see
-  // LaunchAndWaitForProcessHandle); use with AsyncLaunch.
-  RefPtr<HandlePromise> WhenProcessHandleReady();
-
   virtual void InitializeChannel();
 
   virtual bool CanShutdown() override { return true; }
 
   IPC::Channel* GetChannel() {
     return channelp();
   }
 
@@ -176,29 +167,28 @@ protected:
   int32_t mSandboxLevel;
 #endif
 #endif // XP_WIN
 
   ProcessHandle mChildProcessHandle;
 #if defined(OS_MACOSX)
   task_t mChildTask;
 #endif
-  RefPtr<HandlePromise::Private> mHandlePromise;
 
   bool OpenPrivilegedHandle(base::ProcessId aPid);
 
 private:
   DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost);
 
-  // Does the actual work for AsyncLaunch; run in a thread pool
-  // (or, on Windows, a dedicated thread).
+  // Does the actual work for AsyncLaunch, on the IO thread.
+  // (TODO, bug 1487287: move this to its own thread(s).)
   bool PerformAsyncLaunch(StringVector aExtraOpts);
 
-  // Called on the I/O thread; creates channel, dispatches
-  // PerformAsyncLaunch, and consolidates error handling.
+  // Also called on the I/O thread; creates channel, launches, and
+  // consolidates error handling.
   bool RunPerformAsyncLaunch(StringVector aExtraOpts);
 
   enum class BinaryPathType {
     Self,
     PluginContainer
   };
 
   static BinaryPathType GetPathToBinary(FilePath& exePath, GeckoProcessType processType);
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -671,18 +671,20 @@ IProtocol::ManagedState::GetActorEventTa
 {
   return mProtocol->Manager()->GetActorEventTarget(aActor);
 }
 
 IToplevelProtocol::IToplevelProtocol(const char* aName,
                                      ProtocolId aProtoId,
                                      Side aSide)
   : IProtocol(aSide, MakeUnique<ToplevelState>(aName, this, aSide))
+  , mMonitor("mozilla.ipc.IToplevelProtocol.mMonitor")
   , mProtocolId(aProtoId)
   , mOtherPid(mozilla::ipc::kInvalidProcessId)
+  , mOtherPidState(ProcessIdState::eUnstarted)
   , mIsMainThreadProtocol(false)
 {
 }
 
 IToplevelProtocol::~IToplevelProtocol()
 {
   mState = nullptr;
   if (mTrans) {
@@ -697,31 +699,48 @@ IToplevelProtocol::OtherPid() const
   base::ProcessId pid = OtherPidMaybeInvalid();
   MOZ_RELEASE_ASSERT(pid != kInvalidProcessId);
   return pid;
 }
 
 base::ProcessId
 IToplevelProtocol::OtherPidMaybeInvalid() const
 {
+  MonitorAutoLock lock(mMonitor);
+
+  if (mOtherPidState == ProcessIdState::eUnstarted) {
+    // If you're asking for the pid of a process we haven't even tried to
+    // start, you get an invalid pid back immediately.
+    return kInvalidProcessId;
+  }
+
+  while (mOtherPidState < ProcessIdState::eReady) {
+    lock.Wait();
+  }
+  MOZ_RELEASE_ASSERT(mOtherPidState == ProcessIdState::eReady);
+
   return mOtherPid;
 }
 
 void
-IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid)
+IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid,
+                                     ProcessIdState aState)
 {
+  MonitorAutoLock lock(mMonitor);
   // When recording an execution, all communication we do is forwarded from
   // the middleman to the parent process, so use its pid instead of the
   // middleman's pid.
   if (recordreplay::IsRecordingOrReplaying() &&
       aOtherPid == recordreplay::child::MiddlemanProcessId()) {
     mOtherPid = recordreplay::child::ParentProcessId();
   } else {
     mOtherPid = aOtherPid;
   }
+  mOtherPidState = aState;
+  lock.NotifyAll();
 }
 
 bool
 IToplevelProtocol::TakeMinidump(nsIFile** aDump, uint32_t* aSequence)
 {
   MOZ_RELEASE_ASSERT(GetSide() == ParentSide);
   return XRE_TakeMinidumpForChild(OtherPid(), aDump, aSequence);
 }
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -414,16 +414,23 @@ class IToplevelProtocol : public IProtoc
     template<class PFooSide> friend class Endpoint;
 
 protected:
     explicit IToplevelProtocol(const char* aName, ProtocolId aProtoId,
                                Side aSide);
     ~IToplevelProtocol();
 
 public:
+    enum ProcessIdState {
+        eUnstarted,
+        ePending,
+        eReady,
+        eError
+    };
+
     class ToplevelState final : public ProtocolState
     {
 #ifdef FUZZING
       friend class mozilla::ipc::ProtocolFuzzerHelper;
 #endif
 
     public:
         ToplevelState(const char* aName, IToplevelProtocol* aProtocol, Side aSide);
@@ -475,17 +482,18 @@ public:
         mTrans = std::move(aTrans);
     }
 
     Transport* GetTransport() const { return mTrans.get(); }
 
     ProtocolId GetProtocolId() const { return mProtocolId; }
 
     base::ProcessId OtherPid() const final;
-    void SetOtherProcessId(base::ProcessId aOtherPid);
+    void SetOtherProcessId(base::ProcessId aOtherPid,
+                           ProcessIdState aState = ProcessIdState::eReady);
 
     bool TakeMinidump(nsIFile** aDump, uint32_t* aSequence);
 
     virtual void OnChannelClose() = 0;
     virtual void OnChannelError() = 0;
     virtual void ProcessingError(Result aError, const char* aMsgName) {}
     virtual void OnChannelConnected(int32_t peer_pid) {}
 
@@ -615,22 +623,26 @@ protected:
     virtual already_AddRefed<nsIEventTarget>
     GetConstructedEventTarget(const Message& aMsg) { return nullptr; }
 
     // Override this method in top-level protocols to change the event target
     // for specific messages.
     virtual already_AddRefed<nsIEventTarget>
     GetSpecificMessageEventTarget(const Message& aMsg) { return nullptr; }
 
+    // This monitor protects mOtherPid and mOtherPidState. All other fields
+    // should only be accessed on the worker thread.
+    mutable mozilla::Monitor mMonitor;
   private:
     base::ProcessId OtherPidMaybeInvalid() const;
 
     ProtocolId mProtocolId;
     UniquePtr<Transport> mTrans;
     base::ProcessId mOtherPid;
+    ProcessIdState mOtherPidState;
     bool mIsMainThreadProtocol;
 };
 
 class IShmemAllocator
 {
 public:
   virtual bool AllocShmem(size_t aSize,
                           mozilla::ipc::SharedMemory::SharedMemoryType aShmType,
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -262,27 +262,16 @@
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "kgupta@mozilla.com"],
     "bug_numbers": [1238040],
     "expires_in_version": "70",
     "kind": "exponential",
     "high": 1073741824,
     "n_buckets": 50,
     "description": "Opaque measure of the severity of a checkerboard event"
   },
-  "CHILD_PROCESS_LAUNCH_MS" : {
-    "record_in_processes": ["main"],
-    "alert_emails": ["jld@mozilla.com", "mconley@mozilla.com"],
-    "expires_in_version": "70",
-    "bug_numbers": [1474991],
-    "kind": "exponential",
-    "high": 64000,
-    "n_buckets": 100,
-    "releaseChannelCollection": "opt-out",
-    "description": "Time spent in the generic child process launching code, which is run off-main-thread and used by all child process types"
-  },
   "COMPOSITE_TIME" : {
     "record_in_processes": ["main", "content", "gpu"],
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "rhunt@mozilla.com"],
     "expires_in_version": "never",
     "description": "Composite times in milliseconds",
     "kind": "exponential",
     "high": 1000,
     "n_buckets": 50
@@ -291,57 +280,26 @@
     "record_in_processes": ["main", "content", "gpu"],
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "rhunt@mozilla.com"],
     "expires_in_version": "never",
     "description": "Time from vsync to finishing a composite in milliseconds.",
     "kind": "exponential",
     "high": 1000,
     "n_buckets": 50
   },
-  "CONTENT_PROCESS_LAUNCH_MAINTHREAD_MS" : {
-    "record_in_processes": ["main"],
-    "alert_emails": ["jld@mozilla.com", "mconley@mozilla.com"],
-    "expires_in_version": "70",
-    "bug_numbers": [1474991],
-    "kind": "exponential",
-    "high": 64000,
-    "n_buckets": 100,
-    "releaseChannelCollection": "opt-out",
-    "description": "Time spent on the main thread during asynchronous content process launch."
-  },
-  "CONTENT_PROCESS_LAUNCH_TOTAL_MS" : {
-    "record_in_processes": ["main"],
-    "alert_emails": ["jld@mozilla.com", "mconley@mozilla.com"],
-    "expires_in_version": "70",
-    "bug_numbers": [1474991],
+  "CONTENT_PROCESS_LAUNCH_TIME_MS" : {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["fgomes@mozilla.com", "mconley@mozilla.com"],
+    "expires_in_version": "67",
+    "bug_numbers": [1304790],
     "kind": "exponential",
     "high": 64000,
     "n_buckets": 100,
     "releaseChannelCollection": "opt-out",
-    "description": "Total time elapsed during asynchronous content process launch, until the process is usable for loading content."
-  },
-  "CONTENT_PROCESS_SYNC_LAUNCH_MS" : {
-    "record_in_processes": ["main"],
-    "alert_emails": ["jld@mozilla.com", "mconley@mozilla.com"],
-    "expires_in_version": "67",
-    "bug_numbers": [1474991],
-    "kind": "exponential",
-    "high": 64000,
-    "n_buckets": 100,
-    "releaseChannelCollection": "opt-out",
-    "description": "Time elapsed during synchronous content process launch until the process is usable for loading content."
-  },
-  "CONTENT_PROCESS_LAUNCH_IS_SYNC" : {
-    "record_in_processes": ["main"],
-    "alert_emails": ["jld@mozilla.com", "mconley@mozilla.com"],
-    "expires_in_version": "67",
-    "bug_numbers": [1474991],
-    "kind": "boolean",
-    "releaseChannelCollection": "opt-out",
-    "description": "Whether a content process was launched synchronously (unnecessarily delaying UI response)."
+    "description": "Content process launch time until the GetXPCOMProcessAttributes message is received, in milliseconds"
   },
   "CONTENT_RESPONSE_DURATION" : {
     "record_in_processes": ["main", "content", "gpu"],
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "kgupta@mozilla.com"],
     "bug_numbers": [1261373],
     "expires_in_version": "70",
     "description": "Main thread response times for APZ notifications about input events (ms)",
     "kind" : "exponential",
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -31,17 +31,16 @@
 #include <fstream>
 #include <sstream>
 #include <errno.h>
 
 #include "platform.h"
 #include "PlatformMacros.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
-#include "mozilla/Printf.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Vector.h"
 #include "GeckoProfiler.h"
 #include "VTuneProfiler.h"
 #include "GeckoProfilerReporter.h"
 #include "ProfilerIOInterposeObserver.h"
 #include "mozilla/AutoProfilerLabel.h"
 #include "mozilla/ExtensionPolicyService.h"
@@ -2967,58 +2966,80 @@ profiler_get_start_params(int* aCapacity
 
   const Vector<std::string>& filters = ActivePS::Filters(lock);
   MOZ_ALWAYS_TRUE(aFilters->resize(filters.length()));
   for (uint32_t i = 0; i < filters.length(); ++i) {
     (*aFilters)[i] = filters[i].c_str();
   }
 }
 
-namespace mozilla {
-
-void
-GetProfilerEnvVarsForChildProcess(
-  std::function<void(const char* key, const char* value)>&& aSetEnv)
+AutoSetProfilerEnvVarsForChildProcess::AutoSetProfilerEnvVarsForChildProcess(
+  MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+  : mSetCapacity()
+  , mSetInterval()
+  , mSetFeaturesBitfield()
+  , mSetFilters()
 {
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
   if (!ActivePS::Exists(lock)) {
-    aSetEnv("MOZ_PROFILER_STARTUP", "");
+    PR_SetEnv("MOZ_PROFILER_STARTUP=");
     return;
   }
 
-  aSetEnv("MOZ_PROFILER_STARTUP", "1");
-  auto capacityString = Smprintf("%d", ActivePS::Capacity(lock));
-  aSetEnv("MOZ_PROFILER_STARTUP_ENTRIES", capacityString.get());
-
-  // Use AppendFloat instead of Smprintf with %f because the decimal
+  PR_SetEnv("MOZ_PROFILER_STARTUP=1");
+  SprintfLiteral(mSetCapacity, "MOZ_PROFILER_STARTUP_ENTRIES=%d",
+                 ActivePS::Capacity(lock));
+  PR_SetEnv(mSetCapacity);
+
+  // Use AppendFloat instead of SprintfLiteral with %f because the decimal
   // separator used by %f is locale-dependent. But the string we produce needs
   // to be parseable by strtod, which only accepts the period character as a
   // decimal separator. AppendFloat always uses the period character.
-  nsCString intervalString;
-  intervalString.AppendFloat(ActivePS::Interval(lock));
-  aSetEnv("MOZ_PROFILER_STARTUP_INTERVAL", intervalString.get());
-
-  auto featuresString = Smprintf("%d", ActivePS::Features(lock));
-  aSetEnv("MOZ_PROFILER_STARTUP_FEATURES_BITFIELD", featuresString.get());
+  nsCString setInterval;
+  setInterval.AppendLiteral("MOZ_PROFILER_STARTUP_INTERVAL=");
+  setInterval.AppendFloat(ActivePS::Interval(lock));
+  strncpy(mSetInterval, setInterval.get(), MOZ_ARRAY_LENGTH(mSetInterval));
+  mSetInterval[MOZ_ARRAY_LENGTH(mSetInterval) - 1] = '\0';
+  PR_SetEnv(mSetInterval);
+
+  SprintfLiteral(mSetFeaturesBitfield,
+                 "MOZ_PROFILER_STARTUP_FEATURES_BITFIELD=%d",
+                 ActivePS::Features(lock));
+  PR_SetEnv(mSetFeaturesBitfield);
 
   std::string filtersString;
   const Vector<std::string>& filters = ActivePS::Filters(lock);
   for (uint32_t i = 0; i < filters.length(); ++i) {
     filtersString += filters[i];
     if (i != filters.length() - 1) {
       filtersString += ",";
     }
   }
-  aSetEnv("MOZ_PROFILER_STARTUP_FILTERS", filtersString.c_str());
+  SprintfLiteral(mSetFilters, "MOZ_PROFILER_STARTUP_FILTERS=%s",
+                 filtersString.c_str());
+  PR_SetEnv(mSetFilters);
 }
 
-} // namespace mozilla
+AutoSetProfilerEnvVarsForChildProcess::~AutoSetProfilerEnvVarsForChildProcess()
+{
+  // Our current process doesn't look at these variables after startup, so we
+  // can just unset all the variables. This allows us to use literal strings,
+  // which will be valid for the whole life time of the program and can be
+  // passed to PR_SetEnv without problems.
+  PR_SetEnv("MOZ_PROFILER_STARTUP=");
+  PR_SetEnv("MOZ_PROFILER_STARTUP_ENTRIES=");
+  PR_SetEnv("MOZ_PROFILER_STARTUP_INTERVAL=");
+  PR_SetEnv("MOZ_PROFILER_STARTUP_FEATURES_BITFIELD=");
+  PR_SetEnv("MOZ_PROFILER_STARTUP_FILTERS=");
+}
 
 static void
 locked_profiler_save_profile_to_file(PSLockRef aLock, const char* aFilename,
                                      bool aIsShuttingDown = false)
 {
   LOG("locked_profiler_save_profile_to_file(%s)", aFilename);
 
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -924,21 +924,30 @@ public:
 protected:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   const char* mCategory;
   const char* mMarkerName;
   const mozilla::Maybe<nsID> mDocShellId;
   const mozilla::Maybe<uint32_t> mDocShellHistoryId;
 };
 
-// Get the MOZ_PROFILER_STARTUP* environment variables that should be
-// supplied to a child process that is about to be launched, in order
-// to make that child process start with the same profiler settings as
-// in the current process.  The given function is invoked once for
-// each variable to be set.
-void GetProfilerEnvVarsForChildProcess(
-  std::function<void(const char* key, const char* value)>&& aSetEnv);
+// Set MOZ_PROFILER_STARTUP* environment variables that will be inherited into
+// a child process that is about to be launched, in order to make that child
+// process start with the same profiler settings as in the current process.
+class MOZ_RAII AutoSetProfilerEnvVarsForChildProcess
+{
+public:
+  explicit AutoSetProfilerEnvVarsForChildProcess(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+  ~AutoSetProfilerEnvVarsForChildProcess();
+
+private:
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+  char mSetCapacity[64];
+  char mSetInterval[64];
+  char mSetFeaturesBitfield[64];
+  char mSetFilters[1024];
+};
 
 } // namespace mozilla
 
 #endif // !MOZ_GECKO_PROFILER
 
 #endif  // GeckoProfiler_h