Bug 1401786 - Move base::LaunchApp options into a LaunchOptions struct, like upstream Chromium. r?billm draft
authorJed Davis <jld@mozilla.com>
Fri, 08 Sep 2017 20:35:06 -0600
changeset 691858 e4d89f103f98a2ae2302672e0739faefab60ae75
parent 691857 cf779fbce3d6e2341f4d1fc48013bc16bbc657c8
child 691859 f52e81058679e015144760de482b49ebddb32e83
push id87332
push userbmo:jld@mozilla.com
push dateThu, 02 Nov 2017 04:15:49 +0000
reviewersbillm
bugs1401786
milestone58.0a1
Bug 1401786 - Move base::LaunchApp options into a LaunchOptions struct, like upstream Chromium. r?billm MozReview-Commit-ID: 74IXV4oGeWR
ipc/chromium/src/base/process_util.h
ipc/chromium/src/base/process_util_bsd.cc
ipc/chromium/src/base/process_util_linux.cc
ipc/chromium/src/base/process_util_mac.mm
ipc/chromium/src/base/process_util_win.cc
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/GeckoChildProcessHost.h
widget/LSBUtils.cpp
xpcom/threads/nsProcessCommon.cpp
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -90,56 +90,68 @@ ProcessId GetProcId(ProcessHandle proces
 // TODO(agl): remove this function
 // WARNING: do not use. It's inherently race-prone in the face of
 // multi-threading.
 void SetAllFDsToCloseOnExec();
 // Close all file descriptors, expect those which are a destination in the
 // given multimap. Only call this function in a child process where you know
 // that there aren't any other threads.
 void CloseSuperfluousFds(const base::InjectiveMultimap& saved_map);
+
+typedef std::vector<std::pair<int, int> > file_handle_mapping_vector;
+typedef std::map<std::string, std::string> environment_map;
 #endif
 
+struct LaunchOptions {
+  // If true, wait for the process to terminate.  Otherwise, return
+  // immediately.
+  bool wait = false;
+
+#if defined(OS_WIN)
+  bool start_hidden = false;
+#endif
+
+#if defined(OS_POSIX)
+  // Environment variables to be applied in addition to the current
+  // process's environment, replacing them where necessary.
+  environment_map environ;
+
+  // A mapping of (src fd -> dest fd) to propagate into the child
+  // process.  All other fds will be closed, except std{in,out,err}.
+  file_handle_mapping_vector fds_to_remap;
+#endif
+};
+
 #if defined(OS_WIN)
 // Runs the given application name with the given command line. Normally, the
 // first command line argument should be the path to the process, and don't
 // forget to quote it.
 //
-// If wait is true, it will block and wait for the other process to finish,
-// otherwise, it will just continue asynchronously.
-//
 // Example (including literal quotes)
 //  cmdline = "c:\windows\explorer.exe" -foo "c:\bar\"
 //
 // If process_handle is non-NULL, the process handle of the launched app will be
 // stored there on a successful launch.
 // NOTE: In this case, the caller is responsible for closing the handle so
 //       that it doesn't leak!
 bool LaunchApp(const std::wstring& cmdline,
-               bool wait, bool start_hidden, ProcessHandle* process_handle);
+               const LaunchOptions& options,
+               ProcessHandle* process_handle);
+
 #elif defined(OS_POSIX)
 // Runs the application specified in argv[0] with the command line argv.
-// Before launching all FDs open in the parent process will be marked as
-// close-on-exec.  |fds_to_remap| defines a mapping of src fd->dest fd to
-// propagate FDs into the child process.
 //
-// As above, if wait is true, execute synchronously. The pid will be stored
-// in process_handle if that pointer is non-null.
+// The pid will be stored in process_handle if that pointer is
+// non-null.
 //
 // Note that the first argument in argv must point to the filename,
-// and must be fully specified.
-typedef std::vector<std::pair<int, int> > file_handle_mapping_vector;
+// and must be fully specified (i.e., this will not search $PATH).
 bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               bool wait, ProcessHandle* process_handle);
