Bug 1438678 - Pass early prefs via shared memory instead of the command line. r=bobowen,jld,glandium.
authorNicholas Nethercote <nnethercote@mozilla.com>
Fri, 16 Feb 2018 17:54:16 +1100
changeset 407868 32d6774930e55be5c03e8d631fc067a995623c1e
parent 407867 b9282e42079685a2360a40fad53d51b54a3a7a9d
child 407869 95dbe9cb93d791ceca6738218824b38a5094c1c5
push id33621
push userapavel@mozilla.com
push dateTue, 13 Mar 2018 17:12:05 +0000
treeherdermozilla-central@deb7714a7bcd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbobowen, jld, glandium
bugs1438678, 1436911
milestone61.0a1
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
Bug 1438678 - Pass early prefs via shared memory instead of the command line. r=bobowen,jld,glandium. This patch replaces the large -intPrefs/-boolPrefs/-stringPrefs flags with a short-lived, anonymous, shared memory segment that is used to pass the early prefs. Removing the bloat from the command line is nice, but more important is the fact that this will let us pass more prefs at content process start-up, which will allow us to remove the early/late prefs split (bug 1436911). Although this mechanism is only used for prefs, it's conceivable that it could be used for other data that must be received very early by children, and for which the command line isn't ideal. Notable details: - Much of the patch deals with the various platform-specific ways of passing handles/fds to children. - Linux and Mac: we use a fixed fd (8) in combination with the new GeckoChildProcessHost::AddFdToRemap() function (which ensures the child won't close the fd). - Android: like Linux and Mac, but the handles get passed via "parcels" and we use the new SetPrefsFd() function instead of the fixed fd. - Windows: there is no need to duplicate the handle because Windows handles are system-wide. But we do use the new GeckoChildProcessHost::AddHandleToShare() function to add it to the list of inheritable handles. We also ensure that list is processed on all paths (MOZ_SANDBOX with sandbox, MOZ_SANDBOX without sandbox, non-MOZ_SANDBOX) so that the handles are marked as inheritable. The handle is passed via the -prefsHandle flag. The -prefsLen flag is used on all platforms to indicate the size of the shared memory segment. - The patch also moves the serialization/deserialization of the prefs in/out of the shared memory into libpref, which is a better spot for it. (This means Preferences::MustSendToContentProcesses() can be removed.) MozReview-Commit-ID: 8fREEBiYFvc
dom/ipc/ContentParent.cpp
dom/ipc/ContentProcess.cpp
dom/ipc/ContentProcess.h
ipc/chromium/src/base/process_util_win.cc
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/GeckoChildProcessHost.h
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
modules/libpref/Preferences.cpp
modules/libpref/Preferences.h
mozglue/android/APKOpen.cpp
toolkit/xre/Bootstrap.cpp
toolkit/xre/Bootstrap.h
toolkit/xre/nsEmbedFunctions.cpp
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
xpcom/build/nsXULAppAPI.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/DebugOnly.h"
 
 #include "base/basictypes.h"
+#include "base/shared_memory.h"
 
 #include "ContentParent.h"
 #include "TabParent.h"
 
 #if defined(ANDROID) || defined(LINUX)
 # include <sys/time.h>
 # include <sys/resource.h>
 #endif
@@ -1993,79 +1994,75 @@ ContentParent::LaunchSubprocess(ProcessP
 
   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");
 
-  nsAutoCStringN<1024> boolPrefs;
-  nsAutoCStringN<1024> intPrefs;
-  nsAutoCStringN<1024> stringPrefs;
-
-  size_t prefsLen;
-  ContentPrefs::GetEarlyPrefs(&prefsLen);
-
-  for (unsigned int i = 0; i < prefsLen; i++) {
-    const char* prefName = ContentPrefs::GetEarlyPref(i);
-    MOZ_ASSERT(i == 0 || strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0,
-               "Content process preferences should be sorted alphabetically.");
-
-    if (!Preferences::MustSendToContentProcesses(prefName)) {
-      continue;
-    }
-
-    switch (Preferences::GetType(prefName)) {
-    case nsIPrefBranch::PREF_INT:
-      intPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetInt(prefName)));
-      break;
-    case nsIPrefBranch::PREF_BOOL:
-      boolPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetBool(prefName)));
-      break;
-    case nsIPrefBranch::PREF_STRING: {
-      nsAutoCString value;
-      Preferences::GetCString(prefName, value);
-      stringPrefs.Append(nsPrintfCString("%u:%d;%s|", i, value.Length(), value.get()));
-      }
-      break;
-    case nsIPrefBranch::PREF_INVALID:
-      break;
-    default:
-      printf("preference type: %x\n", Preferences::GetType(prefName));
-      MOZ_CRASH();
-    }
-  }
-
-  nsCString schedulerPrefs = Scheduler::GetPrefs();
-
-  // Only do these ones if they're non-empty.
-  if (!intPrefs.IsEmpty()) {
-    extraArgs.push_back("-intPrefs");
-    extraArgs.push_back(intPrefs.get());
-  }
-  if (!boolPrefs.IsEmpty()) {
-    extraArgs.push_back("-boolPrefs");
-    extraArgs.push_back(boolPrefs.get());
-  }
-  if (!stringPrefs.IsEmpty()) {
-    extraArgs.push_back("-stringPrefs");
-    extraArgs.push_back(stringPrefs.get());
-  }
+  // Prefs information is passed via anonymous shared memory to avoid bloating
+  // the command line.
+
+  // Serialize the early prefs.
+  nsAutoCStringN<1024> prefs;
+  Preferences::SerializeEarlyPreferences(prefs);
+
+  // Set up the shared memory.
+  base::SharedMemory shm;
+  if (!shm.Create("", /* read_only */ false, /* open_existing */ false,
+                  prefs.Length())) {
+    NS_ERROR("failed to create shared memory in the parent");
+    MarkAsDead();
+    return false;
+  }
+  if (!shm.Map(prefs.Length())) {
+    NS_ERROR("failed to map shared memory in the parent");
+    MarkAsDead();
+    return false;
+  }
+
+  // Copy the serialized prefs into the shared memory.
+  memcpy(static_cast<char*>(shm.memory()), prefs.get(), prefs.Length());
+
+#if defined(XP_WIN)
+  // Record the handle as to-be-shared, and pass it via a command flag. This
+  // works because Windows handles are system-wide.
+  HANDLE prefsHandle = shm.handle();
+  mSubprocess->AddHandleToShare(prefsHandle);
+  extraArgs.push_back("-prefsHandle");
+  extraArgs.push_back(
+    nsPrintfCString("%zu", reinterpret_cast<uintptr_t>(prefsHandle)).get());
+#else
+  // In contrast, Unix fds are per-process. So remap the fd to a fixed one that
+  // will be used in the child.
+  // XXX: bug 1440207 is about improving how fixed fds are used.
+  //
+  // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
+  // and the fixed fd isn't used. However, we still need to mark it for
+  // remapping so it doesn't get closed in the child.
+  mSubprocess->AddFdToRemap(shm.handle().fd, kPrefsFileDescriptor);
+#endif
+
+  // Pass the length via a command flag.
+  extraArgs.push_back("-prefsLen");
+  extraArgs.push_back(nsPrintfCString("%zu", uintptr_t(prefs.Length())).get());
 
   // Scheduler prefs need to be handled differently because the scheduler needs
   // to start up in the content process before the normal preferences service.
