Merge mozilla-central to autoland. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 30 Apr 2019 06:51:00 +0300
changeset 530685 0962663e2a2884f0aa26d2d0603014232d12ae82
parent 530684 23ca4e6edd55d1261e9829ed29b64166fd44fa18 (current diff)
parent 530668 da2b564f6df03fd8ce37f2eb394fd48289d43a55 (diff)
child 530686 ebbfc83259094cb4ef11929388172e78d20c30a4
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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 autoland. a=merge CLOSED TREE
--- a/devtools/client/debugger/src/client/firefox/workers.js
+++ b/devtools/client/debugger/src/client/firefox/workers.js
@@ -21,33 +21,39 @@ export async function updateWorkerClient
   if (!supportsWorkers(tabTarget)) {
     return {};
   }
 
   const newWorkerClients = {};
 
   const { workers } = await tabTarget.listWorkers();
   for (const workerTargetFront of workers) {
-    await workerTargetFront.attach();
-    const [, workerThread] = await workerTargetFront.attachThread(options);
+    try {
+      await workerTargetFront.attach();
+      const [, workerThread] = await workerTargetFront.attachThread(options);
 
-    if (workerClients[workerThread.actor]) {
-      if (workerClients[workerThread.actor].thread != workerThread) {
-        throw new Error(`Multiple clients for actor ID: ${workerThread.actor}`);
+      const actor = workerThread.actor;
+      if (workerClients[actor]) {
+        if (workerClients[actor].thread != workerThread) {
+          console.error(`Multiple clients for actor ID: ${workerThread.actor}`);
+        }
+        newWorkerClients[actor] = workerClients[actor];
+      } else {
+        addThreadEventListeners(workerThread);
+        workerThread.resume();
+
+        const consoleFront = await workerTargetFront.getFront("console");
+        await consoleFront.startListeners([]);
+
+        newWorkerClients[actor] = {
+          url: workerTargetFront.url,
+          thread: workerThread,
+          console: consoleFront
+        };
       }
-      newWorkerClients[workerThread.actor] = workerClients[workerThread.actor];
-    } else {
-      addThreadEventListeners(workerThread);
-      workerThread.resume();
-
-      const consoleFront = await workerTargetFront.getFront("console");
-      await consoleFront.startListeners([]);
-
-      newWorkerClients[workerThread.actor] = {
-        url: workerTargetFront.url,
-        thread: workerThread,
-        console: consoleFront
-      };
+    } catch (e) {
+      // If any of the workers have terminated since the list command initiated
+      // then we will get errors. Ignore these.
     }
   }
 
   return newWorkerClients;
 }
--- a/devtools/shared/ThreadSafeDevToolsUtils.js
+++ b/devtools/shared/ThreadSafeDevToolsUtils.js
@@ -4,16 +4,18 @@
 
 "use strict";
 
 /**
  * General utilities used throughout devtools that can also be used in
  * workers.
  */
 
+var flags = require("./flags");
+
 /**
  * Immutably reduce the given `...objs` into one object. The reduction is
  * applied from left to right, so `immutableUpdate({ a: 1 }, { a: 2 })` will
  * result in `{ a: 2 }`. The resulting object is frozen.
  *
  * Example usage:
  *
  *     const original = { foo: 1, bar: 2, baz: 3 };
@@ -107,17 +109,19 @@ exports.makeInfallible = function(handle
   return function() {
     try {
       return handler.apply(this, arguments);
     } catch (ex) {
       let who = "Handler function";
       if (name) {
         who += " " + name;
       }
-      exports.reportException(who, ex);
+      if (!flags.quiet) {
+        exports.reportException(who, ex);
+      }
       return undefined;
     }
   };
 };
 
 /**
  * Turn the |error| into a string, without fail.
  *
--- a/devtools/shared/flags.js
+++ b/devtools/shared/flags.js
@@ -53,13 +53,20 @@ makePrefTrackedFlag(exports, "wantLoggin
 /**
  * Setting the "devtools.debugger.log.verbose" preference to true will enable a
  * more verbose logging of the the RDP. The "devtools.debugger.log" preference
  * must be set to true as well for this to have any effect.
  */
 makePrefTrackedFlag(exports, "wantVerbose", "devtools.debugger.log.verbose");
 
 /**
+ * Setting the "devtools.debugger.quiet" preference to true will turn off
+ * logging for protocol errors that were caught and handled. These errors can
+ * happen in normal operation when threads shut down.
+ */
+makePrefTrackedFlag(exports, "quiet", "devtools.debugger.quiet");
+
+/**
  * Setting the "devtools.testing" preference to true will toggle on certain
  * behaviors that can differ from the production version of the code. These
  * behaviors typically enable easier testing or enhanced debugging features.
  */
 makePrefTrackedFlag(exports, "testing", "devtools.testing");
--- a/devtools/shared/fronts/targets/target-mixin.js
+++ b/devtools/shared/fronts/targets/target-mixin.js
@@ -7,16 +7,18 @@
 // We are requiring a module from client whereas this module is from shared.
 // This shouldn't happen, but Fronts should rather be part of client anyway.
 // Otherwise gDevTools is only used for local tabs and should propably only
 // used by a subclass, specific to local tabs.
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "getFront", "devtools/shared/protocol", true);
 