-
-typedef std::map<std::string, std::string> environment_map;
-bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               const environment_map& env_vars_to_set,
-               bool wait, ProcessHandle* process_handle);
+               const LaunchOptions& options,
+               ProcessHandle* process_handle);
 
 // Deleter for the array of strings allocated within BuildEnvironmentArray.
 struct FreeEnvVarsArray
 {
   void operator()(char** array);
 };
 
 typedef mozilla::UniquePtr<char*[], FreeEnvVarsArray> EnvironmentArray;
@@ -147,17 +159,18 @@ typedef mozilla::UniquePtr<char*[], Free
 // Merge an environment map with the current environment.
 // Existing variables are overwritten by env_vars_to_set.
 EnvironmentArray BuildEnvironmentArray(const environment_map& env_vars_to_set);
 #endif
 
 // Executes the application specified by cl. This function delegates to one
 // of the above two platform-specific functions.
 bool LaunchApp(const CommandLine& cl,
-               bool wait, bool start_hidden, ProcessHandle* process_handle);
+               const LaunchOptions&,
+               ProcessHandle* process_handle);
 
 // Used to filter processes by process ID.
 class ProcessFilter {
  public:
   // Returns true to indicate set-inclusion and false otherwise.  This method
   // should not have side-effects and should be idempotent.
   virtual bool Includes(ProcessId pid, ProcessId parent_pid) const = 0;
   virtual ~ProcessFilter() { }
--- a/ipc/chromium/src/base/process_util_bsd.cc
+++ b/ipc/chromium/src/base/process_util_bsd.cc
@@ -19,52 +19,44 @@
 namespace {
 
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
 
-bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               bool wait, ProcessHandle* process_handle) {
-  return LaunchApp(argv, fds_to_remap, environment_map(),
-                   wait, process_handle);
-}
 
 bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               const environment_map& env_vars_to_set,
-               bool wait, ProcessHandle* process_handle) {
+               const LaunchOptions& options,
+               ProcessHandle* process_handle)
+{
   bool retval = true;
 
   char* argv_copy[argv.size() + 1];
   for (size_t i = 0; i < argv.size(); i++) {
     argv_copy[i] = const_cast<char*>(argv[i].c_str());
   }
   argv_copy[argv.size()] = NULL;
 
   // Make sure we don't leak any FDs to the child process by marking all FDs
   // as close-on-exec.
   SetAllFDsToCloseOnExec();
 
-  EnvironmentArray vars = BuildEnvironmentArray(env_vars_to_set);
+  EnvironmentArray vars = BuildEnvironmentArray(options.environ);
 
   posix_spawn_file_actions_t file_actions;
   if (posix_spawn_file_actions_init(&file_actions) != 0) {
     return false;
   }
 
   // Turn fds_to_remap array into a set of dup2 calls.
-  for (file_handle_mapping_vector::const_iterator it = fds_to_remap.begin();
-       it != fds_to_remap.end();
-       ++it) {
-    int src_fd = it->first;
-    int dest_fd = it->second;
+  for (const auto& fd_map : options.fds_to_remap) {
+    int src_fd = fd_map.first;
+    int dest_fd = fd_map.second;
 
     if (src_fd == dest_fd) {
       int flags = fcntl(src_fd, F_GETFD);
       if (flags != -1) {
         fcntl(src_fd, F_SETFD, flags & ~FD_CLOEXEC);
       }
     } else {
       if (posix_spawn_file_actions_adddup2(&file_actions, src_fd, dest_fd) != 0) {
@@ -85,26 +77,25 @@ bool LaunchApp(const std::vector<std::st
   posix_spawn_file_actions_destroy(&file_actions);
 
   bool process_handle_valid = pid > 0;
   if (!spawn_succeeded || !process_handle_valid) {
     retval = false;
   } else {
     gProcessLog.print("==> process %d launched child process %d\n",
                       GetCurrentProcId(), pid);
-    if (wait)
+    if (options.wait)
       HANDLE_EINTR(waitpid(pid, 0, 0));
 
     if (process_handle)
       *process_handle = pid;
   }
 
   return retval;
 }
 
 bool LaunchApp(const CommandLine& cl,
-               bool wait, bool start_hidden, ProcessHandle* process_handle) {
-  // TODO(playmobil): Do we need to respect the start_hidden flag?
-  file_handle_mapping_vector no_files;
-  return LaunchApp(cl.argv(), no_files, wait, process_handle);
+               const LaunchOptions& options,
+               ProcessHandle* process_handle) {
+  return LaunchApp(cl.argv(), options, process_handle);
 }
 
 }  // namespace base
--- a/ipc/chromium/src/base/process_util_linux.cc
+++ b/ipc/chromium/src/base/process_util_linux.cc
@@ -20,43 +20,36 @@ namespace {
 
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
 
 bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               bool wait, ProcessHandle* process_handle) {
-  return LaunchApp(argv, fds_to_remap, environment_map(),
-                   wait, process_handle);
-}
-
-bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               const environment_map& env_vars_to_set,
-               bool wait, ProcessHandle* process_handle) {
+               const LaunchOptions& options,
+               ProcessHandle* process_handle)
+{
   mozilla::UniquePtr<char*[]> argv_cstr(new char*[argv.size() + 1]);
   // Illegal to allocate memory after fork and before execvp
   InjectiveMultimap fd_shuffle1, fd_shuffle2;
-  fd_shuffle1.reserve(fds_to_remap.size());
-  fd_shuffle2.reserve(fds_to_remap.size());
+  fd_shuffle1.reserve(options.fds_to_remap.size());
+  fd_shuffle2.reserve(options.fds_to_remap.size());
 
-  EnvironmentArray envp = BuildEnvironmentArray(env_vars_to_set);
+  EnvironmentArray envp = BuildEnvironmentArray(options.environ);
 
   pid_t pid = fork();
   if (pid < 0)
     return false;
 
   if (pid == 0) {
-    for (file_handle_mapping_vector::const_iterator
-        it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) {
-      fd_shuffle1.push_back(InjectionArc(it->first, it->second, false));
-      fd_shuffle2.push_back(InjectionArc(it->first, it->second, false));
+    // In the child:
+    for (const auto& fd_map : options.fds_to_remap) {
+      fd_shuffle1.push_back(InjectionArc(fd_map.first, fd_map.second, false));
+      fd_shuffle2.push_back(InjectionArc(fd_map.first, fd_map.second, false));
     }
 
     if (!ShuffleFileDescriptors(&fd_shuffle1))
       _exit(127);
 
     CloseSuperfluousFds(fd_shuffle2);
 
     for (size_t i = 0; i < argv.size(); i++)
@@ -64,29 +57,29 @@ bool LaunchApp(const std::vector<std::st
     argv_cstr[argv.size()] = NULL;
 
     execve(argv_cstr[0], argv_cstr.get(), envp.get());
     // if we get here, we're in serious trouble and should complain loudly
     // NOTE: This is async signal unsafe; it could deadlock instead.  (But
     // only on debug builds; otherwise it's a signal-safe no-op.)
     DLOG(ERROR) << "FAILED TO exec() CHILD PROCESS, path: " << argv_cstr[0];
     _exit(127);
-  } else {
-    gProcessLog.print("==> process %d launched child process %d\n",
-                      GetCurrentProcId(), pid);
-    if (wait)
-      HANDLE_EINTR(waitpid(pid, 0, 0));
+  }
 
-    if (process_handle)
-      *process_handle = pid;
-  }
+  // In the parent:
+  gProcessLog.print("==> process %d launched child process %d\n",
+                    GetCurrentProcId(), pid);
+  if (options.wait)
+    HANDLE_EINTR(waitpid(pid, 0, 0));
+
+  if (process_handle)
+    *process_handle = pid;
 
   return true;
 }
 
 bool LaunchApp(const CommandLine& cl,
-               bool wait, bool start_hidden,
+               const LaunchOptions& options,
                ProcessHandle* process_handle) {
-  file_handle_mapping_vector no_files;
-  return LaunchApp(cl.argv(), no_files, wait, process_handle);
+  return LaunchApp(cl.argv(), options, process_handle);
 }
 
 }  // namespace base
--- a/ipc/chromium/src/base/process_util_mac.mm
+++ b/ipc/chromium/src/base/process_util_mac.mm
@@ -1,13 +1,14 @@
+/* -*- 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) 2008 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.
 
-
 #include "base/process_util.h"
 
 #include <fcntl.h>
 #include <spawn.h>
 #include <sys/wait.h>
 
 #include <string>
 
@@ -19,63 +20,55 @@ namespace {
 
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
 
 bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               bool wait, ProcessHandle* process_handle) {
-  return LaunchApp(argv, fds_to_remap, environment_map(),
-                   wait, process_handle);
-}
-
-bool LaunchApp(const std::vector<std::string>& argv,
-               const file_handle_mapping_vector& fds_to_remap,
-               const environment_map& env_vars_to_set,
-               bool wait, ProcessHandle* process_handle) {
+               const LaunchOptions& options,
+               ProcessHandle* process_handle)
+{
   bool retval = true;
 
   char* argv_copy[argv.size() + 1];
   for (size_t i = 0; i < argv.size(); i++) {
     argv_copy[i] = const_cast<char*>(argv[i].c_str());
   }
   argv_copy[argv.size()] = NULL;
 
-  EnvironmentArray vars = BuildEnvironmentArray(env_vars_to_set);
+  EnvironmentArray vars = BuildEnvironmentArray(options.environ);
 
   posix_spawn_file_actions_t file_actions;
   if (posix_spawn_file_actions_init(&file_actions) != 0) {
     return false;
   }
   auto file_actions_guard = mozilla::MakeScopeExit([&file_actions] {
     posix_spawn_file_actions_destroy(&file_actions);
   });
 
   // Turn fds_to_remap array into a set of dup2 calls.
-  for (file_handle_mapping_vector::const_iterator it = fds_to_remap.begin();
-       it != fds_to_remap.end();
-       ++it) {
-    int src_fd = it->first;
-    int dest_fd = it->second;
+  for (const auto& fd_map : options.fds_to_remap) {
+    int src_fd = fd_map.first;
+    int dest_fd = fd_map.second;
 
     if (src_fd == dest_fd) {
       int flags = fcntl(src_fd, F_GETFD);
       if (flags != -1) {
         fcntl(src_fd, F_SETFD, flags & ~FD_CLOEXEC);
       }
     } else {
       if (posix_spawn_file_actions_adddup2(&file_actions, src_fd, dest_fd) != 0) {
         return false;
       }
     }
   }
 
+  // Initialize spawn attributes.
   posix_spawnattr_t spawnattr;
   if (posix_spawnattr_init(&spawnattr) != 0) {
     return false;
   }
   auto spawnattr_guard = mozilla::MakeScopeExit([&spawnattr] {
     posix_spawnattr_destroy(&spawnattr);
   });
 
@@ -102,26 +95,25 @@ bool LaunchApp(const std::vector<std::st
                                       vars.get()) == 0);
 
   bool process_handle_valid = pid > 0;
   if (!spawn_succeeded || !process_handle_valid) {
     retval = false;
   } else {
     gProcessLog.print("==> process %d launched child process %d\n",
                       GetCurrentProcId(), pid);
-    if (wait)
+    if (options.wait)
       HANDLE_EINTR(waitpid(pid, 0, 0));
 
     if (process_handle)
       *process_handle = pid;
   }
 
   return retval;
 }
 
 bool LaunchApp(const CommandLine& cl,
-               bool wait, bool start_hidden, ProcessHandle* process_handle) {
-  // TODO(playmobil): Do we need to respect the start_hidden flag?
-  file_handle_mapping_vector no_files;
-  return LaunchApp(cl.argv(), no_files, wait, process_handle);
+               const LaunchOptions& options,
+               ProcessHandle* process_handle) {
+  return LaunchApp(cl.argv(), options, process_handle);
 }
 
 }  // namespace base
--- a/ipc/chromium/src/base/process_util_win.cc
+++ b/ipc/chromium/src/base/process_util_win.cc
@@ -267,34 +267,35 @@ fail:
 void FreeThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList) {
   // must be impossible to get a NULL DeleteProcThreadAttributeListPtr, as
   // we already checked it existed when creating the data we are now freeing.
   (*DeleteProcThreadAttributeListPtr)(lpAttributeList);
   free(lpAttributeList);
 }
 
 bool LaunchApp(const std::wstring& cmdline,
-               bool wait, bool start_hidden, ProcessHandle* process_handle) {
+               const LaunchOptions& options,
+               ProcessHandle* process_handle) {
 
   // We want to inherit the std handles so dump() statements and assertion
   // messages in the child process can be seen - but we *do not* want to
   // blindly have all handles inherited.  Vista and later has a technique
   // where only specified handles are inherited - so we use this technique.
   // If that fails we just don't inherit anything.
   DWORD dwCreationFlags = 0;
   BOOL bInheritHandles = FALSE;
 
   // We use a STARTUPINFOEX, but if we can't do the thread attribute thing, we
   // just pass the size of a STARTUPINFO.
   STARTUPINFOEX startup_info_ex;
   ZeroMemory(&startup_info_ex, sizeof(startup_info_ex));
   STARTUPINFO &startup_info = startup_info_ex.StartupInfo;
   startup_info.cb = sizeof(startup_info);
   startup_info.dwFlags = STARTF_USESHOWWINDOW;
-  startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW;
+  startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW;
 
   // Per the comment in CreateThreadAttributeList, lpAttributeList will contain
   // a pointer to handlesToInherit, so make sure they have the same lifetime.
   LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
   HANDLE handlesToInherit[2];
   int handleCount = 0;
 
   // setup our handle array first - if we end up with no handles that can
@@ -335,32 +336,32 @@ bool LaunchApp(const std::wstring& cmdli
   gProcessLog.print("==> process %d launched child process %d (%S)\n",
                     GetCurrentProcId(),
                     process_info.dwProcessId,
                     cmdline.c_str());
 
   // Handles must be closed or they will leak
   CloseHandle(process_info.hThread);
 
-  if (wait)
+  if (options.wait)
     WaitForSingleObject(process_info.hProcess, INFINITE);
 
   // If the caller wants the process handle, we won't close it.
   if (process_handle) {
     *process_handle = process_info.hProcess;
   } else {
     CloseHandle(process_info.hProcess);
   }
   return true;
 }
 
 bool LaunchApp(const CommandLine& cl,
-               bool wait, bool start_hidden, ProcessHandle* process_handle) {
-  return LaunchApp(cl.command_line_string(), wait,
-                   start_hidden, process_handle);
+               const LaunchOptions& options,
+               ProcessHandle* process_handle) {
+  return LaunchApp(cl.command_line_string(), options, process_handle);
 }
 
 bool KillProcess(ProcessHandle process, int exit_code, bool wait) {
   bool result = (TerminateProcess(process, exit_code) != FALSE);
   if (result && wait) {
     // The process may not end immediately due to pending I/O
     if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000))
       DLOG(ERROR) << "Error waiting for process exit: " << GetLastError();
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -81,16 +81,17 @@ ShouldHaveDirectoryService()
   return GeckoProcessType_Default == XRE_GetProcessType();
 }
 
 GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
                                              bool aIsFileContent)
   : mProcessType(aProcessType),
     mIsFileContent(aIsFileContent),
     mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"),
+    mLaunchOptions(MakeUnique<base::LaunchOptions>()),
     mProcessState(CREATING_CHANNEL),
 #if defined(MOZ_SANDBOX) && defined(XP_WIN)
     mEnableSandboxLogging(false),
     mSandboxLevel(0),
 #endif
     mChildProcessHandle(0)
 #if defined(MOZ_WIDGET_COCOA)
   , mChildTask(MACH_PORT_NULL)
@@ -637,27 +638,26 @@ GeckoChildProcessHost::PerformAsyncLaunc
 #if defined(OS_POSIX)
   // For POSIX, we have to be extremely anal about *not* using
   // std::wstring in code compiled with Mozilla's -fshort-wchar
   // configuration, because chromium is compiled with -fno-short-wchar
   // and passing wstrings from one config to the other is unsafe.  So
   // we split the logic here.
 
 # if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
-  base::environment_map newEnvVars;
 
 #  if defined(MOZ_WIDGET_GTK)
   if (mProcessType == GeckoProcessType_Content) {
     // disable IM module to avoid sandbox violation
-    newEnvVars["GTK_IM_MODULE"] = "gtk-im-context-simple";
+    mLaunchOptions->environ["GTK_IM_MODULE"] = "gtk-im-context-simple";
 
     // Disable ATK accessibility code in content processes because it conflicts
     // with the sandbox, and we proxy that information through the main process
     // anyway.
-    newEnvVars["NO_AT_BRIDGE"] = "1";
+    mLaunchOptions->environ["NO_AT_BRIDGE"] = "1";
   }
 #  endif // defined(MOZ_WIDGET_GTK)
 
   // XPCOM may not be initialized in some subprocesses.  We don't want
   // to initialize XPCOM just for the directory service, especially
   // since LD_LIBRARY_PATH is already set correctly in subprocesses
   // (meaning that we don't need to set that up in the environment).
   if (ShouldHaveDirectoryService()) {
@@ -673,20 +673,20 @@ GeckoChildProcessHost::PerformAsyncLaunc
       new_ld_lib_path.AppendLiteral("/gtk2:");
       new_ld_lib_path.Append(path.get());
     }
 #   endif // (MOZ_WIDGET_GTK == 3)
     if (ld_library_path && *ld_library_path) {
       new_ld_lib_path.Append(':');
       new_ld_lib_path.Append(ld_library_path);
     }
-    newEnvVars["LD_LIBRARY_PATH"] = new_ld_lib_path.get();
+    mLaunchOptions->environ["LD_LIBRARY_PATH"] = new_ld_lib_path.get();
 
 #  elif OS_MACOSX // defined(OS_LINUX) || defined(OS_BSD)
-    newEnvVars["DYLD_LIBRARY_PATH"] = path.get();
+    mLaunchOptions->environ["DYLD_LIBRARY_PATH"] = path.get();
     // XXX DYLD_INSERT_LIBRARIES should only be set when launching a plugin
     //     process, and has no effect on other subprocesses (the hooks in
     //     libplugin_child_interpose.dylib become noops).  But currently it
     //     gets set when launching any kind of subprocess.
     //
     // Trigger "dyld interposing" for the dylib that contains
     // plugin_child_interpose.mm.  This allows us to hook OS calls in the
     // plugin process (ones that don't work correctly in a background
@@ -695,17 +695,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
     const char* prevInterpose = PR_GetEnv("DYLD_INSERT_LIBRARIES");
     nsCString interpose;
     if (prevInterpose && strlen(prevInterpose) > 0) {
       interpose.Assign(prevInterpose);
       interpose.Append(':');
     }
     interpose.Append(path.get());
     interpose.AppendLiteral("/libplugin_child_interpose.dylib");
-    newEnvVars["DYLD_INSERT_LIBRARIES"] = interpose.get();
+    mLaunchOptions->environ["DYLD_INSERT_LIBRARIES"] = interpose.get();
 #  endif // defined(OS_LINUX) || defined(OS_BSD)
   }
 # endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
 
   FilePath exePath;
   BinaryPathType pathType = GetPathToBinary(exePath, mProcessType);
 
 # if defined(XP_LINUX) && defined(MOZ_SANDBOX)
@@ -720,25 +720,26 @@ GeckoChildProcessHost::PerformAsyncLaunc
     preload.AssignLiteral("libmozsandbox.so");
     if (const char* oldPreload = PR_GetEnv("LD_PRELOAD")) {
       // Doesn't matter if oldPreload is ""; extra separators are ignored.
       preload.Append(' ');
       preload.Append(oldPreload);
     }
     // Explicitly construct the std::string to make it clear that this
     // isn't retaining a pointer to the nsCString's buffer.
-    newEnvVars["LD_PRELOAD"] = std::string(preload.get());
+    mLaunchOptions->environ["LD_PRELOAD"] = std::string(preload.get());
   }
 # endif // defined(XP_LINUX) && defined(MOZ_SANDBOX)
 
   // remap the IPC socket fd to a well-known int, as the OS does for
   // STDOUT_FILENO, for example
   int srcChannelFd, dstChannelFd;
   channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
-  mFileMap.push_back(std::pair<int,int>(srcChannelFd, dstChannelFd));
+  mLaunchOptions->fds_to_remap
+    .push_back(std::pair<int,int>(srcChannelFd, dstChannelFd));
 
   // no need for kProcessChannelID, the child process inherits the
   // other end of the socketpair() from us
 
   std::vector<std::string> childArgv;
 
   childArgv.push_back(exePath.value());
 
@@ -785,17 +786,18 @@ GeckoChildProcessHost::PerformAsyncLaunc
 
 # if defined(MOZ_CRASHREPORTER)
 #  if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
   int childCrashFd, childCrashRemapFd;
   if (!CrashReporter::CreateNotificationPipeForChild(
         &childCrashFd, &childCrashRemapFd))
     return false;
   if (0 <= childCrashFd) {
-    mFileMap.push_back(std::pair<int,int>(childCrashFd, childCrashRemapFd));
+    mLaunchOptions->fds_to_remap
+      .push_back(std::pair<int,int>(childCrashFd, childCrashRemapFd));
     // "true" == crash reporting enabled
     childArgv.push_back("true");
   }
   else {
     // "false" == crash reporting disabled
     childArgv.push_back("false");
   }
 #  elif defined(MOZ_WIDGET_COCOA) // defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
@@ -803,17 +805,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
 #  endif  // defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
 # endif // defined(MOZ_CRASHREPORTER)
 
 # if defined(XP_LINUX) && defined(MOZ_SANDBOX)
   {
     int srcFd, dstFd;
     SandboxReporter::Singleton()
       ->GetClientFileDescriptorMapping(&srcFd, &dstFd);
-    mFileMap.push_back(std::make_pair(srcFd, dstFd));
+    mLaunchOptions->fds_to_remap.push_back(std::make_pair(srcFd, dstFd));
   }
 # endif // defined(XP_LINUX) && defined(MOZ_SANDBOX)
 
 # ifdef MOZ_WIDGET_COCOA
   // Add a mach port to the command line so the child can communicate its
   // 'task_t' back to the parent.
   //
   // Put a random number into the channel name, so that a compromised renderer
@@ -821,23 +823,20 @@ GeckoChildProcessHost::PerformAsyncLaunc
   std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d",
                                                   base::RandInt(0, std::numeric_limits<int>::max()));
   childArgv.push_back(mach_connection_name.c_str());
 # endif // MOZ_WIDGET_COCOA
 
   childArgv.push_back(childProcessType);
 
 # if defined(MOZ_WIDGET_ANDROID)
-  LaunchAndroidService(childProcessType, childArgv, mFileMap, &process);
+  LaunchAndroidService(childProcessType, childArgv,
+                       mLaunchOptions->fds_to_remap, &process);
 # else // goes with defined(MOZ_WIDGET_ANDROID)
-  base::LaunchApp(childArgv, mFileMap,
-#  if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
-                  newEnvVars,
-#  endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
-                  false, &process);
+  base::LaunchApp(childArgv, *mLaunchOptions, &process);
 # endif // defined(MOZ_WIDGET_ANDROID)
 
   // We're in the parent and the child was launched. Close the child FD in the
   // parent as soon as possible, which will allow the parent to detect when the
   // child closes its FD (either due to normal exit or due to crash).
   GetChannel()->CloseClientFileDescriptor();
 
 # ifdef MOZ_WIDGET_COCOA
@@ -1060,17 +1059,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
       EnvironmentLog("MOZ_PROCESS_LOG").print(
         "==> process %d launched child process %d (%S)\n",
         base::GetCurrentProcId(), base::GetProcId(process),
         cmdLine.command_line_string().c_str());
     }
   } else
 # endif // defined(XP_WIN) && defined(MOZ_SANDBOX)
   {
-    base::LaunchApp(cmdLine, false, false, &process);
+    base::LaunchApp(cmdLine, *mLaunchOptions, &process);
 
 # ifdef MOZ_SANDBOX
     // We need to be able to duplicate handles to some types of non-sandboxed
     // child processes.
     if (mProcessType == GeckoProcessType_Content ||
         mProcessType == GeckoProcessType_GPU ||
         mProcessType == GeckoProcessType_GMPlugin) {
       if (!mSandboxBroker.AddTargetPeer(process)) {
@@ -1107,16 +1106,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
 #endif // XP_WIN
      ) {
     MOZ_CRASH("cannot open handle to child process");
   }
   MonitorAutoLock lock(mMonitor);
   mProcessState = PROCESS_CREATED;
   lock.Notify();
 
+  mLaunchOptions = nullptr;
   return true;
 }
 
 bool
 GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid)
 {
   if (mChildProcessHandle) {
     MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle));
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -11,16 +11,17 @@
 #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/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
 
 #include "nsCOMPtr.h"
 #include "nsXULAppAPI.h"        // for GeckoProcessType
 #include "nsString.h"
 
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
 #include "sandboxBroker.h"
 #endif
@@ -113,16 +114,21 @@ public:
 
   static void EnableSameExecutableForContentProc() { sRunSelfAsContentProc = true; }
 
 protected:
   GeckoProcessType mProcessType;
   bool mIsFileContent;
   Monitor mMonitor;
   FilePath mProcessPath;
+  // GeckoChildProcessHost holds the launch options so they can be set
+  // up on the main thread using main-thread-only APIs like prefs, and
+  // then used for the actual launch on another thread.  This pointer
+  // is set to null to free the options after the child is launched.
+  UniquePtr<base::LaunchOptions> mLaunchOptions;
 
   // This value must be accessed while holding mMonitor.
   enum {
     // This object has been constructed, but the OS process has not
     // yet.
     CREATING_CHANNEL = 0,
     // The IPC channel for our subprocess has been created, but the OS
     // process has still not been created.
@@ -147,20 +153,16 @@ protected:
 #ifdef MOZ_SANDBOX
   SandboxBroker mSandboxBroker;
   std::vector<std::wstring> mAllowedFilesRead;
   bool mEnableSandboxLogging;
   int32_t mSandboxLevel;
 #endif
 #endif // XP_WIN
 
-#if defined(OS_POSIX)
-  base::file_handle_mapping_vector mFileMap;
-#endif
-
   ProcessHandle mChildProcessHandle;
 #if defined(OS_MACOSX)
   task_t mChildTask;
 #endif
 
   bool OpenPrivilegedHandle(base::ProcessId aPid);
 
 private:
--- a/widget/LSBUtils.cpp
+++ b/widget/LSBUtils.cpp
@@ -29,24 +29,24 @@ GetLSBRelease(nsACString& aDistributor,
     NS_WARNING("pipe() failed!");
     return false;
   }
 
   std::vector<std::string> argv = {
     gLsbReleasePath, "-idrc"
   };
 
-  std::vector<std::pair<int, int>> fdMap = {
-    { pipefd[1], STDOUT_FILENO }
-  };
+  base::LaunchOptions options;
+  options.fds_to_remap.push_back({ pipefd[1], STDOUT_FILENO });
+  options.wait = true;
 
   base::ProcessHandle process;
-  base::LaunchApp(argv, fdMap, true, &process);
+  bool ok = base::LaunchApp(argv, options, &process);
   close(pipefd[1]);
-  if (!process) {
+  if (!ok) {
     NS_WARNING("Failed to spawn lsb_release!");
     close(pipefd[0]);
     return false;
   }
 
   FILE* stream = fdopen(pipefd[0], "r");
   if (!stream) {
     NS_WARNING("Could not wrap fd!");
--- a/xpcom/threads/nsProcessCommon.cpp
+++ b/xpcom/threads/nsProcessCommon.cpp
@@ -571,23 +571,23 @@ nsProcess::RunProcess(bool aBlocking, ch
   mPid = static_cast<int32_t>(newPid);
 
   posix_spawnattr_destroy(&spawnattr);
 
   if (result != 0) {
     return NS_ERROR_FAILURE;
   }
 #elif defined(XP_UNIX)
-  base::file_handle_mapping_vector fdMap;
+  base::LaunchOptions options;
   std::vector<std::string> argvVec;
   for (char** arg = aMyArgv; *arg != nullptr; ++arg) {
     argvVec.push_back(*arg);
   }
   pid_t newPid;
-  if (base::LaunchApp(argvVec, fdMap, false, &newPid)) {
+  if (base::LaunchApp(argvVec, options, &newPid)) {
     static_assert(sizeof(pid_t) <= sizeof(int32_t),
                   "mPid is large enough to hold a pid");
     mPid = static_cast<int32_t>(newPid);
   } else {
     return NS_ERROR_FAILURE;
   }
 #else
   mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr);