Bug 1507359 Part 1 - Change breakpoint state to be a set of positions, r=mccr8.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 14 Nov 2018 15:56:49 -1000
changeset 503366 70a8eb10c67f46134779b3106f74f4353ea37f7a
parent 503357 f77121fb76fc00afa520b03f847c20e8baa88715
child 503367 1c7fc8389e012c987347efefca6b35f3948b742a
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1507359
milestone65.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 1507359 Part 1 - Change breakpoint state to be a set of positions, r=mccr8.
toolkit/recordreplay/ipc/Channel.cpp
toolkit/recordreplay/ipc/Channel.h
toolkit/recordreplay/ipc/ChildIPC.cpp
toolkit/recordreplay/ipc/ChildInternal.h
toolkit/recordreplay/ipc/ChildNavigation.cpp
toolkit/recordreplay/ipc/ChildProcess.cpp
toolkit/recordreplay/ipc/JSControl.cpp
toolkit/recordreplay/ipc/JSControl.h
toolkit/recordreplay/ipc/ParentIPC.cpp
toolkit/recordreplay/ipc/ParentInternal.h
--- a/toolkit/recordreplay/ipc/Channel.cpp
+++ b/toolkit/recordreplay/ipc/Channel.cpp
@@ -249,35 +249,32 @@ Channel::PrintMessage(const char* aPrefi
     data.AppendPrintf("Id %d Endpoint %d Duration %.2f ms",
                       (int) nmsg.mCheckpointId, nmsg.mRecordingEndpoint,
                       nmsg.mDurationMicroseconds / 1000.0);
     break;
   }
   case MessageType::HitBreakpoint: {
     const HitBreakpointMessage& nmsg = (const HitBreakpointMessage&) aMsg;
     data.AppendPrintf("Endpoint %d", nmsg.mRecordingEndpoint);
-    for (size_t i = 0; i < nmsg.NumBreakpoints(); i++) {
-      data.AppendPrintf(" Id %d", nmsg.Breakpoints()[i]);
-    }
     break;
   }
   case MessageType::Resume: {
     const ResumeMessage& nmsg = (const ResumeMessage&) aMsg;
     data.AppendPrintf("Forward %d", nmsg.mForward);
     break;
   }
   case MessageType::RestoreCheckpoint: {
     const RestoreCheckpointMessage& nmsg = (const RestoreCheckpointMessage&) aMsg;
     data.AppendPrintf("Id %d", (int) nmsg.mCheckpoint);
     break;
   }
-  case MessageType::SetBreakpoint: {
-    const SetBreakpointMessage& nmsg = (const SetBreakpointMessage&) aMsg;
-    data.AppendPrintf("Id %d, Kind %s, Script %d, Offset %d, Frame %d",
-                      (int) nmsg.mId, nmsg.mPosition.KindString(), (int) nmsg.mPosition.mScript,
+  case MessageType::AddBreakpoint: {
+    const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&) aMsg;
+    data.AppendPrintf("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()));
     break;
   }
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -69,18 +69,24 @@ namespace recordreplay {
   /* Poke a child that is recording to create an artificial checkpoint, rather than */ \
   /* (potentially) idling indefinitely. This has no effect on a replaying process. */ \
   _Macro(CreateCheckpoint)                                     \
                                                                \
   /* Debugger JSON messages are initially sent from the parent. The child unpauses */ \
   /* after receiving the message and will pause after it sends a DebuggerResponse. */ \
   _Macro(DebuggerRequest)                                      \
                                                                \
-  /* Set or clear a JavaScript breakpoint. */                  \
-  _Macro(SetBreakpoint)                                        \
+  /* Add a breakpoint position to stop at. Because a single entry point is used for */ \
+  /* calling into the ReplayDebugger after pausing, the set of breakpoints is simply */ \
+  /* a set of positions at which the child process should pause and send a HitBreakpoint */ \
+  /* message. */                                               \
+  _Macro(AddBreakpoint)                                        \
+                                                               \
+  /* Clear all installed breakpoints. */                       \
+  _Macro(ClearBreakpoints)                                     \
                                                                \
   /* Unpause the child and play execution either to the next point when a */ \
   /* breakpoint is hit, or to the next checkpoint. Resumption may be either */ \
   /* forward or backward. */                                   \
   _Macro(Resume)                                               \
                                                                \
   /* Rewind to a particular saved checkpoint in the past. */   \
   _Macro(RestoreCheckpoint)                                    \
@@ -275,32 +281,28 @@ struct JSONMessage : public Message
     PodCopy(res->Data<JSONMessage<Type>, char16_t>(), aBuffer, aBufferSize);
     return res;
   }
 };
 
 typedef JSONMessage<MessageType::DebuggerRequest> DebuggerRequestMessage;
 typedef JSONMessage<MessageType::DebuggerResponse> DebuggerResponseMessage;
 
-struct SetBreakpointMessage : public Message
+struct AddBreakpointMessage : public Message
 {
-  // ID of the breakpoint to change.
-  size_t mId;
-
-  // New position of the breakpoint. If this is invalid then the breakpoint is
-  // being cleared.
   js::BreakpointPosition mPosition;
 
-  SetBreakpointMessage(size_t aId, const js::BreakpointPosition& aPosition)
-    : Message(MessageType::SetBreakpoint, sizeof(*this))
-    , mId(aId)
+  explicit AddBreakpointMessage(const js::BreakpointPosition& aPosition)
+    : Message(MessageType::AddBreakpoint, sizeof(*this))
     , mPosition(aPosition)
   {}
 };
 
+typedef EmptyMessage<MessageType::ClearBreakpoints> ClearBreakpointsMessage;
+
 struct ResumeMessage : public Message
 {
   // Whether to travel forwards or backwards.
   bool mForward;
 
   explicit ResumeMessage(bool aForward)
     : Message(MessageType::Resume, sizeof(*this))
     , mForward(aForward)
@@ -416,32 +418,20 @@ struct HitCheckpointMessage : public Mes
     , mDurationMicroseconds(aDurationMicroseconds)
   {}
 };
 
 struct HitBreakpointMessage : public Message
 {
   bool mRecordingEndpoint;
 
-  HitBreakpointMessage(uint32_t aSize, bool aRecordingEndpoint)
-    : Message(MessageType::HitBreakpoint, aSize)
+  explicit HitBreakpointMessage(bool aRecordingEndpoint)
+    : Message(MessageType::HitBreakpoint, sizeof(*this))
     , mRecordingEndpoint(aRecordingEndpoint)
   {}
-
-  const uint32_t* Breakpoints() const { return Data<HitBreakpointMessage, uint32_t>(); }
-  uint32_t NumBreakpoints() const { return DataSize<HitBreakpointMessage, uint32_t>(); }
-
-  static HitBreakpointMessage* New(bool aRecordingEndpoint,
-                                   const uint32_t* aBreakpoints, size_t aNumBreakpoints) {
-    HitBreakpointMessage* res =
-      NewWithData<HitBreakpointMessage, uint32_t>(aNumBreakpoints, aRecordingEndpoint);
-    MOZ_RELEASE_ASSERT(res->NumBreakpoints() == aNumBreakpoints);
-    PodCopy(res->Data<HitBreakpointMessage, uint32_t>(), aBreakpoints, aNumBreakpoints);
-    return res;
-  }
 };
 
 typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints> AlwaysMarkMajorCheckpointsMessage;
 
 template <MessageType Type>
 struct BinaryMessage : public Message
 {
   explicit BinaryMessage(uint32_t aSize)
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -134,23 +134,27 @@ ChannelMessageHandler(Message* aMsg)
   }
   case MessageType::DebuggerRequest: {
     const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&) *aMsg;
     js::CharBuffer* buf = new js::CharBuffer();
     buf->append(nmsg.Buffer(), nmsg.BufferSize());
     PauseMainThreadAndInvokeCallback([=]() { navigation::DebuggerRequest(buf); });
     break;
   }