+const flags = require("../../flags");
+
 /**
  * A Target represents a debuggable context. It can be a browser tab, a tab on
  * a remote device, like a tab on Firefox for Android. But it can also be an add-on,
  * as well as firefox parent process, or just one of its content process.
  * A Target is related to a given TargetActor, for which we derive this class.
  *
  * Providing a generalized abstraction of a web-page or web-browser (available
  * either locally or remotely) is beyond the scope of this class (and maybe
@@ -546,25 +548,29 @@ function TargetMixin(parentClass) {
         } else if (this.detach && this.actorID) {
           // The client was handed to us, so we are not responsible for closing
           // it. We just need to detach from the tab, if already attached.
           // |detach| may fail if the connection is already dead, so proceed with
           // cleanup directly after this.
           try {
             await this.detach();
           } catch (e) {
-            console.warn(`Error while detaching target: ${e.message}`);
+            if (!flags.quiet) {
+              console.warn(`Error while detaching target: ${e.message}`);
+            }
           }
         }
 
         if (this.threadClient) {
           try {
             await this.threadClient.detach();
           } catch (e) {
-            console.warn(`Error while detaching the thread front: ${e.message}`);
+            if (!flags.quiet) {
+              console.warn(`Error while detaching the thread front: ${e.message}`);
+            }
           }
         }
 
         // Do that very last in order to let a chance to dispatch `detach` requests.
         super.destroy();
 
         this._cleanup();
       })();
--- a/devtools/shared/preferences/devtools-shared.js
+++ b/devtools/shared/preferences/devtools-shared.js
@@ -27,16 +27,17 @@ pref("devtools.debugger.remote-enabled",
 // In local builds, enable the browser toolbox by default
 pref("devtools.chrome.enabled", true, sticky);
 pref("devtools.debugger.remote-enabled", true, sticky);
 #endif
 
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 pref("devtools.debugger.log.verbose", false);
+pref("devtools.debugger.quiet", false);
 
 pref("devtools.debugger.remote-port", 6000);
 pref("devtools.debugger.remote-websocket", false);
 // Force debugger server binding on the loopback interface
 pref("devtools.debugger.force-local", true);
 
 // Limit for intercepted request and response bodies (1 MB)
 // Possible values:
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5167,16 +5167,20 @@ pref("gfx.direct2d.force-enabled", false
 
 // Whether to defer destruction of Direct2D DrawTargets to the paint thread
 // when using OMTP.
 pref("gfx.direct2d.destroy-dt-on-paintthread", true);
 
 pref("gfx.direct3d11.enable-debug-layer", false);
 pref("gfx.direct3d11.break-on-error", false);
 
+// Prefer flipping between two buffers over copying from our back buffer
+// to the OS.
+pref("gfx.direct3d11.use-double-buffering", true);
+
 pref("layers.prefer-opengl", false);
 #endif
 
 // Copy-on-write canvas
 pref("layers.shared-buffer-provider.enabled", true);
 
 // Force all possible layers to be always active layers
 pref("layers.force-active", false);
--- a/toolkit/recordreplay/ipc/Channel.cpp
+++ b/toolkit/recordreplay/ipc/Channel.cpp
@@ -48,29 +48,32 @@ void OpenChannel(base::ProcessId aMiddle
   MOZ_RELEASE_ASSERT(rv >= 0);
 
   *aConnection = ipc::FileDescriptor(connectionFd);
   close(connectionFd);
 }
 
 }  // namespace parent
 
+static void InitializeSimulatedDelayState();
+
 struct HelloMessage {
   int32_t mMagic;
 };
 
 Channel::Channel(size_t aId, bool aMiddlemanRecording,
                  const MessageHandler& aHandler)
     : mId(aId),
       mHandler(aHandler),
       mInitialized(false),
       mConnectionFd(0),
       mFd(0),
       mMessageBuffer(nullptr),
-      mMessageBytes(0) {
+      mMessageBytes(0),
+      mSimulateDelays(false) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (IsRecordingOrReplaying()) {
     MOZ_RELEASE_ASSERT(AreThreadEventsPassedThrough());
 
     mFd = socket(AF_UNIX, SOCK_STREAM, 0);
     MOZ_RELEASE_ASSERT(mFd > 0);
 
@@ -96,16 +99,22 @@ Channel::Channel(size_t aId, bool aMiddl
       MOZ_RELEASE_ASSERT(connection.IsValid());
     }
 
     mConnectionFd = connection.ClonePlatformHandle().release();
     int rv = listen(mConnectionFd, 1);
     MOZ_RELEASE_ASSERT(rv >= 0);
   }
 
+  // Simulate message delays in channels used to communicate with a replaying
+  // process.
+  mSimulateDelays = IsMiddleman() ? !aMiddlemanRecording : IsReplaying();
+
+  InitializeSimulatedDelayState();
+
   Thread::SpawnNonRecordedThread(ThreadMain, this);
 }
 
 /* static */
 void Channel::ThreadMain(void* aChannelArg) {
   Channel* channel = (Channel*)aChannelArg;
 
   static const int32_t MagicValue = 0x914522b9;
@@ -124,47 +133,126 @@ void Channel::ThreadMain(void* aChannelA
 
     HelloMessage msg;
     msg.mMagic = MagicValue;
 
     int rv = HANDLE_EINTR(send(channel->mFd, &msg, sizeof(msg), 0));
     MOZ_RELEASE_ASSERT(rv == sizeof(msg));
   }
 
+  channel->mStartTime = channel->mAvailableTime = TimeStamp::Now();
+
   {
     MonitorAutoLock lock(channel->mMonitor);
     channel->mInitialized = true;
     channel->mMonitor.Notify();
   }
 
   while (true) {
     Message::UniquePtr msg = channel->WaitForMessage();
     if (!msg) {
       break;
     }
     channel->mHandler(std::move(msg));
   }
 }
 
