Merge mozilla-central to inbound. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Wed, 13 Jun 2018 00:54:34 +0300
changeset 476608 33407b8862a5a726639ba9760624fd47fd449d62
parent 476607 7827081fdb37234f3c5b9878572b664a88f6b3bc (current diff)
parent 476577 49efc43b14387db6c68d70bbf5e9b8195dd7f80f (diff)
child 476609 bb0f9b9ac3c2079e8b566d6edb041e366328ef7d
push id9374
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:43:20 +0000
treeherdermozilla-beta@160e085dfb0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.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
Merge mozilla-central to inbound. a=merge CLOSED TREE
ipc/chromium/src/base/file_descriptor_shuffle.cc
ipc/chromium/src/base/file_descriptor_shuffle.h
--- a/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html
@@ -44,17 +44,17 @@ function* test(testDriver) {
   var config = getHitTestConfig();
 
   var subframe = document.getElementById('front');
 
   // Set a displayport to ensure the subframe is layerized.
   // This is not required for exercising the behavior we want to test,
   // but it's needed to be able to assert the results reliably.
   config.utils.setDisplayPortForElement(0, 0, 1000, 1000, subframe, 1);
-  yield waitForAllPaints(testDriver);
+  yield waitForApzFlushedRepaints(testDriver);
 
   var subframeViewId = config.utils.getViewId(subframe);
 
   var {hitInfo, scrollId} = hitTest(centerOf(subframe));
 
   is(scrollId, subframeViewId,
      "hit the scroll frame behind the backface-visibility:hidden element");
 
--- a/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
@@ -46,17 +46,17 @@ function* test(testDriver) {
     expectThumb: true,
     layerState: LayerState.INACTIVE
   });
 
   // activate the scrollframe but keep the main-thread scroll position at 0.
   // also apply a async scroll offset in the y-direction such that the
   // scrollframe scrolls to the bottom of its range.
   utils.setDisplayPortForElement(0, 0, 500, 500, scroller, 1);
