Bug 1547084 Part 3 - C++ changes and removal for new control logic, r=loganfsmyth.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 12 May 2019 13:16:36 -1000
changeset 474173 4d7ef85fc81f1f3d37098ec0809ca14c62d79ccd
parent 474172 ae4aa5c6c43010a553f7f30e30334333179c4647
child 474174 ffc633295190897ae09451a56a0b255fff4d0682
push id36023
push userncsoregi@mozilla.com
push dateThu, 16 May 2019 21:56:43 +0000
treeherdermozilla-central@786f094a30ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersloganfsmyth
bugs1547084
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
Bug 1547084 Part 3 - C++ changes and removal for new control logic, r=loganfsmyth.
toolkit/recordreplay/Callback.cpp
toolkit/recordreplay/MemorySnapshot.cpp
toolkit/recordreplay/MemorySnapshot.h
toolkit/recordreplay/ProcessRecordReplay.cpp
toolkit/recordreplay/ProcessRecordReplay.h
toolkit/recordreplay/ProcessRedirectDarwin.cpp
toolkit/recordreplay/ProcessRewind.cpp
toolkit/recordreplay/ProcessRewind.h
toolkit/recordreplay/ThreadSnapshot.h
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/ParentForwarding.cpp
toolkit/recordreplay/ipc/ParentGraphics.cpp
toolkit/recordreplay/ipc/ParentIPC.cpp
toolkit/recordreplay/ipc/ParentInternal.h
toolkit/recordreplay/moz.build
--- a/toolkit/recordreplay/Callback.cpp
+++ b/toolkit/recordreplay/Callback.cpp
@@ -36,17 +36,17 @@ void RegisterCallbackData(void* aData) {
 }
 
 void BeginCallback(size_t aCallbackId) {
   MOZ_RELEASE_ASSERT(IsRecording());
   MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
 
   Thread* thread = Thread::Current();
   if (thread->IsMainThread()) {
-    child::EndIdleTime();
+    js::EndIdleTime();
   }
   thread->SetPassThrough(false);
 
   RecordingEventSection res(thread);
   MOZ_RELEASE_ASSERT(res.CanAccessEvents());
 
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteCallback);
   thread->Events().WriteScalar(aCallbackId);