-void Channel::SendMessage(const Message& aMsg) {
+// Simulated one way latency between middleman and replaying children, in ms.
+static size_t gSimulatedLatency;
+
+// Simulated bandwidth for data transferred between middleman and replaying
+// children, in bytes/ms.
+static size_t gSimulatedBandwidth;
+
+static size_t LoadEnvValue(const char* aEnv) {
+  const char* value = getenv(aEnv);
+  if (value && value[0]) {
+    int n = atoi(value);
+    return n >= 0 ? n : 0;
+  }
+  return 0;
+}
+
+static void InitializeSimulatedDelayState() {
+  // In preparation for shifting computing resources into the cloud when
+  // debugging a recorded execution (see bug 1547081), we need to be able to
+  // test expected performance when there is a significant distance between the
+  // user's machine (running the UI, middleman, and recording process) and
+  // machines in the cloud (running replaying processes). To assess this
+  // expected performance, the environment variables below can be used to
+  // specify the one-way latency and bandwidth to simulate for connections
+  // between the middleman and replaying processes.
+  //
+  // This simulation is approximate: the bandwidth tracked is per connection
+  // instead of the total across all connections, and network restrictions are
+  // not yet simulated when transferring graphics data.
+  //
+  // If there are multiple channels then we will do this initialization multiple
+  // times, so this needs to be idempotent.
+  gSimulatedLatency = LoadEnvValue("MOZ_RECORD_REPLAY_SIMULATED_LATENCY");
+  gSimulatedBandwidth = LoadEnvValue("MOZ_RECORD_REPLAY_SIMULATED_BANDWIDTH");
+}
+
+static bool MessageSubjectToSimulatedDelay(MessageType aType) {
+  switch (aType) {
+    // Middleman call messages are not subject to delays. When replaying
+    // children are in the cloud they will use a local process to perform
+    // middleman calls.
+    case MessageType::MiddlemanCallResponse:
+    case MessageType::MiddlemanCallRequest:
+    case MessageType::ResetMiddlemanCalls:
+    // Don't call system functions when we're in the process of crashing.
+    case MessageType::BeginFatalError:
+    case MessageType::FatalError:
+      return false;
+    default:
+      return true;
+  }
+}
+
+void Channel::SendMessage(Message&& aMsg) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread() ||
                      aMsg.mType == MessageType::BeginFatalError ||
                      aMsg.mType == MessageType::FatalError ||
                      aMsg.mType == MessageType::MiddlemanCallRequest);
 
   // Block until the channel is initialized.
   if (!mInitialized) {
     MonitorAutoLock lock(mMonitor);
     while (!mInitialized) {
       mMonitor.Wait();
     }
   }
 
   PrintMessage("SendMsg", aMsg);
 