+  nsCString schedulerPrefs = Scheduler::GetPrefs();
   extraArgs.push_back("-schedulerPrefs");
   extraArgs.push_back(schedulerPrefs.get());
 
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
   }
 
   if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
+    NS_ERROR("failed to launch child in the parent");
     MarkAsDead();
     return false;
   }
 
   base::ProcessId procId = base::GetProcId(mSubprocess->GetChildProcessHandle());
 
   Open(mSubprocess->GetChannel(), procId);
 
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -3,24 +3,25 @@
 /* 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 "mozilla/ipc/IOThreadChild.h"
 
 #include "ContentProcess.h"
 #include "ContentPrefs.h"
+#include "base/shared_memory.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/Scheduler.h"
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 #include <stdlib.h>
 #endif
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
-#include "mozilla/Preferences.h"
 #include "mozilla/SandboxSettings.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryService.h"
 #include "nsDirectoryServiceDefs.h"
 #endif
 
 using mozilla::ipc::IOThreadChild;
 
@@ -76,39 +77,51 @@ SetUpSandboxEnvironment()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   SetTmpEnvironmentVariable(sandboxedContentTemp);
 }
 #endif
 
+#ifdef ANDROID
+static int gPrefsFd = -1;
+
+void
+SetPrefsFd(int aFd)
+{
+  gPrefsFd = aFd;
+}
+#endif
+
 bool
 ContentProcess::Init(int aArgc, char* aArgv[])
 {
   // If passed in grab the application path for xpcom init
   bool foundAppdir = false;
   bool foundChildID = false;
   bool foundIsForBrowser = false;
-  bool foundIntPrefs = false;
-  bool foundBoolPrefs = false;
-  bool foundStringPrefs = false;
+#ifdef XP_WIN
+  bool foundPrefsHandle = false;
+#endif
+  bool foundPrefsLen = false;
   bool foundSchedulerPrefs = false;
 
   uint64_t childID;
   bool isForBrowser;
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
   // If passed in grab the profile path for sandboxing
   bool foundProfile = false;
   nsCOMPtr<nsIFile> profileDir;
 #endif
 
   char* schedulerPrefs = nullptr;
-  InfallibleTArray<Pref> prefsArray;
+  base::SharedMemoryHandle prefsHandle = base::SharedMemory::NULLHandle();
+  size_t prefsLen = 0;
   for (int idx = aArgc; idx > 0; idx--) {
     if (!aArgv[idx]) {
       continue;
     }
 
     if (!strcmp(aArgv[idx], "-appdir")) {
       MOZ_ASSERT(!foundAppdir);
       if (foundAppdir) {
@@ -129,64 +142,34 @@ ContentProcess::Init(int aArgc, char* aA
       }
     } else if (!strcmp(aArgv[idx], "-isForBrowser") || !strcmp(aArgv[idx], "-notForBrowser")) {
       MOZ_ASSERT(!foundIsForBrowser);
       if (foundIsForBrowser) {
         continue;
       }
       isForBrowser = strcmp(aArgv[idx], "-notForBrowser");
       foundIsForBrowser = true;
-    } else if (!strcmp(aArgv[idx], "-intPrefs")) {
-      char* str = aArgv[idx + 1];
-      while (*str) {
-        int32_t index = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ':');
-        str++;
-        MaybePrefValue value(PrefValue(static_cast<int32_t>(strtol(str, &str, 10))));
-        MOZ_ASSERT(str[0] == '|');
-        str++;
-        // XXX: we assume these values as default values, which may not be
-        // true. We also assume they are unlocked. Fortunately, these prefs
-        // get reset properly by the first IPC message.
-        Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
-                  /* isLocked */ false, value, MaybePrefValue());
-        prefsArray.AppendElement(pref);
-      }
-      foundIntPrefs = true;
-    } else if (!strcmp(aArgv[idx], "-boolPrefs")) {
+#ifdef XP_WIN
+    } else if (!strcmp(aArgv[idx], "-prefsHandle")) {
       char* str = aArgv[idx + 1];
-      while (*str) {
-        int32_t index = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ':');
-        str++;
-        MaybePrefValue value(PrefValue(!!strtol(str, &str, 10)));
-        MOZ_ASSERT(str[0] == '|');
-        str++;
-        Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
-                  /* isLocked */ false, value, MaybePrefValue());
-        prefsArray.AppendElement(pref);
-      }
-      foundBoolPrefs = true;
-    } else if (!strcmp(aArgv[idx], "-stringPrefs")) {
+      MOZ_ASSERT(str[0] != '\0');
+      // ContentParent uses %zu to print a word-sized unsigned integer. So even
+      // though strtoull() returns a long long int, it will fit in a uintptr_t.
+      prefsHandle = reinterpret_cast<HANDLE>(strtoull(str, &str, 10));
+      MOZ_ASSERT(str[0] == '\0');
+      foundPrefsHandle = true;
+#endif
+    } else if (!strcmp(aArgv[idx], "-prefsLen")) {
       char* str = aArgv[idx + 1];
-      while (*str) {
-        int32_t index = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ':');
-        str++;
-        int32_t length = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ';');
-        str++;
-        MaybePrefValue value(PrefValue(nsCString(str, length)));
-        Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
-                  /* isLocked */ false, value, MaybePrefValue());
-        prefsArray.AppendElement(pref);
-        str += length + 1;
-        MOZ_ASSERT(*(str - 1) == '|');
-      }
-      foundStringPrefs = true;
+      MOZ_ASSERT(str[0] != '\0');
+      // ContentParent uses %zu to print a word-sized unsigned integer. So even
+      // though strtoull() returns a long long int, it will fit in a uintptr_t.
+      prefsLen = strtoull(str, &str, 10);
+      MOZ_ASSERT(str[0] == '\0');
+      foundPrefsLen = true;
     } else if (!strcmp(aArgv[idx], "-schedulerPrefs")) {
       schedulerPrefs = aArgv[idx + 1];
       foundSchedulerPrefs = true;
     } else if (!strcmp(aArgv[idx], "-safeMode")) {
       gSafeMode = true;
     }
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
@@ -204,31 +187,53 @@ ContentProcess::Init(int aArgc, char* aA
       }
       foundProfile = true;
     }
 #endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */
 
     bool allFound = foundAppdir
                  && foundChildID
                  && foundIsForBrowser