-  yield waitForAllPaints(testDriver);
+  yield waitForApzFlushedRepaints(testDriver);
   var scrollY = scroller.scrollTopMax;
   utils.setAsyncScrollOffset(scroller, 0, scrollY);
   if (config.isWebRender) {
     // Tick the refresh driver once to make sure the compositor has applied the
     // async scroll offset (for APZ hit-testing this doesn't matter, but for
     // WebRender hit-testing we need to make sure WR has the latest info).
     utils.advanceTimeAndRefresh(16);
     utils.restoreNormalRefresh();
--- a/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html
@@ -23,17 +23,17 @@ function* test(testDriver) {
 
   var scroller = document.getElementById('scroller');
 
   // Activate the scrollframe but keep the main-thread scroll position at 0.
   // Also apply an async scroll offset in the y-direction such that the
   // scrollframe scrolls all the way to the bottom of its range, where it's
   // sure to checkerboard.
   utils.setDisplayPortForElement(0, 0, 300, 1000, scroller, 1);
-  yield waitForAllPaints(testDriver);
+  yield waitForApzFlushedRepaints(testDriver);
   var scrollY = scroller.scrollTopMax;
   utils.setAsyncScrollOffset(scroller, 0, scrollY);
   if (config.isWebRender) {
     // Tick the refresh driver once to make sure the compositor has applied the
     // async scroll offset (for APZ hit-testing this doesn't matter, but for
     // WebRender hit-testing we need to make sure WR has the latest info).
     utils.advanceTimeAndRefresh(16);
     utils.restoreNormalRefresh();
--- a/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html
@@ -65,17 +65,17 @@ body {
 
 function* test(testDriver) {
   var config = getHitTestConfig();
   var utils = config.utils;
 
   // layerize the scrollable frame
   var subframe = document.querySelector('.subframe');
   utils.setDisplayPortForElement(0, 0, 800, 2000, subframe, 1);
-  yield waitForAllPaints(testDriver);
+  yield waitForApzFlushedRepaints(testDriver);
 
   var target = document.querySelector('.absoluteClip');
   checkHitResult(hitTest(centerOf(target)),
                  APZHitResultFlags.VISIBLE,
                  utils.getViewId(subframe),
                  "fixed item inside a scrolling transform");
 
   subtestDone();
--- a/ipc/chromium/moz.build
+++ b/ipc/chromium/moz.build
@@ -53,17 +53,16 @@ if os_win:
     ]
 
 elif not CONFIG['MOZ_SYSTEM_LIBEVENT']:
     DIRS += ['src/third_party']
 
 if os_posix:
     UNIFIED_SOURCES += [
         'src/base/condition_variable_posix.cc',
-        'src/base/file_descriptor_shuffle.cc',
         'src/base/file_util_posix.cc',
         'src/base/lock_impl_posix.cc',
         'src/base/message_pump_libevent.cc',
         'src/base/platform_thread_posix.cc',
         'src/base/process_util_posix.cc',
         'src/base/rand_util_posix.cc',
         'src/base/shared_memory_posix.cc',
         'src/base/string16.cc',
deleted file mode 100644
--- a/ipc/chromium/src/base/file_descriptor_shuffle.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-/* -*- 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) 2009 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/file_descriptor_shuffle.h"
-
-#include <errno.h>
-#include <unistd.h>
-
-#include "base/eintr_wrapper.h"
-#include "base/logging.h"
-
-namespace base {
-
-bool PerformInjectiveMultimapDestructive(
-    InjectiveMultimap* m, InjectionDelegate* delegate) {
-  static const size_t kMaxExtraFDs = 16;
-  int extra_fds[kMaxExtraFDs];
-  unsigned next_extra_fd = 0;
-
-  // DANGER: this function may not allocate.
-
-  for (InjectiveMultimap::iterator i = m->begin(); i != m->end(); ++i) {
-    int temp_fd = -1;
-
-    // We DCHECK the injectiveness of the mapping.
-    for (InjectiveMultimap::iterator j = i + 1; j != m->end(); ++j) {
-      DCHECK(i->dest != j->dest) << "Both fd " << i->source
-          << " and " << j->source << " map to " << i->dest;
-    }
-
-    const bool is_identity = i->source == i->dest;
-
-    for (InjectiveMultimap::iterator j = i + 1; j != m->end(); ++j) {
-      if (!is_identity && i->dest == j->source) {
-        if (temp_fd == -1) {
-          if (!delegate->Duplicate(&temp_fd, i->dest))
-            return false;
-          if (next_extra_fd < kMaxExtraFDs) {
-            extra_fds[next_extra_fd++] = temp_fd;
-          } else {
-              DLOG(ERROR) << "PerformInjectiveMultimapDestructive overflowed "
-                          << "extra_fds. Leaking file descriptors!";
-          }
-        }
-
-        j->source = temp_fd;
-        j->close = false;
-      }
-
-      if (i->close && i->source == j->dest)
-        i->close = false;
-
-      if (i->close && i->source == j->source) {
-        i->close = false;
-        j->close = true;
-      }
-    }
-
-    if (!is_identity) {
-      if (!delegate->Move(i->source, i->dest))
-        return false;
-    }
-
-    if (!is_identity && i->close)
-      delegate->Close(i->source);
-  }
-
-  for (unsigned i = 0; i < next_extra_fd; i++)
-    delegate->Close(extra_fds[i]);
-
-  return true;
-}
-
-bool PerformInjectiveMultimap(const InjectiveMultimap& m_in,
-                              InjectionDelegate* delegate) {
-    InjectiveMultimap m(m_in);
-    return PerformInjectiveMultimapDestructive(&m, delegate);
-}
-
-bool FileDescriptorTableInjection::Duplicate(int* result, int fd) {
-  *result = HANDLE_EINTR(dup(fd));
-  return *result >= 0;
-}
-
-bool FileDescriptorTableInjection::Move(int src, int dest) {
-  return HANDLE_EINTR(dup2(src, dest)) != -1;
-}
-
-void FileDescriptorTableInjection::Close(int fd) {
-  IGNORE_EINTR(close(fd));
-}
-
-}  // namespace base
deleted file mode 100644
--- a/ipc/chromium/src/base/file_descriptor_shuffle.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- 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) 2009 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 BASE_FILE_DESCRIPTOR_SHUFFLE_H_
-#define BASE_FILE_DESCRIPTOR_SHUFFLE_H_
-
-#include "mozilla/Attributes.h"
-
-// This code exists to perform the shuffling of file descriptors which is
-// commonly needed when forking subprocesses. The naive approve is very simple,
-// just call dup2 to setup the desired descriptors, but wrong. It's tough to
-// handle the edge cases (like mapping 0 -> 1, 1 -> 0) correctly.
-//
-// In order to unittest this code, it's broken into the abstract action (an
-// injective multimap) and the concrete code for dealing with file descriptors.
-// Users should use the code like this:
-//   base::InjectiveMultimap file_descriptor_map;
-//   file_descriptor_map.push_back(base::InjectionArc(devnull, 0, true));
-//   file_descriptor_map.push_back(base::InjectionArc(devnull, 2, true));
-//   file_descriptor_map.push_back(base::InjectionArc(pipe[1], 1, true));
-//   base::ShuffleFileDescriptors(file_descriptor_map);
-//
-// and trust the the Right Thing will get done.
-
-#include <vector>
-
-namespace base {
-
-// A Delegate which performs the actions required to perform an injective
-// multimapping in place.
-class InjectionDelegate {
- public:
-  // Duplicate |fd|, an element of the domain, and write a fresh element of the
-  // domain into |result|. Returns true iff successful.
-  virtual bool Duplicate(int* result, int fd) = 0;
-  // Destructively move |src| to |dest|, overwriting |dest|. Returns true iff
-  // successful.
-  virtual bool Move(int src, int dest) = 0;
-  // Delete an element of the domain.
-  virtual void Close(int fd) = 0;
-};
-
-// An implementation of the InjectionDelegate interface using the file
-// descriptor table of the current process as the domain.
-class FileDescriptorTableInjection : public InjectionDelegate {
-  virtual bool Duplicate(int* result, int fd) override;
-  virtual bool Move(int src, int dest) override;
-  virtual void Close(int fd) override;
-};
-
-// A single arc of the directed graph which describes an injective multimapping.
-struct InjectionArc {
-  InjectionArc(int in_source, int in_dest, bool in_close)
-      : source(in_source),
-        dest(in_dest),
-        close(in_close) {
-  }
-
-  int source;
-  int dest;
-  bool close;  // if true, delete the source element after performing the
-               // mapping.
-};
-
-typedef std::vector<InjectionArc> InjectiveMultimap;
-
-bool PerformInjectiveMultimap(const InjectiveMultimap& map,
-                              InjectionDelegate* delegate);
-bool PerformInjectiveMultimapDestructive(InjectiveMultimap* map,
-                                         InjectionDelegate* delegate);
-
-// This function will not call malloc but will mutate |map|
-static inline bool ShuffleFileDescriptors(InjectiveMultimap *map) {
-  FileDescriptorTableInjection delegate;
-  return PerformInjectiveMultimapDestructive(map, &delegate);
-}
-
-}  // namespace base
-
-#endif  // !BASE_FILE_DESCRIPTOR_SHUFFLE_H_
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -22,32 +22,29 @@
 #elif defined(OS_LINUX) || defined(__GLIBC__)
 #include <dirent.h>
 #include <limits.h>
 #include <sys/types.h>
 #elif defined(OS_MACOSX)
 #include <mach/mach.h>
 #endif
 
+#include <functional>
 #include <map>
 #include <string>
 #include <vector>
 #include <stdio.h>
 #include <stdlib.h>
 #ifndef OS_WIN
 #include <unistd.h>
 #endif
 
 #include "base/command_line.h"
 #include "base/process.h"
 
-#if defined(OS_POSIX)
-#include "base/file_descriptor_shuffle.h"
-#endif
-
 #include "mozilla/UniquePtr.h"
 #include "mozilla/ipc/EnvironmentMap.h"
 
 #if defined(OS_MACOSX)
 struct kinfo_proc;
 #endif
 
 namespace base {
@@ -87,20 +84,21 @@ ProcessId GetProcId(ProcessHandle proces
 
 #if defined(OS_POSIX)
 // Sets all file descriptors to close on exec except for stdin, stdout
 // and stderr.
 // 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);
+// Close all file descriptors, except for std{in,out,err} and those
+// for which the given function returns true.  Only call this function
+// in a child process where you know that there aren't any other
+// threads.
+void CloseSuperfluousFds(std::function<bool(int)>&& should_preserve);
 
 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.
--- a/ipc/chromium/src/base/process_util_linux.cc
+++ b/ipc/chromium/src/base/process_util_linux.cc
@@ -8,54 +8,55 @@
 
 #include <string>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include "base/eintr_wrapper.h"
 #include "base/logging.h"
-#include "mozilla/Move.h"
+#include "mozilla/ipc/FileDescriptorShuffle.h"
 #include "mozilla/UniquePtr.h"
 
 namespace {
 
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
 
 bool LaunchApp(const std::vector<std::string>& argv,
                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(options.fds_to_remap.size());
-  fd_shuffle2.reserve(options.fds_to_remap.size());
 
   EnvironmentArray envp = BuildEnvironmentArray(options.env_map);
+  mozilla::ipc::FileDescriptorShuffle shuffle;
+  if (!shuffle.Init(options.fds_to_remap)) {
+    return false;
+  }
 
   pid_t pid = options.fork_delegate ? options.fork_delegate->Fork() : fork();
   if (pid < 0)
     return false;
 
   if (pid == 0) {
     // 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));
+    for (const auto& fds : shuffle.Dup2Sequence()) {
+      if (HANDLE_EINTR(dup2(fds.first, fds.second)) != fds.second) {
+        // This shouldn't happen, but check for it.  And see below
+        // about logging being unsafe here, so this is debug only.
+        DLOG(ERROR) << "dup2 failed";
+        _exit(127);
+      }
     }
 
-    if (!ShuffleFileDescriptors(&fd_shuffle1))
-      _exit(127);
-
-    CloseSuperfluousFds(fd_shuffle2);
+    CloseSuperfluousFds(shuffle.MapsToFunc());
 
     for (size_t i = 0; i < argv.size(); i++)
       argv_cstr[i] = const_cast<char*>(argv[i].c_str());
     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
--- a/ipc/chromium/src/base/process_util_posix.cc
+++ b/ipc/chromium/src/base/process_util_posix.cc
@@ -116,19 +116,20 @@ class ScopedDIRClose {
     if (x) {
       closedir(x);
     }
   }
 };
 typedef mozilla::UniquePtr<DIR, ScopedDIRClose> ScopedDIR;
 
 
-void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) {
-  // DANGER: no calls to malloc are allowed from now on:
-  // http://crbug.com/36678
+void CloseSuperfluousFds(std::function<bool(int)>&& should_preserve) {
+  // DANGER: no calls to malloc (or locks, etc.) are allowed from now on:
+  // https://crbug.com/36678
+  // Also, beware of STL iterators: https://crbug.com/331459
 #if defined(ANDROID)
   static const rlim_t kSystemDefaultMaxFds = 1024;
   static const char kFDDir[] = "/proc/self/fd";
 #elif defined(OS_LINUX) || defined(OS_SOLARIS)
   static const rlim_t kSystemDefaultMaxFds = 8192;
   static const char kFDDir[] = "/proc/self/fd";
 #elif defined(OS_MACOSX)
   static const rlim_t kSystemDefaultMaxFds = 256;
@@ -155,56 +156,46 @@ void CloseSuperfluousFds(const base::Inj
     max_fds = INT_MAX;
 
   DirReaderPosix fd_dir(kFDDir);
 
   if (!fd_dir.IsValid()) {
     // Fallback case: Try every possible fd.
     for (rlim_t i = 0; i < max_fds; ++i) {
       const int fd = static_cast<int>(i);
-      if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO)
+      if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO ||
+          should_preserve(fd)) {
         continue;
-      InjectiveMultimap::const_iterator j;
-      for (j = saved_mapping.begin(); j != saved_mapping.end(); j++) {
-        if (fd == j->dest)
-          break;
       }
-      if (j != saved_mapping.end())
-        continue;
 
       // Since we're just trying to close anything we can find,
       // ignore any error return values of close().
-      IGNORE_EINTR(close(fd));
+      close(fd);
     }
     return;
   }
 
   const int dir_fd = fd_dir.fd();
 
   for ( ; fd_dir.Next(); ) {
     // Skip . and .. entries.
     if (fd_dir.name()[0] == '.')
       continue;
 
     char *endptr;
     errno = 0;
     const long int fd = strtol(fd_dir.name(), &endptr, 10);
     if (fd_dir.name()[0] == 0 || *endptr || fd < 0 || errno)
       continue;
-    if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO)
-      continue;
-    InjectiveMultimap::const_iterator i;
-    for (i = saved_mapping.begin(); i != saved_mapping.end(); i++) {
-      if (fd == i->dest)
-        break;
-    }
-    if (i != saved_mapping.end())
-      continue;
     if (fd == dir_fd)
       continue;
+    if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO ||
+        should_preserve(fd)) {
+      continue;
+    }
 
     // When running under Valgrind, Valgrind opens several FDs for its
     // own use and will complain if we try to close them.  All of
     // these FDs are >= |max_fds|, so we can check against that here
     // before closing.  See https://bugs.kde.org/show_bug.cgi?id=191758
     if (fd < static_cast<int>(max_fds)) {
       int ret = IGNORE_EINTR(close(fd));
       if (ret != 0) {
new file mode 100644
--- /dev/null
+++ b/ipc/glue/FileDescriptorShuffle.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileDescriptorShuffle.h"
+
+#include "base/eintr_wrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+
+#include <algorithm>
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace mozilla {
+namespace ipc {
+
+// F_DUPFD_CLOEXEC is like F_DUPFD (see below) but atomically makes
+// the new fd close-on-exec.  This is useful to prevent accidental fd
+// leaks into processes created by plain fork/exec, but IPC uses
+// CloseSuperfluousFds so it's not essential.
+//
+// F_DUPFD_CLOEXEC is in POSIX 2008, but as of 2018 there are still
+// some OSes that don't support it.  (Specifically: Solaris 10 doesn't
+// have it, and Android should have kernel support but doesn't define
+// the constant until API 21 (Lollipop).  We also don't use this for
+// IPC child launching on Android, so there's no point in hard-coding
+// the definitions like we do for Android in some other cases.)
+#ifdef F_DUPFD_CLOEXEC
+static const int kDupFdCmd = F_DUPFD_CLOEXEC;
+#else
+static const int kDupFdCmd = F_DUPFD;
+#endif
+
+// This implementation ensures that the *ranges* of the source and
+// destination fds don't overlap, which is unnecessary but sufficient
+// to avoid conflicts or identity mappings.
+//
+// In practice, the source fds will usually be large and the
+// destination fds will all be relatively small, so there mostly won't
+// be temporary fds.  This approach has the advantage of being simple
+// (and linear-time, but hopefully there aren't enough fds for that to
+// matter).
+bool
+FileDescriptorShuffle::Init(MappingRef aMapping)
+{
+  MOZ_ASSERT(mMapping.IsEmpty());
+
+  // Find the maximum destination fd; any source fds not greater than
+  // this will be duplicated.
+  int maxDst = STDERR_FILENO;
+  for (const auto& elem : aMapping) {
+    maxDst = std::max(maxDst, elem.second);
+  }
+  mMaxDst = maxDst;
+
+#ifdef DEBUG
+  // Increase the limit to make sure the F_DUPFD case gets test coverage.
+  if (!aMapping.IsEmpty()) {
+    // Try to find a value that will create a nontrivial partition.
+    int fd0 = aMapping[0].first;
+    int fdn = aMapping.rbegin()->first;
+    maxDst = std::max(maxDst, (fd0 + fdn) / 2);
+  }
+#endif
+
+  for (const auto& elem : aMapping) {
+    int src = elem.first;
+    // F_DUPFD is like dup() but allows placing a lower bound
+    // on the new fd, which is exactly what we want.
+    if (src <= maxDst) {
+      src = fcntl(src, kDupFdCmd, maxDst + 1);
+      if (src < 0) {
+        return false;
+      }
+      mTempFds.AppendElement(src);
+    }
+    MOZ_ASSERT(src > maxDst);
+#ifdef DEBUG
+    // Check for accidentally mapping two different fds to the same
+    // destination.  (This is O(n^2) time, but it shouldn't matter.)
+    for (const auto& otherElem : mMapping) {
+      MOZ_ASSERT(elem.second != otherElem.second);
+    }
+#endif
+    mMapping.AppendElement(std::make_pair(src, elem.second));
+  }
+  return true;
+}
+
+bool
+FileDescriptorShuffle::MapsTo(int aFd) const
+{
+  // Prune fds that are too large to be a destination, rather than
+  // searching; this should be the common case.
+  if (aFd > mMaxDst) {
+    return false;
+  }
+  for (const auto& elem : mMapping) {
+    if (elem.second == aFd) {
+      return true;
+    }
+  }
+  return false;
+}
+
+FileDescriptorShuffle::~FileDescriptorShuffle()
+{
+  for (const auto& fd : mTempFds) {
+    mozilla::DebugOnly<int> rv = IGNORE_EINTR(close(fd));
+    MOZ_ASSERT(rv == 0);
+  }
+}
+
+} // namespace ipc
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/glue/FileDescriptorShuffle.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ipc_FileDescriptorShuffle_h
+#define mozilla_ipc_FileDescriptorShuffle_h
+
+#include "mozilla/Span.h"
+#include "nsTArray.h"
+
+#include <functional>
+#include <utility>
+
+// This class converts a set of file descriptor mapping, which may
+// contain conflicts (like {a->b, b->c} or {a->b, b->a}) into a
+// sequence of dup2() operations that can be performed between fork
+// and exec, or with posix_spawn_file_actions_adddup2.  It may create
+// temporary duplicates of fds to use as the source of a dup2; they
+// are closed on destruction.
+//
+// The dup2 sequence is guaranteed to not contain dup2(x, x) for any
+// x; if such an element is present in the input, it will be dup2()ed
+// from a temporary fd to ensure that the close-on-exec bit is cleared.
+//
+// In general, this is *not* guaranteed to minimize the use of
+// temporary fds.
+
+namespace mozilla {
+namespace ipc {
+
+class FileDescriptorShuffle
+{
+public:
+  FileDescriptorShuffle() = default;
+  ~FileDescriptorShuffle();
+
+  using MappingRef = mozilla::Span<const std::pair<int, int>>;
+
+  // Translate the given mapping, creating temporary fds as needed.
+  // Can fail (return false) on failure to duplicate fds.
+  bool Init(MappingRef aMapping);
+
+  // Accessor for the dup2() sequence.  Do not use the returned value
+  // or the fds contained in it after this object is destroyed.
+  MappingRef Dup2Sequence() const { return mMapping; }
+
+  // Tests whether the given fd is used as a destination in this mapping.
+  // Can be used to close other fds after performing the dup2()s.
+  bool MapsTo(int aFd) const;
+
+  // Wraps MapsTo in a function object, as a convenience for use with
+  // base::CloseSuperfluousFds.
+  std::function<bool(int)> MapsToFunc() const {
+    return [this](int fd) { return MapsTo(fd); };
+  }
+
+private:
+  nsTArray<std::pair<int, int>> mMapping;
+  nsTArray<int> mTempFds;
+  int mMaxDst;
+
+  FileDescriptorShuffle(const FileDescriptorShuffle&) = delete;
+  void operator=(const FileDescriptorShuffle&) = delete;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptorShuffle_h
--- a/ipc/glue/IPCStreamSource.cpp
+++ b/ipc/glue/IPCStreamSource.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "IPCStreamSource.h"
+#include "BackgroundParent.h" // for AssertIsOnBackgroundThread
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsIAsyncInputStream.h"
 #include "nsICancelableRunnable.h"
 #include "nsIRunnable.h"
 #include "nsISerialEventTarget.h"
 #include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
 
--- a/ipc/glue/IPCStreamSource.h
+++ b/ipc/glue/IPCStreamSource.h
@@ -23,16 +23,18 @@ class nsIContentParent;
 namespace wr {
 struct ByteBuffer;
 } // wr namespace
 
 namespace ipc {
 
 class PBackgroundChild;
 class PBackgroundParent;
+class PChildToParentStreamChild;
+class PParentToChildStreamParent;
 
 // The IPCStream IPC actor is designed to push an nsIInputStream from child to
 // parent or parent to child incrementally.  This is mainly needed for streams
 // such as nsPipe that may not yet have all their data available when the
 // stream must be sent across an IPC boundary.  While many streams are handled
 // by SerializeInputStream(), these streams cannot be serialized and must be
 // sent using this actor.
 //
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -121,16 +121,24 @@ elif CONFIG['OS_ARCH'] == 'Darwin':
     UNIFIED_SOURCES += [
         'ProcessUtils_mac.mm'
     ]
 else:
     UNIFIED_SOURCES += [
         'ProcessUtils_none.cpp',
     ]
 
+if CONFIG['OS_ARCH'] != 'WINNT':
+    EXPORTS.mozilla.ipc += [
+        'FileDescriptorShuffle.h',
+    ]
+    UNIFIED_SOURCES += [
+        'FileDescriptorShuffle.cpp',
+    ]
+
 EXPORTS.ipc += [
     'IPCMessageUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'BackgroundImpl.cpp',
     'BackgroundUtils.cpp',
     'BrowserProcessSubThread.cpp',
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1766,17 +1766,17 @@ fuzzy-if(Android,4,400) == 815593-1.html
 fuzzy-if(skiaContent,1,5) == 816948-1.html 816948-1-ref.html
 == 817019-1.html about:blank
 fuzzy-if(skiaContent,1,5) == 818276-1.html 818276-1-ref.html
 fuzzy-if(asyncPan,190,510) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,510) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == 825999.html 825999-ref.html # Bug 1392106
 == 827577-1a.html 827577-1-ref.html
 == 827577-1b.html 827577-1-ref.html
 == 827799-1.html about:blank
 == 829958.html 829958-ref.html
-fuzzy-if(webrender&&gtkWidget,1-2,44000-135600) == 836844-1.html 836844-1-ref.html
+fuzzy-if(webrender&&gtkWidget,1-2,44000-152400) == 836844-1.html 836844-1-ref.html
 == 841192-1.html 841192-1-ref.html
 == 844178.html 844178-ref.html
 fuzzy-if(OSX,1,364) fuzzy-if(skiaContent,1,320) == 846144-1.html 846144-1-ref.html
 == 847850-1.html 847850-1-ref.html
 == 848421-1.html 848421-1-ref.html
 == 849407-1.html 849407-1-ref.html
 == 849996-1.html 849996-1-ref.html
 == 858803-1.html 858803-1-ref.html
--- a/modules/libjar/nsJARURI.h
+++ b/modules/libjar/nsJARURI.h
@@ -106,17 +106,17 @@ private:
     nsresult SetPassword(const nsACString &input);
     nsresult SetHostPort(const nsACString &aValue);
     nsresult SetHost(const nsACString &input);
     nsresult SetPort(int32_t port);
     nsresult SetPathQueryRef(const nsACString &input);
     nsresult SetRef(const nsACString &input);
     nsresult SetFilePath(const nsACString &input);
     nsresult SetQuery(const nsACString &input);
-    nsresult SetQueryWithEncoding(const nsACString &input, const Encoding* encoding);
+    nsresult SetQueryWithEncoding(const nsACString &input, const mozilla::Encoding* encoding);
     bool Deserialize(const mozilla::ipc::URIParams&);
     nsresult ReadPrivate(nsIObjectInputStream *aStream);
 
     nsresult SetFileNameInternal(const nsACString& fileName);
     nsresult SetFileBaseNameInternal(const nsACString& fileBaseName);
     nsresult SetFileExtensionInternal(const nsACString& fileExtension);
 
 public:
--- a/python/mozbuild/mozbuild/controller/building.py
+++ b/python/mozbuild/mozbuild/controller/building.py
@@ -752,16 +752,18 @@ class CCacheStats(object):
     Instances can be subtracted from each other to obtain differences.
     print() or str() the object to show a ``ccache -s`` like output
     of the captured stats.
 
     """
     STATS_KEYS = [
         # (key, description)
         # Refer to stats.c in ccache project for all the descriptions.
+        ('stats_zero_time', 'stats zero time'),
+        ('stats_updated', 'stats updated'),
         ('cache_hit_direct', 'cache hit (direct)'),
         ('cache_hit_preprocessed', 'cache hit (preprocessed)'),
         ('cache_hit_rate', 'cache hit rate'),
         ('cache_miss', 'cache miss'),
         ('link', 'called for link'),
         ('preprocessing', 'called for preprocessing'),
         ('multiple', 'multiple source files'),
         ('stdout', 'compiler produced stdout'),
@@ -833,16 +835,23 @@ class CCacheStats(object):
                 raise ValueError('Failed to parse ccache stats output: %s' % line)
 
     @staticmethod
     def _strip_prefix(line, prefix):
         return line[len(prefix):].strip() if line.startswith(prefix) else line
 
     @staticmethod
     def _parse_value(raw_value):
+        try:
+            # ccache calls strftime with '%c' (src/stats.c)
+            ts = time.strptime(raw_value, '%c')
+            return int(time.mktime(ts))
+        except ValueError:
+            pass
+
         value = raw_value.split()
         unit = ''
         if len(value) == 1:
             numeric = value[0]
         elif len(value) == 2:
             numeric, unit = value
         else:
             raise ValueError('Failed to parse ccache stats value: %s' % raw_value)
--- a/python/mozbuild/mozbuild/test/controller/test_ccachestats.py
+++ b/python/mozbuild/mozbuild/test/controller/test_ccachestats.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from __future__ import unicode_literals
 
+import time
 import unittest
 
 from mozunit import main
 
 from mozbuild.controller.building import CCacheStats
 
 
 class TestCcacheStats(unittest.TestCase):
@@ -169,16 +170,40 @@ class TestCcacheStats(unittest.TestCase)
     unsupported code directive             2
     no input file                       2378
     cleanups performed                  1792
     files in cache                      3479
     cache size                           4.4 GB
     max cache size                       5.0 GB
     """
 
+    # Substitute a locally-generated timestamp because the timestamp format is
+    # locale-dependent.
+    STAT8 = """
+    cache directory                     /home/psimonyi/.ccache
+    primary config                      /home/psimonyi/.ccache/ccache.conf
+    secondary config      (readonly)    /etc/ccache.conf
+    stats zero time                     {timestamp}
+    cache hit (direct)                   571
+    cache hit (preprocessed)            1203
+    cache miss                         11747
+    cache hit rate                     13.12 %
+    called for link                      623
+    called for preprocessing            7194
+    compile failed                        32
+    preprocessor error                   137
+    bad compiler arguments                 4
+    autoconf compile/link                348
+    no input file                        162
+    cleanups performed                    77
+    files in cache                     13464
+    cache size                           6.2 GB
+    max cache size                       7.0 GB
+    """.format(timestamp=time.strftime('%c'))
+
     def test_parse_garbage_stats_message(self):
         self.assertRaises(ValueError, CCacheStats, self.STAT_GARBAGE)
 
     def test_parse_zero_stats_message(self):
         stats = CCacheStats(self.STAT0)
         self.assertEqual(stats.cache_dir, "/home/tlin/.ccache")
         self.assertEqual(stats.hit_rates(), (0, 0, 0))
 
@@ -226,10 +251,15 @@ class TestCcacheStats(unittest.TestCase)
         self.assertTrue(stat6)
         self.assertTrue(stat3)
         self.assertTrue(stats_diff)
 
         # Test stats for 3.3.3.
         stat7 = CCacheStats(self.STAT7)
         self.assertTrue(stat7)
 
+    def test_stats_version34(self):
+        # Test parsing 3.4 output.
+        stat8 = CCacheStats(self.STAT8)
+        self.assertTrue(stat8)
+
 if __name__ == '__main__':
     main()
--- a/security/sandbox/linux/launch/SandboxLaunch.cpp
+++ b/security/sandbox/linux/launch/SandboxLaunch.cpp
@@ -207,18 +207,16 @@ public:
 
   void PrepareMapping(base::file_handle_mapping_vector* aMap);
   pid_t Fork() override;
 
 private:
   int mFlags;
   int mChrootServer;
   int mChrootClient;
-  // For CloseSuperfluousFds in the chroot helper process:
-  base::InjectiveMultimap mChrootMap;
 
   void StartChrootServer();
   SandboxFork(const SandboxFork&) = delete;
   SandboxFork& operator=(const SandboxFork&) = delete;
 };
 
 static int
 GetEffectiveSandboxLevel(GeckoProcessType aType)
@@ -342,20 +340,16 @@ SandboxFork::SandboxFork(int aFlags, boo
     int fds[2];
     int rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
     if (rv != 0) {
       SANDBOX_LOG_ERROR("socketpair: %s", strerror(errno));
       MOZ_CRASH("socketpair failed");
     }
     mChrootClient = fds[0];
     mChrootServer = fds[1];
-    // Do this here because the child process won't be able to malloc.
-    mChrootMap.push_back(base::InjectionArc(mChrootServer,
-                                            mChrootServer,
-                                            false));
   }
 }
 
 void
 SandboxFork::PrepareMapping(base::file_handle_mapping_vector* aMap)
 {
   if (mChrootClient >= 0) {
     aMap->push_back({ mChrootClient, kSandboxChrootClientFd });
@@ -591,17 +585,17 @@ SandboxFork::StartChrootServer()
 
   LinuxCapabilities caps;
   caps.Effective(CAP_SYS_CHROOT) = true;
   if (!caps.SetCurrent()) {
     SANDBOX_LOG_ERROR("capset (chroot helper): %s", strerror(errno));
     MOZ_DIAGNOSTIC_ASSERT(false);
   }
 
-  CloseSuperfluousFds(mChrootMap);
+  base::CloseSuperfluousFds([this](int fd) { return fd == mChrootServer; });
 
   char msg;
   ssize_t msgLen = HANDLE_EINTR(read(mChrootServer, &msg, 1));
   if (msgLen == 0) {
     // Process exited before chrooting (or chose not to chroot?).
     _exit(0);
   }
   MOZ_RELEASE_ASSERT(msgLen == 1);
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -3708,17 +3708,17 @@ nsWindow::Create(nsIWidget* aParent,
         bool useAlphaVisual = (mWindowType == eWindowType_popup &&
                                aInitData->mSupportTranslucency);
 
         // mozilla.widget.use-argb-visuals is a hidden pref defaulting to false
         // to allow experimentation
         if (Preferences::GetBool("mozilla.widget.use-argb-visuals", false))
             useAlphaVisual = true;
 
-#ifdef GL_PROVIDER_GLX
+#ifdef MOZ_X11
         // Ensure gfxPlatform is initialized, since that is what initializes
         // gfxVars, used below.
         Unused << gfxPlatform::GetPlatform();
 
         bool useWebRender = gfx::gfxVars::UseWebRender() &&
             AllowWebRenderForThisWindow();
 
         // If using WebRender on X11, we need to select a visual with a depth buffer,
@@ -3735,17 +3735,17 @@ nsWindow::Create(nsIWidget* aParent,
                                          &visualId)) {
                 // If we're using CSD, rendering will go through mContainer, but
                 // it will inherit this visual as it is a child of mShell.
                 gtk_widget_set_visual(mShell,
                                       gdk_x11_screen_lookup_visual(screen,
                                                                    visualId));
             }
         } else
-#endif // GL_PROVIDER_GLX
+#endif // MOZ_X11
         {
             if (useAlphaVisual) {
                 GdkScreen *screen = gtk_widget_get_screen(mShell);
                 if (gdk_screen_is_composited(screen)) {
                     GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
                     gtk_widget_set_visual(mShell, visual);
                 }
             }