+  if (gSimulatedLatency &&
+      gSimulatedBandwidth &&
+      mSimulateDelays &&
+      MessageSubjectToSimulatedDelay(aMsg.mType)) {
+    AutoEnsurePassThroughThreadEvents pt;
+
+    // Find the time this message will start sending.
+    TimeStamp sendTime = TimeStamp::Now();
+    if (sendTime < mAvailableTime) {
+      sendTime = mAvailableTime;
+    }
+
+    // Find the time spent sending the message over the channel.
+    size_t sendDurationMs = aMsg.mSize / gSimulatedBandwidth;
+    mAvailableTime = sendTime + TimeDuration::FromMilliseconds(sendDurationMs);
+
+    // The receive time of the message is the time the message finishes sending
+    // plus the connection latency.
+    TimeStamp receiveTime =
+      mAvailableTime + TimeDuration::FromMilliseconds(gSimulatedLatency);
+
+    aMsg.mReceiveTime = (receiveTime - mStartTime).ToMilliseconds();
+  }
+
   const char* ptr = (const char*)&aMsg;
   size_t nbytes = aMsg.mSize;
   while (nbytes) {
     int rv = HANDLE_EINTR(send(mFd, ptr, nbytes, 0));
     if (rv < 0) {
       // If the other side of the channel has crashed, don't send the message.
       // Avoid crashing in this process too, so that we don't generate another
       // minidump that masks the original crash.
@@ -222,16 +310,26 @@ Message::UniquePtr Channel::WaitForMessa
   // Remove the message we just received from the incoming buffer.
   size_t remaining = mMessageBytes - messageSize;
   if (remaining) {
     memmove(mMessageBuffer->begin(), &mMessageBuffer->begin()[messageSize],
             remaining);
   }
   mMessageBytes = remaining;
 
+  // If there is a simulated delay on the message, wait until it completes.
+  if (res->mReceiveTime) {
+    TimeStamp receiveTime =
+      mStartTime + TimeDuration::FromMilliseconds(res->mReceiveTime);
+    while (receiveTime > TimeStamp::Now()) {
+      MonitorAutoLock lock(mMonitor);
+      mMonitor.WaitUntil(receiveTime);
+    }
+  }
+
   PrintMessage("RecvMsg", *res);
   return res;
 }
 
 void Channel::PrintMessage(const char* aPrefix, const Message& aMsg) {
   if (!SpewEnabled()) {
     return;
   }
@@ -264,24 +362,24 @@ void Channel::PrintMessage(const char* a
           "Kind %s, Script %d, Offset %d, Frame %d",
           nmsg.mPosition.KindString(), (int)nmsg.mPosition.mScript,
           (int)nmsg.mPosition.mOffset, (int)nmsg.mPosition.mFrameIndex);
       break;
     }
     case MessageType::DebuggerRequest: {
       const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&)aMsg;
       data = NS_ConvertUTF16toUTF8(
-          nsDependentString(nmsg.Buffer(), nmsg.BufferSize()));
+          nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize()));
       break;
     }
     case MessageType::DebuggerResponse: {
       const DebuggerResponseMessage& nmsg =
           (const DebuggerResponseMessage&)aMsg;
       data = NS_ConvertUTF16toUTF8(
-          nsDependentString(nmsg.Buffer(), nmsg.BufferSize()));
+          nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize()));
       break;
     }
     case MessageType::SetIsActive: {
       const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)aMsg;
       data.AppendPrintf("%d", nmsg.mActive);
       break;
     }
     case MessageType::SetSaveCheckpoint: {
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -147,21 +147,26 @@ enum class MessageType {
 #define DefineEnum(Kind) Kind,
   ForEachMessageType(DefineEnum)
 #undef DefineEnum
 };
 
 struct Message {
   MessageType mType;
 
+  // When simulating message delays, the time this message should be received,
+  // relative to when the channel was opened.
+  uint32_t mReceiveTime;
+
   // Total message size, including the header.
   uint32_t mSize;
 
  protected:
-  Message(MessageType aType, uint32_t aSize) : mType(aType), mSize(aSize) {
+  Message(MessageType aType, uint32_t aSize)
+    : mType(aType), mReceiveTime(0), mSize(aSize) {
     MOZ_RELEASE_ASSERT(mSize >= sizeof(*this));
   }
 
  public:
   struct FreePolicy {
     void operator()(Message* msg) { /*free(msg);*/
     }
   };
@@ -462,16 +467,26 @@ class Channel {
   // Buffer for message data received from the other side of the channel.
   typedef InfallibleVector<char, 0, AllocPolicy<MemoryKind::Generic>>
       MessageBuffer;
   MessageBuffer* mMessageBuffer;
 
   // The number of bytes of data already in the message buffer.
   size_t mMessageBytes;
 
+  // Whether this channel is subject to message delays during simulation.
+  bool mSimulateDelays;
+
+  // The time this channel was opened, for use in simulating message delays.
+  TimeStamp mStartTime;
+
+  // When simulating message delays, the time at which old messages will have
+  // finished sending and new messages may be sent.
+  TimeStamp mAvailableTime;
+
   // If spew is enabled, print a message and associated info to stderr.
   void PrintMessage(const char* aPrefix, const Message& aMsg);
 
   // Block until a complete message is received from the other side of the
   // channel.
   Message::UniquePtr WaitForMessage();
 
   // Main routine for the channel's thread.
@@ -481,17 +496,17 @@ class Channel {
   // Initialize this channel, connect to the other side, and spin up a thread
   // to process incoming messages by calling aHandler.
   Channel(size_t aId, bool aMiddlemanRecording, const MessageHandler& aHandler);
 
   size_t GetId() { return mId; }
 
   // Send a message to the other side of the channel. This must be called on
   // the main thread, except for fatal error messages.
-  void SendMessage(const Message& aMsg);
+  void SendMessage(Message&& aMsg);
 };
 
 // Command line option used to specify the middleman pid for a child process.
 static const char* gMiddlemanPidOption = "-middlemanPid";
 
 // Command line option used to specify the channel ID for a child process.
 static const char* gChannelIDOption = "-recordReplayChannelID";
 
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -377,17 +377,17 @@ void ReportFatalError(const Maybe<Minidu
   char msgBuf[4096];
   size_t header = sizeof(FatalErrorMessage);
   size_t len = std::min(strlen(buf) + 1, sizeof(msgBuf) - header);
   FatalErrorMessage* msg = new (msgBuf) FatalErrorMessage(header + len);
   memcpy(&msgBuf[header], buf, len);
   msgBuf[sizeof(msgBuf) - 1] = 0;
 
   // Don't take the message lock when sending this, to avoid touching the heap.
-  gChannel->SendMessage(*msg);
+  gChannel->SendMessage(std::move(*msg));
 
   DirectPrint("***** Fatal Record/Replay Error *****\n");
   DirectPrint(buf);
   DirectPrint("\n");
 
   // Block until we get a terminate message and die.
   Thread::WaitForeverNoIdle();
 }
@@ -672,33 +672,33 @@ void HitExecutionPoint(const js::Executi
 
 ///////////////////////////////////////////////////////////////////////////////
 // Message Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 void RespondToRequest(const js::CharBuffer& aBuffer) {
   DebuggerResponseMessage* msg =
       DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
-  gChannel->SendMessage(*msg);
+  gChannel->SendMessage(std::move(*msg));
   free(msg);
 }
 
 void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
                               InfallibleVector<char>* aOutputData) {
   AutoPassThroughThreadEvents pt;
   MonitorAutoLock lock(*gMonitor);
 
   while (gWaitingForCallResponse) {
     gMonitor->Wait();
   }
   gWaitingForCallResponse = true;
 
   MiddlemanCallRequestMessage* msg =
       MiddlemanCallRequestMessage::New(aInputData, aInputSize);
-  gChannel->SendMessage(*msg);
+  gChannel->SendMessage(std::move(*msg));
   free(msg);
 
   while (!gCallResponseMessage) {
     gMonitor->Wait();
   }
 
   aOutputData->append(gCallResponseMessage->BinaryData(),
                       gCallResponseMessage->BinaryDataSize());
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -94,28 +94,28 @@ void ChildProcessInfo::OnIncomingMessage
     case MessageType::MiddlemanCallRequest: {
       const MiddlemanCallRequestMessage& nmsg =
           static_cast<const MiddlemanCallRequestMessage&>(aMsg);
       InfallibleVector<char> outputData;
       ProcessMiddlemanCall(GetId(), nmsg.BinaryData(), nmsg.BinaryDataSize(),
                            &outputData);
       Message::UniquePtr response(MiddlemanCallResponseMessage::New(
           outputData.begin(), outputData.length()));
-      SendMessage(*response);
+      SendMessage(std::move(*response));
       break;
     }
     case MessageType::ResetMiddlemanCalls:
       ResetMiddlemanCalls(GetId());
       break;
     default:
       break;
   }
 }
 
-void ChildProcessInfo::SendMessage(const Message& aMsg) {
+void ChildProcessInfo::SendMessage(Message&& aMsg) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // Update paused state.
   MOZ_RELEASE_ASSERT(IsPaused() || aMsg.CanBeSentWhileUnpaused());
   switch (aMsg.mType) {
     case MessageType::Resume:
     case MessageType::RestoreCheckpoint:
     case MessageType::RunToPoint:
@@ -123,17 +123,17 @@ void ChildProcessInfo::SendMessage(const
     case MessageType::FlushRecording:
       mPaused = false;
       break;
     default:
       break;
   }
 
   mLastMessageTime = TimeStamp::Now();
-  mChannel->SendMessage(aMsg);
+  mChannel->SendMessage(std::move(aMsg));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Subprocess Management
 ///////////////////////////////////////////////////////////////////////////////
 
 ipc::GeckoChildProcessHost* gRecordingProcess;
 
@@ -203,17 +203,17 @@ void ChildProcessInfo::LaunchSubprocess(
 
   SendGraphicsMemoryToChild();
 
   // The child should send us a HitExecutionPoint message with an invalid point
   // to pause.
   WaitUntilPaused();
 
   MOZ_RELEASE_ASSERT(gIntroductionMessage);
-  SendMessage(*gIntroductionMessage);
+  SendMessage(std::move(*gIntroductionMessage));
 
   // Always save the first checkpoint in replaying child processes.
   if (!IsRecording()) {
     SendMessage(SetSaveCheckpointMessage(CheckpointId::First, true));
   }
 
   // Always run forward to the first checkpoint after the primordial one.
   SendMessage(ResumeMessage(/* aForward = */ true));
--- a/toolkit/recordreplay/ipc/JSControl.cpp
+++ b/toolkit/recordreplay/ipc/JSControl.cpp
@@ -515,17 +515,17 @@ static bool Middleman_SendDebuggerReques
 
   CharBuffer responseBuffer;
 
   MOZ_RELEASE_ASSERT(!gResponseBuffer);
   gResponseBuffer = &responseBuffer;
 
   DebuggerRequestMessage* msg = DebuggerRequestMessage::New(
       requestBuffer.begin(), requestBuffer.length());
-  child->SendMessage(*msg);
+  child->SendMessage(std::move(*msg));
   free(msg);
 
   // Wait for the child to respond to the query.
   child->WaitUntilPaused();
   MOZ_RELEASE_ASSERT(gResponseBuffer == &responseBuffer);
   MOZ_RELEASE_ASSERT(gResponseBuffer->length() != 0);
   gResponseBuffer = nullptr;
 
--- a/toolkit/recordreplay/ipc/ParentInternal.h
+++ b/toolkit/recordreplay/ipc/ParentInternal.h
@@ -154,23 +154,18 @@ class ChildProcessInfo {
   explicit ChildProcessInfo(
       const Maybe<RecordingProcessData>& aRecordingProcessData);
   ~ChildProcessInfo();
 
   size_t GetId() { return mChannel->GetId(); }
   bool IsRecording() { return mRecording; }
   bool IsPaused() { return mPaused; }
 
-  void SendMessage(const Message& aMessage);
-
-  // Recover to the same state as another process.
-  void Recover(ChildProcessInfo* aTargetProcess);
-
-  // Recover to be paused at a checkpoint with no breakpoints set.
-  void RecoverToCheckpoint(size_t aCheckpoint);
+  // Send a message over the underlying channel.
+  void SendMessage(Message&& aMessage);
 
   // Handle incoming messages from this process (and no others) until it pauses.
   // The return value is null if it is already paused, otherwise the message
   // which caused it to pause. In the latter case, OnIncomingMessage will *not*
   // be called with the message.
   Message::UniquePtr WaitUntilPaused();
 
   static void SetIntroductionMessage(IntroductionMessage* aMessage);