-                 && foundIntPrefs
-                 && foundBoolPrefs
-                 && foundStringPrefs
-                 && foundSchedulerPrefs;
-
+                 && foundPrefsLen
+                 && foundSchedulerPrefs
+#ifdef XP_WIN
+                 && foundPrefsHandle
+#endif
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-    allFound &= foundProfile;
+                 && foundProfile
 #endif
+                 && true;
 
     if (allFound) {
       break;
     }
   }
 
-  Preferences::SetEarlyPreferences(&prefsArray);
+#ifdef ANDROID
+  // Android is different; get the FD via gPrefsFd instead of a fixed fd.
+  MOZ_RELEASE_ASSERT(gPrefsFd != -1);
+  prefsHandle = base::FileDescriptor(gPrefsFd, /* auto_close */ true);
+#elif XP_UNIX
+  prefsHandle = base::FileDescriptor(kPrefsFileDescriptor,
+                                     /* auto_close */ true);
+#endif
+
+  // Set up early prefs from the shared memory.
+  base::SharedMemory shm;
+  if (!shm.SetHandle(prefsHandle, /* read_only */ true)) {
+    NS_ERROR("failed to open shared memory in the child");
+    return false;
+  }
+  if (!shm.Map(prefsLen)) {
+    NS_ERROR("failed to map shared memory in the child");
+    return false;
+  }
+  Preferences::DeserializeEarlyPreferences(static_cast<char*>(shm.memory()),
+                                           prefsLen);
+
   Scheduler::SetPrefs(schedulerPrefs);
   mContent.Init(IOThreadChild::message_loop(),
                 ParentPid(),
                 IOThreadChild::channel(),
                 childID,
                 isForBrowser);
   mXREEmbed.Start();
 #if (defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
--- a/dom/ipc/ContentProcess.h
+++ b/dom/ipc/ContentProcess.h
@@ -44,12 +44,17 @@ private:
 #if defined(XP_WIN)
   // This object initializes and configures COM.
   mozilla::mscom::MainThreadRuntime mCOMRuntime;
 #endif
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentProcess);
 };
 
+#ifdef ANDROID
+// Android doesn't use -prefsHandle, it gets that FD another way.
+void SetPrefsFd(int aFd);
+#endif
+
 } // namespace dom
 } // namespace mozilla
 
 #endif  // ifndef dom_tabs_ContentThread_h