@@ -54,17 +54,17 @@ void BeginCallback(size_t aCallbackId) {
 
 void EndCallback() {
   MOZ_RELEASE_ASSERT(IsRecording());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
 
   Thread* thread = Thread::Current();
   if (thread->IsMainThread()) {
-    child::BeginIdleTime();
+    js::BeginIdleTime();
   }
   thread->SetPassThrough(true);
 }
 
 void SaveOrRestoreCallbackData(void** aData) {
   MOZ_RELEASE_ASSERT(gCallbackData);
 
   Thread* thread = Thread::Current();
@@ -94,22 +94,22 @@ void RemoveCallbackData(void* aData) {
 
 void PassThroughThreadEventsAllowCallbacks(const std::function<void()>& aFn) {
   Thread* thread = Thread::Current();
   RecordingEventSection res(thread);
   MOZ_RELEASE_ASSERT(res.CanAccessEvents());
 
   if (IsRecording()) {
     if (thread->IsMainThread()) {
-      child::BeginIdleTime();
+      js::BeginIdleTime();
     }
     thread->SetPassThrough(true);
     aFn();
     if (thread->IsMainThread()) {
-      child::EndIdleTime();
+      js::EndIdleTime();
     }
     thread->SetPassThrough(false);
     thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CallbacksFinished);
   } else {
     while (true) {
       ThreadEvent ev = (ThreadEvent)thread->Events().ReadScalar();
       if (ev != ThreadEvent::ExecuteCallback) {
         MOZ_RELEASE_ASSERT(ev == ThreadEvent::CallbacksFinished);
--- a/toolkit/recordreplay/MemorySnapshot.cpp
+++ b/toolkit/recordreplay/MemorySnapshot.cpp
@@ -156,26 +156,26 @@ struct DirtyPage {
 // A set of dirty pages that can be searched quickly.
 typedef SplayTree<DirtyPage, DirtyPage::AddressSort,
                   AllocPolicy<MemoryKind::SortedDirtyPageSet>, 4>
     SortedDirtyPageSet;
 
 // A set of dirty pages associated with some checkpoint.
 struct DirtyPageSet {
   // Checkpoint associated with this set.
-  CheckpointId mCheckpoint;
+  size_t mCheckpoint;
 
   // All dirty pages in the set. Pages may be added or destroyed by the main
   // thread when all other threads are idle, by the dirty memory handler when
   // it is active and this is the active page set, and by the snapshot thread
   // which owns this set.
   InfallibleVector<DirtyPage, 256, AllocPolicy<MemoryKind::DirtyPageSet>>
       mPages;
 
-  explicit DirtyPageSet(const CheckpointId& aCheckpoint)
+  explicit DirtyPageSet(size_t aCheckpoint)
       : mCheckpoint(aCheckpoint) {}
 };
 
 // Worklist used by each snapshot thread.
 struct SnapshotThreadWorklist {
   // Index into gMemoryInfo->mSnapshotWorklists of the thread.
   size_t mThreadIndex;
 
@@ -344,27 +344,20 @@ struct MemoryInfo {
 
   // Information for memory allocation.
   Atomic<ssize_t, Relaxed, Behavior::DontPreserve>
       mMemoryBalance[(size_t)MemoryKind::Count];
 
   // Recent dirty memory faults.
   void* mDirtyMemoryFaults[50];
 
-  // Whether RecordReplayDirective may crash this process.
-  bool mIntentionalCrashesAllowed;
-
-  // Whether the CrashSoon directive has been given to this process.
-  bool mCrashSoon;
-
   MemoryInfo()
       : mMemoryChangesAllowed(true),
         mFreeUntrackedRegions(MemoryKind::FreeRegions),
-        mStartTime(CurrentTime()),
-        mIntentionalCrashesAllowed(true) {
+        mStartTime(CurrentTime()) {
     // The singleton MemoryInfo is allocated with zeroed memory, so other
     // fields do not need explicit initialization.
   }
 };
 
 static MemoryInfo* gMemoryInfo = nullptr;
 
 void SetMemoryChangesAllowed(bool aAllowed) {
@@ -427,52 +420,16 @@ void DumpTimers() {
     uint32_t hits = gMemoryInfo->mTimeHits[i];
     double time = gMemoryInfo->mTimeTotals[i];
     Print("%s: %d hits, %.2fs\n", gTimerKindNames[i], (int)hits,
           time / 1000000.0);
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Directives
-///////////////////////////////////////////////////////////////////////////////
-
-void SetAllowIntentionalCrashes(bool aAllowed) {
-  gMemoryInfo->mIntentionalCrashesAllowed = aAllowed;
-}
-
-extern "C" {
-
-MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayDirective(
-    long aDirective) {
-  switch ((Directive)aDirective) {
-    case Directive::CrashSoon:
-      gMemoryInfo->mCrashSoon = true;
-      break;
-    case Directive::MaybeCrash:
-      if (gMemoryInfo->mIntentionalCrashesAllowed && gMemoryInfo->mCrashSoon) {
-        PrintSpew("Intentionally Crashing!\n");
-        MOZ_CRASH("RecordReplayDirective intentional crash");
-      }
-      gMemoryInfo->mCrashSoon = false;
-      break;
-    case Directive::AlwaysSaveTemporaryCheckpoints:
-      navigation::AlwaysSaveTemporaryCheckpoints();
-      break;
-    case Directive::AlwaysMarkMajorCheckpoints:
-      child::NotifyAlwaysMarkMajorCheckpoints();
-      break;
-    default:
-      MOZ_CRASH("Unknown directive");
-  }
-}
-
-}  // extern "C"
-
-///////////////////////////////////////////////////////////////////////////////
 // Snapshot Thread Conditions
 ///////////////////////////////////////////////////////////////////////////////
 
 void SnapshotThreadCondition::ActivateBegin() {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   MOZ_RELEASE_ASSERT(!mActive);
   mActive = true;
   for (size_t i = 0; i < NumSnapshotThreads; i++) {
@@ -640,17 +597,17 @@ void UnrecoverableSnapshotFailure() {
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Initial Memory Region Processing
 ///////////////////////////////////////////////////////////////////////////////
 
 void AddInitialUntrackedMemoryRegion(uint8_t* aBase, size_t aSize) {
-  MOZ_RELEASE_ASSERT(!HasSavedCheckpoint());
+  MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint());
 
   if (gInitializationFailureMessage) {
     return;
   }
 
   static void* gSkippedRegion;
   if (!gSkippedRegion) {
     // We are allocating gMemoryInfo itself, and will directly call this
@@ -670,17 +627,17 @@ void AddInitialUntrackedMemoryRegion(uin
     }
   }
 
   // If we end up here then MaxInitialUntrackedRegions should be larger.
   MOZ_CRASH();
 }
 
 static void RemoveInitialUntrackedRegion(uint8_t* aBase, size_t aSize) {
-  MOZ_RELEASE_ASSERT(!HasSavedCheckpoint());
+  MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint());
   AutoSpinLock lock(gMemoryInfo->mInitialUntrackedRegionsLock);
 
   for (AllocatedMemoryRegion& region : gMemoryInfo->mInitialUntrackedRegions) {
     if (region.mBase == aBase) {
       MOZ_RELEASE_ASSERT(region.mSize == aSize);
       region.mBase = nullptr;
       region.mSize = 0;
       return;
@@ -1037,31 +994,31 @@ bool FreeRegionSet::Intersects(void* aAd
 void RegisterAllocatedMemory(void* aBaseAddress, size_t aSize,
                              MemoryKind aKind) {
   MOZ_RELEASE_ASSERT(aBaseAddress == PageBase(aBaseAddress));
   MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize));
 
   uint8_t* aAddress = reinterpret_cast<uint8_t*>(aBaseAddress);
 
   if (aKind != MemoryKind::Tracked) {
-    if (!HasSavedCheckpoint()) {
+    if (!HasSavedAnyCheckpoint()) {
       AddInitialUntrackedMemoryRegion(aAddress, aSize);
     }
-  } else if (HasSavedCheckpoint()) {
+  } else if (HasSavedAnyCheckpoint()) {
     EnsureMemoryChangesAllowed();
     DirectWriteProtectMemory(aAddress, aSize, true);
     AddTrackedRegion(aAddress, aSize, true);
   }
 }
 
 void CheckFixedMemory(void* aAddress, size_t aSize) {
   MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress));
   MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize));
 
-  if (!HasSavedCheckpoint()) {
+  if (!HasSavedAnyCheckpoint()) {
     return;
   }
 
   {
     // The memory should already be tracked. Check each page in the allocation
     // because there might be tracked regions adjacent to one another, neither
     // of which entirely contains this memory.
     AutoSpinLock lock(gMemoryInfo->mTrackedRegionsLock);
@@ -1082,17 +1039,17 @@ void CheckFixedMemory(void* aAddress, si
     MOZ_CRASH("Fixed memory is currently free!");
   }
 }
 
 void RestoreWritableFixedMemory(void* aAddress, size_t aSize) {
   MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress));
   MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize));
 
-  if (!HasSavedCheckpoint()) {
+  if (!HasSavedAnyCheckpoint()) {
     return;
   }
 
   AutoSpinLock lock(gMemoryInfo->mActiveDirtyLock);
   for (size_t offset = 0; offset < aSize; offset += PageSize) {
     uint8_t* page = (uint8_t*)aAddress + offset;
     if (gMemoryInfo->mActiveDirty.maybeLookup(page)) {
       DirectUnprotectMemory(page, PageSize, true);
@@ -1103,17 +1060,17 @@ void RestoreWritableFixedMemory(void* aA
 void* AllocateMemoryTryAddress(void* aAddress, size_t aSize, MemoryKind aKind) {
   MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress));
   aSize = RoundupSizeToPageBoundary(aSize);
 
   if (gMemoryInfo) {
     gMemoryInfo->mMemoryBalance[(size_t)aKind] += aSize;
   }
 
-  if (HasSavedCheckpoint()) {
+  if (HasSavedAnyCheckpoint()) {
     if (void* res = FreeRegionSet::Get(aKind).Extract(aAddress, aSize)) {
       return res;
     }
   }
 
   void* res = DirectAllocateMemory(aAddress, aSize);
   RegisterAllocatedMemory(res, aSize, aKind);
   return res;
@@ -1136,17 +1093,17 @@ void DeallocateMemory(void* aAddress, si
     return;
   }
 
   if (gMemoryInfo) {
     gMemoryInfo->mMemoryBalance[(size_t)aKind] -= aSize;
   }
 
   // Memory is returned to the system before saving the first checkpoint.
-  if (!HasSavedCheckpoint()) {
+  if (!HasSavedAnyCheckpoint()) {
     if (IsReplaying() && aKind != MemoryKind::Tracked) {
       RemoveInitialUntrackedRegion((uint8_t*)aAddress, aSize);
     }
     DirectDeallocateMemory(aAddress, aSize);
     return;
   }
 
   if (aKind == MemoryKind::Tracked) {
@@ -1167,17 +1124,17 @@ void DeallocateMemory(void* aAddress, si
 ///////////////////////////////////////////////////////////////////////////////
 // Snapshot Threads
 ///////////////////////////////////////////////////////////////////////////////
 
 // While on a snapshot thread, restore the contents of all pages belonging to
 // this thread which were modified since the last recorded diff snapshot.
 static void SnapshotThreadRestoreLastDiffSnapshot(
     SnapshotThreadWorklist* aWorklist) {
-  CheckpointId checkpoint = GetLastSavedCheckpoint();
+  size_t checkpoint = GetLastSavedCheckpoint();
 
   DirtyPageSet& set = aWorklist->mSets.back();
   MOZ_RELEASE_ASSERT(set.mCheckpoint == checkpoint);
 
   // Copy the original contents of all pages.
   for (size_t index = 0; index < set.mPages.length(); index++) {
     const DirtyPage& page = set.mPages[index];
     MOZ_RELEASE_ASSERT(page.mOriginal);
--- a/toolkit/recordreplay/MemorySnapshot.h
+++ b/toolkit/recordreplay/MemorySnapshot.h
@@ -97,20 +97,16 @@ bool HandleDirtyMemoryFault(uint8_t* aAd
 // For debugging, note a point where we hit an unrecoverable failure and try
 // to make things easier for the debugger.
 void UnrecoverableSnapshotFailure();
 
 // After rewinding, mark all memory that has been allocated since the snapshot
 // was taken as free.
 void FixupFreeRegionsAfterRewind();
 
-// Set whether to allow intentionally crashing in this process via the
-// RecordReplayDirective method.
-void SetAllowIntentionalCrashes(bool aAllowed);
-
 // When WANT_COUNTDOWN_THREAD is defined (see MemorySnapshot.cpp), set a count
 // that, after a thread consumes it, causes the thread to report a fatal error.
 // This is used for debugging and is a workaround for lldb often being unable
 // to interrupt a running process.
 void StartCountdown(size_t aCount);
 
 // Per StartCountdown, set a countdown and remove it on destruction.
 struct MOZ_RAII AutoCountdown {
--- a/toolkit/recordreplay/ProcessRecordReplay.cpp
+++ b/toolkit/recordreplay/ProcessRecordReplay.cpp
@@ -51,16 +51,19 @@ char* gRecordingFilename;
 static int gPid;
 
 // ID of the process which produced the recording.
 static int gRecordingPid;
 
 // Whether to spew record/replay messages to stderr.
 static bool gSpewEnabled;
 
+// Whether this is the main child.
+static bool gMainChild;
+
 extern "C" {
 
 MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
   // Parse command line options for the process kind and recording file.
   Maybe<ProcessKind> processKind;
   Maybe<char*> recordingFile;
   for (int i = 0; i < aArgc; i++) {
     if (!strcmp(aArgv[i], gProcessKindOption)) {
@@ -90,26 +93,30 @@ MOZ_EXPORT void RecordReplayInterface_In
     case ProcessKind::MiddlemanReplaying:
       gIsMiddleman = true;
       fprintf(stderr, "MIDDLEMAN %d %s\n", getpid(), recordingFile.ref());
       break;
     default:
       MOZ_CRASH("Bad ProcessKind");
   }
 
-  if (IsRecordingOrReplaying() && TestEnv("WAIT_AT_START")) {
+  if (IsRecording() && TestEnv("MOZ_RECORDING_WAIT_AT_START")) {
     BusyWait();
   }
 
-  if (IsMiddleman() && TestEnv("MIDDLEMAN_WAIT_AT_START")) {
+  if (IsReplaying() && TestEnv("MOZ_REPLAYING_WAIT_AT_START")) {
+    BusyWait();
+  }
+
+  if (IsMiddleman() && TestEnv("MOZ_MIDDLEMAN_WAIT_AT_START")) {
     BusyWait();
   }
 
   gPid = getpid();
-  if (TestEnv("RECORD_REPLAY_SPEW")) {
+  if (TestEnv("MOZ_RECORD_REPLAY_SPEW")) {
     gSpewEnabled = true;
   }
 
   EarlyInitializeRedirections();
 
   if (!IsRecordingOrReplaying()) {
     InitializeMiddlemanCalls();
     return;
@@ -154,16 +161,18 @@ MOZ_EXPORT void RecordReplayInterface_In
   // Don't create a stylo thread pool when recording or replaying.
   putenv((char*)"STYLO_THREADS=1");
 
   thread->SetPassThrough(false);
 
   InitializeRewindState();
   gRecordingPid = RecordReplayValue(gPid);
 
+  gMainChild = IsRecording();
+
   gInitialized = true;
 }
 
 MOZ_EXPORT size_t
 RecordReplayInterface_InternalRecordReplayValue(size_t aValue) {
   Thread* thread = Thread::Current();
   RecordingEventSection res(thread);
   if (!res.CanAccessEvents()) {
@@ -196,35 +205,28 @@ MOZ_EXPORT void RecordReplayInterface_In
     child::ReportFatalError(Nothing(),
                             "Recording invalidated while replaying: %s", aWhy);
   }
   Unreachable();
 }
 
 }  // extern "C"
 
-// How many recording endpoints have been flushed to the recording.
-static size_t gNumEndpoints;
-
 void FlushRecording() {
   MOZ_RELEASE_ASSERT(IsRecording());
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
 
-  // Save the endpoint of the recording.
-  js::ExecutionPoint endpoint = navigation::GetRecordingEndpoint();
+  // The recording can only be flushed when we are at a checkpoint.
+  // Save this endpoint to the recording.
+  size_t endpoint = GetLastCheckpoint();
   Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
-  endpointStream->WriteScalar(++gNumEndpoints);
-  endpointStream->WriteBytes(&endpoint, sizeof(endpoint));
+  endpointStream->WriteScalar(endpoint);
 
   gRecordingFile->PreventStreamWrites();
-
   gRecordingFile->Flush();
-
-  child::NotifyFlushedRecording();
-
   gRecordingFile->AllowStreamWrites();
 }
 
 // Try to load another recording index, returning whether one was found.
 static bool LoadNextRecordingIndex() {
   Thread::WaitForIdleThreads();
 
   InfallibleVector<Stream*> updatedStreams;
@@ -241,60 +243,47 @@ static bool LoadNextRecordingIndex() {
       }
     }
   }
 
   Thread::ResumeIdleThreads();
   return found;
 }
 
-bool HitRecordingEndpoint() {
-  MOZ_RELEASE_ASSERT(IsReplaying());
-  MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
-
-  // The debugger will call this method in a loop, so we don't have to do
-  // anything fancy to try to get the most up to date endpoint. As long as we
-  // can make some progress in attempting to find a later endpoint, we can
-  // return control to the debugger.
-
-  // Check if there is a new endpoint in the endpoint data stream.
-  Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
-  if (!endpointStream->AtEnd()) {
-    js::ExecutionPoint endpoint;
-    size_t index = endpointStream->ReadScalar();
-    endpointStream->ReadBytes(&endpoint, sizeof(endpoint));
-    navigation::SetRecordingEndpoint(index, endpoint);
-    return true;
-  }
-
-  // Check if there is more data in the recording.
-  if (LoadNextRecordingIndex()) {
-    return true;
-  }
-
-  // OK, we hit the most up to date endpoint in the recording.
-  return false;
-}
-
 void HitEndOfRecording() {
   MOZ_RELEASE_ASSERT(IsReplaying());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
 
   if (Thread::CurrentIsMainThread()) {
     // Load more data from the recording. The debugger is not allowed to let us
     // go past the recording endpoint, so there should be more data.
     bool found = LoadNextRecordingIndex();
     MOZ_RELEASE_ASSERT(found);
   } else {
     // Non-main threads may wait until more recording data is loaded by the
     // main thread.
     Thread::Wait();
   }
 }
 
+// When replaying, the last endpoint loaded from the recording.
+static size_t gRecordingEndpoint;
+
+size_t RecordingEndpoint() {
+  MOZ_RELEASE_ASSERT(IsReplaying());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+
+  Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
+  while (!endpointStream->AtEnd()) {
+    gRecordingEndpoint = endpointStream->ReadScalar();
+  }
+
+  return gRecordingEndpoint;
+}
+
 bool SpewEnabled() { return gSpewEnabled; }
 
 void InternalPrint(const char* aFormat, va_list aArgs) {
   char buf1[2048];
   VsprintfLiteral(buf1, aFormat, aArgs);
   char buf2[2048];
   SprintfLiteral(buf2, "Spew[%d]: %s", gPid, buf1);
   DirectPrint(buf2);
@@ -310,16 +299,19 @@ const char* ThreadEventName(ThreadEvent 
         case ThreadEvent::CallStart : break;
   }
   size_t callId = (size_t)aEvent - (size_t)ThreadEvent::CallStart;
   return GetRedirection(callId).mName;
 }
 
 int GetRecordingPid() { return gRecordingPid; }
 
+bool IsMainChild() { return gMainChild; }
+void SetMainChild() { gMainChild = true; }
+
 ///////////////////////////////////////////////////////////////////////////////
 // Record/Replay Assertions
 ///////////////////////////////////////////////////////////////////////////////
 
 extern "C" {
 
 MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayAssert(
     const char* aFormat, va_list aArgs) {
--- a/toolkit/recordreplay/ProcessRecordReplay.h
+++ b/toolkit/recordreplay/ProcessRecordReplay.h
@@ -102,34 +102,23 @@ static inline void AssertEventsAreNotPas
 }
 
 // Flush any new recording data to disk.
 void FlushRecording();
 
 // Called when any thread hits the end of its event stream.
 void HitEndOfRecording();
 
-// Called when the main thread hits the latest recording endpoint it knows
-// about.
-bool HitRecordingEndpoint();
-
-// Possible directives to give via the RecordReplayDirective function.
-enum class Directive {
-  // Crash at the next use of MaybeCrash.
-  CrashSoon = 1,
+// Called in a replaying process to load the last checkpoint in the recording.
+size_t RecordingEndpoint();
 
-  // Irrevocably crash if CrashSoon has ever been used on the process.
-  MaybeCrash = 2,
-
-  // Always save temporary checkpoints when stepping around in the debugger.
-  AlwaysSaveTemporaryCheckpoints = 3,
-
-  // Mark all future checkpoints as major checkpoints in the middleman.
-  AlwaysMarkMajorCheckpoints = 4
-};
+// Access the flag for whether this is the main child. The main child never
+// rewinds and sends graphics updates to the middleman while running forward.
+bool IsMainChild();
+void SetMainChild();
 
 // Get the process kind and recording file specified at the command line.
 // These are available in the middleman as well as while recording/replaying.
 extern ProcessKind gProcessKind;
 extern char* gRecordingFilename;
 
 ///////////////////////////////////////////////////////////////////////////////
 // Helper Functions
@@ -278,18 +267,18 @@ enum class MemoryKind {
 
   // Memory used by various parts of the memory snapshot system.
   TrackedRegions,
   FreeRegions,
   DirtyPageSet,
   SortedDirtyPageSet,
   PageCopy,
 
-  // Memory used for navigation state.
-  Navigation,
+  // Memory used by various parts of JS integration.
+  ScriptHits,
 
   Count
 };
 
 // Allocate or deallocate a block of memory of a particular kind. Allocated
 // memory is initially zeroed.
 void* AllocateMemory(size_t aSize, MemoryKind aKind);
 void DeallocateMemory(void* aAddress, size_t aSize, MemoryKind aKind);
--- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp
+++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp
@@ -399,17 +399,17 @@ static PreambleResult MiddlemanPreamble_
     totalSize += msg->msg_iov[i].iov_len;
   }
   aArguments->Rval<size_t>() = totalSize;
   return PreambleResult::Veto;
 }
 
 static PreambleResult Preamble_mprotect(CallArguments* aArguments) {
   // Ignore any mprotect calls that occur after saving a checkpoint.
-  if (!HasSavedCheckpoint()) {
+  if (!HasSavedAnyCheckpoint()) {
     return PreambleResult::PassThrough;
   }
   aArguments->Rval<ssize_t>() = 0;
   return PreambleResult::Veto;
 }
 
 static PreambleResult Preamble_mmap(CallArguments* aArguments) {
   auto& address = aArguments->Arg<0, void*>();
@@ -427,33 +427,33 @@ static PreambleResult Preamble_mmap(Call
   }
 
   void* memory = nullptr;
   if ((flags & MAP_ANON) ||
       (IsReplaying() && !AreThreadEventsPassedThrough())) {
     // Get an anonymous mapping for the result.
     if (flags & MAP_FIXED) {
       // For fixed allocations, make sure this memory region is mapped and zero.
-      if (!HasSavedCheckpoint()) {
+      if (!HasSavedAnyCheckpoint()) {
         // Make sure this memory region is writable.
         CallFunction<int>(gOriginal_mprotect, address, size,
                           PROT_READ | PROT_WRITE | PROT_EXEC);
       }
       memset(address, 0, size);
       memory = address;
     } else {
       memory = AllocateMemoryTryAddress(
           address, RoundupSizeToPageBoundary(size), MemoryKind::Tracked);
     }
   } else {
     // We have to call mmap itself, which can change memory protection flags
     // for memory that is already allocated. If we haven't saved a checkpoint
     // then this is no problem, but after saving a checkpoint we have to make
     // sure that protection flags are what we expect them to be.
-    int newProt = HasSavedCheckpoint() ? (PROT_READ | PROT_EXEC) : prot;
+    int newProt = HasSavedAnyCheckpoint() ? (PROT_READ | PROT_EXEC) : prot;
     memory = CallFunction<void*>(gOriginal_mmap, address, size, newProt, flags,
                                  fd, offset);
 
     if (flags & MAP_FIXED) {
       MOZ_RELEASE_ASSERT(memory == address);
       RestoreWritableFixedMemory(memory, RoundupSizeToPageBoundary(size));
     } else if (memory && memory != (void*)-1) {
       RegisterAllocatedMemory(memory, RoundupSizeToPageBoundary(size),
@@ -669,19 +669,21 @@ static ssize_t WaitForCvar(pthread_mutex
   }
   MOZ_RELEASE_ASSERT(rv == 0);
   return 0;
 }
 
 static PreambleResult Preamble_pthread_cond_wait(CallArguments* aArguments) {
   auto& cond = aArguments->Arg<0, pthread_cond_t*>();
   auto& mutex = aArguments->Arg<1, pthread_mutex_t*>();
+  js::BeginIdleTime();
   aArguments->Rval<ssize_t>() = WaitForCvar(mutex, cond, false, [=]() {
     return CallFunction<ssize_t>(gOriginal_pthread_cond_wait, cond, mutex);
   });
+  js::EndIdleTime();
   return PreambleResult::Veto;
 }
 
 static PreambleResult Preamble_pthread_cond_timedwait(
     CallArguments* aArguments) {
   auto& cond = aArguments->Arg<0, pthread_cond_t*>();
   auto& mutex = aArguments->Arg<1, pthread_mutex_t*>();
   auto& timeout = aArguments->Arg<2, timespec*>();
@@ -920,32 +922,32 @@ static PreambleResult Preamble_mach_vm_d
 }
 
 static PreambleResult Preamble_mach_vm_map(CallArguments* aArguments) {
   if (IsRecording()) {
     return PreambleResult::PassThrough;
   } else if (AreThreadEventsPassedThrough()) {
     // We should only reach this at startup, when initializing the graphics
     // shared memory block.
-    MOZ_RELEASE_ASSERT(!HasSavedCheckpoint());
+    MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint());
     return PreambleResult::PassThrough;
   }
 
   auto size = aArguments->Arg<2, size_t>();
   auto address = aArguments->Arg<1, void**>();
 
   *address = AllocateMemory(size, MemoryKind::Tracked);
   aArguments->Rval<size_t>() = KERN_SUCCESS;
   return PreambleResult::Veto;
 }
 
 static PreambleResult Preamble_mach_vm_protect(CallArguments* aArguments) {
   // Ignore any mach_vm_protect calls that occur after saving a checkpoint, as
   // for mprotect.
-  if (!HasSavedCheckpoint()) {
+  if (!HasSavedAnyCheckpoint()) {
     return PreambleResult::PassThrough;
   }
   aArguments->Rval<size_t>() = KERN_SUCCESS;
   return PreambleResult::Veto;
 }
 
 static PreambleResult Preamble_vm_purgable_control(CallArguments* aArguments) {
   // Never allow purging of volatile memory, to simplify memory snapshots.
--- a/toolkit/recordreplay/ProcessRewind.cpp
+++ b/toolkit/recordreplay/ProcessRewind.cpp
@@ -19,21 +19,17 @@
 
 namespace mozilla {
 namespace recordreplay {
 
 // Information about the current rewinding state. The contents of this structure
 // are in untracked memory.
 struct RewindInfo {
   // The most recent checkpoint which was encountered.
-  CheckpointId mLastCheckpoint;
-
-  // Whether this is the active child process. See the comment under
-  // 'Child Roles' in ParentIPC.cpp.
-  bool mIsActiveChild;
+  size_t mLastCheckpoint;
 
   // Checkpoints which have been saved. This includes only entries from
   // mShouldSaveCheckpoints, plus all temporary checkpoints.
   InfallibleVector<SavedCheckpoint, 1024, AllocPolicy<MemoryKind::Generic>>
       mSavedCheckpoints;
 
   // Unsorted list of checkpoints which the middleman has instructed us to
   // save. All those equal to or prior to mLastCheckpoint will have been saved.
@@ -50,95 +46,91 @@ static Monitor* gMainThreadCallbackMonit
 // gMainThreadCallbackMonitor.
 static StaticInfallibleVector<std::function<void()>> gMainThreadCallbacks;
 
 void InitializeRewindState() {
   MOZ_RELEASE_ASSERT(gRewindInfo == nullptr);
   void* memory = AllocateMemory(sizeof(RewindInfo), MemoryKind::Generic);
   gRewindInfo = new (memory) RewindInfo();
 
+  // The first checkpoint is implicitly saved while replaying: we won't be able
+  // to get a manifest from the middleman telling us what to save until after
+  // this checkpoint has been reached.
+  if (IsReplaying()) {
+    gRewindInfo->mShouldSaveCheckpoints.append(FirstCheckpointId);
+  }
+
   gMainThreadCallbackMonitor = new Monitor();
 }
 
-static bool CheckpointPrecedes(const CheckpointId& aFirst,
-                               const CheckpointId& aSecond) {
-  return aFirst.mNormal < aSecond.mNormal ||
-         aFirst.mTemporary < aSecond.mTemporary;
-}
-
-void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) {
+void RestoreCheckpointAndResume(size_t aCheckpoint) {
   MOZ_RELEASE_ASSERT(IsReplaying());
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   MOZ_RELEASE_ASSERT(
       aCheckpoint == gRewindInfo->mLastCheckpoint ||
-      CheckpointPrecedes(aCheckpoint, gRewindInfo->mLastCheckpoint));
+      aCheckpoint < gRewindInfo->mLastCheckpoint);
 
   // Make sure we don't lose pending main thread callbacks due to rewinding.
   {
     MonitorAutoLock lock(*gMainThreadCallbackMonitor);
     MOZ_RELEASE_ASSERT(gMainThreadCallbacks.empty());
   }
 
   Thread::WaitForIdleThreads();
 
   double start = CurrentTime();
 
   {
     // Rewind heap memory to the target checkpoint, which must have been saved.
     AutoDisallowMemoryChanges disallow;
-    CheckpointId newCheckpoint =
+    size_t newCheckpoint =
         gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
     RestoreMemoryToLastSavedCheckpoint();
-    while (CheckpointPrecedes(aCheckpoint, newCheckpoint)) {
+    while (aCheckpoint < newCheckpoint) {
       gRewindInfo->mSavedCheckpoints.back().ReleaseContents();
       gRewindInfo->mSavedCheckpoints.popBack();
       RestoreMemoryToLastSavedDiffCheckpoint();
       newCheckpoint = gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
     }
     MOZ_RELEASE_ASSERT(newCheckpoint == aCheckpoint);
   }
 
   FixupFreeRegionsAfterRewind();
 
   double end = CurrentTime();
-  PrintSpew("Restore #%d:%d -> #%d:%d %.2fs\n",
-            (int)gRewindInfo->mLastCheckpoint.mNormal,
-            (int)gRewindInfo->mLastCheckpoint.mTemporary,
-            (int)aCheckpoint.mNormal, (int)aCheckpoint.mTemporary,
+  PrintSpew("Restore #%d -> #%d %.2fs\n",
+            (int)gRewindInfo->mLastCheckpoint, (int)aCheckpoint,
             (end - start) / 1000000.0);
 
   // Finally, let threads restore themselves to their stacks at the checkpoint
   // we are rewinding to.
   RestoreAllThreads(gRewindInfo->mSavedCheckpoints.back());
   Unreachable();
 }
 
 void SetSaveCheckpoint(size_t aCheckpoint, bool aSave) {
-  MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint.mNormal);
+  MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint);
   VectorAddOrRemoveEntry(gRewindInfo->mShouldSaveCheckpoints, aCheckpoint,
                          aSave);
 }
 
-bool NewCheckpoint(bool aTemporary) {
+bool NewCheckpoint() {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
-  MOZ_RELEASE_ASSERT(IsReplaying() || !aTemporary);
 
-  navigation::BeforeCheckpoint();
+  js::BeforeCheckpoint();
 
   // Get the ID of the new checkpoint.
-  CheckpointId checkpoint =
-      gRewindInfo->mLastCheckpoint.NextCheckpoint(aTemporary);
+  size_t checkpoint = gRewindInfo->mLastCheckpoint + 1;
 
   // Save all checkpoints the middleman tells us to, and temporary checkpoints
   // (which the middleman never knows about).
-  bool save = aTemporary || VectorContains(gRewindInfo->mShouldSaveCheckpoints,
-                                           checkpoint.mNormal);
+  bool save = VectorContains(gRewindInfo->mShouldSaveCheckpoints, checkpoint);
   bool reachedCheckpoint = true;
 
   if (save) {
     Thread::WaitForIdleThreads();
 
     PrintSpew("Starting checkpoint...\n");
 
     double start = CurrentTime();
@@ -151,36 +143,37 @@ bool NewCheckpoint(bool aTemporary) {
     }
     gRewindInfo->mSavedCheckpoints.emplaceBack(checkpoint);
 
     double end = CurrentTime();
 
     // Save all thread stacks for the checkpoint. If we rewind here from a
     // later point of execution then this will return false.
     if (SaveAllThreads(gRewindInfo->mSavedCheckpoints.back())) {
-      PrintSpew("Saved checkpoint #%d:%d %.2fs\n", (int)checkpoint.mNormal,
-                (int)checkpoint.mTemporary, (end - start) / 1000000.0);
+      PrintSpew("Saved checkpoint #%d %.2fs\n", (int)checkpoint,
+                (end - start) / 1000000.0);
     } else {
-      PrintSpew("Restored checkpoint #%d:%d\n", (int)checkpoint.mNormal,
-                (int)checkpoint.mTemporary);
+      PrintSpew("Restored checkpoint #%d\n", (int)checkpoint);
 
       reachedCheckpoint = false;
 
       // After restoring, make sure all threads have updated their stacks
       // before letting any of them resume execution. Threads might have
       // pointers into each others' stacks.
       WaitForIdleThreadsToRestoreTheirStacks();
     }
 
     Thread::ResumeIdleThreads();
+  } else {
+    PrintSpew("Skipping checkpoint #%d\n", (int)checkpoint);
   }
 
   gRewindInfo->mLastCheckpoint = checkpoint;
 
-  navigation::AfterCheckpoint(checkpoint);
+  js::AfterCheckpoint(checkpoint, !reachedCheckpoint);
 
   return reachedCheckpoint;
 }
 
 static bool gUnhandledDivergeAllowed;
 
 void DivergeFromRecording() {
   MOZ_RELEASE_ASSERT(IsReplaying());
@@ -232,52 +225,59 @@ void EnsureNotDivergedFromRecording() {
 
     PrintSpew("Unhandled recording divergence, restoring checkpoint...\n");
     RestoreCheckpointAndResume(
         gRewindInfo->mSavedCheckpoints.back().mCheckpoint);
     Unreachable();
   }
 }
 
-bool HasSavedCheckpoint() {
+bool HasSavedAnyCheckpoint() {
   return gRewindInfo && !gRewindInfo->mSavedCheckpoints.empty();
 }
 
-CheckpointId GetLastSavedCheckpoint() {
-  MOZ_RELEASE_ASSERT(HasSavedCheckpoint());
-  return gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
+bool HasSavedCheckpoint(size_t aCheckpoint) {
+  if (!gRewindInfo) {
+    return false;
+  }
+  for (const SavedCheckpoint& saved : gRewindInfo->mSavedCheckpoints) {
+    if (saved.mCheckpoint == aCheckpoint) {
+      return true;
+    }
+  }
+  return false;
 }
 
-CheckpointId GetLastSavedCheckpointPriorTo(const CheckpointId& aCheckpoint) {
-  MOZ_RELEASE_ASSERT(HasSavedCheckpoint());
-  for (size_t i = gRewindInfo->mSavedCheckpoints.length() - 1; i >= 1; i--) {
-    if (gRewindInfo->mSavedCheckpoints[i].mCheckpoint == aCheckpoint) {
-      return gRewindInfo->mSavedCheckpoints[i - 1].mCheckpoint;
-    }
-  }
-  MOZ_CRASH("GetLastSavedCheckpointPriorTo");
+size_t GetLastCheckpoint() {
+  return gRewindInfo ? gRewindInfo->mLastCheckpoint : InvalidCheckpointId;
+}
+
+size_t GetLastSavedCheckpoint() {
+  MOZ_RELEASE_ASSERT(HasSavedAnyCheckpoint());
+  return gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
 }
 
 static bool gMainThreadShouldPause = false;
 
 bool MainThreadShouldPause() { return gMainThreadShouldPause; }
 
 void PauseMainThreadAndServiceCallbacks() {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
-  MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
   AssertEventsAreNotPassedThrough();
 
   // Whether there is a PauseMainThreadAndServiceCallbacks frame on the stack.
   static bool gMainThreadIsPaused = false;
 
   if (gMainThreadIsPaused) {
     return;
   }
   gMainThreadIsPaused = true;
 
+  MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
+
   MonitorAutoLock lock(*gMainThreadCallbackMonitor);
 
   // Loop and invoke callbacks until one of them unpauses this thread.
   while (gMainThreadShouldPause) {
     if (!gMainThreadCallbacks.empty()) {
       std::function<void()> callback = gMainThreadCallbacks[0];
       gMainThreadCallbacks.erase(&gMainThreadCallbacks[0]);
       {
@@ -315,14 +315,10 @@ void PauseMainThreadAndInvokeCallback(co
 }
 
 void ResumeExecution() {
   MonitorAutoLock lock(*gMainThreadCallbackMonitor);
   gMainThreadShouldPause = false;
   gMainThreadCallbackMonitor->Notify();
 }
 
-void SetIsActiveChild(bool aActive) { gRewindInfo->mIsActiveChild = aActive; }
-
-bool IsActiveChild() { return gRewindInfo->mIsActiveChild; }
-
 }  // namespace recordreplay
 }  // namespace mozilla
--- a/toolkit/recordreplay/ProcessRewind.h
+++ b/toolkit/recordreplay/ProcessRewind.h
@@ -74,49 +74,19 @@ namespace recordreplay {
 // will trigger an unhandled divergence from the recording via
 // EnsureNotDivergedFromRecording, causing the process to rewind to the most
 // recent saved checkpoint. The debugger will recognize this rewind and play
 // back in a way that restores the state when DivergeFromRecording() was
 // called, but without performing the later operation that triggered the
 // rewind.
 ///////////////////////////////////////////////////////////////////////////////
 
-// The ID of a checkpoint in a child process. Checkpoints are either normal or
-// temporary. Normal checkpoints occur at the same point in the recording and
-// all replays, while temporary checkpoints are not used while recording and
-// may be at different points in different replays.
-struct CheckpointId {
-  // ID of the most recent normal checkpoint, which are numbered in sequence
-  // starting at FirstCheckpointId.
-  size_t mNormal;
-
-  // Special IDs for normal checkpoints.
-  static const size_t Invalid = 0;
-  static const size_t First = 1;
-
-  // How many temporary checkpoints have been generated since the most recent
-  // normal checkpoint, zero if this represents the normal checkpoint itself.
-  size_t mTemporary;
-
-  explicit CheckpointId(size_t aNormal = Invalid, size_t aTemporary = 0)
-      : mNormal(aNormal), mTemporary(aTemporary) {}
-
-  inline bool operator==(const CheckpointId& o) const {
-    return mNormal == o.mNormal && mTemporary == o.mTemporary;
-  }
-
-  inline bool operator!=(const CheckpointId& o) const {
-    return mNormal != o.mNormal || mTemporary != o.mTemporary;
-  }
-
-  CheckpointId NextCheckpoint(bool aTemporary) const {
-    return CheckpointId(aTemporary ? mNormal : mNormal + 1,
-                        aTemporary ? mTemporary + 1 : 0);
-  }
-};
+// Special IDs for normal checkpoints.
+static const size_t InvalidCheckpointId = 0;
+static const size_t FirstCheckpointId = 1;
 
 // Initialize state needed for rewinding.
 void InitializeRewindState();
 
 // Set whether this process should save a particular checkpoint.
 void SetSaveCheckpoint(size_t aCheckpoint, bool aSave);
 
 // Invoke a callback on the main thread, and pause it until ResumeExecution or
@@ -129,27 +99,30 @@ void PauseMainThreadAndInvokeCallback(co
 // mean it is paused, but it will pause at the earliest opportunity.
 bool MainThreadShouldPause();
 
 // Pause the current main thread and service any callbacks until the thread no
 // longer needs to pause.
 void PauseMainThreadAndServiceCallbacks();
 
 // Return whether any checkpoints have been saved.
-bool HasSavedCheckpoint();
+bool HasSavedAnyCheckpoint();
+
+// Return whether a specific checkpoint has been saved.
+bool HasSavedCheckpoint(size_t aCheckpoint);
+
+// Get the ID of the most recently encountered checkpoint.
+size_t GetLastCheckpoint();
 
 // Get the ID of the most recent saved checkpoint.
-CheckpointId GetLastSavedCheckpoint();
-
-// Get the ID of the saved checkpoint prior to aCheckpoint.
-CheckpointId GetLastSavedCheckpointPriorTo(const CheckpointId& aCheckpoint);
+size_t GetLastSavedCheckpoint();
 
 // When paused at a breakpoint or at a checkpoint, restore a checkpoint that
 // was saved earlier and resume execution.
-void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint);
+void RestoreCheckpointAndResume(size_t aCheckpoint);
 
 // When paused at a breakpoint or at a checkpoint, unpause and proceed with
 // execution.
 void ResumeExecution();
 
 // Allow execution after this point to diverge from the recording. Execution
 // will remain diverged until an earlier checkpoint is restored.
 //
@@ -164,22 +137,17 @@ void DivergeFromRecording();
 // call to DivergeFromRecording, or to an explicit restore of an earlier
 // checkpoint.
 void DisallowUnhandledDivergeFromRecording();
 
 // Make sure that execution has not diverged from the recording after a call to
 // DivergeFromRecording, by rewinding to the last saved checkpoint if so.
 void EnsureNotDivergedFromRecording();
 
-// Access the flag for whether this is the active child process.
-void SetIsActiveChild(bool aActive);
-bool IsActiveChild();
-
 // Note a checkpoint at the current execution position. This checkpoint will be
-// saved if either (a) it is temporary, or (b) the middleman has instructed
-// this process to save this normal checkpoint. This method returns true if the
-// checkpoint was just saved, and false if it was just restored.
-bool NewCheckpoint(bool aTemporary);
+// saved if it was instructed to do so via a manifest. This method returns true
+// if the checkpoint was just saved, and false if it was just restored.
+bool NewCheckpoint();
 
 }  // namespace recordreplay
 }  // namespace mozilla
 
 #endif  // mozilla_recordreplay_ProcessRewind_h
--- a/toolkit/recordreplay/ThreadSnapshot.h
+++ b/toolkit/recordreplay/ThreadSnapshot.h
@@ -68,20 +68,20 @@ struct SavedThreadStack {
   void ReleaseContents() {
     if (mStackBytes) {
       DeallocateMemory(mStack, mStackBytes, MemoryKind::ThreadSnapshot);
     }
   }
 };
 
 struct SavedCheckpoint {
-  CheckpointId mCheckpoint;
+  size_t mCheckpoint;
   SavedThreadStack mStacks[MaxRecordedThreadId];
 
-  explicit SavedCheckpoint(CheckpointId aCheckpoint)
+  explicit SavedCheckpoint(size_t aCheckpoint)
       : mCheckpoint(aCheckpoint) {}
 
   void ReleaseContents() {
     for (SavedThreadStack& stack : mStacks) {
       stack.ReleaseContents();
     }
   }
 };
--- a/toolkit/recordreplay/ipc/Channel.cpp
+++ b/toolkit/recordreplay/ipc/Channel.cpp
@@ -331,68 +331,29 @@ Message::UniquePtr Channel::WaitForMessa
 
 void Channel::PrintMessage(const char* aPrefix, const Message& aMsg) {
   if (!SpewEnabled()) {
     return;
   }
   AutoEnsurePassThroughThreadEvents pt;
   nsCString data;
   switch (aMsg.mType) {
-    case MessageType::HitExecutionPoint: {
-      const HitExecutionPointMessage& nmsg =
-          (const HitExecutionPointMessage&)aMsg;
-      nmsg.mPoint.ToString(data);
-      data.AppendPrintf(" Endpoint %d Duration %.2f ms",
-                        nmsg.mRecordingEndpoint,
-                        nmsg.mDurationMicroseconds / 1000.0);
-      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::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;
+    case MessageType::ManifestStart: {
+      const ManifestStartMessage& nmsg = (const ManifestStartMessage&)aMsg;
       data = NS_ConvertUTF16toUTF8(
           nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize()));
       break;
     }
-    case MessageType::DebuggerResponse: {
-      const DebuggerResponseMessage& nmsg =
-          (const DebuggerResponseMessage&)aMsg;
+    case MessageType::ManifestFinished: {
+      const ManifestFinishedMessage& nmsg =
+          (const ManifestFinishedMessage&)aMsg;
       data = NS_ConvertUTF16toUTF8(
           nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize()));
       break;
     }
-    case MessageType::SetIsActive: {
-      const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)aMsg;
-      data.AppendPrintf("%d", nmsg.mActive);
-      break;
-    }
-    case MessageType::SetSaveCheckpoint: {
-      const SetSaveCheckpointMessage& nmsg =
-          (const SetSaveCheckpointMessage&)aMsg;
-      data.AppendPrintf("Id %d, Save %d", (int)nmsg.mCheckpoint, nmsg.mSave);
-      break;
-    }
     default:
       break;
   }
   const char* kind =
       IsMiddleman() ? "Middleman" : (IsRecording() ? "Recording" : "Replaying");
   PrintSpew("%s%s:%d %s %s\n", kind, aPrefix, (int)mId, aMsg.TypeString(),
             data.get());
 }
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -37,116 +37,64 @@ namespace recordreplay {
 // rewind itself.
 //
 // Messages can be sent from the child process to the middleman only when the
 // child process is unpaused, and messages can only be sent from the middleman
 // to the child process when the child process is paused. This prevents
 // messages from being lost when they are sent from the middleman as the
 // replaying process rewinds itself. A few exceptions to this rule are noted
 // below.
-//
-// Some additional synchronization is needed between different child processes:
-// replaying processes can read from the same file which a recording process is
-// writing to. While it is ok for a replaying process to read from the file
-// while the recording process is appending new chunks to it (see File.cpp),
-// all replaying processes must be paused when the recording process is
-// flushing a new index to the file.
 
 #define ForEachMessageType(_Macro)                             \
   /* Messages sent from the middleman to the child process. */ \
                                                                \
   /* Sent at startup. */                                       \
   _Macro(Introduction)                                         \
                                                                \
   /* Sent to recording processes to indicate that the middleman will be running */ \
   /* developer tools server-side code instead of the recording process itself. */ \
   _Macro(SetDebuggerRunsInMiddleman)                           \
                                                                \
   /* Sent to recording processes when exiting, or to force a hanged replaying */ \
   /* process to crash. */                                      \
   _Macro(Terminate)                                            \
                                                                \
-  /* Flush the current recording to disk. */                   \
-  _Macro(FlushRecording)                                       \
-                                                               \
   /* 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)                                      \
-                                                               \
-  /* 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 */ \
-  /* HitExecutionPoint 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)                                    \
-                                                               \
-  /* Run forward to a particular execution point between the current checkpoint */ \
-  /* and the next one. */                                      \
-  _Macro(RunToPoint)                                           \
-                                                               \
-  /* Notify the child whether it is the active child and should send paint and similar */ \
-  /* messages to the middleman. */                             \
-  _Macro(SetIsActive)                                          \
-                                                               \
-  /* Set whether to perform intentional crashes, for testing. */ \
-  _Macro(SetAllowIntentionalCrashes)                           \
-                                                               \
-  /* Set whether to save a particular checkpoint. */           \
-  _Macro(SetSaveCheckpoint)                                    \
+  /* Unpause the child and perform a debugger-defined operation. */ \
+  _Macro(ManifestStart)                                             \
                                                                \
   /* Respond to a MiddlemanCallRequest message. */             \
   _Macro(MiddlemanCallResponse)                                \
                                                                \
   /* Messages sent from the child process to the middleman. */ \
                                                                \
-  /* Sent in response to a FlushRecording, telling the middleman that the flush */ \
-  /* has finished. */                                          \
-  _Macro(RecordingFlushed)                                     \
+  /* Pause after executing a manifest, specifying its response. */ \
+  _Macro(ManifestFinished)                                     \
                                                                \
   /* A critical error occurred and execution cannot continue. The child will */ \
   /* stop executing after sending this message and will wait to be terminated. */ \
   /* A minidump for the child has been generated. */           \
   _Macro(FatalError)                                           \
                                                                \
   /* Sent when a fatal error has occurred, but before the minidump has been */ \
   /* generated. */                                             \
   _Macro(BeginFatalError)                                      \
                                                                \
   /* The child's graphics were repainted. */                   \
   _Macro(Paint)                                                \
                                                                \
-  /* Notify the middleman that the child has hit an execution point and paused. */ \
-  _Macro(HitExecutionPoint)                                    \
-                                                               \
-  /* Send a response to a DebuggerRequest message. */          \
-  _Macro(DebuggerResponse)                                     \
-                                                               \
   /* Call a system function from the middleman process which the child has */ \
   /* encountered after diverging from the recording. */        \
   _Macro(MiddlemanCallRequest)                                 \
                                                                \
   /* Reset all information generated by previous MiddlemanCallRequest messages. */ \
-  _Macro(ResetMiddlemanCalls)                                  \
-                                                               \
-  /* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \
-  _Macro(AlwaysMarkMajorCheckpoints)
+  _Macro(ResetMiddlemanCalls)
 
 enum class MessageType {
 #define DefineEnum(Kind) Kind,
   ForEachMessageType(DefineEnum)
 #undef DefineEnum
 };
 
 struct Message {
@@ -190,17 +138,18 @@ struct Message {
   }
 
   // Return whether this is a middleman->child message that can be sent while
   // the child is unpaused.
   bool CanBeSentWhileUnpaused() const {
     return mType == MessageType::CreateCheckpoint ||
            mType == MessageType::SetDebuggerRunsInMiddleman ||
            mType == MessageType::MiddlemanCallResponse ||
-           mType == MessageType::Terminate;
+           mType == MessageType::Terminate ||
+           mType == MessageType::Introduction;
   }
 
  protected:
   template <typename T, typename Elem>
   Elem* Data() {
     return (Elem*)(sizeof(T) + (char*)this);
   }
 
@@ -270,17 +219,16 @@ template <MessageType Type>
 struct EmptyMessage : public Message {
   EmptyMessage() : Message(Type, sizeof(*this)) {}
 };
 
 typedef EmptyMessage<MessageType::SetDebuggerRunsInMiddleman>
     SetDebuggerRunsInMiddlemanMessage;
 typedef EmptyMessage<MessageType::Terminate> TerminateMessage;
 typedef EmptyMessage<MessageType::CreateCheckpoint> CreateCheckpointMessage;
-typedef EmptyMessage<MessageType::FlushRecording> FlushRecordingMessage;
 
 template <MessageType Type>
 struct JSONMessage : public Message {
   explicit JSONMessage(uint32_t aSize) : Message(Type, aSize) {}
 
   const char16_t* Buffer() const { return Data<JSONMessage<Type>, char16_t>(); }
   size_t BufferSize() const { return DataSize<JSONMessage<Type>, char16_t>(); }
 
@@ -288,85 +236,18 @@ struct JSONMessage : public Message {
     JSONMessage<Type>* res =
         NewWithData<JSONMessage<Type>, char16_t>(aBufferSize);
     MOZ_RELEASE_ASSERT(res->BufferSize() == aBufferSize);
     PodCopy(res->Data<JSONMessage<Type>, char16_t>(), aBuffer, aBufferSize);
     return res;
   }
 };
 
-typedef JSONMessage<MessageType::DebuggerRequest> DebuggerRequestMessage;
-typedef JSONMessage<MessageType::DebuggerResponse> DebuggerResponseMessage;
-
-struct AddBreakpointMessage : public Message {
-  js::BreakpointPosition mPosition;
-
-  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) {}
-};
-
-struct RestoreCheckpointMessage : public Message {
-  // The checkpoint to restore.
-  size_t mCheckpoint;
-
-  explicit RestoreCheckpointMessage(size_t aCheckpoint)
-      : Message(MessageType::RestoreCheckpoint, sizeof(*this)),
-        mCheckpoint(aCheckpoint) {}
-};
-
-struct RunToPointMessage : public Message {
-  // The target execution point.
-  js::ExecutionPoint mTarget;
-
-  explicit RunToPointMessage(const js::ExecutionPoint& aTarget)
-      : Message(MessageType::RunToPoint, sizeof(*this)), mTarget(aTarget) {}
-};
-
-struct SetIsActiveMessage : public Message {
-  // Whether this is the active child process (see ParentIPC.cpp).
-  bool mActive;
-
-  explicit SetIsActiveMessage(bool aActive)
-      : Message(MessageType::SetIsActive, sizeof(*this)), mActive(aActive) {}
-};
-
-struct SetAllowIntentionalCrashesMessage : public Message {
-  // Whether to allow intentional crashes in the future or not.
-  bool mAllowed;
-
-  explicit SetAllowIntentionalCrashesMessage(bool aAllowed)
-      : Message(MessageType::SetAllowIntentionalCrashes, sizeof(*this)),
-        mAllowed(aAllowed) {}
-};
-
-struct SetSaveCheckpointMessage : public Message {
-  // The checkpoint in question.
-  size_t mCheckpoint;
-
-  // Whether to save this checkpoint whenever it is encountered.
-  bool mSave;
-
-  SetSaveCheckpointMessage(size_t aCheckpoint, bool aSave)
-      : Message(MessageType::SetSaveCheckpoint, sizeof(*this)),
-        mCheckpoint(aCheckpoint),
-        mSave(aSave) {}
-};
-
-typedef EmptyMessage<MessageType::RecordingFlushed> RecordingFlushedMessage;
+typedef JSONMessage<MessageType::ManifestStart> ManifestStartMessage;
+typedef JSONMessage<MessageType::ManifestFinished> ManifestFinishedMessage;
 
 struct FatalErrorMessage : public Message {
   explicit FatalErrorMessage(uint32_t aSize)
       : Message(MessageType::FatalError, aSize) {}
 
   const char* Error() const { return Data<FatalErrorMessage, const char>(); }
 };
 
@@ -386,39 +267,16 @@ struct PaintMessage : public Message {
 
   PaintMessage(uint32_t aCheckpointId, uint32_t aWidth, uint32_t aHeight)
       : Message(MessageType::Paint, sizeof(*this)),
         mCheckpointId(aCheckpointId),
         mWidth(aWidth),
         mHeight(aHeight) {}
 };
 
-struct HitExecutionPointMessage : public Message {
-  // The point the child paused at.
-  js::ExecutionPoint mPoint;
-
-  // Whether the pause occurred due to hitting the end of the recording.
-  bool mRecordingEndpoint;
-
-  // The amount of non-idle time taken to get to this pause from the last time
-  // the child paused.
-  double mDurationMicroseconds;
-
-  HitExecutionPointMessage(const js::ExecutionPoint& aPoint,
-                           bool aRecordingEndpoint,
-                           double aDurationMicroseconds)
-      : Message(MessageType::HitExecutionPoint, sizeof(*this)),
-        mPoint(aPoint),
-        mRecordingEndpoint(aRecordingEndpoint),
-        mDurationMicroseconds(aDurationMicroseconds) {}
-};
-
-typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints>
-    AlwaysMarkMajorCheckpointsMessage;
-
 template <MessageType Type>
 struct BinaryMessage : public Message {
   explicit BinaryMessage(uint32_t aSize) : Message(Type, aSize) {}
 
   const char* BinaryData() const { return Data<BinaryMessage<Type>, char>(); }
   size_t BinaryDataSize() const {
     return DataSize<BinaryMessage<Type>, char>();
   }
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -72,27 +72,29 @@ static UniquePtr<MiddlemanCallResponseMe
 static bool gWaitingForCallResponse;
 
 // Processing routine for incoming channel messages.
 static void ChannelMessageHandler(Message::UniquePtr aMsg) {
   MOZ_RELEASE_ASSERT(MainThreadShouldPause() || aMsg->CanBeSentWhileUnpaused());
 
   switch (aMsg->mType) {
     case MessageType::Introduction: {
+      MonitorAutoLock lock(*gMonitor);
       MOZ_RELEASE_ASSERT(!gIntroductionMessage);
       gIntroductionMessage.reset(
           static_cast<IntroductionMessage*>(aMsg.release()));
+      gMonitor->NotifyAll();
       break;
     }
     case MessageType::CreateCheckpoint: {
       MOZ_RELEASE_ASSERT(IsRecording());
 
       // Ignore requests to create checkpoints before we have reached the first
       // paint and finished initializing.
-      if (navigation::IsInitialized()) {
+      if (js::IsInitialized()) {
         uint8_t data = 0;
         DirectWrite(gCheckpointWriteFd, &data, 1);
       }
       break;
     }
     case MessageType::SetDebuggerRunsInMiddleman: {
       MOZ_RELEASE_ASSERT(IsRecording());
       PauseMainThreadAndInvokeCallback(
@@ -108,76 +110,24 @@ static void ChannelMessageHandler(Messag
       if (IsRecording()) {
         PrintSpew("Terminate message received, exiting...\n");
         _exit(0);
       } else {
         ReportFatalError(Nothing(), "Hung replaying process");
       }
       break;
     }
-    case MessageType::SetIsActive: {
-      const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)*aMsg;
-      PauseMainThreadAndInvokeCallback(
-          [=]() { SetIsActiveChild(nmsg.mActive); });
-      break;
-    }
-    case MessageType::SetAllowIntentionalCrashes: {
-      const SetAllowIntentionalCrashesMessage& nmsg =
-          (const SetAllowIntentionalCrashesMessage&)*aMsg;
-      PauseMainThreadAndInvokeCallback(
-          [=]() { SetAllowIntentionalCrashes(nmsg.mAllowed); });
-      break;
-    }
-    case MessageType::SetSaveCheckpoint: {
-      const SetSaveCheckpointMessage& nmsg =
-          (const SetSaveCheckpointMessage&)*aMsg;
-      PauseMainThreadAndInvokeCallback(
-          [=]() { SetSaveCheckpoint(nmsg.mCheckpoint, nmsg.mSave); });
-      break;
-    }
-    case MessageType::FlushRecording: {
-      PauseMainThreadAndInvokeCallback(FlushRecording);
-      break;
-    }
-    case MessageType::DebuggerRequest: {
-      const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&)*aMsg;
+    case MessageType::ManifestStart: {
+      const ManifestStartMessage& nmsg = (const ManifestStartMessage&)*aMsg;
       js::CharBuffer* buf = new js::CharBuffer();
       buf->append(nmsg.Buffer(), nmsg.BufferSize());
-      PauseMainThreadAndInvokeCallback(
-          [=]() { navigation::DebuggerRequest(buf); });
-      break;
-    }
-    case MessageType::AddBreakpoint: {
-      const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&)*aMsg;
-      PauseMainThreadAndInvokeCallback(
-          [=]() { 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: {
-      const RestoreCheckpointMessage& nmsg =
-          (const RestoreCheckpointMessage&)*aMsg;
-      PauseMainThreadAndInvokeCallback(
-          [=]() { navigation::RestoreCheckpoint(nmsg.mCheckpoint); });
-      break;
-    }
-    case MessageType::RunToPoint: {
-      const RunToPointMessage& nmsg = (const RunToPointMessage&)*aMsg;
-      PauseMainThreadAndInvokeCallback(
-          [=]() { navigation::RunToPoint(nmsg.mTarget); });
+      PauseMainThreadAndInvokeCallback([=]() {
+          js::ManifestStart(*buf);
+          delete buf;
+        });
       break;
     }
     case MessageType::MiddlemanCallResponse: {
       MonitorAutoLock lock(*gMonitor);
       MOZ_RELEASE_ASSERT(gWaitingForCallResponse);
       MOZ_RELEASE_ASSERT(!gCallResponseMessage);
       gCallResponseMessage.reset(
           static_cast<MiddlemanCallResponseMessage*>(aMsg.release()));
@@ -196,18 +146,17 @@ static void ChannelMessageHandler(Messag
 // thread's behavior will be replicated exactly when replaying and new
 // checkpoints will be created at the same point as during recording.
 static void ListenForCheckpointThreadMain(void*) {
   while (true) {
     uint8_t data = 0;
     ssize_t rv = HANDLE_EINTR(read(gCheckpointReadFd, &data, 1));
     if (rv > 0) {
       NS_DispatchToMainThread(NewRunnableFunction("NewCheckpoint",
-                                                  NewCheckpoint,
-                                                  /* aTemporary = */ false));
+                                                  NewCheckpoint));
     } else {
       MOZ_RELEASE_ASSERT(errno == EIO);
       MOZ_RELEASE_ASSERT(HasDivergedFromRecording());
       Thread::WaitForever();
     }
   }
 }
 
@@ -287,26 +236,30 @@ void InitRecordingOrReplayingProcess(int
   // The graphics shared memory contents are excluded from snapshots. We do not
   // want checkpoint restores in this child to interfere with drawing being
   // performed by another child.
   AddInitialUntrackedMemoryRegion((uint8_t*)gGraphicsShmem,
                                   parent::GraphicsMemorySize);
 
   pt.reset();
 
-  // We are ready to receive initialization messages from the middleman, pause
-  // so they can be sent.
-  HitExecutionPoint(js::ExecutionPoint(), /* aRecordingEndpoint = */ false);
-
   // If we failed to initialize then report it to the user.
   if (gInitializationFailureMessage) {
     ReportFatalError(Nothing(), "%s", gInitializationFailureMessage);
     Unreachable();
   }
 
+  // Wait for the parent to send us the introduction message.
+  {
+    MonitorAutoLock lock(*gMonitor);
+    while (!gIntroductionMessage) {
+      gMonitor->Wait();
+    }
+  }
+
   // Process the introduction message to fill in arguments.
   MOZ_RELEASE_ASSERT(gParentArgv.empty());
 
   gParentPid = gIntroductionMessage->mParentPid;
 
   // Record/replay the introduction message itself so we get consistent args
   // between recording and replaying.
   {
@@ -339,17 +292,17 @@ base::ProcessId MiddlemanProcessId() { r
 base::ProcessId ParentProcessId() { return gParentPid; }
 
 bool DebuggerRunsInMiddleman() {
   return RecordReplayValue(gDebuggerRunsInMiddleman);
 }
 
 void CreateCheckpoint() {
   if (!HasDivergedFromRecording()) {
-    NewCheckpoint(/* aTemporary = */ false);
+    NewCheckpoint();
   }
 }
 
 void ReportFatalError(const Maybe<MinidumpInfo>& aMinidump, const char* aFormat,
                       ...) {
   // Notify the middleman that we are crashing and are going to try to write a
   // minidump.
   gChannel->SendMessage(BeginFatalErrorMessage());
@@ -387,26 +340,16 @@ void ReportFatalError(const Maybe<Minidu
   DirectPrint("***** Fatal Record/Replay Error *****\n");
   DirectPrint(buf);
   DirectPrint("\n");
 
   // Block until we get a terminate message and die.
   Thread::WaitForeverNoIdle();
 }
 
-void NotifyFlushedRecording() {
-  gChannel->SendMessage(RecordingFlushedMessage());
-}
-
-void NotifyAlwaysMarkMajorCheckpoints() {
-  if (IsActiveChild()) {
-    gChannel->SendMessage(AlwaysMarkMajorCheckpointsMessage());
-  }
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Vsyncs
 ///////////////////////////////////////////////////////////////////////////////
 
 static VsyncObserver* gVsyncObserver;
 
 void SetVsyncObserver(VsyncObserver* aObserver) {
   MOZ_RELEASE_ASSERT(!gVsyncObserver || !aObserver);
@@ -538,20 +481,19 @@ static void PaintFromMainThread() {
     // so don't send a Paint message.
     return;
   }
 
   // If all paints have completed, the compositor cannot be simultaneously
   // operating on the draw target buffer.
   MOZ_RELEASE_ASSERT(!gNumPendingPaints);
 
-  if (IsActiveChild() && navigation::ShouldSendPaintMessage() &&
-      gDrawTargetBuffer) {
+  if (IsMainChild() && gDrawTargetBuffer) {
     memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
-    gChannel->SendMessage(PaintMessage(navigation::LastNormalCheckpoint(),
+    gChannel->SendMessage(PaintMessage(GetLastCheckpoint(),
                                        gPaintWidth, gPaintHeight));
   }
 }
 
 void NotifyPaintComplete() {
   MOZ_RELEASE_ASSERT(!gCompositorThreadId ||
                      Thread::Current()->Id() == gCompositorThreadId);
 
@@ -626,64 +568,27 @@ void Repaint(size_t* aWidth, size_t* aHe
   }
 }
 
 bool CurrentRepaintCannotFail() {
   return gRepainting && !gAllowRepaintFailures;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Checkpoint Messages
-///////////////////////////////////////////////////////////////////////////////
-
-// The time when the last HitExecutionPoint message was sent.
-static double gLastPauseTime;
-
-// When recording and we are idle, the time when we became idle.
-static double gIdleTimeStart;
-
-void BeginIdleTime() {
-  MOZ_RELEASE_ASSERT(IsRecording() && NS_IsMainThread() && !gIdleTimeStart);
-  gIdleTimeStart = CurrentTime();
-}
-
-void EndIdleTime() {
-  MOZ_RELEASE_ASSERT(IsRecording() && NS_IsMainThread() && gIdleTimeStart);
-
-  // Erase the idle time from our measurements by advancing the last checkpoint
-  // time.
-  gLastPauseTime += CurrentTime() - gIdleTimeStart;
-  gIdleTimeStart = 0;
-}
-
-void HitExecutionPoint(const js::ExecutionPoint& aPoint,
-                       bool aRecordingEndpoint) {
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  double time = CurrentTime();
-  PauseMainThreadAndInvokeCallback([=]() {
-    double duration = 0;
-    if (gLastPauseTime) {
-      duration = time - gLastPauseTime;
-      MOZ_RELEASE_ASSERT(duration > 0);
-    }
-    gChannel->SendMessage(
-        HitExecutionPointMessage(aPoint, aRecordingEndpoint, duration));
-  });
-  gLastPauseTime = time;
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // Message Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
-void RespondToRequest(const js::CharBuffer& aBuffer) {
-  DebuggerResponseMessage* msg =
-      DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
-  gChannel->SendMessage(std::move(*msg));
-  free(msg);
+void ManifestFinished(const js::CharBuffer& aBuffer) {
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  ManifestFinishedMessage* msg =
+    ManifestFinishedMessage::New(aBuffer.begin(), aBuffer.length());
+  PauseMainThreadAndInvokeCallback([=]() {
+    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) {
--- a/toolkit/recordreplay/ipc/ChildInternal.h
+++ b/toolkit/recordreplay/ipc/ChildInternal.h
@@ -8,96 +8,23 @@
 #define mozilla_recordreplay_ChildInternal_h
 
 #include "Channel.h"
 #include "ChildIPC.h"
 #include "JSControl.h"
 #include "MiddlemanCall.h"
 #include "Monitor.h"
 
-namespace mozilla {
-namespace recordreplay {
-
 // This file has internal definitions for communication between the main
 // record/replay infrastructure and child side IPC code.
 
-// The navigation namespace has definitions for managing breakpoints and all
-// other state that persists across rewinds, and for keeping track of the
-// precise execution position of the child process. The middleman will send the
-// child process Resume messages to travel forward and backward, but it is up
-// to the child process to keep track of the rewinding and resuming necessary
-// to find the next or previous point where a breakpoint or checkpoint is hit.
-namespace navigation {
-
-// Navigation state is initialized when the first checkpoint is reached.
-bool IsInitialized();
-
-// In a recording process, get the current execution point, aka the endpoint
-// of the recording.
-js::ExecutionPoint GetRecordingEndpoint();
-
-// In a replaying process, set the recording endpoint. |index| is used to
-// differentiate different endpoints that have been sequentially written to
-// 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 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();
-
-// Notify navigation that a position was hit.
-void PositionHit(const js::BreakpointPosition& aPosition);
-
-// Get an execution point for hitting the specified position right now.
-js::ExecutionPoint CurrentExecutionPoint(
-    const Maybe<js::BreakpointPosition>& aPosition);
-
-// Convert an identifier from NewTimeWarpTarget() which we have seen while
-// executing into an ExecutionPoint.
-js::ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget);
-
-// Synchronously paint the current contents into the graphics shared memory
-// object, returning the size of the painted area via aWidth/aHeight.
-void Repaint(size_t* aWidth, size_t* aHeight);
-
-// Called when running forward, immediately before hitting a normal or
-// temporary checkpoint.
-void BeforeCheckpoint();
-
-// Called immediately after hitting a normal or temporary checkpoint, either
-// when running forward or immediately after rewinding.
-void AfterCheckpoint(const CheckpointId& aCheckpoint);
-
-// Get the ID of the last normal checkpoint.
-size_t LastNormalCheckpoint();
-
-// Whether to send a paint message for the last normal checkpoint reached.
-bool ShouldSendPaintMessage();
-
-}  // namespace navigation
-
+namespace mozilla {
+namespace recordreplay {
 namespace child {
 
-// IPC activity that can be triggered by navigation.
-void RespondToRequest(const js::CharBuffer& aBuffer);
-void HitExecutionPoint(const js::ExecutionPoint& aPoint,
-                       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;
   mach_port_t mThread;
 
@@ -110,36 +37,28 @@ struct MinidumpInfo {
 
 // Generate a minidump and report a fatal error to the middleman process.
 void ReportFatalError(const Maybe<MinidumpInfo>& aMinidumpInfo,
                       const char* aFormat, ...);
 
 // Monitor used for various synchronization tasks.
 extern Monitor* gMonitor;
 
-// Notify the middleman that the recording was flushed.
-void NotifyFlushedRecording();
-
-// Notify the middleman about an AlwaysMarkMajorCheckpoints directive.
-void NotifyAlwaysMarkMajorCheckpoints();
-
-// Mark a time span when the main thread is idle.
-void BeginIdleTime();
-void EndIdleTime();
-
 // Whether the middleman runs developer tools server code.
 bool DebuggerRunsInMiddleman();
 
+// Notify the middleman that the last manifest was finished.
+void ManifestFinished(const js::CharBuffer& aResponse);
+
 // Send messages operating on middleman calls.
 void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
                               InfallibleVector<char>* aOutputData);
 void SendResetMiddlemanCalls();
 
 // Return whether a repaint is in progress and is not allowed to trigger an
 // unhandled recording divergence per preferences.
 bool CurrentRepaintCannotFail();
 
 }  // namespace child
-
 }  // namespace recordreplay
 }  // namespace mozilla
 
 #endif  // mozilla_recordreplay_ChildInternal_h
deleted file mode 100644
--- a/toolkit/recordreplay/ipc/ChildNavigation.cpp
+++ /dev/null
@@ -1,1195 +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:
- * 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 "ChildInternal.h"
-
-#include "ProcessRecordReplay.h"
-
-namespace mozilla {
-namespace recordreplay {
-namespace navigation {
-
-typedef js::BreakpointPosition BreakpointPosition;
-typedef js::ExecutionPoint ExecutionPoint;
-
-///////////////////////////////////////////////////////////////////////////////
-// Navigation State
-///////////////////////////////////////////////////////////////////////////////
-
-// The navigation state of a recording/replaying process describes where the
-// process currently is and what it is doing in order to respond to messages
-// from the middleman process.
-//
-// At all times, the navigation state will be in exactly one of the following
-// phases:
-//
-// - Paused: The process is paused somewhere.
-// - Forward: The process is running forward and scanning for breakpoint hits.
-// - ReachBreakpoint: The process is running forward from a checkpoint to a
-//     particular execution point before the next checkpoint.
-// - FindLastHit: The process is running forward and keeping track of the last
-//     point a breakpoint was hit within an execution region.
-//
-// This file manages data associated with each of these phases and the
-// transitions that occur between them as the process executes or new
-// messages are received from the middleman.
-
-typedef AllocPolicy<MemoryKind::Navigation> UntrackedAllocPolicy;
-
-// Abstract class for where we are at in the navigation state machine.
-// Each subclass has a single instance contained in NavigationState (see below)
-// and it and all its data are allocated using untracked memory that is not
-// affected by restoring earlier checkpoints.
-class NavigationPhase {
-  // All virtual members should only be accessed through NavigationState.
-  friend class NavigationState;
-
- private:
-  MOZ_NORETURN void Unsupported(const char* aOperation) {
-    nsAutoCString str;
-    ToString(str);
-
-    Print("Operation %s not supported: %s\n", aOperation, str.get());
-    MOZ_CRASH("Unsupported navigation operation");
-  }
-
- public:
-  virtual void ToString(nsAutoCString& aStr) = 0;
-
-  // The process has just reached or rewound to a checkpoint.
-  virtual void AfterCheckpoint(const CheckpointId& aCheckpoint) {
-    Unsupported("AfterCheckpoint");
-  }
-
-  // Called when some position with an installed handler has been reached.
-  virtual void PositionHit(const ExecutionPoint& aPoint) {
-    Unsupported("PositionHit");
-  }
-
-  // Called after receiving a resume command from the middleman.
-  virtual void Resume(bool aForward) { Unsupported("Resume"); }
-
-  // Called after the middleman tells us to rewind to a specific checkpoint.
-  virtual void RestoreCheckpoint(size_t aCheckpoint) {
-    Unsupported("RestoreCheckpoint");
-  }
-
-  // Called after the middleman tells us to run forward to a specific point.
-  virtual void RunToPoint(const ExecutionPoint& aTarget) {
-    Unsupported("RunToPoint");
-  }
-
-  // Process an incoming debugger request from the middleman.
-  virtual void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) {
-    Unsupported("HandleDebuggerRequest");
-  }
-
-  // Called when a debugger request wants to try an operation that may
-  // trigger an unhandled divergence from the recording.
-  virtual bool MaybeDivergeFromRecording() {
-    Unsupported("MaybeDivergeFromRecording");
-  }
-
-  // Get the current execution point.
-  virtual ExecutionPoint CurrentExecutionPoint() {
-    Unsupported("CurrentExecutionPoint");
-  }
-
-  // Called when execution reaches the endpoint of the recording.
-  virtual void HitRecordingEndpoint(const ExecutionPoint& aPoint) {
-    Unsupported("HitRecordingEndpoint");
-  }
-
-  // Called when a paint has occurred for the last normal checkpoint.
-  virtual bool ShouldSendPaintMessage() {
-    Unsupported("ShouldSendPaintMessage");
-  }
-};
-
-// Information about a debugger request sent by the middleman.
-struct RequestInfo {
-  // JSON contents for the request and response.
-  InfallibleVector<char16_t, 0, UntrackedAllocPolicy> mRequestBuffer;
-  InfallibleVector<char16_t, 0, UntrackedAllocPolicy> mResponseBuffer;
-
-  // Whether processing this request triggered an unhandled divergence.
-  bool mUnhandledDivergence;
-
-  RequestInfo() : mUnhandledDivergence(false) {}
-
-  RequestInfo(const RequestInfo& o)
-      : 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;
-
-// 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;
-
-  // All debugger requests we have seen while paused here.
-  UntrackedRequestVector mRequests;
-
-  // Index of the request currently being processed. Normally this is the
-  // last entry in |mRequests|, though may be earlier if we are recovering
-  // from an unhandled divergence.
-  size_t mRequestIndex;
-
-  // Whether we have saved a temporary checkpoint.
-  bool mSavedTemporaryCheckpoint;
-
-  // Whether we had to restore a checkpoint to deal with an unhandled
-  // 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, 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;
-  void Resume(bool aForward) override;
-  void RestoreCheckpoint(size_t aCheckpoint) override;
-  void RunToPoint(const ExecutionPoint& aTarget) override;
-  void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) override;
-  bool MaybeDivergeFromRecording() override;
-  ExecutionPoint CurrentExecutionPoint() override;
-
-  bool EnsureTemporaryCheckpoint();
-};
-
-// Phase when execution is proceeding forwards in search of breakpoint hits.
-class ForwardPhase final : public NavigationPhase {
-  // Some execution point in the recent past. There are no checkpoints or
-  // breakpoint hits between this point and the current point of execution.
-  ExecutionPoint mPoint;
-
- public:
-  void Enter(const ExecutionPoint& aPoint);
-
-  void ToString(nsAutoCString& aStr) override { aStr.AppendPrintf("Forward"); }
-
-  void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
-  void PositionHit(const ExecutionPoint& aPoint) override;
-  void HitRecordingEndpoint(const ExecutionPoint& aPoint) override;
-  bool ShouldSendPaintMessage() override;
-};
-
-// Phase when the replaying process is running forward from a checkpoint to a
-// breakpoint at a particular execution point.
-class ReachBreakpointPhase final : public NavigationPhase {
- private:
-  // Where to start running from.
-  CheckpointId mStart;
-
-  // The point we are running to.
-  ExecutionPoint mPoint;
-
-  // Point at which to decide whether to save a temporary checkpoint.
-  Maybe<ExecutionPoint> mTemporaryCheckpoint;
-
-  // Whether we have saved a temporary checkpoint at the specified point.
-  bool mSavedTemporaryCheckpoint;
-
-  // The time at which we started running forward from the initial
-  // checkpoint, in microseconds.
-  double mStartTime;
-
- public:
-  void Enter(const CheckpointId& aStart, bool aRewind,
-             const ExecutionPoint& aPoint,
-             const Maybe<ExecutionPoint>& aTemporaryCheckpoint);
-
-  void ToString(nsAutoCString& aStr) override {
-    aStr.AppendPrintf("ReachBreakpoint: ");
-    mPoint.ToString(aStr);
-    if (mTemporaryCheckpoint.isSome()) {
-      aStr.AppendPrintf(" TemporaryCheckpoint: ");
-      mTemporaryCheckpoint.ref().ToString(aStr);
-    }
-  }
-
-  void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
-  void PositionHit(const ExecutionPoint& aPoint) override;
-  bool ShouldSendPaintMessage() override;
-};
-
-// Phase when the replaying process is searching forward from a checkpoint to
-// find the last point a breakpoint is hit before reaching an execution point.
-class FindLastHitPhase final : public NavigationPhase {
-  // Where we started searching from.
-  CheckpointId mStart;
-
-  // Endpoint of the search.
-  ExecutionPoint mEnd;
-
-  // Whether the endpoint itself is considered to be part of the search space.
-  bool mIncludeEnd;
-
-  // Counter that increases as we run forward, for ordering hits.
-  size_t mCounter;
-
-  // All positions we are interested in hits for, including all breakpoint
-  // positions (and possibly other positions).
-  struct TrackedPosition {
-    BreakpointPosition mPosition;
-
-    // The last time this was hit so far, or invalid.
-    ExecutionPoint mLastHit;
-
-    // The value of the counter when the last hit occurred.
-    size_t mLastHitCount;
-
-    explicit TrackedPosition(const BreakpointPosition& aPosition)
-        : mPosition(aPosition), mLastHitCount(0) {}
-  };
-  InfallibleVector<TrackedPosition, 4, UntrackedAllocPolicy> mTrackedPositions;
-
-  const TrackedPosition& FindTrackedPosition(const BreakpointPosition& aPos);
-  void CheckForRegionEnd(const ExecutionPoint& aPoint);
-  void OnRegionEnd();
-
- public:
-  // Note: this always rewinds.
-  void Enter(const CheckpointId& aStart, const ExecutionPoint& aEnd,
-             bool aIncludeEnd);
-
-  void ToString(nsAutoCString& aStr) override {
-    aStr.AppendPrintf("FindLastHit #%zu:%zu", mStart.mNormal,
-                      mStart.mTemporary);
-    mEnd.ToString(aStr);
-  }
-
-  void AfterCheckpoint(const CheckpointId& aCheckpoint) override;
-  void PositionHit(const ExecutionPoint& aPoint) override;
-  void HitRecordingEndpoint(const ExecutionPoint& aPoint) override;
-  bool ShouldSendPaintMessage() override;
-};
-
-// Structure which manages state about the breakpoints in existence and about
-// how the process is being navigated through. This is allocated in untracked
-// memory and its contents will not change when restoring an earlier
-// checkpoint.
-class NavigationState {
-  // When replaying, the last known recording endpoint. There may be other,
-  // later endpoints we haven't been informed about.
-  ExecutionPoint mRecordingEndpoint;
-  size_t mRecordingEndpointIndex;
-
-  // 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.
-  InfallibleVector<BreakpointPosition, 4, UntrackedAllocPolicy> mBreakpoints;
-
-  CheckpointId LastCheckpoint() { return mLastCheckpoint; }
-
-  // The current phase of the process.
-  NavigationPhase* mPhase;
-
-  void SetPhase(NavigationPhase* phase) {
-    mPhase = phase;
-
-    if (SpewEnabled()) {
-      nsAutoCString str;
-      mPhase->ToString(str);
-
-      PrintSpew("SetNavigationPhase %s\n", str.get());
-    }
-  }
-
-  PausedPhase mPausedPhase;
-  ForwardPhase mForwardPhase;
-  ReachBreakpointPhase mReachBreakpointPhase;
-  FindLastHitPhase mFindLastHitPhase;
-
-  // For testing, specify that temporary checkpoints should be taken regardless
-  // of how much time has elapsed.
-  bool mAlwaysSaveTemporaryCheckpoints;
-
-  // Progress counts for all checkpoints that have been encountered.
-  InfallibleVector<ProgressCounter, 0, UntrackedAllocPolicy>
-      mCheckpointProgress;
-
-  // Note: NavigationState is initially zeroed.
-  NavigationState() : mPhase(&mForwardPhase) {
-    if (IsReplaying()) {
-      // The recording must include everything up to the first
-      // checkpoint. After that point we will ask the record/replay
-      // system to notify us about any further endpoints.
-      mRecordingEndpoint = ExecutionPoint(CheckpointId::First, 0);
-    }
-    mCheckpointProgress.append(0);
-  }
-
-  void AfterCheckpoint(const CheckpointId& aCheckpoint) {
-    mLastCheckpoint = aCheckpoint;
-
-    // Forget any temporary checkpoints we just rewound past, or made
-    // obsolete by reaching the next normal checkpoint.
-    while (mTemporaryCheckpoints.length() > aCheckpoint.mTemporary) {
-      mTemporaryCheckpoints.popBack();
-    }
-
-    // Update the progress counter for each normal checkpoint.
-    if (!aCheckpoint.mTemporary) {
-      ProgressCounter progress = *ExecutionProgressCounter();
-      if (aCheckpoint.mNormal < mCheckpointProgress.length()) {
-        MOZ_RELEASE_ASSERT(progress ==
-                           mCheckpointProgress[aCheckpoint.mNormal]);
-      } else {
-        MOZ_RELEASE_ASSERT(aCheckpoint.mNormal == mCheckpointProgress.length());
-        mCheckpointProgress.append(progress);
-      }
-    }
-
-    mPhase->AfterCheckpoint(aCheckpoint);
-
-    // Make sure we don't run past the end of the recording.
-    if (!aCheckpoint.mTemporary) {
-      CheckForRecordingEndpoint(CheckpointExecutionPoint(aCheckpoint.mNormal));
-    }
-
-    MOZ_RELEASE_ASSERT(IsRecording() ||
-                       aCheckpoint.mNormal <= mRecordingEndpoint.mCheckpoint);
-    if (aCheckpoint.mNormal == mRecordingEndpoint.mCheckpoint &&
-        mRecordingEndpoint.HasPosition()) {
-      js::EnsurePositionHandler(mRecordingEndpoint.mPosition);
-    }
-  }
-
-  void PositionHit(const ExecutionPoint& aPoint) {
-    mPhase->PositionHit(aPoint);
-    CheckForRecordingEndpoint(aPoint);
-  }
-
-  void Resume(bool aForward) { mPhase->Resume(aForward); }
-
-  void RestoreCheckpoint(size_t aCheckpoint) {
-    mPhase->RestoreCheckpoint(aCheckpoint);
-  }
-
-  void RunToPoint(const ExecutionPoint& aTarget) {
-    mPhase->RunToPoint(aTarget);
-  }
-
-  void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) {
-    mPhase->HandleDebuggerRequest(aRequestBuffer);
-  }
-
-  bool MaybeDivergeFromRecording() {
-    return mPhase->MaybeDivergeFromRecording();
-  }
-
-  ExecutionPoint CurrentExecutionPoint() {
-    return mPhase->CurrentExecutionPoint();
-  }
-
-  bool ShouldSendPaintMessage() { return mPhase->ShouldSendPaintMessage(); }
-
-  void SetRecordingEndpoint(size_t aIndex, const ExecutionPoint& aEndpoint) {
-    // Ignore endpoints older than the last one we know about.
-    if (aIndex <= mRecordingEndpointIndex) {
-      return;
-    }
-    MOZ_RELEASE_ASSERT(mRecordingEndpoint.mCheckpoint <= aEndpoint.mCheckpoint);
-    mRecordingEndpointIndex = aIndex;
-    mRecordingEndpoint = aEndpoint;
-    if (aEndpoint.HasPosition()) {
-      js::EnsurePositionHandler(aEndpoint.mPosition);
-    }
-  }
-
-  void CheckForRecordingEndpoint(const ExecutionPoint& aPoint) {
-    while (aPoint == mRecordingEndpoint) {
-      // The recording ended after the checkpoint, but maybe there is
-      // another, later endpoint now. This may call back into
-      // setRecordingEndpoint and notify us there is more recording data
-      // available.
-      if (!recordreplay::HitRecordingEndpoint()) {
-        mPhase->HitRecordingEndpoint(mRecordingEndpoint);
-      }
-    }
-  }
-
-  ExecutionPoint LastRecordingEndpoint() {
-    // Get the last recording endpoint in the recording file.
-    while (recordreplay::HitRecordingEndpoint()) {
-    }
-    return mRecordingEndpoint;
-  }
-
-  bool SaveTemporaryCheckpoint(const ExecutionPoint& aPoint) {
-    MOZ_RELEASE_ASSERT(aPoint.mCheckpoint == mLastCheckpoint.mNormal);
-    mTemporaryCheckpoints.append(aPoint);
-    return NewCheckpoint(/* aTemporary = */ true);
-  }
-
-  ExecutionPoint LastTemporaryCheckpointLocation() {
-    MOZ_RELEASE_ASSERT(!mTemporaryCheckpoints.empty());
-    return mTemporaryCheckpoints.back();
-  }
-
-  ExecutionPoint CheckpointExecutionPoint(size_t aCheckpoint) {
-    MOZ_RELEASE_ASSERT(aCheckpoint < mCheckpointProgress.length());
-    return ExecutionPoint(aCheckpoint, mCheckpointProgress[aCheckpoint]);
-  }
-};
-
-static NavigationState* gNavigation;
-
-// When searching backwards in the execution space, we need to ignore any
-// temporary checkpoints associated with old normal checkpoints. We don't
-// remember what execution points these old temporary checkpoints are
-// associated with.
-static CheckpointId SkipUnknownTemporaryCheckpoints(
-    const CheckpointId& aCheckpoint) {
-  CheckpointId rval = aCheckpoint;
-  while (rval.mTemporary &&
-         rval.mNormal != gNavigation->LastCheckpoint().mNormal) {
-    rval = GetLastSavedCheckpointPriorTo(rval);
-  }
-  return rval;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Paused Phase
-///////////////////////////////////////////////////////////////////////////////
-
-static bool ThisProcessCanRewind() { return HasSavedCheckpoint(); }
-
-void 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);
-
-  if (aRewind) {
-    MOZ_RELEASE_ASSERT(!aPoint.HasPosition());
-    RestoreCheckpointAndResume(CheckpointId(aPoint.mCheckpoint));
-    Unreachable();
-  }
-
-  child::HitExecutionPoint(aPoint, aRecordingEndpoint);
-}
-
-void PausedPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
-  MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
-  if (!aCheckpoint.mTemporary) {
-    // We just rewound here, and are now where we should pause.
-    MOZ_RELEASE_ASSERT(
-        mPoint == gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal));
-    child::HitExecutionPoint(mPoint, mRecordingEndpoint);
-  } else {
-    // We just saved or restored the temporary checkpoint taken while
-    // processing debugger requests here.
-    MOZ_RELEASE_ASSERT(ThisProcessCanRewind());
-    MOZ_RELEASE_ASSERT(mSavedTemporaryCheckpoint);
-  }
-}
-
-void PausedPhase::PositionHit(const ExecutionPoint& aPoint) {
-  // Ignore positions hit while paused (we're probably doing an eval).
-}
-
-void PausedPhase::Resume(bool aForward) {
-  MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
-  MOZ_RELEASE_ASSERT(!mResumeForward);
-
-  if (aForward) {
-    // If we have saved any temporary checkpoint, we performed an operation
-    // that may have side effects. Clear these unwanted changes by restoring
-    // the temporary checkpoint we saved earlier.
-    if (mSavedTemporaryCheckpoint) {
-      mResumeForward = true;
-      RestoreCheckpointAndResume(gNavigation->LastCheckpoint());
-      Unreachable();
-    }
-
-    js::ClearPausedState();
-
-    // Run forward from the current execution point.
-    gNavigation->mForwardPhase.Enter(mPoint);
-    return;
-  }
-
-  // Search backwards in the execution space, from the last saved checkpoint to
-  // where we are paused.
-  CheckpointId start = GetLastSavedCheckpoint();
-
-  // Skip over any temporary checkpoint we saved.
-  if (mSavedTemporaryCheckpoint) {
-    start = GetLastSavedCheckpointPriorTo(start);
-  }
-
-  // Skip to the previous saved checkpoint if we are paused at a checkpoint.
-  if (!mPoint.HasPosition() && start == CheckpointId(mPoint.mCheckpoint)) {
-    start = GetLastSavedCheckpointPriorTo(start);
-  }
-
-  start = SkipUnknownTemporaryCheckpoints(start);
-  gNavigation->mFindLastHitPhase.Enter(start, mPoint,
-                                       /* aIncludeEnd = */ false);
-  Unreachable();
-}
-
-void PausedPhase::RestoreCheckpoint(size_t aCheckpoint) {
-  ExecutionPoint target = gNavigation->CheckpointExecutionPoint(aCheckpoint);
-  bool rewind = target != mPoint;
-  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());
-
-  ResumeExecution();
-
-  // If we saved a temporary checkpoint, we need to rewind to erase any side
-  // effects that have happened, as when resuming forward.
-  gNavigation->mReachBreakpointPhase.Enter(
-      gNavigation->LastCheckpoint(), /* aRewind = */ mSavedTemporaryCheckpoint,
-      aTarget, /* aTemporaryCheckpoint = */ Nothing());
-}
-
-void PausedPhase::HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) {
-  MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
-  MOZ_RELEASE_ASSERT(!mResumeForward);
-
-  mRequests.emplaceBack();
-  size_t index = mRequests.length() - 1;
-  mRequests[index].mRequestBuffer.append(aRequestBuffer->begin(),
-                                         aRequestBuffer->length());
-
-  mRequestIndex = index;
-
-  js::CharBuffer responseBuffer;
-  js::ProcessRequest(aRequestBuffer->begin(), aRequestBuffer->length(),
-                     &responseBuffer);
-
-  delete aRequestBuffer;
-
-  if (gNavigation->mPhase != this) {
-    // We saved a temporary checkpoint by calling MaybeDivergeFromRecording
-    // within ProcessRequest, then restored it while scanning backwards.
-    ResumeExecution();
-    return;
-  }
-
-  if (!mResumeForward && !mRecoveringFromDivergence) {
-    // We processed this request normally. Remember the response and send it to
-    // the middleman process.
-    MOZ_RELEASE_ASSERT(index == mRequestIndex);
-    mRequests[index].mResponseBuffer.append(responseBuffer.begin(),
-                                            responseBuffer.length());
-    child::RespondToRequest(responseBuffer);
-    return;
-  }
-
-  if (mResumeForward) {
-    // We rewound to erase side effects from the temporary checkpoint we saved
-    // under ProcessRequest. Just start running forward.
-    MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
-    gNavigation->mForwardPhase.Enter(mPoint);
-    return;
-  }
-
-  // We rewound after having an unhandled recording divergence while processing
-  // mRequests[index] or some later request. We need to redo all requests up to
-  // the last request we received.
-
-  // Remember that the last request triggered an unhandled divergence.
-  MOZ_RELEASE_ASSERT(!mRequests.back().mUnhandledDivergence);
-  mRequests.back().mUnhandledDivergence = true;
-
-  for (size_t i = index; i < mRequests.length(); i++) {
-    RequestInfo& info = mRequests[i];
-    mRequestIndex = i;
-
-    if (i == index) {
-      // We just performed this request, and responseBuffer has the right
-      // contents.
-    } else {
-      responseBuffer.clear();
-      js::ProcessRequest(info.mRequestBuffer.begin(),
-                         info.mRequestBuffer.length(), &responseBuffer);
-    }
-
-    if (i < mRequests.length() - 1) {
-      // This is an old request, and we don't need to send another
-      // response to it. Make sure the response we just generated matched
-      // the earlier one we sent, though.
-      MOZ_RELEASE_ASSERT(responseBuffer.length() ==
-                         info.mResponseBuffer.length());
-      MOZ_RELEASE_ASSERT(
-          memcmp(responseBuffer.begin(), info.mResponseBuffer.begin(),
-                 responseBuffer.length() * sizeof(char16_t)) == 0);
-    } else {
-      // This is the current request we need to respond to.
-      MOZ_RELEASE_ASSERT(info.mResponseBuffer.empty());
-      info.mResponseBuffer.append(responseBuffer.begin(),
-                                  responseBuffer.length());
-      child::RespondToRequest(responseBuffer);
-    }
-  }
-
-  // We've finished recovering, and can now process new incoming requests.
-  mRecoveringFromDivergence = false;
-}
-
-bool PausedPhase::MaybeDivergeFromRecording() {
-  if (!ThisProcessCanRewind()) {
-    // Recording divergence is not supported if we can't rewind. We can't
-    // simply allow execution to proceed from here as if we were not
-    // diverged, since any events or other activity that show up afterwards
-    // will not be reflected in the recording.
-    return false;
-  }
-
-  size_t index = mRequestIndex;
-
-  if (!EnsureTemporaryCheckpoint()) {
-    // One of the premature exit cases was hit in EnsureTemporaryCheckpoint.
-    // Don't allow any operations that can diverge from the recording.
-    return false;
-  }
-
-  if (mRequests[index].mUnhandledDivergence) {
-    // We tried to process this request before and had an unhandled divergence.
-    // Disallow the request handler from doing anything that might diverge from
-    // the recording.
-    return false;
-  }
-
-  DivergeFromRecording();
-  return true;
-}
-
-bool PausedPhase::EnsureTemporaryCheckpoint() {
-  if (mSavedTemporaryCheckpoint) {
-    return true;
-  }
-
-  // We need to save a temporary checkpoint that we can restore if we hit
-  // a recording divergence.
-  mSavedTemporaryCheckpoint = true;
-
-  size_t index = mRequestIndex;
-  if (gNavigation->SaveTemporaryCheckpoint(mPoint)) {
-    // We just saved the temporary checkpoint.
-    return true;
-  }
-
-  // We just rewound here.
-  if (gNavigation->mPhase != this) {
-    // We are no longer paused at this point. We should be searching
-    // backwards in the region after this temporary checkpoint was taken.
-    // Return false to ensure we don't perform any side effects before
-    // resuming forward.
-    return false;
-  }
-
-  // We are still paused at this point. Either we had an unhandled
-  // recording divergence, or we intentionally rewound to erase side
-  // effects that occurred while paused here.
-  MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence);
-
-  if (mResumeForward) {
-    // We can't diverge from the recording before resuming forward execution.
-    return false;
-  }
-
-  mRecoveringFromDivergence = true;
-
-  if (index == mRequestIndex) {
-    // We had an unhandled divergence for the same request where we
-    // created the temporary checkpoint. mUnhandledDivergence hasn't been
-    // set yet, but return now to avoid triggering the same divergence
-    // and rewinding again.
-    return false;
-  }
-
-  // Allow the caller to check mUnhandledDivergence.
-  return true;
-}
-
-ExecutionPoint PausedPhase::CurrentExecutionPoint() { return mPoint; }
-
-///////////////////////////////////////////////////////////////////////////////
-// ForwardPhase
-///////////////////////////////////////////////////////////////////////////////
-
-void ForwardPhase::Enter(const ExecutionPoint& aPoint) {
-  mPoint = aPoint;
-
-  gNavigation->SetPhase(this);
-
-  // Install handlers for all breakpoints.
-  for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    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) {
-  bool hitBreakpoint = false;
-  for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    if (breakpoint.Subsumes(aPoint.mPosition)) {
-      hitBreakpoint = true;
-    }
-  }
-
-  if (hitBreakpoint) {
-    gNavigation->mPausedPhase.Enter(aPoint);
-  }
-}
-
-void ForwardPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) {
-  gNavigation->mPausedPhase.Enter(aPoint, /* aRewind = */ false,
-                                  /* aRecordingEndpoint = */ true);
-}
-
-bool ForwardPhase::ShouldSendPaintMessage() { return true; }
-
-///////////////////////////////////////////////////////////////////////////////
-// ReachBreakpointPhase
-///////////////////////////////////////////////////////////////////////////////
-
-void ReachBreakpointPhase::Enter(
-    const CheckpointId& aStart, bool aRewind, const ExecutionPoint& aPoint,
-    const Maybe<ExecutionPoint>& aTemporaryCheckpoint) {
-  MOZ_RELEASE_ASSERT(aPoint.HasPosition());
-  MOZ_RELEASE_ASSERT(aTemporaryCheckpoint.isNothing() ||
-                     (aTemporaryCheckpoint.ref().HasPosition() &&
-                      aTemporaryCheckpoint.ref() != aPoint));
-  mStart = aStart;
-  mPoint = aPoint;
-  mTemporaryCheckpoint = aTemporaryCheckpoint;
-  mSavedTemporaryCheckpoint = false;
-
-  gNavigation->SetPhase(this);
-
-  if (aRewind) {
-    RestoreCheckpointAndResume(aStart);
-    Unreachable();
-  } else {
-    AfterCheckpoint(aStart);
-  }
-}
-
-void ReachBreakpointPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
-  // We can't run past our target point.
-  MOZ_RELEASE_ASSERT(aCheckpoint.mNormal <= mPoint.mCheckpoint);
-
-  if (aCheckpoint == mStart) {
-    // Remember the time we started running forwards from the initial
-    // checkpoint.
-    mStartTime = CurrentTime();
-  }
-
-  js::EnsurePositionHandler(mPoint.mPosition);
-
-  if (mTemporaryCheckpoint.isSome()) {
-    js::EnsurePositionHandler(mTemporaryCheckpoint.ref().mPosition);
-  }
-}
-
-// The number of milliseconds to elapse during a ReachBreakpoint search before
-// we will save a temporary checkpoint.
-static const double kTemporaryCheckpointThresholdMs = 10;
-
-void AlwaysSaveTemporaryCheckpoints() {
-  gNavigation->mAlwaysSaveTemporaryCheckpoints = true;
-}
-
-void ReachBreakpointPhase::PositionHit(const ExecutionPoint& aPoint) {
-  if (mTemporaryCheckpoint.isSome() && mTemporaryCheckpoint.ref() == aPoint) {
-    // We've reached the point at which we have the option of saving a
-    // temporary checkpoint.
-    double elapsedMs = (CurrentTime() - mStartTime) / 1000.0;
-    if (elapsedMs >= kTemporaryCheckpointThresholdMs ||
-        gNavigation->mAlwaysSaveTemporaryCheckpoints) {
-      MOZ_RELEASE_ASSERT(!mSavedTemporaryCheckpoint);
-      mSavedTemporaryCheckpoint = true;
-
-      if (!gNavigation->SaveTemporaryCheckpoint(aPoint)) {
-        // We just restored the checkpoint, and could be in any phase.
-        gNavigation->PositionHit(aPoint);
-        return;
-      }
-    }
-  }
-
-  if (mPoint == aPoint) {
-    gNavigation->mPausedPhase.Enter(aPoint);
-  }
-}
-
-bool ReachBreakpointPhase::ShouldSendPaintMessage() {
-  // We don't need to send paint messages when reaching the breakpoint, as we
-  // will be pausing at the breakpoint and doing a repaint of the state there.
-  return false;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// FindLastHitPhase
-///////////////////////////////////////////////////////////////////////////////
-
-void FindLastHitPhase::Enter(const CheckpointId& aStart,
-                             const ExecutionPoint& aEnd, bool aIncludeEnd) {
-  mStart = aStart;
-  mEnd = aEnd;
-  mIncludeEnd = aIncludeEnd;
-  mCounter = 0;
-  mTrackedPositions.clear();
-
-  gNavigation->SetPhase(this);
-
-  // All breakpoints are tracked positions.
-  for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    if (breakpoint.IsValid()) {
-      mTrackedPositions.emplaceBack(breakpoint);
-    }
-  }
-
-  // Entry points to scripts containing breakpoints are tracked positions.
-  for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    Maybe<BreakpointPosition> entry = GetEntryPosition(breakpoint);
-    if (entry.isSome()) {
-      mTrackedPositions.emplaceBack(entry.ref());
-    }
-  }
-
-  RestoreCheckpointAndResume(mStart);
-  Unreachable();
-}
-
-void FindLastHitPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
-  // We can't run past our endpoint.
-  MOZ_RELEASE_ASSERT(aCheckpoint.mNormal <= mEnd.mCheckpoint);
-
-  if (!mEnd.HasPosition() && mEnd.mCheckpoint == aCheckpoint.mNormal) {
-    MOZ_RELEASE_ASSERT(!aCheckpoint.mTemporary);
-    OnRegionEnd();
-    Unreachable();
-  }
-
-  for (const TrackedPosition& tracked : mTrackedPositions) {
-    js::EnsurePositionHandler(tracked.mPosition);
-  }
-
-  if (mEnd.HasPosition()) {
-    js::EnsurePositionHandler(mEnd.mPosition);
-  }
-}
-
-void FindLastHitPhase::PositionHit(const ExecutionPoint& aPoint) {
-  if (!mIncludeEnd) {
-    CheckForRegionEnd(aPoint);
-  }
-
-  ++mCounter;
-
-  for (TrackedPosition& tracked : mTrackedPositions) {
-    if (tracked.mPosition.Subsumes(aPoint.mPosition)) {
-      tracked.mLastHit = aPoint;
-      tracked.mLastHitCount = mCounter;
-      break;
-    }
-  }
-
-  if (mIncludeEnd) {
-    CheckForRegionEnd(aPoint);
-  }
-}
-
-void FindLastHitPhase::CheckForRegionEnd(const ExecutionPoint& aPoint) {
-  if (mEnd == aPoint) {
-    OnRegionEnd();
-    Unreachable();
-  }
-}
-
-void FindLastHitPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) {
-  OnRegionEnd();
-  Unreachable();
-}
-
-bool FindLastHitPhase::ShouldSendPaintMessage() {
-  // If the region we're searching contains multiple normal checkpoints, we
-  // only want to send paint messages for the first one. We won't pause at the
-  // later checkpoints, and sending paint messages for them will clobber the
-  // one for the checkpoint we will end up pausing at.
-  return gNavigation->LastCheckpoint().mNormal == mStart.mNormal;
-}
-
-const FindLastHitPhase::TrackedPosition& FindLastHitPhase::FindTrackedPosition(
-    const BreakpointPosition& aPos) {
-  for (const TrackedPosition& tracked : mTrackedPositions) {
-    if (tracked.mPosition == aPos) {
-      return tracked;
-    }
-  }
-  MOZ_CRASH("Could not find tracked position");
-}
-
-void FindLastHitPhase::OnRegionEnd() {
-  // Find the point of the last hit which coincides with a breakpoint.
-  Maybe<TrackedPosition> lastBreakpoint;
-  for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) {
-    const TrackedPosition& tracked = FindTrackedPosition(breakpoint);
-    if (tracked.mLastHit.HasPosition() &&
-        (lastBreakpoint.isNothing() ||
-         lastBreakpoint.ref().mLastHitCount < tracked.mLastHitCount)) {
-      lastBreakpoint = Some(tracked);
-    }
-  }
-
-  if (lastBreakpoint.isNothing()) {
-    // No breakpoints were encountered in the search space.
-    if (mStart.mTemporary) {
-      // We started searching forwards from a temporary checkpoint.
-      // Continue searching backwards without notifying the middleman.
-      CheckpointId start = GetLastSavedCheckpointPriorTo(mStart);
-      start = SkipUnknownTemporaryCheckpoints(start);
-      ExecutionPoint end = gNavigation->LastTemporaryCheckpointLocation();
-      if (end.HasPosition() || end.mCheckpoint != start.mNormal) {
-        // The temporary checkpoint comes immediately after its associated
-        // execution point. As we search backwards we need to look for hits at
-        // that execution point itself.
-        gNavigation->mFindLastHitPhase.Enter(start, end,
-                                             /* aIncludeEnd = */ true);
-        Unreachable();
-      } else {
-        // 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),
-        /* 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.
-  //
-  // Instead, try to place a temporary checkpoint at the last time the
-  // breakpoint's script was entered. This optimizes for the case of stepping
-  // around within a frame.
-  Maybe<BreakpointPosition> baseEntry =
-      GetEntryPosition(lastBreakpoint.ref().mPosition);
-  if (baseEntry.isSome()) {
-    const TrackedPosition& tracked = FindTrackedPosition(baseEntry.ref());
-    if (tracked.mLastHit.HasPosition() &&
-        tracked.mLastHitCount < lastBreakpoint.ref().mLastHitCount) {
-      gNavigation->mReachBreakpointPhase.Enter(mStart, /* aRewind = */ true,
-                                               lastBreakpoint.ref().mLastHit,
-                                               Some(tracked.mLastHit));
-      Unreachable();
-    }
-  }
-
-  // There was no suitable place for a temporary checkpoint, so rewind to the
-  // last checkpoint and play forward to the last breakpoint hit we found.
-  gNavigation->mReachBreakpointPhase.Enter(
-      mStart, /* aRewind = */ true, lastBreakpoint.ref().mLastHit, Nothing());
-  Unreachable();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Hooks
-///////////////////////////////////////////////////////////////////////////////
-
-bool IsInitialized() { return !!gNavigation; }
-
-void BeforeCheckpoint() {
-  if (!IsInitialized()) {
-    void* navigationMem =
-        AllocateMemory(sizeof(NavigationState), MemoryKind::Navigation);
-    gNavigation = new (navigationMem) NavigationState();
-
-    js::SetupDevtoolsSandbox();
-
-    // Set the progress counter to zero before the first checkpoint. Execution
-    // that occurred before this checkpoint cannot be rewound to.
-    *ExecutionProgressCounter() = 0;
-  }
-
-  AutoDisallowThreadEvents disallow;
-
-  // Reset the debugger to a consistent state before each checkpoint.
-  js::ClearPositionHandlers();
-}
-
-void AfterCheckpoint(const CheckpointId& aCheckpoint) {
-  AutoDisallowThreadEvents disallow;
-
-  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
-  gNavigation->AfterCheckpoint(aCheckpoint);
-}
-
-size_t LastNormalCheckpoint() { return gNavigation->LastCheckpoint().mNormal; }
-
-void DebuggerRequest(js::CharBuffer* aRequestBuffer) {
-  gNavigation->HandleDebuggerRequest(aRequestBuffer);
-}
-
-void AddBreakpoint(const BreakpointPosition& 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) {
-    ResumeExecution();
-    return;
-  }
-  gNavigation->Resume(aForward);
-}
-
-void RestoreCheckpoint(size_t aId) { gNavigation->RestoreCheckpoint(aId); }
-
-void RunToPoint(const ExecutionPoint& aTarget) {
-  gNavigation->RunToPoint(aTarget);
-}
-
-ExecutionPoint GetRecordingEndpoint() {
-  if (IsRecording()) {
-    return gNavigation->CurrentExecutionPoint();
-  } else {
-    return gNavigation->LastRecordingEndpoint();
-  }
-}
-
-void SetRecordingEndpoint(size_t aIndex, const ExecutionPoint& aEndpoint) {
-  MOZ_RELEASE_ASSERT(IsReplaying());
-  gNavigation->SetRecordingEndpoint(aIndex, aEndpoint);
-}
-
-static ProgressCounter gProgressCounter;
-
-extern "C" {
-
-MOZ_EXPORT ProgressCounter* RecordReplayInterface_ExecutionProgressCounter() {
-  return &gProgressCounter;
-}
-
-}  // extern "C"
-
-ExecutionPoint CurrentExecutionPoint(
-    const Maybe<BreakpointPosition>& aPosition) {
-  if (aPosition.isSome()) {
-    return ExecutionPoint(gNavigation->LastCheckpoint().mNormal,
-                          gProgressCounter, aPosition.ref());
-  }
-  return gNavigation->CurrentExecutionPoint();
-}
-
-void PositionHit(const BreakpointPosition& position) {
-  AutoDisallowThreadEvents disallow;
-  gNavigation->PositionHit(CurrentExecutionPoint(Some(position)));
-}
-
-extern "C" {
-
-MOZ_EXPORT ProgressCounter RecordReplayInterface_NewTimeWarpTarget() {
-  if (AreThreadEventsDisallowed()) {
-    return 0;
-  }
-
-  // NewTimeWarpTarget() must be called at consistent points between recording
-  // and replaying.
-  RecordReplayAssert("NewTimeWarpTarget");
-
-  if (!gNavigation) {
-    return 0;
-  }
-
-  // Advance the progress counter for each time warp target. This can be called
-  // at any place and any number of times where recorded events are allowed.
-  ProgressCounter progress = ++gProgressCounter;
-
-  PositionHit(BreakpointPosition(BreakpointPosition::WarpTarget));
-  return progress;
-}
-
-}  // extern "C"
-
-ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget) {
-  // To construct an ExecutionPoint, we need the most recent checkpoint prior
-  // to aTarget. We could do a binary search here, but this code is cold and a
-  // linear search is more straightforwardly correct.
-  size_t checkpoint;
-  for (checkpoint = gNavigation->mCheckpointProgress.length() - 1;
-       checkpoint >= CheckpointId::First; checkpoint--) {
-    if (gNavigation->mCheckpointProgress[checkpoint] < aTarget) {
-      break;
-    }
-  }
-  MOZ_RELEASE_ASSERT(checkpoint >= CheckpointId::First);
-
-  return ExecutionPoint(checkpoint, aTarget,
-                        BreakpointPosition(BreakpointPosition::WarpTarget));
-}
-
-bool MaybeDivergeFromRecording() {
-  return gNavigation->MaybeDivergeFromRecording();
-}
-
-bool ShouldSendPaintMessage() { return gNavigation->ShouldSendPaintMessage(); }
-
-}  // namespace navigation
-}  // namespace recordreplay
-}  // namespace mozilla
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -48,53 +48,37 @@ ChildProcessInfo::ChildProcessInfo(
 
 ChildProcessInfo::~ChildProcessInfo() {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (IsRecording()) {
     SendMessage(TerminateMessage());
   }
 }
 
-void ChildProcessInfo::OnIncomingMessage(const Message& aMsg,
-                                         bool aForwardToControl) {
+void ChildProcessInfo::OnIncomingMessage(const Message& aMsg) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mLastMessageTime = TimeStamp::Now();
 
   switch (aMsg.mType) {
     case MessageType::BeginFatalError:
       mHasBegunFatalError = true;
       return;
     case MessageType::FatalError: {
       mHasFatalError = true;
       const FatalErrorMessage& nmsg =
           static_cast<const FatalErrorMessage&>(aMsg);
       OnCrash(nmsg.Error());
       return;
     }
-    case MessageType::HitExecutionPoint: {
-      const HitExecutionPointMessage& nmsg =
-          static_cast<const HitExecutionPointMessage&>(aMsg);
-      mPaused = true;
-      if (this == GetActiveChild() && !nmsg.mPoint.HasPosition()) {
-        MaybeUpdateGraphicsAtCheckpoint(nmsg.mPoint.mCheckpoint);
-      }
-      if (aForwardToControl) {
-        js::ForwardHitExecutionPointMessage(GetId(), nmsg);
-      }
+    case MessageType::Paint:
+      UpdateGraphicsInUIProcess(&static_cast<const PaintMessage&>(aMsg));
       break;
-    }
-    case MessageType::Paint:
-      MaybeUpdateGraphicsAtPaint(static_cast<const PaintMessage&>(aMsg));
-      break;
-    case MessageType::DebuggerResponse:
+    case MessageType::ManifestFinished:
       mPaused = true;
-      js::OnDebuggerResponse(aMsg);
-      break;
-    case MessageType::RecordingFlushed:
-      mPaused = true;
+      js::ForwardManifestFinished(this, aMsg);
       break;
     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(
@@ -110,26 +94,18 @@ void ChildProcessInfo::OnIncomingMessage
   }
 }
 
 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:
-    case MessageType::DebuggerRequest:
-    case MessageType::FlushRecording:
-      mPaused = false;
-      break;
-    default:
-      break;
+  if (aMsg.mType == MessageType::ManifestStart) {
+    mPaused = false;
   }
 
   mLastMessageTime = TimeStamp::Now();
   mChannel->SendMessage(std::move(aMsg));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Subprocess Management
@@ -198,30 +174,18 @@ void ChildProcessInfo::LaunchSubprocess(
   } else {
     dom::ContentChild::GetSingleton()->SendCreateReplayingProcess(channelId);
   }
 
   mLastMessageTime = TimeStamp::Now();
 
   SendGraphicsMemoryToChild();
 
-  // The child should send us a HitExecutionPoint message with an invalid point
-  // to pause.
-  WaitUntilPaused();
-
   MOZ_RELEASE_ASSERT(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));
 }
 
 void ChildProcessInfo::OnCrash(const char* aWhy) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // If a child process crashes or hangs then annotate the crash report.
   CrashReporter::AnnotateCrashReport(
       CrashReporter::Annotation::RecordReplayError, nsAutoCString(aWhy));
@@ -286,35 +250,35 @@ static Message::UniquePtr ExtractChildMe
 // Whether there is a pending task on the main thread's message loop to handle
 // all pending messages.
 static bool gHasPendingMessageRunnable;
 
 // How many seconds to wait without hearing from an unpaused child before
 // considering that child to be hung.
 static const size_t HangSeconds = 30;
 
-Message::UniquePtr ChildProcessInfo::WaitUntilPaused() {
+void ChildProcessInfo::WaitUntilPaused() {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (IsPaused()) {
-    return nullptr;
+    return;
   }
 
   bool sentTerminateMessage = false;
   while (true) {
     MonitorAutoLock lock(*gMonitor);
 
     // Search for the first message received from this process.
     ChildProcessInfo* process = this;
     Message::UniquePtr msg = ExtractChildMessage(&process);
 
     if (msg) {
-      OnIncomingMessage(*msg, /* aForwardToControl = */ false);
+      OnIncomingMessage(*msg);
       if (IsPaused()) {
-        return msg;
+        return;
       }
     } else {
       if (gChildrenAreDebugging || IsRecording()) {
         // Don't watch for hangs when children are being debugged. Recording
         // children are never treated as hanged both because they cannot be
         // restarted and because they may just be idling.
         gMonitor->Wait();
       } else {
@@ -352,17 +316,17 @@ void ChildProcessInfo::MaybeProcessPendi
   MOZ_RELEASE_ASSERT(gHasPendingMessageRunnable);
   gHasPendingMessageRunnable = false;
   while (true) {
     ChildProcessInfo* process = nullptr;
     Message::UniquePtr msg = ExtractChildMessage(&process);
 
     if (msg) {
       MonitorAutoUnlock unlock(*gMonitor);
-      process->OnIncomingMessage(*msg, /* aForwardToControl = */ true);
+      process->OnIncomingMessage(*msg);
     } else {
       break;
     }
   }
 }
 
 // Execute a task that processes a message received from the child. This is
 // called on a channel thread, and the function executes asynchronously on
--- a/toolkit/recordreplay/ipc/JSControl.cpp
+++ b/toolkit/recordreplay/ipc/JSControl.cpp
@@ -37,211 +37,32 @@ static bool FillCharBufferCallback(const
 static JSObject* NonNullObject(JSContext* aCx, HandleValue aValue) {
   if (!aValue.isObject()) {
     JS_ReportErrorASCII(aCx, "Expected object");
     return nullptr;
   }
   return &aValue.toObject();
 }
 
-template <typename T>
-static bool MaybeGetNumberProperty(JSContext* aCx, HandleObject aObject,
-                                   const char* aProperty, T* aResult) {
-  RootedValue v(aCx);
-  if (!JS_GetProperty(aCx, aObject, aProperty, &v)) {
-    return false;
-  }
-  if (v.isNumber()) {
-    *aResult = v.toNumber();
-  }
-  return true;
-}
-
-template <typename T>
-static bool GetNumberProperty(JSContext* aCx, HandleObject aObject,
-                              const char* aProperty, T* aResult) {
-  RootedValue v(aCx);
-  if (!JS_GetProperty(aCx, aObject, aProperty, &v)) {
-    return false;
-  }
-  if (!v.isNumber()) {
-    JS_ReportErrorASCII(aCx, "Object missing required property");
-    return false;
-  }
-  *aResult = v.toNumber();
-  return true;
-}
-
 static parent::ChildProcessInfo* GetChildById(JSContext* aCx,
                                               const Value& aValue,
                                               bool aAllowUnpaused = false) {
   if (!aValue.isNumber()) {
     JS_ReportErrorASCII(aCx, "Expected child ID");
     return nullptr;
   }
   parent::ChildProcessInfo* child = parent::GetChildProcess(aValue.toNumber());
   if (!child || (!aAllowUnpaused && !child->IsPaused())) {
     JS_ReportErrorASCII(aCx, "Unpaused or bad child ID");
     return nullptr;
   }
   return child;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// BreakpointPosition Conversion
-///////////////////////////////////////////////////////////////////////////////
-
-// Names of properties which JS code uses to specify the contents of a
-// BreakpointPosition.
-static const char gKindProperty[] = "kind";
-static const char gScriptProperty[] = "script";
-static const char gOffsetProperty[] = "offset";
-static const char gFrameIndexProperty[] = "frameIndex";
-
-JSObject* BreakpointPosition::Encode(JSContext* aCx) const {
-  RootedString kindString(aCx, JS_NewStringCopyZ(aCx, KindString()));
-  RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
-  if (!kindString || !obj ||
-      !JS_DefineProperty(aCx, obj, gKindProperty, kindString,
-                         JSPROP_ENUMERATE) ||
-      (mScript != BreakpointPosition::EMPTY_SCRIPT &&
-       !JS_DefineProperty(aCx, obj, gScriptProperty, mScript,
-                          JSPROP_ENUMERATE)) ||
-      (mOffset != BreakpointPosition::EMPTY_OFFSET &&
-       !JS_DefineProperty(aCx, obj, gOffsetProperty, mOffset,
-                          JSPROP_ENUMERATE)) ||
-      (mFrameIndex != BreakpointPosition::EMPTY_FRAME_INDEX &&
-       !JS_DefineProperty(aCx, obj, gFrameIndexProperty, mFrameIndex,
-                          JSPROP_ENUMERATE))) {
-    return nullptr;
-  }
-  return obj;
-}
-
-bool BreakpointPosition::Decode(JSContext* aCx, HandleObject aObject) {
-  RootedValue v(aCx);
-  if (!JS_GetProperty(aCx, aObject, gKindProperty, &v)) {
-    return false;
-  }
-
-  RootedString str(aCx, ::ToString(aCx, v));
-  for (size_t i = BreakpointPosition::Invalid + 1;
-       i < BreakpointPosition::sKindCount; i++) {
-    BreakpointPosition::Kind kind = (BreakpointPosition::Kind)i;
-    bool match;
-    if (!JS_StringEqualsAscii(
-            aCx, str, BreakpointPosition::StaticKindString(kind), &match))
-      return false;
-    if (match) {
-      mKind = kind;
-      break;
-    }
-  }
-  if (mKind == BreakpointPosition::Invalid) {
-    JS_ReportErrorASCII(aCx, "Could not decode breakpoint position kind");
-    return false;
-  }
-
-  if (!MaybeGetNumberProperty(aCx, aObject, gScriptProperty, &mScript) ||
-      !MaybeGetNumberProperty(aCx, aObject, gOffsetProperty, &mOffset) ||
-      !MaybeGetNumberProperty(aCx, aObject, gFrameIndexProperty,
-                              &mFrameIndex)) {
-    return false;
-  }
-
-  return true;
-}
-
-void BreakpointPosition::ToString(nsCString& aStr) const {
-  aStr.AppendPrintf("{ Kind: %s, Script: %d, Offset: %d, Frame: %d }",
-                    KindString(), (int)mScript, (int)mOffset, (int)mFrameIndex);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// ExecutionPoint Conversion
-///////////////////////////////////////////////////////////////////////////////
-
-// Names of properties which JS code uses to specify the contents of an
-// ExecutionPoint.
-static const char gCheckpointProperty[] = "checkpoint";
-static const char gProgressProperty[] = "progress";
-static const char gPositionProperty[] = "position";
-
-JSObject* ExecutionPoint::Encode(JSContext* aCx) const {
-  RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
-  if (!obj ||
-      !JS_DefineProperty(aCx, obj, gCheckpointProperty, (double)mCheckpoint,
-                         JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(aCx, obj, gProgressProperty, (double)mProgress,
-                         JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-  if (HasPosition()) {
-    RootedObject position(aCx, mPosition.Encode(aCx));
-    if (!position || !JS_DefineProperty(aCx, obj, gPositionProperty, position,
-                                        JSPROP_ENUMERATE)) {
-      return nullptr;
-    }
-  }
-  return obj;
-}
-
-bool ExecutionPoint::Decode(JSContext* aCx, HandleObject aObject) {
-  RootedValue v(aCx);
-  if (!JS_GetProperty(aCx, aObject, gPositionProperty, &v)) {
-    return false;
-  }
-
-  if (v.isUndefined()) {
-    MOZ_RELEASE_ASSERT(!HasPosition());
-  } else {
-    RootedObject positionObject(aCx, NonNullObject(aCx, v));
-    if (!positionObject || !mPosition.Decode(aCx, positionObject)) {
-      return false;
-    }
-  }
-  return GetNumberProperty(aCx, aObject, gCheckpointProperty, &mCheckpoint) &&
-         GetNumberProperty(aCx, aObject, gProgressProperty, &mProgress);
-}
-
-void ExecutionPoint::ToString(nsCString& aStr) const {
-  aStr.AppendPrintf("{ Checkpoint %d", (int)mCheckpoint);
-  if (HasPosition()) {
-    aStr.AppendPrintf(" Progress %llu Position ", mProgress);
-    mPosition.ToString(aStr);
-  }
-  aStr.AppendPrintf(" }");
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Message Conversion
-///////////////////////////////////////////////////////////////////////////////
-
-static JSObject* EncodeChannelMessage(JSContext* aCx,
-                                      const HitExecutionPointMessage& aMsg) {
-  RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
-  if (!obj) {
-    return nullptr;
-  }
-
-  RootedObject pointObject(aCx, aMsg.mPoint.Encode(aCx));
-  if (!pointObject ||
-      !JS_DefineProperty(aCx, obj, "point", pointObject, JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(aCx, obj, "recordingEndpoint", aMsg.mRecordingEndpoint,
-                         JSPROP_ENUMERATE) ||
-      !JS_DefineProperty(aCx, obj, "duration",
-                         aMsg.mDurationMicroseconds / 1000.0,
-                         JSPROP_ENUMERATE)) {
-    return nullptr;
-  }
-
-  return obj;
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // Middleman Control
 ///////////////////////////////////////////////////////////////////////////////
 
 static StaticRefPtr<rrIControl> gControl;
 
 void SetupMiddlemanControl(const Maybe<size_t>& aRecordingChildId) {
   MOZ_RELEASE_ASSERT(!gControl);
 
@@ -259,29 +80,32 @@ void SetupMiddlemanControl(const Maybe<s
   if (aRecordingChildId.isSome()) {
     recordingChildValue.setInt32(aRecordingChildId.ref());
   }
   if (NS_FAILED(gControl->Initialize(recordingChildValue))) {
     MOZ_CRASH("SetupMiddlemanControl");
   }
 }
 
-void ForwardHitExecutionPointMessage(size_t aId,
-                                     const HitExecutionPointMessage& aMsg) {
+void ForwardManifestFinished(parent::ChildProcessInfo* aChild,
+                             const Message& aMsg) {
   MOZ_RELEASE_ASSERT(gControl);
+  const auto& nmsg = static_cast<const ManifestFinishedMessage&>(aMsg);
 
   AutoSafeJSContext cx;
   JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
 
-  JSObject* obj = EncodeChannelMessage(cx, aMsg);
-  MOZ_RELEASE_ASSERT(obj);
+  RootedValue value(cx);
+  if (nmsg.BufferSize() &&
+      !JS_ParseJSON(cx, nmsg.Buffer(), nmsg.BufferSize(), &value)) {
+    MOZ_CRASH("ForwardManifestFinished");
+  }
 
-  RootedValue value(cx, ObjectValue(*obj));
-  if (NS_FAILED(gControl->HitExecutionPoint(aId, value))) {
-    MOZ_CRASH("ForwardMessageToMiddlemanControl");
+  if (NS_FAILED(gControl->ManifestFinished(aChild->GetId(), value))) {
+    MOZ_CRASH("ForwardManifestFinished");
   }
 }
 
 void BeforeSaveRecording() {
   MOZ_RELEASE_ASSERT(gControl);
 
   AutoSafeJSContext cx;
   JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
@@ -362,234 +186,56 @@ static bool Middleman_SpawnReplayingChil
                                           Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   size_t id = parent::SpawnReplayingChild();
   args.rval().setInt32(id);
   return true;
 }
 
-static bool Middleman_SetActiveChild(JSContext* aCx, unsigned aArgc,
-                                     Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
-    return false;
-  }
-
-  parent::SetActiveChild(child);
-
-  args.rval().setUndefined();
-  return true;
-}
-
-static bool Middleman_SendSetSaveCheckpoint(JSContext* aCx, unsigned aArgc,
-                                            Value* aVp) {
+static bool Middleman_SendManifest(JSContext* aCx, unsigned aArgc, Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
-    return false;
-  }
-
-  double checkpoint;
-  if (!ToNumber(aCx, args.get(1), &checkpoint)) {
-    return false;
-  }
-
-  bool shouldSave = ToBoolean(args.get(2));
-
-  child->SendMessage(SetSaveCheckpointMessage(checkpoint, shouldSave));
-
-  args.rval().setUndefined();
-  return true;
-}
-
-static bool Middleman_SendFlushRecording(JSContext* aCx, unsigned aArgc,
-                                         Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
+  RootedObject manifestObject(aCx, NonNullObject(aCx, args.get(1)));
+  if (!manifestObject) {
     return false;
   }
 
-  child->SendMessage(FlushRecordingMessage());
-
-  // The child will unpause until the flush finishes.
-  child->WaitUntilPaused();
-
-  args.rval().setUndefined();
-  return true;
-}
-
-static bool Middleman_SendResume(JSContext* aCx, unsigned aArgc, Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
+  CharBuffer manifestBuffer;
+  if (!ToJSONMaybeSafely(aCx, manifestObject, FillCharBufferCallback,
+                         &manifestBuffer)) {
     return false;
   }
 
-  bool forward = ToBoolean(args.get(1));
-
-  child->SendMessage(ResumeMessage(forward));
-
-  args.rval().setUndefined();
-  return true;
-}
-
-static bool Middleman_SendRestoreCheckpoint(JSContext* aCx, unsigned aArgc,
-                                            Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
-    return false;
-  }
-
-  double checkpoint;
-  if (!ToNumber(aCx, args.get(1), &checkpoint)) {
-    return false;
-  }
-
-  child->SendMessage(RestoreCheckpointMessage(checkpoint));
-
-  args.rval().setUndefined();
-  return true;
-}
-
-static bool Middleman_SendRunToPoint(JSContext* aCx, unsigned aArgc,
-                                     Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
   parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
   if (!child) {
     return false;
   }
 
-  RootedObject pointObject(aCx, NonNullObject(aCx, args.get(1)));
-  if (!pointObject) {
-    return false;
-  }
-
-  ExecutionPoint point;
-  if (!point.Decode(aCx, pointObject)) {
-    return false;
-  }
-
-  child->SendMessage(RunToPointMessage(point));
-
-  args.rval().setUndefined();
-  return true;
-}
-
-// Buffer for receiving the next debugger response.
-static js::CharBuffer* gResponseBuffer;
-
-void OnDebuggerResponse(const Message& aMsg) {
-  const DebuggerResponseMessage& nmsg =
-      static_cast<const DebuggerResponseMessage&>(aMsg);
-  MOZ_RELEASE_ASSERT(gResponseBuffer && gResponseBuffer->empty());
-  gResponseBuffer->append(nmsg.Buffer(), nmsg.BufferSize());
-}
-
-static bool Middleman_SendDebuggerRequest(JSContext* aCx, unsigned aArgc,
-                                          Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
-    return false;
-  }
-
-  RootedObject requestObject(aCx, NonNullObject(aCx, args.get(1)));
-  if (!requestObject) {
-    return false;
-  }
-
-  CharBuffer requestBuffer;
-  if (!ToJSONMaybeSafely(aCx, requestObject, FillCharBufferCallback,
-                         &requestBuffer)) {
-    return false;
-  }
-
-  CharBuffer responseBuffer;
-
-  MOZ_RELEASE_ASSERT(!gResponseBuffer);
-  gResponseBuffer = &responseBuffer;
-
-  DebuggerRequestMessage* msg = DebuggerRequestMessage::New(
-      requestBuffer.begin(), requestBuffer.length());
+  ManifestStartMessage* msg = ManifestStartMessage::New(
+      manifestBuffer.begin(), manifestBuffer.length());
   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;
-
-  return JS_ParseJSON(aCx, responseBuffer.begin(), responseBuffer.length(),
-                      args.rval());
-}
-
-static bool Middleman_SendAddBreakpoint(JSContext* aCx, unsigned aArgc,
-                                        Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
-    return false;
-  }
-
-  RootedObject positionObject(aCx, NonNullObject(aCx, args.get(1)));
-  if (!positionObject) {
-    return false;
-  }
-
-  BreakpointPosition position;
-  if (!position.Decode(aCx, positionObject)) {
-    return false;
-  }
-
-  child->SendMessage(AddBreakpointMessage(position));
-
-  args.rval().setUndefined();
-  return true;
-}
-
-static bool Middleman_SendClearBreakpoints(JSContext* aCx, unsigned aArgc,
-                                           Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
-  if (!child) {
-    return false;
-  }
-
-  child->SendMessage(ClearBreakpointsMessage());
-
   args.rval().setUndefined();
   return true;
 }
 
 static bool Middleman_HadRepaint(JSContext* aCx, unsigned aArgc, Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   if (!args.get(0).isNumber() || !args.get(1).isNumber()) {
     JS_ReportErrorASCII(aCx, "Bad width/height");
     return false;
   }
 
   size_t width = args.get(0).toNumber();
   size_t height = args.get(1).toNumber();
 
-  PaintMessage message(CheckpointId::Invalid, width, height);
+  PaintMessage message(InvalidCheckpointId, width, height);
   parent::UpdateGraphicsInUIProcess(&message);
 
   args.rval().setUndefined();
   return true;
 }
 
 static bool Middleman_HadRepaintFailure(JSContext* aCx, unsigned aArgc,
                                         Value* aVp) {
@@ -627,86 +273,53 @@ static bool Middleman_WaitUntilPaused(JS
   if (!child) {
     return false;
   }
 
   if (ToBoolean(args.get(1))) {
     MaybeCreateCheckpointInChild(child);
   }
 
-  Message::UniquePtr msg = child->WaitUntilPaused();
-
-  if (!msg) {
-    JS_ReportErrorASCII(aCx, "Child process is already paused");
-  }
-
-  MOZ_RELEASE_ASSERT(msg->mType == MessageType::HitExecutionPoint);
-  const HitExecutionPointMessage& nmsg =
-      static_cast<const HitExecutionPointMessage&>(*msg);
-
-  JSObject* obj = EncodeChannelMessage(aCx, nmsg);
-  if (!obj) {
-    return false;
-  }
-
-  args.rval().setObject(*obj);
-  return true;
-}
+  child->WaitUntilPaused();
 
-static bool Middleman_PositionSubsumes(JSContext* aCx, unsigned aArgc,
-                                       Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  RootedObject firstPositionObject(aCx, NonNullObject(aCx, args.get(0)));
-  if (!firstPositionObject) {
-    return false;
-  }
-
-  BreakpointPosition firstPosition;
-  if (!firstPosition.Decode(aCx, firstPositionObject)) {
-    return false;
-  }
-
-  RootedObject secondPositionObject(aCx, NonNullObject(aCx, args.get(1)));
-  if (!secondPositionObject) {
-    return false;
-  }
-
-  BreakpointPosition secondPosition;
-  if (!secondPosition.Decode(aCx, secondPositionObject)) {
-    return false;
-  }
-
-  args.rval().setBoolean(firstPosition.Subsumes(secondPosition));
+  args.rval().setUndefined();
   return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Devtools Sandbox
 ///////////////////////////////////////////////////////////////////////////////
 
 static StaticRefPtr<rrIReplay> gReplay;
 
 // URL of the root script that runs when recording/replaying.
 #define ReplayScriptURL "resource://devtools/server/actors/replay/replay.js"
 
 // Whether to expose chrome:// and resource:// scripts to the debugger.
 static bool gIncludeSystemScripts;
 
+static void InitializeScriptHits();
+
 void SetupDevtoolsSandbox() {
   MOZ_RELEASE_ASSERT(!gReplay);
 
   nsCOMPtr<rrIReplay> replay = do_ImportModule(ReplayScriptURL);
   gReplay = replay.forget();
   ClearOnShutdown(&gReplay);
 
   MOZ_RELEASE_ASSERT(gReplay);
 
   gIncludeSystemScripts =
       Preferences::GetBool("devtools.recordreplay.includeSystemScripts");
+
+  InitializeScriptHits();
+}
+
+bool IsInitialized() {
+  return !!gReplay;
 }
 
 extern "C" {
 
 MOZ_EXPORT bool RecordReplayInterface_ShouldUpdateProgressCounter(
     const char* aURL) {
   // Progress counters are only updated for scripts which are exposed to the
   // debugger. The devtools timeline is based on progress values and we don't
@@ -720,113 +333,95 @@ MOZ_EXPORT bool RecordReplayInterface_Sh
     return aURL && strncmp(aURL, "resource:", 9) && strncmp(aURL, "chrome:", 7);
   }
 }
 
 }  // extern "C"
 
 #undef ReplayScriptURL
 
-void ProcessRequest(const char16_t* aRequest, size_t aRequestLength,
-                    CharBuffer* aResponse) {
+void ManifestStart(const CharBuffer& aContents) {
   AutoDisallowThreadEvents disallow;
   AutoSafeJSContext cx;
   JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
 
-  RootedValue requestValue(cx);
-  if (!JS_ParseJSON(cx, aRequest, aRequestLength, &requestValue)) {
-    MOZ_CRASH("ProcessRequest: ParseJSON failed");
+  RootedValue value(cx);
+  if (!JS_ParseJSON(cx, aContents.begin(), aContents.length(), &value)) {
+    MOZ_CRASH("ManifestStart: ParseJSON failed");
   }
 
-  RootedValue responseValue(cx);
-  if (NS_FAILED(gReplay->ProcessRequest(requestValue, &responseValue))) {
-    MOZ_CRASH("ProcessRequest: Handler failed");
+  if (NS_FAILED(gReplay->ManifestStart(value))) {
+    MOZ_CRASH("ManifestStart: Handler failed");
   }
 
-  // Processing the request may have called into MaybeDivergeFromRecording.
-  // Now that we've finished processing it, don't tolerate future events that
+  // Processing the manifest may have called into MaybeDivergeFromRecording.
+  // If it did so, we should already have finished any processing that required
+  // diverging from the recording. Don't tolerate future events that
   // would otherwise cause us to rewind to the last checkpoint.
   DisallowUnhandledDivergeFromRecording();
+}
 
-  if (!responseValue.isObject()) {
-    MOZ_CRASH("ProcessRequest: Response must be an object");
+void BeforeCheckpoint() {
+  if (!IsInitialized()) {
+    SetupDevtoolsSandbox();
   }
 
-  RootedObject responseObject(cx, &responseValue.toObject());
-  if (!ToJSONMaybeSafely(cx, responseObject, FillCharBufferCallback,
-                         aResponse)) {
-    MOZ_CRASH("ProcessRequest: ToJSONMaybeSafely failed");
+  AutoDisallowThreadEvents disallow;
+  AutoSafeJSContext cx;
+  JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
+
+  if (NS_FAILED(gReplay->BeforeCheckpoint())) {
+    MOZ_CRASH("BeforeCheckpoint");
   }
 }
 
-void EnsurePositionHandler(const BreakpointPosition& aPosition) {
+void AfterCheckpoint(size_t aCheckpoint, bool aRestoredCheckpoint) {
   AutoDisallowThreadEvents disallow;
   AutoSafeJSContext cx;
   JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
 
-  RootedObject obj(cx, aPosition.Encode(cx));
-  if (!obj) {
-    MOZ_CRASH("EnsurePositionHandler");
-  }
-
-  RootedValue objValue(cx, ObjectValue(*obj));
-  if (NS_FAILED(gReplay->EnsurePositionHandler(objValue))) {
-    MOZ_CRASH("EnsurePositionHandler");
+  if (NS_FAILED(gReplay->AfterCheckpoint(aCheckpoint, aRestoredCheckpoint))) {
+    MOZ_CRASH("AfterCheckpoint");
   }
 }
 
-void ClearPositionHandlers() {
-  AutoDisallowThreadEvents disallow;
-  AutoSafeJSContext cx;
-  JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
+static ProgressCounter gProgressCounter;
 
-  if (NS_FAILED(gReplay->ClearPositionHandlers())) {
-    MOZ_CRASH("ClearPositionHandlers");
-  }
+extern "C" {
+
+MOZ_EXPORT ProgressCounter* RecordReplayInterface_ExecutionProgressCounter() {
+  return &gProgressCounter;
 }
 
-void ClearPausedState() {
+MOZ_EXPORT ProgressCounter RecordReplayInterface_NewTimeWarpTarget() {
+  if (AreThreadEventsDisallowed()) {
+    return 0;
+  }
+
+  // NewTimeWarpTarget() must be called at consistent points between recording
+  // and replaying.
+  RecordReplayAssert("NewTimeWarpTarget");
+
+  if (!IsInitialized()) {
+    return 0;
+  }
+
   AutoDisallowThreadEvents disallow;
   AutoSafeJSContext cx;
   JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
 
-  if (NS_FAILED(gReplay->ClearPausedState())) {
-    MOZ_CRASH("ClearPausedState");
-  }
-}
-
-Maybe<BreakpointPosition> GetEntryPosition(
-    const BreakpointPosition& aPosition) {
-  AutoDisallowThreadEvents disallow;
-  AutoSafeJSContext cx;
-  JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
-
-  RootedObject positionObject(cx, aPosition.Encode(cx));
-  if (!positionObject) {
-    MOZ_CRASH("GetEntryPosition");
+  int32_t counter = 0;
+  if (NS_FAILED(gReplay->NewTimeWarpTarget(&counter))) {
+    MOZ_CRASH("NewTimeWarpTarget");
   }
 
-  RootedValue rval(cx);
-  RootedValue positionValue(cx, ObjectValue(*positionObject));
-  if (NS_FAILED(gReplay->GetEntryPosition(positionValue, &rval))) {
-    MOZ_CRASH("GetEntryPosition");
-  }
+  return counter;
+}
 
-  if (!rval.isObject()) {
-    return Nothing();
-  }
-
-  RootedObject rvalObject(cx, &rval.toObject());
-  BreakpointPosition entryPosition;
-  if (!entryPosition.Decode(cx, rvalObject)) {
-    MOZ_CRASH("GetEntryPosition");
-  }
-
-  return Some(entryPosition);
-}
+}  // extern "C"
 
 ///////////////////////////////////////////////////////////////////////////////
 // Replaying process content
 ///////////////////////////////////////////////////////////////////////////////
 
 struct ContentInfo {
   const void* mToken;
   char* mURL;
@@ -974,20 +569,28 @@ static bool FetchContent(JSContext* aCx,
 
 static bool RecordReplay_AreThreadEventsDisallowed(JSContext* aCx,
                                                    unsigned aArgc, Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
   args.rval().setBoolean(AreThreadEventsDisallowed());
   return true;
 }
 
-static bool RecordReplay_MaybeDivergeFromRecording(JSContext* aCx,
-                                                   unsigned aArgc, Value* aVp) {
+static bool RecordReplay_DivergeFromRecording(JSContext* aCx,
+                                              unsigned aArgc, Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
-  args.rval().setBoolean(navigation::MaybeDivergeFromRecording());
+  DivergeFromRecording();
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool RecordReplay_ProgressCounter(JSContext* aCx, unsigned aArgc,
+                                         Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+  args.rval().setNumber((double) gProgressCounter);
   return true;
 }
 
 static bool RecordReplay_AdvanceProgressCounter(JSContext* aCx, unsigned aArgc,
                                                 Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
   AdvanceExecutionProgressCounter();
   args.rval().setUndefined();
@@ -1019,32 +622,318 @@ static bool RecordReplay_ShouldUpdatePro
 
     NS_ConvertUTF16toUTF8 utf8(chars);
     args.rval().setBoolean(ShouldUpdateProgressCounter(utf8.get()));
   }
 
   return true;
 }
 
-static bool RecordReplay_PositionHit(JSContext* aCx, unsigned aArgc,
-                                     Value* aVp) {
+static bool RecordReplay_ManifestFinished(JSContext* aCx, unsigned aArgc,
+                                          Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  CharBuffer responseBuffer;
+  if (args.hasDefined(0)) {
+    RootedObject responseObject(aCx, NonNullObject(aCx, args.get(0)));
+    if (!responseObject) {
+      return false;
+    }
+
+    if (!ToJSONMaybeSafely(aCx, responseObject, FillCharBufferCallback,
+                           &responseBuffer)) {
+      return false;
+    }
+  }
+
+  child::ManifestFinished(responseBuffer);
+
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool RecordReplay_ResumeExecution(JSContext* aCx, unsigned aArgc,
+                                         Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  ResumeExecution();
+
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool RecordReplay_RestoreCheckpoint(JSContext* aCx, unsigned aArgc,
+                                         Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
-  RootedObject obj(aCx, NonNullObject(aCx, args.get(0)));
-  if (!obj) {
+
+  if (!args.get(0).isNumber()) {
+    JS_ReportErrorASCII(aCx, "Bad checkpoint ID");
+    return false;
+  }
+
+  size_t checkpoint = args.get(0).toNumber();
+  if (!HasSavedCheckpoint(checkpoint)) {
+    JS_ReportErrorASCII(aCx, "Only saved checkpoints can be restored");
+    return false;
+  }
+
+  RestoreCheckpointAndResume(checkpoint);
+
+  JS_ReportErrorASCII(aCx, "Unreachable!");
+  return false;
+}
+
+// The total amount of time this process has spent idling.
+static double gIdleTimeTotal;
+
+// When recording and we are idle, the time when we became idle.
+static double gIdleTimeStart;
+
+void BeginIdleTime() {
+  if (IsRecording() && Thread::CurrentIsMainThread()) {
+    MOZ_RELEASE_ASSERT(!gIdleTimeStart);
+    gIdleTimeStart = CurrentTime();
+  }
+}
+
+void EndIdleTime() {
+  if (IsRecording() && Thread::CurrentIsMainThread()) {
+    MOZ_RELEASE_ASSERT(!!gIdleTimeStart);
+    gIdleTimeTotal += CurrentTime() - gIdleTimeStart;
+    gIdleTimeStart = 0;
+  }
+}
+
+static bool RecordReplay_CurrentExecutionTime(JSContext* aCx, unsigned aArgc,
+                                              Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  // Get a current timestamp biased by the amount of time the process has spent
+  // idling. Comparing these timestamps gives the elapsed non-idle time between
+  // them.
+  args.rval().setNumber((CurrentTime() - gIdleTimeTotal) / 1000.0);
+  return true;
+}
+
+static bool RecordReplay_FlushRecording(JSContext* aCx, unsigned aArgc,
+                                        Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  FlushRecording();
+
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool RecordReplay_SetMainChild(JSContext* aCx, unsigned aArgc,
+                                      Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  SetMainChild();
+  size_t endpoint = RecordingEndpoint();
+
+  args.rval().setInt32(endpoint);
+  return true;
+}
+
+static bool RecordReplay_SaveCheckpoint(JSContext* aCx, unsigned aArgc,
+                                        Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  if (!args.get(0).isNumber()) {
+    JS_ReportErrorASCII(aCx, "Bad checkpoint ID");
+    return false;
+  }
+
+  size_t checkpoint = args.get(0).toNumber();
+  if (checkpoint <= GetLastCheckpoint()) {
+    JS_ReportErrorASCII(aCx, "Can't save checkpoint in the past");
     return false;
   }
 
-  BreakpointPosition position;
-  if (!position.Decode(aCx, obj)) {
+  SetSaveCheckpoint(checkpoint, true);
+
+  args.rval().setUndefined();
+  return true;
+}
+
+// How many hits on a script location we will precisely track for a checkpoint.
+static const size_t MaxHitsPerCheckpoint = 10;
+
+struct ScriptHitInfo {
+  // Information about a location where a script offset has been hit, or an
+  // aggregate set of hits.
+  struct ScriptHit {
+    // The most recent checkpoint prior to the hit.
+    uint32_t mCheckpoint;
+
+    // Index of the frame where the hit occurred, or UINT32_MAX if this represents
+    // an aggregate set of hits after the checkpoint.
+    uint32_t mFrameIndex;
+
+    // Progress counter when the hit occurred, invalid if this represents an
+    // aggregate set of hits.
+    ProgressCounter mProgress;
+
+    explicit ScriptHit(uint32_t aCheckpoint)
+        : mCheckpoint(aCheckpoint), mFrameIndex(UINT32_MAX), mProgress(0) {}
+
+    ScriptHit(uint32_t aCheckpoint, uint32_t aFrameIndex,
+              ProgressCounter aProgress)
+        : mCheckpoint(aCheckpoint), mFrameIndex(aFrameIndex),
+          mProgress(aProgress) {}
+  };
+
+  struct ScriptHitChunk {
+    ScriptHit mHits[7];
+    ScriptHitChunk* mPrevious;
+  };
+
+  struct ScriptHitKey {
+    uint32_t mScript;
+    uint32_t mOffset;
+
+    ScriptHitKey(uint32_t aScript, uint32_t aOffset)
+        : mScript(aScript), mOffset(aOffset) {}
+
+    typedef ScriptHitKey Lookup;
+
+    static HashNumber hash(const ScriptHitKey& aKey) {
+      return HashGeneric(aKey.mScript, aKey.mOffset);
+    }
+
+    static bool match(const ScriptHitKey& aFirst, const ScriptHitKey& aSecond) {
+      return aFirst.mScript == aSecond.mScript
+          && aFirst.mOffset == aSecond.mOffset;
+    }
+  };
+
+  typedef HashMap<ScriptHitKey, ScriptHitChunk*, ScriptHitKey,
+                  AllocPolicy<MemoryKind::ScriptHits>> ScriptHitMap;
+  ScriptHitMap mTable;
+  ScriptHitChunk* mFreeChunk;
+
+  ScriptHitInfo() : mFreeChunk(nullptr) {}
+
+  ScriptHitChunk* FindHits(uint32_t aScript, uint32_t aOffset) {
+    ScriptHitKey key(aScript, aOffset);
+    ScriptHitMap::Ptr p = mTable.lookup(key);
+    return p ? p->value() : nullptr;
+  }
+
+  void AddHit(uint32_t aScript, uint32_t aOffset, uint32_t aCheckpoint,
+              uint32_t aFrameIndex, ProgressCounter aProgress) {
+    ScriptHitKey key(aScript, aOffset);
+    ScriptHitMap::AddPtr p = mTable.lookupForAdd(key);
+    if (!p && !mTable.add(p, key, NewChunk(nullptr))) {
+      MOZ_CRASH("ScriptHitInfo::AddScriptHit");
+    }
+
+    ScriptHitChunk* chunk = p->value();
+    p->value() = AddHit(chunk, ScriptHit(aCheckpoint, aFrameIndex, aProgress));
+  }
+
+  ScriptHitChunk* AddHit(ScriptHitChunk* aChunk, const ScriptHit& aHit) {
+    for (int i = ArrayLength(aChunk->mHits) - 1; i >= 0; i--) {
+      if (!aChunk->mHits[i].mCheckpoint) {
+        aChunk->mHits[i] = aHit;
+        return aChunk;
+      }
+    }
+    ScriptHitChunk* newChunk = NewChunk(aChunk);
+    newChunk->mHits[ArrayLength(newChunk->mHits) - 1] = aHit;
+    return newChunk;
+  }
+
+  ScriptHitChunk* NewChunk(ScriptHitChunk* aPrevious) {
+    if (!mFreeChunk) {
+      void* mem = AllocateMemory(PageSize, MemoryKind::ScriptHits);
+      ScriptHitChunk* chunks = reinterpret_cast<ScriptHitChunk*>(mem);
+      size_t numChunks = PageSize / sizeof(ScriptHitChunk);
+      for (size_t i = 0; i < numChunks - 1; i++) {
+        chunks[i].mPrevious = &chunks[i + 1];
+      }
+      mFreeChunk = chunks;
+    }
+    ScriptHitChunk* result = mFreeChunk;
+    mFreeChunk = mFreeChunk->mPrevious;
+    result->mPrevious = aPrevious;
+    return result;
+  }
+};
+
+static ScriptHitInfo* gScriptHits;
+
+static void InitializeScriptHits() {
+  void* mem = AllocateMemory(sizeof(ScriptHitInfo), MemoryKind::ScriptHits);
+  gScriptHits = new (mem) ScriptHitInfo();
+}
+
+static bool RecordReplay_AddScriptHit(JSContext* aCx, unsigned aArgc,
+                                      Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  if (!args.get(0).isNumber() ||
+      !args.get(1).isNumber() ||
+      !args.get(2).isNumber()) {
+    JS_ReportErrorASCII(aCx, "Bad parameters");
     return false;
   }
 
-  navigation::PositionHit(position);
+  uint32_t script = args.get(0).toNumber();
+  uint32_t offset = args.get(1).toNumber();
+  uint32_t frameIndex = args.get(2).toNumber();
+
+  gScriptHits->AddHit(script, offset, GetLastCheckpoint(), frameIndex,
+                      gProgressCounter);
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool RecordReplay_FindScriptHits(JSContext* aCx, unsigned aArgc,
+                                        Value* aVp) {
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  if (!args.get(0).isNumber() || !args.get(1).isNumber()) {
+    JS_ReportErrorASCII(aCx, "Bad parameters");
+    return false;
+  }
+
+  uint32_t script = args.get(0).toNumber();
+  uint32_t offset = args.get(1).toNumber();
+
+  RootedValueVector values(aCx);
 
-  args.rval().setUndefined();
+  ScriptHitInfo::ScriptHitChunk* chunk =
+      gScriptHits ? gScriptHits->FindHits(script, offset) : nullptr;
+  while (chunk) {
+    for (const auto& hit : chunk->mHits) {
+      if (hit.mCheckpoint) {
+        RootedObject hitObject(aCx, JS_NewObject(aCx, nullptr));
+        if (!hitObject ||
+            !JS_DefineProperty(aCx, hitObject, "checkpoint",
+                               hit.mCheckpoint, JSPROP_ENUMERATE) ||
+            !JS_DefineProperty(aCx, hitObject, "progress",
+                               (double) hit.mProgress, JSPROP_ENUMERATE) ||
+            !JS_DefineProperty(aCx, hitObject, "frameIndex",
+                               hit.mFrameIndex, JSPROP_ENUMERATE) ||
+            !values.append(ObjectValue(*hitObject))) {
+          return false;
+        }
+      }
+    }
+    chunk = chunk->mPrevious;
+  }
+
+  JSObject* array = JS_NewArrayObject(aCx, values);
+  if (!array) {
+    return false;
+  }
+
+  args.rval().setObject(*array);
   return true;
 }
 
 static bool RecordReplay_GetContent(JSContext* aCx, unsigned aArgc,
                                     Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
   RootedString url(aCx, ToString(aCx, args.get(0)));
 
@@ -1060,77 +949,16 @@ static bool RecordReplay_GetContent(JSCo
       !JS_DefineProperty(aCx, obj, "content", content, JSPROP_ENUMERATE)) {
     return false;
   }
 
   args.rval().setObject(*obj);
   return true;
 }
 
-static bool RecordReplay_CurrentExecutionPoint(JSContext* aCx, unsigned aArgc,
-                                               Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  Maybe<BreakpointPosition> position;
-  if (!args.get(0).isUndefined()) {
-    RootedObject obj(aCx, NonNullObject(aCx, args.get(0)));
-    if (!obj) {
-      return false;
-    }
-
-    position.emplace();
-    if (!position.ref().Decode(aCx, obj)) {
-      return false;
-    }
-  }
-
-  ExecutionPoint point = navigation::CurrentExecutionPoint(position);
-  RootedObject result(aCx, point.Encode(aCx));
-  if (!result) {
-    return false;
-  }
-
-  args.rval().setObject(*result);
-  return true;
-}
-
-static bool RecordReplay_TimeWarpTargetExecutionPoint(JSContext* aCx,
-                                                      unsigned aArgc,
-                                                      Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-  double timeWarpTarget;
-  if (!ToNumber(aCx, args.get(0), &timeWarpTarget)) {
-    return false;
-  }
-
-  ExecutionPoint point =
-      navigation::TimeWarpTargetExecutionPoint((ProgressCounter)timeWarpTarget);
-  RootedObject result(aCx, point.Encode(aCx));
-  if (!result) {
-    return false;
-  }
-
-  args.rval().setObject(*result);
-  return true;
-}
-
-static bool RecordReplay_RecordingEndpoint(JSContext* aCx, unsigned aArgc,
-                                           Value* aVp) {
-  CallArgs args = CallArgsFromVp(aArgc, aVp);
-
-  ExecutionPoint point = navigation::GetRecordingEndpoint();
-  RootedObject result(aCx, point.Encode(aCx));
-  if (!result) {
-    return false;
-  }
-
-  args.rval().setObject(*result);
-  return true;
-}
-
 static bool RecordReplay_Repaint(JSContext* aCx, unsigned aArgc, Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   size_t width, height;
   child::Repaint(&width, &height);
 
   RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
   if (!obj ||
@@ -1167,46 +995,41 @@ static bool RecordReplay_Dump(JSContext*
 ///////////////////////////////////////////////////////////////////////////////
 // Plumbing
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JSFunctionSpec gMiddlemanMethods[] = {
     JS_FN("registerReplayDebugger", Middleman_RegisterReplayDebugger, 1, 0),
     JS_FN("canRewind", Middleman_CanRewind, 0, 0),
     JS_FN("spawnReplayingChild", Middleman_SpawnReplayingChild, 0, 0),
-    JS_FN("setActiveChild", Middleman_SetActiveChild, 1, 0),
-    JS_FN("sendSetSaveCheckpoint", Middleman_SendSetSaveCheckpoint, 3, 0),
-    JS_FN("sendFlushRecording", Middleman_SendFlushRecording, 1, 0),
-    JS_FN("sendResume", Middleman_SendResume, 2, 0),
-    JS_FN("sendRestoreCheckpoint", Middleman_SendRestoreCheckpoint, 2, 0),
-    JS_FN("sendRunToPoint", Middleman_SendRunToPoint, 2, 0),
-    JS_FN("sendDebuggerRequest", Middleman_SendDebuggerRequest, 2, 0),
-    JS_FN("sendAddBreakpoint", Middleman_SendAddBreakpoint, 2, 0),
-    JS_FN("sendClearBreakpoints", Middleman_SendClearBreakpoints, 1, 0),
+    JS_FN("sendManifest", Middleman_SendManifest, 2, 0),
     JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0),
     JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0),
     JS_FN("inRepaintStressMode", Middleman_InRepaintStressMode, 0, 0),
     JS_FN("waitUntilPaused", Middleman_WaitUntilPaused, 1, 0),
-    JS_FN("positionSubsumes", Middleman_PositionSubsumes, 2, 0),
     JS_FS_END};
 
 static const JSFunctionSpec gRecordReplayMethods[] = {
     JS_FN("areThreadEventsDisallowed", RecordReplay_AreThreadEventsDisallowed,
           0, 0),
-    JS_FN("maybeDivergeFromRecording", RecordReplay_MaybeDivergeFromRecording,
-          0, 0),
+    JS_FN("divergeFromRecording", RecordReplay_DivergeFromRecording, 0, 0),
+    JS_FN("progressCounter", RecordReplay_ProgressCounter, 0, 0),
     JS_FN("advanceProgressCounter", RecordReplay_AdvanceProgressCounter, 0, 0),
     JS_FN("shouldUpdateProgressCounter",
           RecordReplay_ShouldUpdateProgressCounter, 1, 0),
-    JS_FN("positionHit", RecordReplay_PositionHit, 1, 0),
+    JS_FN("manifestFinished", RecordReplay_ManifestFinished, 1, 0),
+    JS_FN("resumeExecution", RecordReplay_ResumeExecution, 0, 0),
+    JS_FN("restoreCheckpoint", RecordReplay_RestoreCheckpoint, 1, 0),
+    JS_FN("currentExecutionTime", RecordReplay_CurrentExecutionTime, 0, 0),
+    JS_FN("flushRecording", RecordReplay_FlushRecording, 0, 0),
+    JS_FN("setMainChild", RecordReplay_SetMainChild, 0, 0),
+    JS_FN("saveCheckpoint", RecordReplay_SaveCheckpoint, 1, 0),
+    JS_FN("addScriptHit", RecordReplay_AddScriptHit, 3, 0),
+    JS_FN("findScriptHits", RecordReplay_FindScriptHits, 2, 0),
     JS_FN("getContent", RecordReplay_GetContent, 1, 0),
-    JS_FN("currentExecutionPoint", RecordReplay_CurrentExecutionPoint, 1, 0),
-    JS_FN("timeWarpTargetExecutionPoint",
-          RecordReplay_TimeWarpTargetExecutionPoint, 1, 0),
-    JS_FN("recordingEndpoint", RecordReplay_RecordingEndpoint, 0, 0),
     JS_FN("repaint", RecordReplay_Repaint, 0, 0),
     JS_FN("dump", RecordReplay_Dump, 1, 0),
     JS_FS_END};
 
 extern "C" {
 
 MOZ_EXPORT bool RecordReplayInterface_DefineRecordReplayControlObject(
     JSContext* aCx, JSObject* aObjectArg) {
--- a/toolkit/recordreplay/ipc/JSControl.h
+++ b/toolkit/recordreplay/ipc/JSControl.h
@@ -14,231 +14,77 @@
 
 #include "mozilla/DefineEnum.h"
 #include "nsString.h"
 
 namespace mozilla {
 namespace recordreplay {
 
 struct Message;
-struct HitExecutionPointMessage;
+
+namespace parent {
+  class ChildProcessInfo;
+}
 
 namespace js {
 
 // This file manages interactions between the record/replay infrastructure and
 // JS code. This interaction can occur in two ways:
 //
 // - In the middleman process, devtools server code can use the
 //   RecordReplayControl object to send requests to the recording/replaying
 //   child process and control its behavior.
 //
 // - In the recording/replaying process, a JS sandbox is created before the
 //   first checkpoint is reached, which responds to the middleman's requests.
 //   The RecordReplayControl object is also provided here, but has a different
 //   interface which allows the JS to query the current process.
 
-// Identification for a position where a breakpoint can be installed in a child
-// process. Breakpoint positions describe all places between checkpoints where
-// the child process can pause and be inspected by the middleman. A particular
-// BreakpointPosition can be reached any number of times during execution of
-// the process.
-struct BreakpointPosition {
-  // clang-format off
-  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(Kind, (
-    Invalid,
-
-    // Break at a script offset. Requires script/offset.
-    Break,
-
-    // Break for an on-step handler within a frame.
-    // Requires script/offset/frameIndex.
-    OnStep,
-
-    // Break either when any frame is popped, or when a specific frame is
-    // popped. Requires script/frameIndex in the latter case.
-    OnPop,
-
-    // Break when entering any frame.
-    EnterFrame,
-
-    // Break when a new top-level script is created.
-    NewScript,
-
-    // Break when a message is logged to the web console.
-    ConsoleMessage,
-
-    // Break when NewTimeWarpTarget() is called.
-    WarpTarget
-  ));
-  // clang-format on
-
-  Kind mKind;
-
-  // Optional information associated with the breakpoint.
-  uint32_t mScript;
-  uint32_t mOffset;
-  uint32_t mFrameIndex;
-
-  static const uint32_t EMPTY_SCRIPT = (uint32_t)-1;
-  static const uint32_t EMPTY_OFFSET = (uint32_t)-1;
-  static const uint32_t EMPTY_FRAME_INDEX = (uint32_t)-1;
-
-  BreakpointPosition()
-      : mKind(Invalid),
-        mScript(EMPTY_SCRIPT),
-        mOffset(EMPTY_OFFSET),
-        mFrameIndex(EMPTY_FRAME_INDEX) {}
-
-  explicit BreakpointPosition(Kind aKind, uint32_t aScript = EMPTY_SCRIPT,
-                              uint32_t aOffset = EMPTY_OFFSET,
-                              uint32_t aFrameIndex = EMPTY_FRAME_INDEX)
-      : mKind(aKind),
-        mScript(aScript),
-        mOffset(aOffset),
-        mFrameIndex(aFrameIndex) {}
-
-  bool IsValid() const { return mKind != Invalid; }
-
-  inline bool operator==(const BreakpointPosition& o) const {
-    return mKind == o.mKind && mScript == o.mScript && mOffset == o.mOffset &&
-           mFrameIndex == o.mFrameIndex;
-  }
-
-  inline bool operator!=(const BreakpointPosition& o) const {
-    return !(*this == o);
-  }
-
-  // Return whether an execution point matching |o| also matches this.
-  inline bool Subsumes(const BreakpointPosition& o) const {
-    return (*this == o) ||
-           (mKind == OnPop && o.mKind == OnPop && mScript == EMPTY_SCRIPT) ||
-           (mKind == Break && o.mKind == OnStep && mScript == o.mScript &&
-            mOffset == o.mOffset);
-  }
-
-  static const char* StaticKindString(Kind aKind) {
-    switch (aKind) {
-      case Invalid:
-        return "Invalid";
-      case Break:
-        return "Break";
-      case OnStep:
-        return "OnStep";
-      case OnPop:
-        return "OnPop";
-      case EnterFrame:
-        return "EnterFrame";
-      case NewScript:
-        return "NewScript";
-      case ConsoleMessage:
-        return "ConsoleMessage";
-      case WarpTarget:
-        return "WarpTarget";
-    }
-    MOZ_CRASH("Bad BreakpointPosition kind");
-  }
-
-  const char* KindString() const { return StaticKindString(mKind); }
-
-  JSObject* Encode(JSContext* aCx) const;
-  bool Decode(JSContext* aCx, JS::HandleObject aObject);
-  void ToString(nsCString& aStr) const;
-};
-
-// Identification for a point in the execution of a child process where it may
-// pause and be inspected by the middleman. A particular execution point will
-// be reached exactly once during the execution of the process.
-struct ExecutionPoint {
-  // ID of the last normal checkpoint prior to this point.
-  size_t mCheckpoint;
-
-  // How much progress execution has made prior to reaching the point.
-  // A given BreakpointPosition may not be reached twice without an intervening
-  // increment of the global progress counter.
-  ProgressCounter mProgress;
-
-  // The position reached after making the specified amount of progress,
-  // invalid if the execution point refers to the checkpoint itself.
-  BreakpointPosition mPosition;
-
-  ExecutionPoint() : mCheckpoint(CheckpointId::Invalid), mProgress(0) {}
-
-  ExecutionPoint(size_t aCheckpoint, ProgressCounter aProgress)
-      : mCheckpoint(aCheckpoint), mProgress(aProgress) {}
-
-  ExecutionPoint(size_t aCheckpoint, ProgressCounter aProgress,
-                 const BreakpointPosition& aPosition)
-      : mCheckpoint(aCheckpoint), mProgress(aProgress), mPosition(aPosition) {
-    // ExecutionPoint positions must be as precise as possible, and cannot
-    // subsume other positions.
-    MOZ_RELEASE_ASSERT(aPosition.IsValid());
-    MOZ_RELEASE_ASSERT(aPosition.mKind != BreakpointPosition::OnPop ||
-                       aPosition.mScript != BreakpointPosition::EMPTY_SCRIPT);
-    MOZ_RELEASE_ASSERT(aPosition.mKind != BreakpointPosition::Break);
-  }
-
-  bool HasPosition() const { return mPosition.IsValid(); }
-
-  inline bool operator==(const ExecutionPoint& o) const {
-    return mCheckpoint == o.mCheckpoint && mProgress == o.mProgress &&
-           mPosition == o.mPosition;
-  }
-
-  inline bool operator!=(const ExecutionPoint& o) const {
-    return !(*this == o);
-  }
-
-  JSObject* Encode(JSContext* aCx) const;
-  bool Decode(JSContext* aCx, JS::HandleObject aObject);
-  void ToString(nsCString& aStr) const;
-};
-
 // Buffer type used for encoding object data.
 typedef InfallibleVector<char16_t> CharBuffer;
 
 // Set up the JS sandbox in the current recording/replaying process and load
 // its target script.
 void SetupDevtoolsSandbox();
 
+// JS state is initialized when the first checkpoint is reached.
+bool IsInitialized();
+
+// Start processing a manifest received from the middleman.
+void ManifestStart(const CharBuffer& aContents);
+
 // The following hooks are used in the middleman process to call methods defined
 // by the middleman control logic.
 
 // Setup the middleman control state.
 void SetupMiddlemanControl(const Maybe<size_t>& aRecordingChildId);
 
 // Handle an incoming message from a child process.
-void ForwardHitExecutionPointMessage(size_t aId,
-                                     const HitExecutionPointMessage& aMsg);
+void ForwardManifestFinished(parent::ChildProcessInfo* aChild,
+                             const Message& aMsg);
 
 // Prepare the child processes so that the recording file can be safely copied.
 void BeforeSaveRecording();
 void AfterSaveRecording();
 
 // 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.
-void ProcessRequest(const char16_t* aRequest, size_t aRequestLength,
-                    CharBuffer* aResponse);
-
-// Ensure there is a handler in place that will call
-// RecordReplayControl.positionHit whenever the specified execution position is
-// reached.
-void EnsurePositionHandler(const BreakpointPosition& aPosition);
+// Called when running forward, immediately before hitting a normal or
+// temporary checkpoint.
+void BeforeCheckpoint();
 
-// Clear all installed position handlers.
-void ClearPositionHandlers();
-
-// Clear all state that is kept while execution is paused.
-void ClearPausedState();
+// Called immediately after hitting a normal or temporary checkpoint, either
+// when running forward or immediately after rewinding. aRestoredCheckpoint is
+// true if we just rewound.
+void AfterCheckpoint(size_t aCheckpoint, bool aRestoredCheckpoint);
 
-// Given an execution position inside a script, get an execution position for
-// the entry point of that script, otherwise return nothing.
-Maybe<BreakpointPosition> GetEntryPosition(const BreakpointPosition& aPosition);
+// Accessors for state which can be accessed from JS.
 
-// Called after receiving a DebuggerResponse from a child.
-void OnDebuggerResponse(const Message& aMsg);
+// Mark a time span when the main thread is idle.
+void BeginIdleTime();
+void EndIdleTime();
 
 }  // namespace js
 }  // namespace recordreplay
 }  // namespace mozilla
 
 #endif  // mozilla_recordreplay_JSControl_h
--- a/toolkit/recordreplay/ipc/ParentForwarding.cpp
+++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp
@@ -270,18 +270,17 @@ class MiddlemanProtocol : public ipc::IT
     nMessage->CopyFrom(aMessage);
     mOppositeMessageLoop->PostTask(
         NewRunnableFunction("ForwardMessageSync", ForwardMessageSync, mOpposite,
                             nMessage, &aReply));
 
     if (mSide == ipc::ChildSide) {
       AutoMarkMainThreadWaitingForIPDLReply blocked;
       while (!aReply) {
-        GetActiveChild()->WaitUntilPaused();
-        GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true));
+        MOZ_CRASH("NYI");
       }
     } else {
       MonitorAutoLock lock(*gMonitor);
       while (!aReply) {
         gMonitor->Wait();
       }
     }
 
@@ -314,18 +313,17 @@ class MiddlemanProtocol : public ipc::IT
     nMessage->CopyFrom(aMessage);
     mOppositeMessageLoop->PostTask(
         NewRunnableFunction("ForwardCallMessage", ForwardCallMessage, mOpposite,
                             nMessage, &aReply));
 
     if (mSide == ipc::ChildSide) {
       AutoMarkMainThreadWaitingForIPDLReply blocked;
       while (!aReply) {
-        GetActiveChild()->WaitUntilPaused();
-        GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true));
+        MOZ_CRASH("NYI");
       }
     } else {
       MonitorAutoLock lock(*gMonitor);
       while (!aReply) {
         gMonitor->Wait();
       }
     }
 
--- a/toolkit/recordreplay/ipc/ParentGraphics.cpp
+++ b/toolkit/recordreplay/ipc/ParentGraphics.cpp
@@ -99,42 +99,30 @@ static size_t gLastPaintWidth, gLastPain
 // the process state at the most recent checkpoint in the past. When running
 // (forwards or backwards) between the checkpoint and the Paint message,
 // we could pause at a breakpoint and repaint the graphics at that point,
 // reflecting the process state at a point later than at the checkpoint.
 // In this case the Paint message's graphics will be stale. To avoid showing
 // its graphics, we wait until both the Paint and the checkpoint itself have
 // been hit, with no intervening repaint.
 
-// The last explicit paint message received from the child, if there has not
-// been an intervening repaint.
-static UniquePtr<PaintMessage> gLastExplicitPaint;
-
-// The last checkpoint the child reached, if there has not been an intervening
-// repaint.
-static size_t gLastCheckpoint;
-
 void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (aMsg) {
     gLastPaintWidth = aMsg->mWidth;
     gLastPaintHeight = aMsg->mHeight;
   }
 
   if (!gLastPaintWidth || !gLastPaintHeight) {
     return;
   }
 
   bool hadFailure = !aMsg;
 
-  // Clear out the last explicit paint information. This may delete aMsg.
-  gLastExplicitPaint = nullptr;
-  gLastCheckpoint = CheckpointId::Invalid;
-
   // Make sure there is a sandbox which is running the graphics JS module.
   if (!gGraphics) {
     InitGraphicsSandbox();
   }
 
   size_t width = gLastPaintWidth;
   size_t height = gLastPaintHeight;
   size_t stride =
@@ -182,33 +170,16 @@ void UpdateGraphicsInUIProcess(const Pai
   }
 
   // Manually detach this ArrayBuffer once this update completes, as the
   // JS::NewArrayBufferWithUserOwnedContents API mandates.  (The API also
   // guarantees that this call always succeeds.)
   MOZ_ALWAYS_TRUE(JS::DetachArrayBuffer(cx, bufferObject));
 }
 
-static void MaybeTriggerExplicitPaint() {
-  if (gLastExplicitPaint &&
-      gLastExplicitPaint->mCheckpointId == gLastCheckpoint) {
-    UpdateGraphicsInUIProcess(gLastExplicitPaint.get());
-  }
-}
-
-void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg) {
-  gLastExplicitPaint.reset(new PaintMessage(aMsg));
-  MaybeTriggerExplicitPaint();
-}
-
-void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId) {
-  gLastCheckpoint = aCheckpointId;
-  MaybeTriggerExplicitPaint();
-}
-
 bool InRepaintStressMode() {
   static bool checked = false;
   static bool rv;
   if (!checked) {
     AutoEnsurePassThroughThreadEvents pt;
     rv = TestEnv("MOZ_RECORD_REPLAY_REPAINT_STRESS");
     checked = true;
   }
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -84,36 +84,24 @@ ChildProcessInfo* GetChildProcess(size_t
 }
 
 size_t SpawnReplayingChild() {
   ChildProcessInfo* child = new ChildProcessInfo(Nothing());
   gReplayingChildren.append(child);
   return child->GetId();
 }
 
-void SetActiveChild(ChildProcessInfo* aChild) {
-  MOZ_RELEASE_ASSERT(aChild->IsPaused());
-
-  if (gActiveChild) {
-    MOZ_RELEASE_ASSERT(gActiveChild->IsPaused());
-    gActiveChild->SendMessage(SetIsActiveMessage(false));
-  }
-
-  aChild->SendMessage(SetIsActiveMessage(true));
-  gActiveChild = aChild;
-}
-
 void ResumeBeforeWaitingForIPDLReply() {
   MOZ_RELEASE_ASSERT(gActiveChild->IsRecording());
 
   // The main thread is about to block while it waits for a sync reply from the
   // recording child process. If the child is paused, resume it immediately so
   // that we don't deadlock.
   if (gActiveChild->IsPaused()) {
-    gActiveChild->SendMessage(ResumeMessage(/* aForward = */ true));
+    MOZ_CRASH("NYI");
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Preferences
 ///////////////////////////////////////////////////////////////////////////////
 
 static bool gChromeRegistered;
--- a/toolkit/recordreplay/ipc/ParentInternal.h
+++ b/toolkit/recordreplay/ipc/ParentInternal.h
@@ -71,21 +71,16 @@ extern void* gGraphicsMemory;
 void InitializeGraphicsMemory();
 void SendGraphicsMemoryToChild();
 
 // Update the graphics painted in the UI process, per painting data received
 // from a child process, or null if a repaint was triggered and failed due to
 // an unhandled recording divergence.
 void UpdateGraphicsInUIProcess(const PaintMessage* aMsg);
 
-// If necessary, update graphics after the active child sends a paint message
-// or reaches a checkpoint.
-void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg);
-void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId);
-
 // ID for the mach message sent from a child process to the middleman to
 // request a port for the graphics shmem.
 static const int32_t GraphicsHandshakeMessageId = 42;
 
 // ID for the mach message sent from the middleman to a child process with the
 // requested memory for.
 static const int32_t GraphicsMemoryMessageId = 43;
 
@@ -136,17 +131,17 @@ class ChildProcessInfo {
   // Whether the process is currently paused.
   bool mPaused;
 
   // Flags for whether we have received messages from the child indicating it
   // is crashing.
   bool mHasBegunFatalError;
   bool mHasFatalError;
 
-  void OnIncomingMessage(const Message& aMsg, bool aForwardToControl);
+  void OnIncomingMessage(const Message& aMsg);
 
   static void MaybeProcessPendingMessageRunnable();
   void ReceiveChildMessageOnMainThread(Message::UniquePtr aMsg);
 
   void OnCrash(const char* aWhy);
   void LaunchSubprocess(
       const Maybe<RecordingProcessData>& aRecordingProcessData);
 
@@ -159,19 +154,18 @@ class ChildProcessInfo {
   bool IsRecording() { return mRecording; }
   bool IsPaused() { return mPaused; }
 
   // 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();
+  // which caused it to pause.
+  void WaitUntilPaused();
 
   static void SetIntroductionMessage(IntroductionMessage* aMessage);
 };
 
 }  // namespace parent
 }  // namespace recordreplay
 }  // namespace mozilla
 
--- a/toolkit/recordreplay/moz.build
+++ b/toolkit/recordreplay/moz.build
@@ -13,17 +13,16 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CON
     UNIFIED_SOURCES += [
         'Assembler.cpp',
         'Callback.cpp',
         'DirtyMemoryHandler.cpp',
         'File.cpp',
         'HashTable.cpp',
         'ipc/Channel.cpp',
         'ipc/ChildIPC.cpp',
-        'ipc/ChildNavigation.cpp',
         'ipc/ChildProcess.cpp',
         'ipc/JSControl.cpp',
         'ipc/ParentForwarding.cpp',
         'ipc/ParentGraphics.cpp',
         'ipc/ParentIPC.cpp',
         'Lock.cpp',
         'MemorySnapshot.cpp',
         'MiddlemanCall.cpp',