-  case MessageType::SetBreakpoint: {
-    const SetBreakpointMessage& nmsg = (const SetBreakpointMessage&) *aMsg;
+  case MessageType::AddBreakpoint: {
+    const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&) *aMsg;
     PauseMainThreadAndInvokeCallback([=]() {
-        navigation::SetBreakpoint(nmsg.mId, nmsg.mPosition);
+        navigation::AddBreakpoint(nmsg.mPosition);
       });
     break;
   }
+  case MessageType::ClearBreakpoints: {
+    PauseMainThreadAndInvokeCallback([=]() { navigation::ClearBreakpoints(); });
+    break;
+  }
   case MessageType::Resume: {
     const ResumeMessage& nmsg = (const ResumeMessage&) *aMsg;
     PauseMainThreadAndInvokeCallback([=]() {
         navigation::Resume(nmsg.mForward);
       });
     break;
   }
   case MessageType::RestoreCheckpoint: {
@@ -699,24 +703,21 @@ RespondToRequest(const js::CharBuffer& a
 {
   DebuggerResponseMessage* msg =
     DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
   gChannel->SendMessage(*msg);
   free(msg);
 }
 
 void
-HitBreakpoint(bool aRecordingEndpoint, const uint32_t* aBreakpoints, size_t aNumBreakpoints)
+HitBreakpoint(bool aRecordingEndpoint)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  HitBreakpointMessage* msg =
-    HitBreakpointMessage::New(aRecordingEndpoint, aBreakpoints, aNumBreakpoints);
   PauseMainThreadAndInvokeCallback([=]() {
-      gChannel->SendMessage(*msg);
-      free(msg);
+      gChannel->SendMessage(HitBreakpointMessage(aRecordingEndpoint));
     });
 }
 
 void
 SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
                          InfallibleVector<char>* aOutputData)
 {
   AutoPassThroughThreadEvents pt;
--- a/toolkit/recordreplay/ipc/ChildInternal.h
+++ b/toolkit/recordreplay/ipc/ChildInternal.h
@@ -39,17 +39,18 @@ js::ExecutionPoint GetRecordingEndpoint(
 // the recording file as it has been flushed.
 void SetRecordingEndpoint(size_t aIndex, const js::ExecutionPoint& aEndpoint);
 
 // Save temporary checkpoints at all opportunities during navigation.
 void AlwaysSaveTemporaryCheckpoints();
 
 // Process incoming requests from the middleman.
 void DebuggerRequest(js::CharBuffer* aBuffer);
-void SetBreakpoint(size_t aId, const js::BreakpointPosition& aPosition);
+void AddBreakpoint(const js::BreakpointPosition& aPosition);
+void ClearBreakpoints();
 void Resume(bool aForward);
 void RestoreCheckpoint(size_t aId);
 void RunToPoint(const js::ExecutionPoint& aPoint);
 
 // Attempt to diverge from the recording so that new recorded events cause
 // the process to rewind. Returns false if the divergence failed: either we
 // can't rewind, or already diverged here and then had an unhandled divergence.
 bool MaybeDivergeFromRecording();
@@ -81,17 +82,17 @@ size_t LastNormalCheckpoint();
 
 } // namespace navigation
 
 namespace child {
 
 // IPC activity that can be triggered by navigation.
 void RespondToRequest(const js::CharBuffer& aBuffer);
 void HitCheckpoint(size_t aId, bool aRecordingEndpoint);
-void HitBreakpoint(bool aRecordingEndpoint, const uint32_t* aBreakpoints, size_t aNumBreakpoints);
+void HitBreakpoint(bool aRecordingEndpoint);
 
 // Optional information about a crash that occurred. If not provided to
 // ReportFatalError, the current thread will be treated as crashed.
 struct MinidumpInfo
 {
   int mExceptionType;
   int mCode;
   int mSubcode;
--- a/toolkit/recordreplay/ipc/ChildNavigation.cpp
+++ b/toolkit/recordreplay/ipc/ChildNavigation.cpp
@@ -141,18 +141,16 @@ struct RequestInfo
     : mUnhandledDivergence(o.mUnhandledDivergence)
   {
     mRequestBuffer.append(o.mRequestBuffer.begin(), o.mRequestBuffer.length());
     mResponseBuffer.append(o.mResponseBuffer.begin(), o.mResponseBuffer.length());
   }
 };
 typedef InfallibleVector<RequestInfo, 4, UntrackedAllocPolicy> UntrackedRequestVector;
 
-typedef InfallibleVector<uint32_t> BreakpointVector;
-
 // Phase when the replaying process is paused.
 class PausedPhase final : public NavigationPhase
 {
   // Location of the pause.
   ExecutionPoint mPoint;
 
   // Whether we are paused at the end of the recording.
   bool mRecordingEndpoint;
@@ -172,17 +170,16 @@ class PausedPhase final : public Navigat
   // recording divergence, and haven't finished rehandling old requests.
   bool mRecoveringFromDivergence;
 
   // Set when we were told to resume forward and need to clean up our state.
   bool mResumeForward;
 
 public:
   void Enter(const ExecutionPoint& aPoint,
-             const BreakpointVector& aBreakpoints = BreakpointVector(),
              bool aRewind = false, bool aRecordingEndpoint = false);
 
   void ToString(nsAutoCString& aStr) override {
     aStr.AppendPrintf("Paused RecoveringFromDivergence %d", mRecoveringFromDivergence);
   }
 
   void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
   void PositionHit(const ExecutionPoint& aPoint) override;
@@ -315,26 +312,19 @@ class NavigationState
   // The last checkpoint we ran forward or rewound to.
   CheckpointId mLastCheckpoint;
 
   // The locations of all temporary checkpoints we have saved. Temporary
   // checkpoints are taken immediately prior to reaching these points.
   InfallibleVector<ExecutionPoint, 0, UntrackedAllocPolicy> mTemporaryCheckpoints;
 
 public:
-  // All the currently installed breakpoints, indexed by their ID.
+  // All the currently installed breakpoints.
   InfallibleVector<BreakpointPosition, 4, UntrackedAllocPolicy> mBreakpoints;
 
-  BreakpointPosition& GetBreakpoint(size_t id) {
-    while (id >= mBreakpoints.length()) {
-      mBreakpoints.emplaceBack();
-    }
-    return mBreakpoints[id];
-  }
-
   CheckpointId LastCheckpoint() {
     return mLastCheckpoint;
   }
 
   // The current phase of the process.
   NavigationPhase* mPhase;
 
   void SetPhase(NavigationPhase* phase) {
@@ -486,63 +476,47 @@ public:
   ExecutionPoint CheckpointExecutionPoint(size_t aCheckpoint) {
     MOZ_RELEASE_ASSERT(aCheckpoint < mCheckpointProgress.length());
     return ExecutionPoint(aCheckpoint, mCheckpointProgress[aCheckpoint]);
   }
 };
 
 static NavigationState* gNavigation;
 
-static void
-GetAllBreakpointHits(const ExecutionPoint& aPoint, BreakpointVector& aHitBreakpoints)
-{
-  MOZ_RELEASE_ASSERT(aPoint.HasPosition());
-  for (size_t id = 0; id < gNavigation->mBreakpoints.length(); id++) {
-    const BreakpointPosition& breakpoint = gNavigation->mBreakpoints[id];
-    if (breakpoint.IsValid() && breakpoint.Subsumes(aPoint.mPosition)) {
-      aHitBreakpoints.append(id);
-    }
-  }
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Paused Phase
 ///////////////////////////////////////////////////////////////////////////////
 
 static bool
 ThisProcessCanRewind()
 {
   return HasSavedCheckpoint();
 }
 
 void
-PausedPhase::Enter(const ExecutionPoint& aPoint, const BreakpointVector& aBreakpoints,
-                   bool aRewind, bool aRecordingEndpoint)
+PausedPhase::Enter(const ExecutionPoint& aPoint, bool aRewind, bool aRecordingEndpoint)
 {
   mPoint = aPoint;
   mRecordingEndpoint = aRecordingEndpoint;
   mRequests.clear();
   mRequestIndex = 0;
   mSavedTemporaryCheckpoint = false;
   mRecoveringFromDivergence = false;
   mResumeForward = false;
 
   gNavigation->SetPhase(this);
 
-  // Breakpoints will never be hit if we are at a checkpoint.
-  MOZ_RELEASE_ASSERT(aPoint.HasPosition() || aBreakpoints.empty());
-
   if (aRewind) {
     MOZ_RELEASE_ASSERT(!aPoint.HasPosition());
     RestoreCheckpointAndResume(CheckpointId(aPoint.mCheckpoint));
     Unreachable();
   }
 
   if (aPoint.HasPosition()) {
-    child::HitBreakpoint(aRecordingEndpoint, aBreakpoints.begin(), aBreakpoints.length());
+    child::HitBreakpoint(aRecordingEndpoint);
   } else {
     child::HitCheckpoint(aPoint.mCheckpoint, aRecordingEndpoint);
   }
 }
 
 void
 PausedPhase::AfterCheckpoint(const CheckpointId& aCheckpoint)
 {
@@ -608,17 +582,17 @@ PausedPhase::Resume(bool aForward)
   Unreachable();
 }
 
 void
 PausedPhase::RestoreCheckpoint(size_t aCheckpoint)
 {
   ExecutionPoint target = gNavigation->CheckpointExecutionPoint(aCheckpoint);
   bool rewind = target != mPoint;
-  Enter(target, BreakpointVector(), rewind, /* aRecordingEndpoint = */ false);
+  Enter(target, rewind, /* aRecordingEndpoint = */ false);
 }
 
 void
 PausedPhase::RunToPoint(const ExecutionPoint& aTarget)
 {
   // This may only be used when we are paused at a normal checkpoint.
   MOZ_RELEASE_ASSERT(!mPoint.HasPosition());
   size_t checkpoint = mPoint.mCheckpoint;
@@ -803,54 +777,52 @@ void
 ForwardPhase::Enter(const ExecutionPoint& aPoint)
 {
   mPoint = aPoint;
 
   gNavigation->SetPhase(this);
 
   // Install handlers for all breakpoints.
   for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    if (breakpoint.IsValid()) {
-      js::EnsurePositionHandler(breakpoint);
-    }
+    js::EnsurePositionHandler(breakpoint);
   }
 
   ResumeExecution();
 }
 
 void
 ForwardPhase::AfterCheckpoint(const CheckpointId& aCheckpoint)
 {
   MOZ_RELEASE_ASSERT(!aCheckpoint.mTemporary &&
                      aCheckpoint.mNormal == mPoint.mCheckpoint + 1);
   gNavigation->mPausedPhase.Enter(gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal));
 }
 
 void
 ForwardPhase::PositionHit(const ExecutionPoint& aPoint)
 {
-  BreakpointVector hitBreakpoints;
-  GetAllBreakpointHits(aPoint, hitBreakpoints);
+  bool hitBreakpoint = false;
+  for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
+    if (breakpoint.Subsumes(aPoint.mPosition)) {
+      hitBreakpoint = true;
+    }
+  }
 
-  if (!hitBreakpoints.empty()) {
-    gNavigation->mPausedPhase.Enter(aPoint, hitBreakpoints);
+  if (hitBreakpoint) {
+    gNavigation->mPausedPhase.Enter(aPoint);
   }
 }
 
 void
 ForwardPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint)
 {
   nsAutoCString str;
   ExecutionPointToString(aPoint, str);
 
-  // Use an empty vector even if there are breakpoints here. If we started
-  // running forward from aPoint and immediately hit the recording endpoint,
-  // we don't want to hit the breakpoints again.
-  gNavigation->mPausedPhase.Enter(aPoint, BreakpointVector(),
-                                  /* aRewind = */ false, /* aRecordingEndpoint = */ true);
+  gNavigation->mPausedPhase.Enter(aPoint, /* aRewind = */ false, /* aRecordingEndpoint = */ true);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // ReachBreakpointPhase
 ///////////////////////////////////////////////////////////////////////////////
 
 void
 ReachBreakpointPhase::Enter(const CheckpointId& aStart,
@@ -921,19 +893,17 @@ ReachBreakpointPhase::PositionHit(const 
         // We just restored the checkpoint, and could be in any phase.
         gNavigation->PositionHit(aPoint);
         return;
       }
     }
   }
 
   if (mPoint == aPoint) {
-    BreakpointVector hitBreakpoints;
-    GetAllBreakpointHits(aPoint, hitBreakpoints);
-    gNavigation->mPausedPhase.Enter(aPoint, hitBreakpoints);
+    gNavigation->mPausedPhase.Enter(aPoint);
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // FindLastHitPhase
 ///////////////////////////////////////////////////////////////////////////////
 
 void
@@ -1027,19 +997,16 @@ FindLastHitPhase::FindTrackedPosition(co
 }
 
 void
 FindLastHitPhase::OnRegionEnd()
 {
   // Find the point of the last hit which coincides with a breakpoint.
   Maybe<TrackedPosition> lastBreakpoint;
   for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    if (!breakpoint.IsValid()) {
-      continue;
-    }
     const TrackedPosition& tracked = FindTrackedPosition(breakpoint);
     if (tracked.mLastHit.HasPosition() &&
         (lastBreakpoint.isNothing() ||
          lastBreakpoint.ref().mLastHitCount < tracked.mLastHitCount))
     {
       lastBreakpoint = Some(tracked);
     }
   }
@@ -1059,17 +1026,17 @@ FindLastHitPhase::OnRegionEnd()
         // The last temporary checkpoint may be at the same execution point as
         // the last normal checkpoint, if it was created while handling
         // debugger requests there.
       }
     }
 
     // Rewind to the last normal checkpoint and pause.
     gNavigation->mPausedPhase.Enter(gNavigation->CheckpointExecutionPoint(mStart.mNormal),
-                                    BreakpointVector(), /* aRewind = */ true);
+                                    /* aRewind = */ true);
     Unreachable();
   }
 
   // When running backwards, we don't want to place temporary checkpoints at
   // the breakpoint where we are going to stop at. If the user continues
   // rewinding then we will just have to discard the checkpoint and waste the
   // work we did in saving it.
   //