--- a/ipc/chromium/src/base/process_util_win.cc
+++ b/ipc/chromium/src/base/process_util_win.cc
@@ -349,16 +349,20 @@ bool LaunchApp(const std::wstring& cmdli
   startup_info.dwFlags = STARTF_USESHOWWINDOW;
   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;
   std::vector<HANDLE> handlesToInherit;
   for (HANDLE h : options.handles_to_inherit) {
+    if (SetHandleInformation(h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) == 0) {
+      MOZ_DIAGNOSTIC_ASSERT(false, "SetHandleInformation failed");
+      return false;
+    }
     handlesToInherit.push_back(h);
   }
 
   // setup our handle array first - if we end up with no handles that can
   // be inherited we can avoid trying to do the ThreadAttributeList dance...
   HANDLE stdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
   HANDLE stdErr = ::GetStdHandle(STD_ERROR_HANDLE);
 
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -1025,29 +1025,31 @@ GeckoChildProcessHost::PerformAsyncLaunc
   // Process id
   cmdLine.AppendLooseValue(UTF8ToWide(pidstring));
 
   cmdLine.AppendLooseValue(
     UTF8ToWide(CrashReporter::GetChildNotificationPipe()));
 
   if (!CrashReporter::IsDummy()) {
     PROsfd h = PR_FileDesc2NativeHandle(crashAnnotationWritePipe);
-# if defined(MOZ_SANDBOX)
-    mSandboxBroker.AddHandleToShare(reinterpret_cast<HANDLE>(h));
-# endif // defined(MOZ_SANDBOX)
     mLaunchOptions->handles_to_inherit.push_back(reinterpret_cast<HANDLE>(h));
     std::string hStr = std::to_string(h);
     cmdLine.AppendLooseValue(UTF8ToWide(hStr));
   }
 
   // Process type
   cmdLine.AppendLooseValue(UTF8ToWide(childProcessType));
 
 # if defined(MOZ_SANDBOX)
   if (shouldSandboxCurrentProcess) {
+    // Mark the handles to inherit as inheritable.
+    for (HANDLE h : mLaunchOptions->handles_to_inherit) {
+      mSandboxBroker.AddHandleToShare(h);
+    }
+
     if (mSandboxBroker.LaunchApp(cmdLine.program().c_str(),
                                  cmdLine.command_line_string().c_str(),
                                  mLaunchOptions->env_map,
                                  mProcessType,
                                  mEnableSandboxLogging,
                                  &process)) {
       EnvironmentLog("MOZ_PROCESS_LOG").print(
         "==> process %d launched child process %d (%S)\n",
@@ -1175,38 +1177,42 @@ bool GeckoChildProcessHost::sRunSelfAsCo
 
 #ifdef MOZ_WIDGET_ANDROID
 void
 GeckoChildProcessHost::LaunchAndroidService(const char* type,
                                             const std::vector<std::string>& argv,
                                             const base::file_handle_mapping_vector& fds_to_remap,
                                             ProcessHandle* process_handle)
 {
-  MOZ_ASSERT((fds_to_remap.size() > 0) && (fds_to_remap.size() <= 3));
+  MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 4));
   JNIEnv* const env = mozilla::jni::GetEnvForThread();
   MOZ_ASSERT(env);
 
   const int argvSize = argv.size();
   jni::ObjectArray::LocalRef jargs = jni::ObjectArray::New<jni::String>(argvSize);
   for (int ix = 0; ix < argvSize; ix++) {
     jargs->SetElement(ix, jni::StringParam(argv[ix].c_str(), env));
   }
-  base::file_handle_mapping_vector::const_iterator it = fds_to_remap.begin();
-  int32_t ipcFd = it->first;
-  it++;
-  // If the Crash Reporter is disabled, there will not be a second file descriptor.
+
+  // XXX: this processing depends entirely on the internals of
+  // ContentParent::LaunchSubprocess()
+  // GeckoChildProcessHost::PerformAsyncLaunchInternal(), and the order in
+  // which they append to fds_to_remap. There must be a better way to do it.
+  // See bug 1440207.
+  int32_t prefsFd = fds_to_remap[0].first;
+  int32_t ipcFd = fds_to_remap[1].first;
   int32_t crashFd = -1;
   int32_t crashAnnotationFd = -1;
-  if (it != fds_to_remap.end() && !CrashReporter::IsDummy()) {
-    crashFd = it->first;
-    it++;
+  if (fds_to_remap.size() == 3) {
+    crashAnnotationFd = fds_to_remap[2].first;
   }
-  if (it != fds_to_remap.end()) {
-    crashAnnotationFd = it->first;
-    it++;
+  if (fds_to_remap.size() == 4) {
+    crashFd = fds_to_remap[2].first;
+    crashAnnotationFd = fds_to_remap[3].first;
   }
-  int32_t handle = java::GeckoProcessManager::Start(type, jargs, ipcFd, crashFd, crashAnnotationFd);
+
+  int32_t handle = java::GeckoProcessManager::Start(type, jargs, prefsFd, ipcFd, crashFd, crashAnnotationFd);
 
   if (process_handle) {
     *process_handle = handle;
   }
 }
 #endif
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -98,16 +98,26 @@ public:
   }
 
 #ifdef XP_MACOSX
   task_t GetChildTask() {
     return mChildTask;
   }
 #endif
 
+#ifdef XP_WIN
+  void AddHandleToShare(HANDLE aHandle) {
+    mLaunchOptions->handles_to_inherit.push_back(aHandle);
+  }
+#else
+  void AddFdToRemap(int aSrcFd, int aDstFd) {
+    mLaunchOptions->fds_to_remap.push_back(std::make_pair(aSrcFd, aDstFd));
+  }
+#endif
+
   /**
    * Must run on the IO thread.  Cause the OS process to exit and
    * ensure its OS resources are cleaned up.
    */
   void Join();
 
   // For bug 943174: Skip the EnsureProcessTerminated call in the destructor.
   void SetAlreadyDead();
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
@@ -7,11 +7,12 @@ package org.mozilla.gecko.process;
 import org.mozilla.gecko.process.IProcessManager;
 
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
 interface IChildProcess {
     int getPid();
     boolean start(in IProcessManager procMan, in String[] args, in Bundle extras,
-                  in ParcelFileDescriptor ipcPfd, in ParcelFileDescriptor crashReporterPfd,
+                  in ParcelFileDescriptor prefsPfd, in ParcelFileDescriptor ipcPfd,
+                  in ParcelFileDescriptor crashReporterPfd,
                   in ParcelFileDescriptor crashAnnotationPfd);
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -123,16 +123,17 @@ public class GeckoThread extends Thread 
     @WrapForJNI
     private static int uiThreadId;
 
     // Main process parameters
     public static final int FLAG_DEBUGGING = 1; // Debugging mode.
     public static final int FLAG_PRELOAD_CHILD = 2; // Preload child during main thread start.
 
     private static final String EXTRA_ARGS = "args";
+    private static final String EXTRA_PREFS_FD = "prefsFd";
     private static final String EXTRA_IPC_FD = "ipcFd";
     private static final String EXTRA_CRASH_FD = "crashFd";
     private static final String EXTRA_CRASH_ANNOTATION_FD = "crashAnnotationFd";
 
     private boolean mInitialized;
     private GeckoProfile mProfile;
     private String[] mArgs;
     private Bundle mExtras;
@@ -144,50 +145,53 @@ public class GeckoThread extends Thread 
 
     @WrapForJNI
     private static boolean isChildProcess() {
         return INSTANCE.mExtras.getInt(EXTRA_IPC_FD, -1) != -1;
     }
 
     private synchronized boolean init(final GeckoProfile profile, final String[] args,
                                       final Bundle extras, final int flags,
-                                      final int ipcFd, final int crashFd,
+                                      final int prefsFd, final int ipcFd,
+                                      final int crashFd,
                                       final int crashAnnotationFd) {
         ThreadUtils.assertOnUiThread();
         uiThreadId = android.os.Process.myTid();
 
         if (mInitialized) {
             return false;
         }
 
         mProfile = profile;
         mArgs = args;
         mFlags = flags;
 
         mExtras = (extras != null) ? new Bundle(extras) : new Bundle(3);
+        mExtras.putInt(EXTRA_PREFS_FD, prefsFd);
         mExtras.putInt(EXTRA_IPC_FD, ipcFd);
         mExtras.putInt(EXTRA_CRASH_FD, crashFd);
         mExtras.putInt(EXTRA_CRASH_ANNOTATION_FD, crashAnnotationFd);
 
         mInitialized = true;
         notifyAll();
         return true;
     }
 
     public static boolean initMainProcess(final GeckoProfile profile, final String[] args,
                                           final Bundle extras, final int flags) {
-        return INSTANCE.init(profile, args, extras, flags,
+        return INSTANCE.init(profile, args, extras, flags, /* fd */ -1,
                              /* fd */ -1, /* fd */ -1, /* fd */ -1);
     }
 
     public static boolean initChildProcess(final String[] args, final Bundle extras,
-                                           final int ipcFd, final int crashFd,
+                                           final int prefsFd, final int ipcFd,
+                                           final int crashFd,
                                            final int crashAnnotationFd) {
         return INSTANCE.init(/* profile */ null, args, extras, /* flags */ 0,
-                             ipcFd, crashFd, crashAnnotationFd);
+                             prefsFd, ipcFd, crashFd, crashAnnotationFd);
     }
 
     private static boolean canUseProfile(final Context context, final GeckoProfile profile,
                                          final String profileName, final File profileDir) {
         if (profileDir != null && !profileDir.isDirectory()) {
             return false;
         }
 
@@ -437,16 +441,17 @@ public class GeckoThread extends Thread 
         if ((mFlags & FLAG_DEBUGGING) != 0) {
             Log.i(LOGTAG, "RunGecko - args = " + TextUtils.join(" ", args));
         }
 
         GeckoLoader.setupGeckoEnvironment(context, context.getFilesDir().getPath(), mExtras);
 
         // And go.
         GeckoLoader.nativeRun(args,
+                              mExtras.getInt(EXTRA_PREFS_FD, -1),
                               mExtras.getInt(EXTRA_IPC_FD, -1),
                               mExtras.getInt(EXTRA_CRASH_FD, -1),
                               mExtras.getInt(EXTRA_CRASH_ANNOTATION_FD, -1));
 
         // And... we're done.
         final boolean restarting = isState(State.RESTARTING);
         setState(State.EXITED);
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -458,15 +458,15 @@ public final class GeckoLoader {
         }
     }
 
     // These methods are implemented in mozglue/android/nsGeckoUtils.cpp
     private static native void putenv(String map);
     public static native boolean verifyCRCs(String apkName);
 
     // These methods are implemented in mozglue/android/APKOpen.cpp
-    public static native void nativeRun(String[] args, int ipcFd, int crashFd, int crashAnnotationFd);
+    public static native void nativeRun(String[] args, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd);
     private static native void loadGeckoLibsNative(String apkName);
     private static native void loadSQLiteLibsNative(String apkName);
     private static native void loadNSSLibsNative(String apkName);
     public static native boolean neonCompatible();
     public static native void suppressCrashDialog();
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
@@ -164,57 +164,60 @@ public final class GeckoProcessManager e
             if (connection.bind() != null) {
                 connection.getPid();
             }
         }
     }
 
     @WrapForJNI
     private static int start(final String type, final String[] args,
-                             final int ipcFd, final int crashFd,
-                             final int crashAnnotationFd) {
-        return INSTANCE.start(type, args, ipcFd, crashFd, crashAnnotationFd, /* retry */ false);
+                             final int prefsFd, final int ipcFd,
+                             final int crashFd, final int crashAnnotationFd) {
+        return INSTANCE.start(type, args, prefsFd, ipcFd, crashFd, crashAnnotationFd, /* retry */ false);
     }
 
-    private int start(final String type, final String[] args, final int ipcFd,
-                      final int crashFd, final int crashAnnotationFd,
-                      final boolean retry) {
+    private int start(final String type, final String[] args, final int prefsFd,
+                      final int ipcFd, final int crashFd,
+                      final int crashAnnotationFd, final boolean retry) {
         final ChildConnection connection = getConnection(type);
         final IChildProcess child = connection.bind();
         if (child == null) {
             return 0;
         }
 
         final Bundle extras = GeckoThread.getActiveExtras();
+        final ParcelFileDescriptor prefsPfd;
         final ParcelFileDescriptor ipcPfd;
         final ParcelFileDescriptor crashPfd;
         final ParcelFileDescriptor crashAnnotationPfd;
         try {
+            prefsPfd = ParcelFileDescriptor.fromFd(prefsFd);
             ipcPfd = ParcelFileDescriptor.fromFd(ipcFd);
             crashPfd = (crashFd >= 0) ? ParcelFileDescriptor.fromFd(crashFd) : null;
             crashAnnotationPfd = (crashAnnotationFd >= 0) ? ParcelFileDescriptor.fromFd(crashAnnotationFd) : null;
         } catch (final IOException e) {
             Log.e(LOGTAG, "Cannot create fd for " + type, e);
             return 0;
         }
 
         boolean started = false;
         try {
-            started = child.start(this, args, extras, ipcPfd, crashPfd, crashAnnotationPfd);
+            started = child.start(this, args, extras, prefsPfd, ipcPfd, crashPfd,
+                                  crashAnnotationPfd);
         } catch (final RemoteException e) {
         }
 
         if (!started) {
             if (retry) {
                 Log.e(LOGTAG, "Cannot restart child " + type);
                 return 0;
             }
             Log.w(LOGTAG, "Attempting to kill running child " + type);
             connection.unbind();
-            return start(type, args, ipcFd, crashFd, crashAnnotationFd, /* retry */ true);
+            return start(type, args, prefsFd, ipcFd, crashFd, crashAnnotationFd, /* retry */ true);
         }
 
         try {
             if (crashAnnotationPfd != null) {
                 crashAnnotationPfd.close();
             }
             if (crashPfd != null) {
                 crashPfd.close();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
@@ -58,37 +58,39 @@ public class GeckoServiceChildProcess ex
         public int getPid() {
             return Process.myPid();
         }
 
         @Override
         public boolean start(final IProcessManager procMan,
                              final String[] args,
                              final Bundle extras,
+                             final ParcelFileDescriptor prefsPfd,
                              final ParcelFileDescriptor ipcPfd,
                              final ParcelFileDescriptor crashReporterPfd,
                              final ParcelFileDescriptor crashAnnotationPfd) {
             synchronized (GeckoServiceChildProcess.class) {
                 if (sProcessManager != null) {
                     Log.e(LOGTAG, "Child process already started");
                     return false;
                 }
                 sProcessManager = procMan;
             }
 
+            final int prefsFd = prefsPfd.detachFd();
             final int ipcFd = ipcPfd.detachFd();
             final int crashReporterFd = crashReporterPfd != null ?
                                         crashReporterPfd.detachFd() : -1;
             final int crashAnnotationFd = crashAnnotationPfd != null ?
                                           crashAnnotationPfd.detachFd() : -1;
 
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
-                    if (GeckoThread.initChildProcess(args, extras, ipcFd, crashReporterFd,
+                    if (GeckoThread.initChildProcess(args, extras, prefsFd, ipcFd, crashReporterFd,
                                                      crashAnnotationFd)) {
                         GeckoThread.launch();
                     }
                 }
             });
             return true;
         }
     };
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -2915,17 +2915,17 @@ public:
   NS_IMETHOD Run() override
   {
     return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
   }
 };
 
 } // namespace
 
-// A list of prefs sent early from the parent, via the command line.
+// A list of prefs sent early from the parent, via shared memory.
 static InfallibleTArray<dom::Pref>* gEarlyDomPrefs;
 
 /* static */ already_AddRefed<Preferences>
 Preferences::GetInstanceForService()
 {
   if (sPreferences) {
     return do_AddRef(sPreferences);
   }
@@ -3076,21 +3076,140 @@ Preferences::~Preferences()
 
 NS_IMPL_ISUPPORTS(Preferences,
                   nsIPrefService,
                   nsIObserver,
                   nsIPrefBranch,
                   nsISupportsWeakReference)
 
 /* static */ void
-Preferences::SetEarlyPreferences(const nsTArray<dom::Pref>* aDomPrefs)
+Preferences::SerializeEarlyPreferences(nsCString& aStr)
+{
+  MOZ_RELEASE_ASSERT(InitStaticMembers());
+
+  nsAutoCStringN<256> boolPrefs, intPrefs, stringPrefs;
+  size_t numEarlyPrefs;
+  dom::ContentPrefs::GetEarlyPrefs(&numEarlyPrefs);
+
+  for (unsigned int i = 0; i < numEarlyPrefs; i++) {
+    const char* prefName = dom::ContentPrefs::GetEarlyPref(i);
+    MOZ_ASSERT_IF(i > 0,
+                  strcmp(prefName, dom::ContentPrefs::GetEarlyPref(i - 1)) > 0);
+
+    Pref* pref = pref_HashTableLookup(prefName);
+    if (!pref || !pref->MustSendToContentProcesses()) {
+      continue;
+    }
+
+    switch (pref->Type()) {
+      case PrefType::Bool:
+        boolPrefs.Append(
+          nsPrintfCString("%u:%d|", i, Preferences::GetBool(prefName)));
+        break;
+      case PrefType::Int:
+        intPrefs.Append(
+          nsPrintfCString("%u:%d|", i, Preferences::GetInt(prefName)));
+        break;
+      case PrefType::String: {
+        nsAutoCString value;
+        Preferences::GetCString(prefName, value);
+        stringPrefs.Append(
+          nsPrintfCString("%u:%d;%s|", i, value.Length(), value.get()));
+      } break;
+      case PrefType::None:
+        break;
+      default:
+        printf_stderr("preference type: %d\n", int(pref->Type()));
+        MOZ_CRASH();
+    }
+  }
+
+  aStr.Truncate();
+  aStr.Append(boolPrefs);
+  aStr.Append('\n');
+  aStr.Append(intPrefs);
+  aStr.Append('\n');
+  aStr.Append(stringPrefs);
+  aStr.Append('\n');
+  aStr.Append('\0');
+}
+
+/* static */ void
+Preferences::DeserializeEarlyPreferences(char* aStr, size_t aStrLen)
 {
   MOZ_ASSERT(!XRE_IsParentProcess());
 
-  gEarlyDomPrefs = new InfallibleTArray<dom::Pref>(mozilla::Move(*aDomPrefs));
+  MOZ_ASSERT(!gEarlyDomPrefs);
+  gEarlyDomPrefs = new InfallibleTArray<dom::Pref>();
+
+  char* p = aStr;
+
+  // XXX: we assume these pref values are default values, which may not be
+  // true. We also assume they are unlocked. Fortunately, these prefs get reset
+  // properly by the first IPC message.
+
+  // Get the bool prefs.
+  while (*p != '\n') {
+    int32_t index = strtol(p, &p, 10);
+    MOZ_ASSERT(p[0] == ':');
+    p++;
+    int v = strtol(p, &p, 10);
+    MOZ_ASSERT(v == 0 || v == 1);
+    dom::MaybePrefValue value(dom::PrefValue(!!v));
+    MOZ_ASSERT(p[0] == '|');
+    p++;
+    dom::Pref pref(nsCString(dom::ContentPrefs::GetEarlyPref(index)),
+                   /* isLocked */ false,
+                   value,
+                   dom::MaybePrefValue());
+    gEarlyDomPrefs->AppendElement(pref);
+  }
+  p++;
+
+  // Get the int prefs.
+  while (*p != '\n') {
+    int32_t index = strtol(p, &p, 10);
+    MOZ_ASSERT(p[0] == ':');
+    p++;
+    dom::MaybePrefValue value(
+      dom::PrefValue(static_cast<int32_t>(strtol(p, &p, 10))));
+    MOZ_ASSERT(p[0] == '|');
+    p++;
+    dom::Pref pref(nsCString(dom::ContentPrefs::GetEarlyPref(index)),
+                   /* isLocked */ false,
+                   value,
+                   dom::MaybePrefValue());
+    gEarlyDomPrefs->AppendElement(pref);
+  }
+  p++;
+
+  // Get the string prefs.
+  while (*p != '\n') {
+    int32_t index = strtol(p, &p, 10);
+    MOZ_ASSERT(p[0] == ':');
+    p++;
+    int32_t length = strtol(p, &p, 10);
+    MOZ_ASSERT(p[0] == ';');
+    p++;
+    dom::MaybePrefValue value(dom::PrefValue(nsCString(p, length)));
+    dom::Pref pref(nsCString(dom::ContentPrefs::GetEarlyPref(index)),
+                   /* isLocked */ false,
+                   value,
+                   dom::MaybePrefValue());
+    gEarlyDomPrefs->AppendElement(pref);
+    p += length + 1;
+    MOZ_ASSERT(*(p - 1) == '|');
+  }
+  p++;
+
+  MOZ_ASSERT(*p == '\0');
+
+  // We finished parsing on a '\0'. That should be the last char in the shared
+  // memory.
+  MOZ_ASSERT(aStr + aStrLen - 1 == p);
 
 #ifdef DEBUG
   MOZ_ASSERT(gPhase == ContentProcessPhase::eNoPrefsSet);
   gPhase = ContentProcessPhase::eEarlyPrefsSet;
 #endif
 }
 
 /* static */ void
@@ -4293,25 +4412,16 @@ Preferences::ClearUser(const char* aPref
 Preferences::HasUserValue(const char* aPrefName)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), false);
 
   Pref* pref = pref_HashTableLookup(aPrefName);
   return pref && pref->HasUserValue();
 }
 
-/* static */ bool
-Preferences::MustSendToContentProcesses(const char* aPrefName)
-{
-  NS_ENSURE_TRUE(InitStaticMembers(), false);
-
-  Pref* pref = pref_HashTableLookup(aPrefName);
-  return pref && pref->MustSendToContentProcesses();
-}
-
 /* static */ int32_t
 Preferences::GetType(const char* aPrefName)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
 
   Pref* pref;
   if (!gHashTable || !(pref = pref_HashTableLookup(aPrefName))) {
     return PREF_INVALID;
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -36,16 +36,21 @@ namespace mozilla {
 
 namespace dom {
 class Pref;
 class PrefValue;
 } // namespace dom
 
 struct PrefsSizes;
 
+#ifdef XP_UNIX
+// XXX: bug 1440207 is about improving how fixed fds such as this are used.
+static const int kPrefsFileDescriptor = 8;
+#endif
+
 // Keep this in sync with PrefType in parser/src/lib.rs.
 enum class PrefValueKind : uint8_t
 {
   Default,
   User
 };
 
 class Preferences final
@@ -225,19 +230,16 @@ public:
   static bool IsLocked(const char* aPrefName);
 
   // Clears user set pref. Fails if run outside the parent process.
   static nsresult ClearUser(const char* aPrefName);
 
   // Whether the pref has a user value or not.
   static bool HasUserValue(const char* aPref);
 
-  // Must the pref be sent to content processes when they start?
-  static bool MustSendToContentProcesses(const char* aPref);
-
   // Adds/Removes the observer for the root pref branch. See nsIPrefBranch.idl
   // for details.
   static nsresult AddStrongObserver(nsIObserver* aObserver, const char* aPref);
   static nsresult AddWeakObserver(nsIObserver* aObserver, const char* aPref);
   static nsresult RemoveObserver(nsIObserver* aObserver, const char* aPref);
 
   // Adds/Removes two or more observers for the root pref branch. Pass to
   // aPrefs an array of const char* whose last item is nullptr.
@@ -323,21 +325,22 @@ public:
                                         const char* aPref,
                                         uint32_t aDefault = 0);
   static nsresult AddFloatVarCache(float* aVariable,
                                    const char* aPref,
                                    float aDefault = 0.0f);
 
   // When a content process is created these methods are used to pass prefs in
   // bulk from the parent process. "Early" preferences are ones that are needed
-  // very early on in the content process's lifetime; they are passed via the
-  // command line. "Late" preferences are the remainder, which are passed via
-  // IPC message.
+  // very early on in the content process's lifetime; they are passed via a
+  // special shared memory segment. "Late" preferences are the remainder, which
+  // are passed via a standard IPC message.
+  static void SerializeEarlyPreferences(nsCString& aStr);
+  static void DeserializeEarlyPreferences(char* aStr, size_t aStrLen);
   static void GetPreferences(InfallibleTArray<dom::Pref>* aSettings);
-  static void SetEarlyPreferences(const nsTArray<dom::Pref>* aSettings);
   static void SetLatePreferences(const nsTArray<dom::Pref>* aSettings);
 
   // When a single pref is changed in the parent process, these methods are
   // used to pass the update to content processes.
   static void GetPreference(dom::Pref* aPref);
   static void SetPreference(const dom::Pref& aPref);
 
 #ifdef DEBUG
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -387,32 +387,32 @@ FreeArgv(char** argv, int argc)
   for (int ix=0; ix < argc; ix++) {
     // String was allocated with strndup, so need to use free to deallocate.
     free(argv[ix]);
   }
   delete[](argv);
 }
 
 extern "C" APKOPEN_EXPORT void MOZ_JNICALL
-Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jobjectArray jargs, int ipcFd, int crashFd, int crashAnnotationFd)
+Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jobjectArray jargs, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd)
 {
   int argc = 0;
   char** argv = CreateArgvFromObjectArray(jenv, jargs, &argc);
 
   if (ipcFd < 0) {
     if (gBootstrap == nullptr) {
       FreeArgv(argv, argc);
       return;
     }
 
     ElfLoader::Singleton.ExpectShutdown(false);
     gBootstrap->GeckoStart(jenv, argv, argc, sAppData);
     ElfLoader::Singleton.ExpectShutdown(true);
   } else {
-    gBootstrap->XRE_SetAndroidChildFds(jenv, ipcFd, crashFd, crashAnnotationFd);
+    gBootstrap->XRE_SetAndroidChildFds(jenv, prefsFd, ipcFd, crashFd, crashAnnotationFd);
     gBootstrap->XRE_SetProcessType(argv[argc - 1]);
 
     XREChildData childData;
     gBootstrap->XRE_InitChildProcess(argc - 1, argv, &childData);
   }
 
   gBootstrap.reset();
   FreeArgv(argv, argc);
--- a/toolkit/xre/Bootstrap.cpp
+++ b/toolkit/xre/Bootstrap.cpp
@@ -73,18 +73,18 @@ public:
     ::XRE_EnableSameExecutableForContentProc();
   }
 
 #ifdef MOZ_WIDGET_ANDROID
   virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, const StaticXREAppData& aAppData) override {
     ::GeckoStart(aEnv, argv, argc, aAppData);
   }
 