@@ -1144,19 +1111,27 @@ LastNormalCheckpoint()
 
 void
 DebuggerRequest(js::CharBuffer* aRequestBuffer)
 {
   gNavigation->HandleDebuggerRequest(aRequestBuffer);
 }
 
 void
-SetBreakpoint(size_t aId, const BreakpointPosition& aPosition)
+AddBreakpoint(const BreakpointPosition& aPosition)
 {
-  gNavigation->GetBreakpoint(aId) = aPosition;
+  gNavigation->mBreakpoints.append(aPosition);
+}
+
+void
+ClearBreakpoints()
+{
+  if (gNavigation) {
+    gNavigation->mBreakpoints.clear();
+  }
 }
 
 void
 Resume(bool aForward)
 {
   // For the primordial resume sent at startup, the navigation state will not
   // have been initialized yet.
   if (!gNavigation) {
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -105,72 +105,24 @@ ChildProcessInfo::IsPausedAtRecordingEnd
   }
   if (mPausedMessage->mType == MessageType::HitBreakpoint) {
     return static_cast<HitBreakpointMessage*>(mPausedMessage)->mRecordingEndpoint;
   }
   return false;
 }
 
 void
-ChildProcessInfo::GetInstalledBreakpoints(Vector<SetBreakpointMessage*>& aBreakpoints)
-{
-  for (Message* msg : mMessages) {
-    if (msg->mType == MessageType::SetBreakpoint) {
-      SetBreakpointMessage* nmsg = static_cast<SetBreakpointMessage*>(msg);
-      for (SetBreakpointMessage*& existing : aBreakpoints) {
-        if (existing->mId == nmsg->mId) {
-          aBreakpoints.erase(&existing);
-          break;
-        }
-      }
-      if (nmsg->mPosition.IsValid()) {
-        if (!aBreakpoints.append(nmsg)) {
-          MOZ_CRASH("OOM");
-        }
-      }
-    }
-  }
-}
-
-bool
-ChildProcessInfo::IsPausedAtMatchingBreakpoint(const BreakpointFilter& aFilter)
+ChildProcessInfo::GetInstalledBreakpoints(InfallibleVector<AddBreakpointMessage*>& aBreakpoints)
 {
-  if (!IsPaused() || mPausedMessage->mType != MessageType::HitBreakpoint) {
-    return false;
-  }
-
-  Vector<SetBreakpointMessage*> installed;
-  GetInstalledBreakpoints(installed);
-
-  HitBreakpointMessage* npaused = static_cast<HitBreakpointMessage*>(mPausedMessage);
-  for (size_t i = 0; i < npaused->NumBreakpoints(); i++) {
-    uint32_t breakpointId = npaused->Breakpoints()[i];
-
-    // Note: this test isn't quite right if new breakpoints have been installed
-    // since the child paused, though this does not affect current callers.
-    for (SetBreakpointMessage* msg : installed) {
-      if (msg->mId == breakpointId && aFilter(msg->mPosition.mKind)) {
-        return true;
-      }
-    }
-  }
-
-  return false;
-}
-
-void
-ChildProcessInfo::GetMatchingInstalledBreakpoints(const BreakpointFilter& aFilter,
-                                                  Vector<uint32_t>& aBreakpointIds)
-{
-  Vector<SetBreakpointMessage*> installed;
-  GetInstalledBreakpoints(installed);
-
-  for (SetBreakpointMessage* msg : installed) {
-    if (aFilter(msg->mPosition.mKind) && !aBreakpointIds.append(msg->mId)) {
-      MOZ_CRASH("OOM");
+  MOZ_RELEASE_ASSERT(aBreakpoints.empty());
+  for (Message* msg : mMessages) {
+    if (msg->mType == MessageType::AddBreakpoint) {
+      aBreakpoints.append(static_cast<AddBreakpointMessage*>(msg));
+    } else if (msg->mType == MessageType::ClearBreakpoints) {
+      aBreakpoints.clear();
     }
   }
 }
 
 void
 ChildProcessInfo::AddMajorCheckpoint(size_t aId)
 {
   // Major checkpoints should be listed in order.
@@ -235,34 +187,28 @@ ChildProcessInfo::OnIncomingMessage(size
     break;
   }
 
   if (aMsg.mType == MessageType::HitCheckpoint) {
     const HitCheckpointMessage& nmsg = static_cast<const HitCheckpointMessage&>(aMsg);
     mLastCheckpoint = nmsg.mCheckpointId;
 
     // All messages sent since the last checkpoint are now obsolete, except
-    // SetBreakpoint messages.
+    // those which establish the set of installed breakpoints.
     InfallibleVector<Message*> newMessages;
     for (Message* msg : mMessages) {
-      if (msg->mType == MessageType::SetBreakpoint) {
-        // Look for an older SetBreakpoint on the same ID to overwrite.
-        bool found = false;
-        for (Message*& older : newMessages) {
-          if (static_cast<SetBreakpointMessage*>(msg)->mId ==
-              static_cast<SetBreakpointMessage*>(older)->mId) {
-            free(older);
-            older = msg;
-            found = true;
+      if (msg->mType == MessageType::AddBreakpoint) {
+        newMessages.append(msg);
+      } else {
+        if (msg->mType == MessageType::ClearBreakpoints) {
+          for (Message* existing : newMessages) {
+            free(existing);
           }
+          newMessages.clear();
         }
-        if (!found) {
-          newMessages.emplaceBack(msg);
-        }
-      } else {
         free(msg);
       }
     }
     mMessages = std::move(newMessages);
   }
 
   // The primordial HitCheckpoint messages is not forwarded to the role, as it
   // has not been initialized yet.
@@ -295,17 +241,18 @@ ChildProcessInfo::SendMessage(const Mess
   }
 
   // Keep track of messages which affect the child's behavior.
   switch (aMsg.mType) {
   case MessageType::Resume:
   case MessageType::RestoreCheckpoint:
   case MessageType::RunToPoint:
   case MessageType::DebuggerRequest:
-  case MessageType::SetBreakpoint:
+  case MessageType::AddBreakpoint:
+  case MessageType::ClearBreakpoints:
     mMessages.emplaceBack(aMsg.Clone());
     break;
   default:
     break;
   }
 
   // Keep track of the checkpoints the process will save.
   if (aMsg.mType == MessageType::SetSaveCheckpoint) {
@@ -333,23 +280,20 @@ ChildProcessInfo::Recover(bool aPaused, 
 
   SendMessageRaw(SetIsActiveMessage(false));
 
   size_t mostRecentCheckpoint = MostRecentCheckpoint();
   bool pausedAtCheckpoint = IsPausedAtCheckpoint();
 
   // Clear out all messages that have been sent to this process.
   for (Message* msg : mMessages) {
-    if (msg->mType == MessageType::SetBreakpoint) {
-      SetBreakpointMessage* nmsg = static_cast<SetBreakpointMessage*>(msg);
-      SendMessageRaw(SetBreakpointMessage(nmsg->mId, js::BreakpointPosition()));
-    }
     free(msg);
   }
   mMessages.clear();
+  SendMessageRaw(ClearBreakpointsMessage());
 
   mPaused = aPaused;
   mPausedMessage = aPausedMessage;
   mLastCheckpoint = aLastCheckpoint;
   for (size_t i = 0; i < aNumMessages; i++) {
     mMessages.append(aMessages[i]->Clone());
   }
 
@@ -445,19 +389,20 @@ ChildProcessInfo::SendNextRecoveryMessag
     if (mNumRecoveredMessages == mMessages.length()) {
       MOZ_RELEASE_ASSERT(IsPaused());
       mRecoveryStage = RecoveryStage::None;
       return;
     }
     msg = mMessages[mNumRecoveredMessages++];
     SendMessageRaw(*msg);
 
-    // If we just sent a SetBreakpoint message then the child process is still
-    // paused, so keep sending more messages.
-  } while (msg->mType == MessageType::SetBreakpoint);
+    // Messages operating on breakpoints preserve the paused state of the
+    // child, so keep sending more messages.
+  } while (msg->mType == MessageType::AddBreakpoint ||
+           msg->mType == MessageType::ClearBreakpoints);
 
   // If we have sent all messages and are in an unpaused state, we are done
   // recovering.
   if (mNumRecoveredMessages == mMessages.length() && !IsPaused()) {
     mRecoveryStage = RecoveryStage::None;
   }
 }
 
--- a/toolkit/recordreplay/ipc/JSControl.cpp
+++ b/toolkit/recordreplay/ipc/JSControl.cpp
@@ -293,103 +293,43 @@ Middleman_SendRequest(JSContext* aCx, un
   }
 
   CharBuffer responseBuffer;
   parent::SendRequest(requestBuffer, &responseBuffer);
 
   return JS_ParseJSON(aCx, responseBuffer.begin(), responseBuffer.length(), args.rval());
 }
 
-struct InstalledBreakpoint
-{
-  PersistentRootedObject mHandler;
-  BreakpointPosition mPosition;
-
-  InstalledBreakpoint(JSContext* aCx, JSObject* aHandler, const BreakpointPosition& aPosition)
-    : mHandler(aCx, aHandler), mPosition(aPosition)
-  {}
-};
-static StaticInfallibleVector<InstalledBreakpoint*> gBreakpoints;
-
 static bool
-Middleman_SetBreakpoint(JSContext* aCx, unsigned aArgc, Value* aVp)
+Middleman_AddBreakpoint(JSContext* aCx, unsigned aArgc, Value* aVp)
 {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
-  RootedObject handler(aCx, NonNullObject(aCx, args.get(0)));
-  RootedObject positionObject(aCx, NonNullObject(aCx, args.get(1)));
-  if (!handler || !positionObject) {
-    return false;
-  }
-
-  handler = ::js::CheckedUnwrap(handler);
-  if (!handler) {
-    ::js::ReportAccessDenied(aCx);
+  RootedObject positionObject(aCx, NonNullObject(aCx, args.get(0)));
+  if (!positionObject) {
     return false;
   }
 
   BreakpointPosition position;
   if (!position.Decode(aCx, positionObject)) {
     return false;
   }
 
-  size_t breakpointId;
-  for (breakpointId = 0; breakpointId < gBreakpoints.length(); breakpointId++) {
-    if (!gBreakpoints[breakpointId]) {
-      break;
-    }
-  }
-  if (breakpointId == gBreakpoints.length()) {
-    gBreakpoints.append(nullptr);
-  }
+  parent::AddBreakpoint(position);
 
-  gBreakpoints[breakpointId] = new InstalledBreakpoint(aCx, handler, position);
-
-  parent::SetBreakpoint(breakpointId, position);
-
-  args.rval().setInt32(breakpointId);
+  args.rval().setUndefined();
   return true;
 }
 
-bool
-HitBreakpoint(JSContext* aCx, size_t aId)
-{
-  InstalledBreakpoint* breakpoint = gBreakpoints[aId];
-  MOZ_RELEASE_ASSERT(breakpoint);
-
-  JSAutoRealm ar(aCx, breakpoint->mHandler);
-
-  RootedValue handlerValue(aCx, ObjectValue(*breakpoint->mHandler));
-  RootedValue rval(aCx);
-  return JS_CallFunctionValue(aCx, nullptr, handlerValue,
-                              HandleValueArray::empty(), &rval)
-      // The replaying process will resume after this hook returns, if it
-      // hasn't already been explicitly resumed.
-      && InvalidateReplayDebuggersAfterUnpause(aCx);
-}
-
 /* static */ bool
-Middleman_ClearBreakpoint(JSContext* aCx, unsigned aArgc, Value* aVp)
+Middleman_ClearBreakpoints(JSContext* aCx, unsigned aArgc, Value* aVp)
 {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
-  if (!args.get(0).isNumber()) {
-    JS_ReportErrorASCII(aCx, "Bad breakpoint ID");
-    return false;
-  }
 
-  size_t breakpointId = (size_t) args.get(0).toNumber();
-  if (breakpointId >= gBreakpoints.length() || !gBreakpoints[breakpointId]) {
-    JS_ReportErrorASCII(aCx, "Bad breakpoint ID");
-    return false;
-  }
-
-  delete gBreakpoints[breakpointId];
-  gBreakpoints[breakpointId] = nullptr;
-
-  parent::SetBreakpoint(breakpointId, BreakpointPosition());
+  parent::ClearBreakpoints();
 
   args.rval().setUndefined();
   return true;
 }
 
 static bool
 Middleman_MaybeSwitchToReplayingChild(JSContext* aCx, unsigned aArgc, Value* aVp)
 {
@@ -987,18 +927,18 @@ RecordReplay_Dump(JSContext* aCx, unsign
 
 static const JSFunctionSpec gMiddlemanMethods[] = {
   JS_FN("registerReplayDebugger", Middleman_RegisterReplayDebugger, 1, 0),
   JS_FN("canRewind", Middleman_CanRewind, 0, 0),
   JS_FN("resume", Middleman_Resume, 1, 0),
   JS_FN("timeWarp", Middleman_TimeWarp, 1, 0),
   JS_FN("pause", Middleman_Pause, 0, 0),
   JS_FN("sendRequest", Middleman_SendRequest, 1, 0),
-  JS_FN("setBreakpoint", Middleman_SetBreakpoint, 2, 0),
-  JS_FN("clearBreakpoint", Middleman_ClearBreakpoint, 1, 0),
+  JS_FN("addBreakpoint", Middleman_AddBreakpoint, 1, 0),
+  JS_FN("clearBreakpoints", Middleman_ClearBreakpoints, 0, 0),
   JS_FN("maybeSwitchToReplayingChild", Middleman_MaybeSwitchToReplayingChild, 0, 0),
   JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0),
   JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0),
   JS_FN("childIsRecording", Middleman_ChildIsRecording, 0, 0),
   JS_FS_END
 };
 
 static const JSFunctionSpec gRecordReplayMethods[] = {
--- a/toolkit/recordreplay/ipc/JSControl.h
+++ b/toolkit/recordreplay/ipc/JSControl.h
@@ -188,19 +188,16 @@ struct ExecutionPoint
 
   JSObject* Encode(JSContext* aCx) const;
   bool Decode(JSContext* aCx, JS::HandleObject aObject);
 };
 
 // Buffer type used for encoding object data.
 typedef InfallibleVector<char16_t> CharBuffer;
 
-// Called in the middleman when a breakpoint with the specified id has been hit.
-bool HitBreakpoint(JSContext* aCx, size_t id);
-
 // Set up the JS sandbox in the current recording/replaying process and load
 // its target script.
 void SetupDevtoolsSandbox();
 
 // The following hooks are used in the recording/replaying process to
 // call methods defined by the JS sandbox.
 
 // Handle an incoming request from the middleman.
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -593,19 +593,19 @@ SwitchActiveChild(ChildProcessInfo* aChi
 {
   MOZ_RELEASE_ASSERT(aChild != gActiveChild);
   ChildProcessInfo* oldActiveChild = gActiveChild;
   aChild->WaitUntilPaused();
   if (!aChild->IsRecording()) {
     if (aRecoverPosition) {
       aChild->Recover(gActiveChild);
     } else {
-      Vector<SetBreakpointMessage*> breakpoints;
+      InfallibleVector<AddBreakpointMessage*> breakpoints;
       gActiveChild->GetInstalledBreakpoints(breakpoints);
-      for (SetBreakpointMessage* msg : breakpoints) {
+      for (AddBreakpointMessage* msg : breakpoints) {
         aChild->SendMessage(*msg);
       }
     }
   }
   aChild->SetRole(MakeUnique<ChildRoleActive>());
   if (oldActiveChild->IsRecording()) {
     oldActiveChild->SetRole(MakeUnique<ChildRoleInert>());
   } else {
@@ -976,28 +976,40 @@ SendRequest(const js::CharBuffer& aBuffe
   // Wait for the child to respond to the query.
   gActiveChild->WaitUntilPaused();
   MOZ_RELEASE_ASSERT(gResponseBuffer == aResponse);
   MOZ_RELEASE_ASSERT(gResponseBuffer->length() != 0);
   gResponseBuffer = nullptr;
 }
 
 void
-SetBreakpoint(size_t aId, const js::BreakpointPosition& aPosition)
+AddBreakpoint(const js::BreakpointPosition& aPosition)
 {
-  MaybeCreateCheckpointInRecordingChild();
-  gActiveChild->WaitUntilPaused();
+  MOZ_RELEASE_ASSERT(gActiveChild->IsPaused());
 
-  gActiveChild->SendMessage(SetBreakpointMessage(aId, aPosition));
+  gActiveChild->SendMessage(AddBreakpointMessage(aPosition));
 
   // Also set breakpoints in any recording child that is not currently active.
   // We can't recover recording processes so need to keep their breakpoints up
   // to date.
   if (!gActiveChild->IsRecording() && gRecordingChild) {
-    gRecordingChild->SendMessage(SetBreakpointMessage(aId, aPosition));
+    gRecordingChild->SendMessage(AddBreakpointMessage(aPosition));
+  }
+}
+
+void
+ClearBreakpoints()
+{
+  MOZ_RELEASE_ASSERT(gActiveChild->IsPaused());
+
+  gActiveChild->SendMessage(ClearBreakpointsMessage());
+
+  // Clear breakpoints in the recording child, as for AddBreakpoint().
+  if (!gActiveChild->IsRecording() && gRecordingChild) {
+    gRecordingChild->SendMessage(ClearBreakpointsMessage());
   }
 }
 
 // Flags for the preferred direction of travel when execution unpauses,
 // according to the last direction we were explicitly given.
 static bool gChildExecuteForward = true;
 static bool gChildExecuteBackward = false;
 
--- a/toolkit/recordreplay/ipc/ParentInternal.h
+++ b/toolkit/recordreplay/ipc/ParentInternal.h
@@ -63,18 +63,19 @@ void Pause();
 
 // Direct the child process to warp to a specific point.
 void TimeWarp(const js::ExecutionPoint& target);
 
 // Send a JSON request to the child process, and synchronously wait for a
 // response.
 void SendRequest(const js::CharBuffer& aBuffer, js::CharBuffer* aResponse);
 
-// Set or clear a breakpoint in the child process.
-void SetBreakpoint(size_t aId, const js::BreakpointPosition& aPosition);
+// Set the breakpoints installed in the child process.
+void AddBreakpoint(const js::BreakpointPosition& aPosition);
+void ClearBreakpoints();
 
 // If possible, make sure the active child is replaying, and that requests
 // which might trigger an unhandled divergence can be processed (recording
 // children cannot process such requests).
 void MaybeSwitchToReplayingChild();
 
 ///////////////////////////////////////////////////////////////////////////////
 // Graphics
@@ -307,27 +308,20 @@ public:
   bool PauseNeeded() { return mPauseNeeded; }
   const InfallibleVector<size_t>& MajorCheckpoints() { return mMajorCheckpoints; }
 
   bool IsPaused() { return mPaused; }
   bool IsPausedAtCheckpoint();
   bool IsPausedAtRecordingEndpoint();
 
   // Get all breakpoints currently installed for this process.
-  void GetInstalledBreakpoints(Vector<SetBreakpointMessage*>& aBreakpoints);
+  void GetInstalledBreakpoints(InfallibleVector<AddBreakpointMessage*>& aBreakpoints);
 
   typedef std::function<bool(js::BreakpointPosition::Kind)> BreakpointFilter;
 
-  // Return whether this process is paused at a breakpoint matching a filter.
-  bool IsPausedAtMatchingBreakpoint(const BreakpointFilter& aFilter);
-
-  // Get the ids of all installed breakpoints matching a filter.
-  void GetMatchingInstalledBreakpoints(const BreakpointFilter& aFilter,
-                                       Vector<uint32_t>& aBreakpointIds);
-
   // Get the checkpoint at or earlier to the process' position. This is either
   // the last reached checkpoint or the previous one.
   size_t MostRecentCheckpoint() {
     return (GetDisposition() == BeforeLastCheckpoint) ? mLastCheckpoint - 1 : mLastCheckpoint;
   }
 
   // Get the checkpoint which needs to be saved in order for this process
   // (or another at the same place) to rewind.