-  virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, int aIPCFd, int aCrashFd, int aCrashAnnotationFd) override {
-    ::XRE_SetAndroidChildFds(aEnv, aIPCFd, aCrashFd, aCrashAnnotationFd);
+  virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, int aPrefsFd, int aIPCFd, int aCrashFd, int aCrashAnnotationFd) override {
+    ::XRE_SetAndroidChildFds(aEnv, aPrefsFd, aIPCFd, aCrashFd, aCrashAnnotationFd);
   }
 #endif
 
 #ifdef LIBFUZZER
   virtual void XRE_LibFuzzerSetDriver(LibFuzzerDriver aDriver) override {
     ::XRE_LibFuzzerSetDriver(aDriver);
   }
 #endif
--- a/toolkit/xre/Bootstrap.h
+++ b/toolkit/xre/Bootstrap.h
@@ -108,17 +108,17 @@ public:
 
   virtual nsresult XRE_InitChildProcess(int argc, char* argv[], const XREChildData* aChildData) = 0;
 
   virtual void XRE_EnableSameExecutableForContentProc() = 0;
 
 #ifdef MOZ_WIDGET_ANDROID
   virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, const StaticXREAppData& aAppData) = 0;
 
-  virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, int aIPCFd, int aCrashFd, int aCrashAnnotationFd) = 0;
+  virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, int aPrefsFd, int aIPCFd, int aCrashFd, int aCrashAnnotationFd) = 0;
 #endif
 
 #ifdef LIBFUZZER
   virtual void XRE_LibFuzzerSetDriver(LibFuzzerDriver aDriver) = 0;
 #endif
 
 #ifdef MOZ_IPDL_TESTS
   virtual int XRE_RunIPDLTest(int argc, char **argv) = 0;
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -238,19 +238,20 @@ XRE_ChildProcessTypeToString(GeckoProces
 namespace mozilla {
 namespace startup {
 GeckoProcessType sChildProcessType = GeckoProcessType_Default;
 } // namespace startup
 } // namespace mozilla
 
 #if defined(MOZ_WIDGET_ANDROID)
 void
-XRE_SetAndroidChildFds (JNIEnv* env, int ipcFd, int crashFd, int crashAnnotationFd)
+XRE_SetAndroidChildFds (JNIEnv* env, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd)
 {
   mozilla::jni::SetGeckoThreadEnv(env);
+  mozilla::dom::SetPrefsFd(prefsFd);
   IPC::Channel::SetClientChannelFd(ipcFd);
   CrashReporter::SetNotificationPipeForChild(crashFd);
   CrashReporter::SetCrashAnnotationPipeForChild(crashAnnotationFd);
 }
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 void
 XRE_SetProcessType(const char* aProcessTypeString)
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -2350,19 +2350,19 @@ const char GeckoProcessManager::name[] =
         "org/mozilla/gecko/process/GeckoProcessManager";
 
 constexpr char GeckoProcessManager::GetEditableParent_t::name[];
 constexpr char GeckoProcessManager::GetEditableParent_t::signature[];
 
 constexpr char GeckoProcessManager::Start_t::name[];
 constexpr char GeckoProcessManager::Start_t::signature[];
 
-auto GeckoProcessManager::Start(mozilla::jni::String::Param a0, mozilla::jni::ObjectArray::Param a1, int32_t a2, int32_t a3, int32_t a4) -> int32_t
+auto GeckoProcessManager::Start(mozilla::jni::String::Param a0, mozilla::jni::ObjectArray::Param a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5) -> int32_t
 {
-    return mozilla::jni::Method<Start_t>::Call(GeckoProcessManager::Context(), nullptr, a0, a1, a2, a3, a4);
+    return mozilla::jni::Method<Start_t>::Call(GeckoProcessManager::Context(), nullptr, a0, a1, a2, a3, a4, a5);
 }
 
 const char GeckoServiceChildProcess::name[] =
         "org/mozilla/gecko/process/GeckoServiceChildProcess";
 
 constexpr char GeckoServiceChildProcess::GetEditableParent_t::name[];
 constexpr char GeckoServiceChildProcess::GetEditableParent_t::signature[];
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -6691,30 +6691,31 @@ public:
         typedef GeckoProcessManager Owner;
         typedef int32_t ReturnType;
         typedef int32_t SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
                 mozilla::jni::ObjectArray::Param,
                 int32_t,
                 int32_t,
+                int32_t,
                 int32_t> Args;
         static constexpr char name[] = "start";
         static constexpr char signature[] =
-                "(Ljava/lang/String;[Ljava/lang/String;III)I";
-        static const bool isStatic = true;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-        static const mozilla::jni::CallingThread callingThread =
-                mozilla::jni::CallingThread::ANY;
-        static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::CURRENT;
-    };
-
-    static auto Start(mozilla::jni::String::Param, mozilla::jni::ObjectArray::Param, int32_t, int32_t, int32_t) -> int32_t;
+                "(Ljava/lang/String;[Ljava/lang/String;IIII)I";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto Start(mozilla::jni::String::Param, mozilla::jni::ObjectArray::Param, int32_t, int32_t, int32_t, int32_t) -> int32_t;
 
     static const mozilla::jni::CallingThread callingThread =
             mozilla::jni::CallingThread::ANY;
 
     template<class Impl> class Natives;
 };
 
 class GeckoServiceChildProcess : public mozilla::jni::ObjectBase<GeckoServiceChildProcess>
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -393,17 +393,17 @@ static_assert(MOZ_ARRAY_LENGTH(kGeckoPro
               GeckoProcessType_End,
               "Array length mismatch");
 
 XRE_API(const char*,
         XRE_ChildProcessTypeToString, (GeckoProcessType aProcessType))
 
 #if defined(MOZ_WIDGET_ANDROID)
 XRE_API(void,
-        XRE_SetAndroidChildFds, (JNIEnv* env, int ipcFd, int crashFd, int crashAnnotationFd))
+        XRE_SetAndroidChildFds, (JNIEnv* env, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd))
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 XRE_API(void,
         XRE_SetProcessType, (const char* aProcessTypeString))
 
 // Used in the "master" parent process hosting the crash server
 XRE_API(bool,
         XRE_TakeMinidumpForChild, (uint32_t aChildPid, nsIFile** aDump,