Merge mozilla-central to autoland. a=merge
authorCosmin Sabou <csabou@mozilla.com>
Fri, 19 Oct 2018 07:20:02 +0300
changeset 500514 b809ef6dba1b6fb18ac5151e44b1afa18e104c94
parent 500479 394a1e5544f65e59ea1e12e19f788f50078ddf20 (current diff)
parent 500513 7d74c59053843bd79ed1456e830a760fdf82656b (diff)
child 500515 48155e26a841cdf691af533a74c958e566c6e2b5
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge
build/build-clang/clang-7-mingw.json
taskcluster/scripts/misc/build-clang-7-mingw.sh
--- a/browser/config/mozconfigs/win32/mingwclang
+++ b/browser/config/mozconfigs/win32/mingwclang
@@ -49,17 +49,17 @@ HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang+
 CC="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang"
 CXX="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang++"
 CXXFLAGS="-fms-extensions"
 CPP="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang -E"
 AR=llvm-ar
 RANLIB=llvm-ranlib
 
 # For Stylo
-BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include"
+BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include"
 
 # Since we use windres from binutils without the rest of tools (like cpp), we need to
 # explicitly specify clang as a preprocessor.
 WINDRES="i686-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
 
 # Bug 1471698 - Work around binutils corrupting mingw clang binaries.
 LDFLAGS="-Wl,-S"
 STRIP=/bin/true
--- a/browser/config/mozconfigs/win64/mingwclang
+++ b/browser/config/mozconfigs/win64/mingwclang
@@ -49,17 +49,17 @@ HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang+
 CC="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang"
 CXX="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang++"
 CXXFLAGS="-fms-extensions"
 CPP="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang -E"
 AR=llvm-ar
 RANLIB=llvm-ranlib
 
 # For Stylo
-BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include"
+BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include"
 
 # Since we use windres from binutils without the rest of tools (like cpp), we need to
 # explicitly specify clang as a preprocessor.
 WINDRES="x86_64-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
 
 # Bug 1471698 - Work around binutils corrupting mingw clang binaries.
 LDFLAGS="-Wl,-S"
 STRIP=/bin/true
rename from build/build-clang/clang-7-mingw.json
rename to build/build-clang/clang-trunk-mingw.json
--- a/build/build-clang/clang-7-mingw.json
+++ b/build/build-clang/clang-trunk-mingw.json
@@ -1,18 +1,18 @@
 {
     "llvm_revision": "342383",
     "stages": "3",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/trunk",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc"
 }
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -57,17 +57,21 @@ ReplayDebugger.prototype = {
   /////////////////////////////////////////////////////////
 
   replaying: true,
 
   canRewind: RecordReplayControl.canRewind,
   replayResumeBackward() { RecordReplayControl.resume(/* forward = */ false); },
   replayResumeForward() { RecordReplayControl.resume(/* forward = */ true); },
   replayTimeWarp: RecordReplayControl.timeWarp,
-  replayPause: RecordReplayControl.pause,
+
+  replayPause() {
+    RecordReplayControl.pause();
+    this._repaint();
+  },
 
   addDebuggee() {},
   removeAllDebuggees() {},
 
   replayingContent(url) {
     return this._sendRequest({ type: "getContent", url });
   },
 
@@ -85,16 +89,28 @@ ReplayDebugger.prototype = {
   // diverge from the recording. In such cases we want to be interacting with a
   // replaying process (if there is one), as recording child processes won't
   // provide useful responses to such requests.
   _sendRequestAllowDiverge(request) {
     RecordReplayControl.maybeSwitchToReplayingChild();
     return this._sendRequest(request);
   },
 
+  // Update graphics according to the current state of the child process. This
+  // should be done anytime we pause and allow the user to interact with the
+  // debugger.
+  _repaint() {
+    const rv = this._sendRequestAllowDiverge({ type: "repaint" });
+    if ("width" in rv && "height" in rv) {
+      RecordReplayControl.hadRepaint(rv.width, rv.height);
+    } else {
+      RecordReplayControl.hadRepaintFailure();
+    }
+  },
+
   _setBreakpoint(handler, position, data) {
     const id = RecordReplayControl.setBreakpoint(handler, position);
     this._breakpoints.push({id, position, data});
   },
 
   _clearMatchingBreakpoints(callback) {
     this._breakpoints = this._breakpoints.filter(breakpoint => {
       if (callback(breakpoint)) {
@@ -158,20 +174,34 @@ ReplayDebugger.prototype = {
 
   _addScript(data) {
     if (!this._scripts[data.id]) {
       this._scripts[data.id] = new ReplayDebuggerScript(this, data);
     }
     return this._scripts[data.id];
   },
 
-  findScripts() {
-    // Note: Debugger's findScripts() method takes a query argument, which
-    // we ignore here.
-    const data = this._sendRequest({ type: "findScripts" });
+  _convertScriptQuery(query) {
+    // Make a copy of the query, converting properties referring to debugger
+    // things into their associated ids.
+    const rv = Object.assign({}, query);
+    if ("global" in query) {
+      rv.global = query.global._data.id;
+    }
+    if ("source" in query) {
+      rv.source = query.source._data.id;
+    }
+    return rv;
+  },
+
+  findScripts(query) {
+    const data = this._sendRequest({
+      type: "findScripts",
+      query: this._convertScriptQuery(query)
+    });
     return data.map(script => this._addScript(script));
   },
 
   findAllConsoleMessages() {
     const messages = this._sendRequest({ type: "findConsoleMessages" });
     return messages.map(this._convertConsoleMessage.bind(this));
   },
 
@@ -310,42 +340,45 @@ ReplayDebugger.prototype = {
   set onNewScript(handler) {
     this._breakpointKindSetter("NewScript", handler,
                                () => handler.call(this, this._getNewScript()));
   },
 
   get onEnterFrame() { return this._breakpointKindGetter("EnterFrame"); },
   set onEnterFrame(handler) {
     this._breakpointKindSetter("EnterFrame", handler,
-                               () => handler.call(this, this.getNewestFrame()));
+                               () => { this._repaint();
+                                       handler.call(this, this.getNewestFrame()); });
   },
 
   get replayingOnPopFrame() {
     return this._searchBreakpoints(({position, data}) => {
       return (position.kind == "OnPop" && !position.script) ? data : null;
     });
   },
 
   set replayingOnPopFrame(handler) {
     if (handler) {
-      this._setBreakpoint(() => handler.call(this, this.getNewestFrame()),
+      this._setBreakpoint(() => { this._repaint();
+                                  handler.call(this, this.getNewestFrame()); },
                           { kind: "OnPop" }, handler);
     } else {
       this._clearMatchingBreakpoints(({position}) => {
         return position.kind == "OnPop" && !position.script;
       });
     }
   },
 
   get replayingOnForcedPause() {
     return this._breakpointKindGetter("ForcedPause");
   },
   set replayingOnForcedPause(handler) {
     this._breakpointKindSetter("ForcedPause", handler,
-                               () => handler.call(this, this.getNewestFrame()));
+                               () => { this._repaint();
+                                       handler.call(this, this.getNewestFrame()); });
   },
 
   getNewConsoleMessage() {
     const message = this._sendRequest({ type: "getNewConsoleMessage" });
     return this._convertConsoleMessage(message);
   },
 
   get onConsoleMessage() {
@@ -383,17 +416,18 @@ ReplayDebuggerScript.prototype = {
   },
 
   getLineOffsets(line) { return this._forward("getLineOffsets", line); },
   getOffsetLocation(pc) { return this._forward("getOffsetLocation", pc); },
   getSuccessorOffsets(pc) { return this._forward("getSuccessorOffsets", pc); },
   getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); },
 
   setBreakpoint(offset, handler) {
-    this._dbg._setBreakpoint(() => { handler.hit(this._dbg.getNewestFrame()); },
+    this._dbg._setBreakpoint(() => { this._dbg._repaint();
+                                     handler.hit(this._dbg.getNewestFrame()); },
                              { kind: "Break", script: this._data.id, offset },
                              handler);
   },
 
   clearBreakpoint(handler) {
     this._dbg._clearMatchingBreakpoints(({position, data}) => {
       return position.script == this._data.id && handler == data;
     });
@@ -500,17 +534,18 @@ ReplayDebuggerFrame.prototype = {
       ({position}) => this._positionMatches(position, "OnStep")
     );
   },
 
   setReplayingOnStep(handler, offsets) {
     this._clearOnStepBreakpoints();
     offsets.forEach(offset => {
       this._dbg._setBreakpoint(
-        () => handler.call(this._dbg.getNewestFrame()),
+        () => { this._dbg._repaint();
+                handler.call(this._dbg.getNewestFrame()); },
         { kind: "OnStep",
           script: this._data.script,
           offset,
           frameIndex: this._data.index },
         handler);
     });
   },
 
@@ -518,16 +553,17 @@ ReplayDebuggerFrame.prototype = {
     return this._dbg._searchBreakpoints(({position, data}) => {
       return this._positionMatches(position, "OnPop") ? data : null;
     });
   },
 
   set onPop(handler) {
     if (handler) {
       this._dbg._setBreakpoint(() => {
+          this._dbg._repaint();
           const result = this._dbg._sendRequest({ type: "popFrameResult" });
           handler.call(this._dbg.getNewestFrame(),
                        this._dbg._convertCompletionValue(result));
         },
         { kind: "OnPop", script: this._data.script, frameIndex: this._data.index },
         handler);
     } else {
       this._dbg._clearMatchingBreakpoints(
@@ -599,41 +635,45 @@ ReplayDebuggerObject.prototype = {
 
   getOwnPropertySymbols() {
     // Symbol properties are not handled yet.
     return [];
   },
 
   getOwnPropertyDescriptor(name) {
     this._ensureProperties();
-    return this._properties[name];
+    const desc = this._properties[name];
+    return desc ? this._convertPropertyDescriptor(desc) : null;
   },
 
   _ensureProperties() {
     if (!this._properties) {
       const properties = this._dbg._sendRequestAllowDiverge({
         type: "getObjectProperties",
         id: this._data.id
       });
       this._properties = {};
-      properties.forEach(({name, desc}) => {
-        if ("value" in desc) {
-          desc.value = this._dbg._convertValue(desc.value);
-        }
-        if ("get" in desc) {
-          desc.get = this._dbg._getObject(desc.get);
-        }
-        if ("set" in desc) {
-          desc.set = this._dbg._getObject(desc.set);
-        }
-        this._properties[name] = desc;
-      });
+      properties.forEach(({name, desc}) => { this._properties[name] = desc; });
     }
   },
 
+  _convertPropertyDescriptor(desc) {
+    const rv = Object.assign({}, desc);
+    if ("value" in desc) {
+      rv.value = this._dbg._convertValue(desc.value);
+    }
+    if ("get" in desc) {
+      rv.get = this._dbg._getObject(desc.get);
+    }
+    if ("set" in desc) {
+      rv.set = this._dbg._getObject(desc.set);
+    }
+    return rv;
+  },
+
   get allocationSite() { NYI(); },
   get errorMessageName() { NYI(); },
   get errorNotes() { NYI(); },
   get errorLineNumber() { NYI(); },
   get errorColumnNumber() { NYI(); },
   get proxyTarget() { NYI(); },
   get proxyHandler() { NYI(); },
   get isPromise() { NYI(); },
--- a/devtools/server/actors/replay/graphics.js
+++ b/devtools/server/actors/replay/graphics.js
@@ -9,17 +9,17 @@
 // which are connected to the compositor in the UI process in the usual way.
 // We need to update the contents of the document to draw the raw graphics data
 // provided by the child process.
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-function updateWindow(window, buffer, width, height) {
+function updateWindow(window, buffer, width, height, hadFailure) {
   // Make sure the window has a canvas filling the screen.
   let canvas = window.middlemanCanvas;
   if (!canvas) {
     canvas = window.document.createElement("canvas");
     window.document.body.style.margin = "0px";
     window.document.body.insertBefore(canvas, window.document.body.firstChild);
     window.middlemanCanvas = canvas;
   }
@@ -31,33 +31,43 @@ function updateWindow(window, buffer, wi
   // been scaled in the child process. To avoid scaling the graphics twice,
   // transform the canvas to undo the scaling.
   const scale = window.devicePixelRatio;
   if (scale != 1) {
     canvas.style.transform =
       `scale(${ 1 / scale }) translate(-${ width / scale }px, -${ height / scale }px)`;
   }
 
+  const cx = canvas.getContext("2d");
+
   const graphicsData = new Uint8Array(buffer);
-  const imageData = canvas.getContext("2d").getImageData(0, 0, width, height);
+  const imageData = cx.getImageData(0, 0, width, height);
   imageData.data.set(graphicsData);
-  canvas.getContext("2d").putImageData(imageData, 0, 0);
+  cx.putImageData(imageData, 0, 0);
+
+  // Indicate to the user when repainting failed and we are showing old painted
+  // graphics instead of the most up-to-date graphics.
+  if (hadFailure) {
+    cx.fillStyle = "red";
+    cx.font = "48px serif";
+    cx.fillText("PAINT FAILURE", 10, 50);
+  }
 
   // Make recording/replaying tabs easier to differentiate from other tabs.
   window.document.title = "RECORD/REPLAY";
 }
 
 // Entry point for when we have some new graphics data from the child process
 // to draw.
 // eslint-disable-next-line no-unused-vars
-function Update(buffer, width, height) {
+function Update(buffer, width, height, hadFailure) {
   try {
     // Paint to all windows we can find. Hopefully there is only one.
     for (const window of Services.ww.getWindowEnumerator()) {
-      updateWindow(window, buffer, width, height);
+      updateWindow(window, buffer, width, height, hadFailure);
     }
   } catch (e) {
     dump("Middleman Graphics Update Exception: " + e + "\n");
   }
 }
 
 // eslint-disable-next-line no-unused-vars
 var EXPORTED_SYMBOLS = [
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -518,20 +518,40 @@ function forwardToScript(name) {
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Handlers
 ///////////////////////////////////////////////////////////////////////////////
 
 const gRequestHandlers = {
 
+  repaint() {
+    if (!RecordReplayControl.maybeDivergeFromRecording()) {
+      return {};
+    }
+    return RecordReplayControl.repaint();
+  },
+
   findScripts(request) {
+    const query = Object.assign({}, request.query);
+    if ("global" in query) {
+      query.global = gPausedObjects.getObject(query.global);
+    }
+    if ("source" in query) {
+      query.source = gScriptSources.getObject(query.source);
+      if (!query.source) {
+        return [];
+      }
+    }
+    const scripts = dbg.findScripts(query);
     const rv = [];
-    gScripts.forEach((id) => {
-      rv.push(getScriptData(id));
+    scripts.forEach(script => {
+      if (considerScript(script)) {
+        rv.push(getScriptData(gScripts.getId(script)));
+      }
     });
     return rv;
   },
 
   getScript(request) {
     return getScriptData(request.id);
   },
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1221,20 +1221,20 @@ TabChild::RecvShow(const ScreenIntSize& 
   ApplyShowInfo(aInfo);
   RecvParentActivated(aParentIsActive);
 
   if (!res) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   // We have now done enough initialization for the record/replay system to
-  // create checkpoints. Try to create the initial checkpoint now, in case this
-  // process never paints later on (the usual place where checkpoints occur).
+  // create checkpoints. Create a checkpoint now, in case this process never
+  // paints later on (the usual place where checkpoints occur).
   if (recordreplay::IsRecordingOrReplaying()) {
-    recordreplay::child::MaybeCreateInitialCheckpoint();
+    recordreplay::child::CreateCheckpoint();
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvInitRendering(const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                             const layers::LayersId& aLayersId,
--- a/gfx/2d/NativeFontResourceDWrite.cpp
+++ b/gfx/2d/NativeFontResourceDWrite.cpp
@@ -267,18 +267,18 @@ NativeFontResourceDWrite::Create(uint8_t
   UINT32 numberOfFaces;
   hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numberOfFaces);
   if (FAILED(hr) || !isSupported) {
     gfxWarning() << "Font file is not supported.";
     return nullptr;
   }
 
   RefPtr<NativeFontResourceDWrite> fontResource =
-    new NativeFontResourceDWrite(factory, fontFile.forget(), faceType,
-                                 numberOfFaces, aNeedsCairo);
+    new NativeFontResourceDWrite(factory, fontFile.forget(), ffsRef.forget(),
+                                 faceType, numberOfFaces, aNeedsCairo);
   return fontResource.forget();
 }
 
 already_AddRefed<UnscaledFont>
 NativeFontResourceDWrite::CreateUnscaledFont(uint32_t aIndex,
                                              const uint8_t* aInstanceData,
                                              uint32_t aInstanceDataLength)
 {
--- a/gfx/2d/NativeFontResourceDWrite.h
+++ b/gfx/2d/NativeFontResourceDWrite.h
@@ -35,24 +35,30 @@ public:
   already_AddRefed<UnscaledFont>
     CreateUnscaledFont(uint32_t aIndex,
                        const uint8_t* aInstanceData,
                        uint32_t aInstanceDataLength) final;
 
 private:
   NativeFontResourceDWrite(IDWriteFactory *aFactory,
                            already_AddRefed<IDWriteFontFile> aFontFile,
+                           already_AddRefed<IDWriteFontFileStream> aFontFileStream,
                            DWRITE_FONT_FACE_TYPE aFaceType,
                            uint32_t aNumberOfFaces, bool aNeedsCairo)
-    : mFactory(aFactory), mFontFile(aFontFile), mFaceType(aFaceType)
-    , mNumberOfFaces(aNumberOfFaces), mNeedsCairo(aNeedsCairo)
+    : mFactory(aFactory)
+    , mFontFile(aFontFile)
+    , mFontFileStream(aFontFileStream)
+    , mFaceType(aFaceType)
+    , mNumberOfFaces(aNumberOfFaces)
+    , mNeedsCairo(aNeedsCairo)
   {}
 
   IDWriteFactory *mFactory;
   RefPtr<IDWriteFontFile> mFontFile;
+  RefPtr<IDWriteFontFileStream> mFontFileStream;
   DWRITE_FONT_FACE_TYPE mFaceType;
   uint32_t mNumberOfFaces;
   bool mNeedsCairo;
 };
 
 } // gfx
 } // mozilla
 
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -766,36 +766,32 @@ ShadowLayerForwarder::EndTransaction(con
     mPaintTiming.serializeMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
     startTime = Some(TimeStamp::Now());
   }
 
   // We delay at the last possible minute, to give the paint thread a chance to
   // finish. If it does we don't have to delay messages at all.
   GetCompositorBridgeChild()->PostponeMessagesIfAsyncPainting();
 
-  if (recordreplay::IsRecordingOrReplaying()) {
-    recordreplay::child::NotifyPaintStart();
-  }
-
   MOZ_LAYERS_LOG(("[LayersForwarder] sending transaction..."));
   RenderTraceScope rendertrace3("Forward Transaction", "000093");
   if (!mShadowManager->SendUpdate(info)) {
     MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending transaction failed!"));
     return false;
   }
 
-  if (recordreplay::IsRecordingOrReplaying()) {
-    recordreplay::child::WaitForPaintToComplete();
-  }
-
   if (startTime) {
     mPaintTiming.sendMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
     mShadowManager->SendRecordPaintTimes(mPaintTiming);
   }
 
+  if (recordreplay::IsRecordingOrReplaying()) {
+    recordreplay::child::NotifyPaintStart();
+  }
+
   *aSent = true;
   mIsFirstPaint = false;
   mFocusTarget = FocusTarget();
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
 RefPtr<CompositableClient>
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -4619,16 +4619,17 @@ impl DebugServer {
 
     pub fn send(&mut self, _: String) {}
 }
 
 // Some basic statistics about the rendered scene
 // that we can use in wrench reftests to ensure that
 // tests are batching and/or allocating on render
 // targets as we expect them to.
+#[repr(C)]
 pub struct RendererStats {
     pub total_draw_calls: usize,
     pub alpha_target_count: usize,
     pub color_target_count: usize,
     pub texture_upload_kb: usize,
 }
 
 impl RendererStats {
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -14,24 +14,16 @@ use internal_types::{RenderTargetInfo, T
 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use resource_cache::CacheItem;
 use std::cell::Cell;
 use std::cmp;
 use std::mem;
 use std::rc::Rc;
 
-// The fixed number of layers for the shared texture cache.
-// There is one array texture per image format, allocated lazily.
-const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4;
-const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1;
-
-// The dimensions of each layer in the texture cache.
-const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
-
 // The size of each region (page) in a texture layer.
 const TEXTURE_REGION_DIMENSIONS: u32 = 512;
 
 // Items in the texture cache can either be standalone textures,
 // or a sub-rect inside the shared cache.
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -233,35 +225,54 @@ pub struct TextureCache {
     // for evicting old cache items.
     shared_entry_handles: Vec<FreeListHandle<CacheEntryMarker>>,
 }
 
 impl TextureCache {
     pub fn new(max_texture_size: u32) -> Self {
         TextureCache {
             max_texture_size,
+            // Used primarily for cached shadow masks. There can be lots of
+            // these on some pages like francine, but most pages don't use it
+            // much.
             array_a8_linear: TextureArray::new(
                 ImageFormat::R8,
                 TextureFilter::Linear,
-                TEXTURE_ARRAY_LAYERS_LINEAR,
+                1024,
+                1,
             ),
+            // Used for experimental hdr yuv texture support, but not used in
+            // production Firefox.
             array_a16_linear: TextureArray::new(
                 ImageFormat::R16,
                 TextureFilter::Linear,
-                TEXTURE_ARRAY_LAYERS_LINEAR,
+                1024,
+                1,
             ),
+            // The primary cache for images, glyphs, etc.
             array_rgba8_linear: TextureArray::new(
                 ImageFormat::BGRA8,
                 TextureFilter::Linear,
-                TEXTURE_ARRAY_LAYERS_LINEAR,
+                2048,
+                4,
             ),
+            // Used for image-rendering: crisp. This is mostly favicons, which
+            // are small. Some other images use it too, but those tend to be
+            // larger than 512x512 and thus don't use the shared cache anyway.
+            //
+            // Ideally we'd use 512 as the dimensions here, since we don't really
+            // need more. But once a page gets something of a given size bucket
+            // assigned to it, all further allocations need to be of that size.
+            // So using 1024 gives us 4 buckets instead of 1, which in practice
+            // is probably enough.
             array_rgba8_nearest: TextureArray::new(
                 ImageFormat::BGRA8,
                 TextureFilter::Nearest,
-                TEXTURE_ARRAY_LAYERS_NEAREST,
+                1024,
+                1,
             ),
             next_id: CacheTextureId(1),
             pending_updates: TextureUpdateList::new(),
             frame_id: FrameId(0),
             entries: FreeList::new(),
             standalone_entry_handles: Vec::new(),
             shared_entry_handles: Vec::new(),
         }
@@ -741,18 +752,18 @@ impl TextureCache {
         // Lazy initialize this texture array if required.
         if texture_array.texture_id.is_none() {
             let texture_id = self.next_id;
             self.next_id.0 += 1;
 
             let update_op = TextureUpdate {
                 id: texture_id,
                 op: TextureUpdateOp::Create {
-                    width: TEXTURE_LAYER_DIMENSIONS,
-                    height: TEXTURE_LAYER_DIMENSIONS,
+                    width: texture_array.dimensions,
+                    height: texture_array.dimensions,
                     format: descriptor.format,
                     filter: texture_array.filter,
                     // TODO(gw): Creating a render target here is only used
                     //           for the texture cache debugger display. In
                     //           the future, we should change the debug
                     //           display to use a shader that blits the
                     //           texture, and then we can remove this
                     //           memory allocation (same for the other
@@ -1076,49 +1087,52 @@ impl TextureRegion {
 
 // A texture array contains a number of texture layers, where
 // each layer contains one or more regions that can act
 // as slab allocators.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureArray {
     filter: TextureFilter,
+    dimensions: u32,
     layer_count: usize,
     format: ImageFormat,
     is_allocated: bool,
     regions: Vec<TextureRegion>,
     texture_id: Option<CacheTextureId>,
 }
 
 impl TextureArray {
     fn new(
         format: ImageFormat,
         filter: TextureFilter,
-        layer_count: usize
+        dimensions: u32,
+        layer_count: usize,
     ) -> Self {
         TextureArray {
             format,
             filter,
+            dimensions,
             layer_count,
             is_allocated: false,
             regions: Vec::new(),
             texture_id: None,
         }
     }
 
     fn clear(&mut self) -> Option<CacheTextureId> {
         self.is_allocated = false;
         self.regions.clear();
         self.texture_id.take()
     }
 
     fn update_profile(&self, counter: &mut ResourceProfileCounter) {
         if self.is_allocated {
-            let size = self.layer_count as u32 * TEXTURE_LAYER_DIMENSIONS *
-                TEXTURE_LAYER_DIMENSIONS * self.format.bytes_per_pixel();
+            let size = self.layer_count as u32 * self.dimensions *
+                self.dimensions * self.format.bytes_per_pixel();
             counter.set(self.layer_count as usize, size as usize);
         } else {
             counter.set(0, 0);
         }
     }
 
     // Allocate space in this texture array.
     fn alloc(
@@ -1127,18 +1141,18 @@ impl TextureArray {
         user_data: [f32; 3],
         frame_id: FrameId,
         uv_rect_kind: UvRectKind,
     ) -> Option<CacheEntry> {
         // Lazily allocate the regions if not already created.
         // This means that very rarely used image formats can be
         // added but won't allocate a cache if never used.
         if !self.is_allocated {
-            debug_assert!(TEXTURE_LAYER_DIMENSIONS % TEXTURE_REGION_DIMENSIONS == 0);
-            let regions_per_axis = TEXTURE_LAYER_DIMENSIONS / TEXTURE_REGION_DIMENSIONS;
+            debug_assert!(self.dimensions % TEXTURE_REGION_DIMENSIONS == 0);
+            let regions_per_axis = self.dimensions / TEXTURE_REGION_DIMENSIONS;
             for layer_index in 0 .. self.layer_count {
                 for y in 0 .. regions_per_axis {
                     for x in 0 .. regions_per_axis {
                         let origin = DeviceUintPoint::new(
                             x * TEXTURE_REGION_DIMENSIONS,
                             y * TEXTURE_REGION_DIMENSIONS,
                         );
                         let region = TextureRegion::new(
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-74f265e447d2927c27d4320c676779956d39eaf0
+b648c76e2dc2cbcbd635322cdf94ab9d5320e0c1
--- a/ipc/chromium/src/base/message_pump_default.cc
+++ b/ipc/chromium/src/base/message_pump_default.cc
@@ -55,16 +55,29 @@ void MessagePumpDefault::Run(Delegate* d
     if (did_work)
       continue;
 
     if (delayed_work_time_.is_null()) {
       hangMonitor.NotifyWait();
       AUTO_PROFILER_LABEL("MessagePumpDefault::Run:Wait", IDLE);
       {
         AUTO_PROFILER_THREAD_SLEEP;
+        // Some threads operating a message pump (i.e. the compositor thread)
+        // can diverge from Web Replay recordings, after which the thread needs
+        // to be explicitly notified in order to coordinate with the
+        // record/replay checkpoint system.
+        if (mozilla::recordreplay::IsRecordingOrReplaying()) {
+          // Workaround static analysis. Message pumps for which the lambda
+          // below might be called will not be destroyed.
+          void* thiz = this;
+
+          mozilla::recordreplay::MaybeWaitForCheckpointSave();
+          mozilla::recordreplay::NotifyUnrecordedWait([=]() { ((MessagePumpDefault*)thiz)->ScheduleWork(); },
+                                                      /* aOnlyWhenDiverged = */ true);
+        }
         event_.Wait();
       }
     } else {
       TimeDelta delay = delayed_work_time_ - TimeTicks::Now();
       if (delay > TimeDelta()) {
         hangMonitor.NotifyWait();
         AUTO_PROFILER_LABEL("MessagePumpDefault::Run:Wait", IDLE);
         {
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -990,16 +990,27 @@ MessageChannel::Echo(Message* aMsg)
 
     mLink->EchoMessage(msg.release());
     return true;
 }
 
 bool
 MessageChannel::Send(Message* aMsg)
 {
+    if (recordreplay::HasDivergedFromRecording() &&
+        recordreplay::child::SuppressMessageAfterDiverge(aMsg))
+    {
+        // Only certain IPDL messages are allowed to be sent in a replaying
+        // process after it has diverged from the recording, to avoid
+        // deadlocking with threads that remain idle. The browser remains
+        // paused after diverging from the recording, and other IPDL messages
+        // do not need to be sent.
+        return true;
+    }
+
     if (aMsg->size() >= kMinTelemetryMessageSize) {
         Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
     }
 
     // If the message was created by the IPC bindings, the create time will be
     // recorded. Use this information to report the IPC_WRITE_MAIN_THREAD_LATENCY_MS (time
     // from message creation to it being sent).
     if (NS_IsMainThread() && aMsg->create_time()) {
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -351,31 +351,31 @@ const v2vSigSection = sigSection([v2vSig
 
 function checkMiscPrefixed(opcode, expect_failure) {
     let binary = moduleWithSections(
            [v2vSigSection, declSection([0]), memorySection(1),
             bodySection(
                 [funcBody(
                     {locals:[],
                      body:[0x41, 0x0, 0x41, 0x0, 0x41, 0x0, // 3 x const.i32 0
-                           MiscPrefix, opcode]})])]);
+                           MiscPrefix, ...opcode]})])]);
     if (expect_failure) {
         assertErrorMessage(() => new WebAssembly.Module(binary),
                            WebAssembly.CompileError, /unrecognized opcode/);
     } else {
-        assertEq(true, WebAssembly.validate(binary));
+        assertEq(WebAssembly.validate(binary), true);
     }
 }
 
 //-----------------------------------------------------------
 // Verification cases for memory.copy/fill opcode encodings
 
-checkMiscPrefixed(0x0a, false); // memory.copy
-checkMiscPrefixed(0x0b, false); // memory.fill
-checkMiscPrefixed(0x0f, true);  // table.copy+1, which is currently unassigned
+checkMiscPrefixed([0x0a, 0x00], false); // memory.copy, flags=0
+checkMiscPrefixed([0x0b, 0x00], false); // memory.fill, flags=0
+checkMiscPrefixed([0x0f], true);        // table.copy+1, which is currently unassigned
 
 //-----------------------------------------------------------
 // Verification cases for memory.copy/fill arguments
 
 // Invalid argument types
 {
     const tys = ['i32', 'f32', 'i64', 'f64'];
     const ops = ['copy', 'fill'];
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -157,17 +157,18 @@ MaybeWaitForRecordReplayCheckpointSave(A
             mozilla::recordreplay::MaybeWaitForCheckpointSave();
         }
 
         // For the std::function destructor invoked below.
         JS::AutoSuppressGCAnalysis nogc;
 
         // Now that we are holding the helper thread state lock again,
         // notify the record/replay system that we might block soon.
-        mozilla::recordreplay::NotifyUnrecordedWait(HelperThread::WakeupAll);
+        mozilla::recordreplay::NotifyUnrecordedWait(HelperThread::WakeupAll,
+                                                    /* aOnlyWhenDiverged = */ false);
     }
 }
 
 bool
 js::StartOffThreadWasmCompile(wasm::CompileTask* task, wasm::CompileMode mode)
 {
     AutoLockHelperThreadState lock;
 
--- a/js/src/vm/TypedArrayObject-inl.h
+++ b/js/src/vm/TypedArrayObject-inl.h
@@ -274,88 +274,79 @@ class ElementSpecific
         SharedMem<T*> dest = target->dataPointerEither().template cast<T*>() + offset;
         uint32_t count = source->length();
 
         if (source->type() == target->type()) {
             Ops::podCopy(dest, source->dataPointerEither().template cast<T*>(), count);
             return true;
         }
 
-        // Inhibit unaligned accesses on ARM (bug 1097253, a compiler bug).
-#if defined(__arm__) && MOZ_IS_GCC
-#  define JS_VOLATILE_ARM volatile
-#else
-#  define JS_VOLATILE_ARM
-#endif
-
         SharedMem<void*> data = Ops::extract(source);
         switch (source->type()) {
           case Scalar::Int8: {
-            SharedMem<JS_VOLATILE_ARM int8_t*> src = data.cast<JS_VOLATILE_ARM int8_t*>();
+            SharedMem<int8_t*> src = data.cast<int8_t*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Uint8:
           case Scalar::Uint8Clamped: {
-            SharedMem<JS_VOLATILE_ARM uint8_t*> src = data.cast<JS_VOLATILE_ARM uint8_t*>();
+            SharedMem<uint8_t*> src = data.cast<uint8_t*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Int16: {
-            SharedMem<JS_VOLATILE_ARM int16_t*> src = data.cast<JS_VOLATILE_ARM int16_t*>();
+            SharedMem<int16_t*> src = data.cast<int16_t*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Uint16: {
-            SharedMem<JS_VOLATILE_ARM uint16_t*> src = data.cast<JS_VOLATILE_ARM uint16_t*>();
+            SharedMem<uint16_t*> src = data.cast<uint16_t*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Int32: {
-            SharedMem<JS_VOLATILE_ARM int32_t*> src = data.cast<JS_VOLATILE_ARM int32_t*>();
+            SharedMem<int32_t*> src = data.cast<int32_t*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Uint32: {
-            SharedMem<JS_VOLATILE_ARM uint32_t*> src = data.cast<JS_VOLATILE_ARM uint32_t*>();
+            SharedMem<uint32_t*> src = data.cast<uint32_t*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Float32: {
-            SharedMem<JS_VOLATILE_ARM float*> src = data.cast<JS_VOLATILE_ARM float*>();
+            SharedMem<float*> src = data.cast<float*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           case Scalar::Float64: {
-            SharedMem<JS_VOLATILE_ARM double*> src = data.cast<JS_VOLATILE_ARM double*>();
+            SharedMem<double*> src = data.cast<double*>();
             for (uint32_t i = 0; i < count; ++i) {
                 Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
             }
             break;
           }
           default:
             MOZ_CRASH("setFromTypedArray with a typed array with bogus type");
         }
 
-#undef JS_VOLATILE_ARM
-
         return true;
     }
 
     /*
      * Copy |source[0]| to |source[len]| (exclusive) elements into the typed
      * array |target|, starting at index |offset|.  |source| must not be a
      * typed array.
      */
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -9234,18 +9234,25 @@ BaseCompiler::emitInstanceCall(uint32_t 
         }
         passArg(t, peek(numArgs - i), &baselineCall);
     }
     builtinInstanceMethodCall(builtin, instanceArg, baselineCall);
     endCall(baselineCall, stackSpace);
 
     popValueStackBy(numArgs);
 
-    // Note, a number of clients of emitInstanceCall currently assume that the
-    // following operation does not destroy ReturnReg.
+    // Note, many clients of emitInstanceCall currently assume that pushing the
+    // result here does not destroy ReturnReg.
+    //
+    // Furthermore, clients assume that even if retType == ExprType::Void, the
+    // callee may have returned a status result and left it in ReturnReg for us
+    // to find, and that that register will not be destroyed here (or above).
+    // In this case the callee will have a C++ declaration stating that there is
+    // a return value.  Examples include memory and table operations that are
+    // implemented as callouts.
 
     pushReturnedIfNonVoid(baselineCall, retType);
 }
 
 bool
 BaseCompiler::emitGrowMemory()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
@@ -10075,42 +10082,31 @@ BaseCompiler::emitStructNarrow()
     }
 
     // AnyRef -> AnyRef is a no-op, just leave the value on the stack.
 
     if (inputType == ValType::AnyRef && outputType == ValType::AnyRef) {
         return true;
     }
 
-    // Null pointers are just passed through.
-
-    Label done;
-    Label doTest;
     RegPtr rp = popRef();
-    masm.branchTestPtr(Assembler::NonZero, rp, rp, &doTest);
-    pushRef(NULLREF_VALUE);
-    masm.jump(&done);
 
     // AnyRef -> (ref T) must first unbox; leaves rp or null
 
     bool mustUnboxAnyref = inputType == ValType::AnyRef;
 
     // Dynamic downcast (ref T) -> (ref U), leaves rp or null
 
     const StructType& outputStruct = env_.types[outputType.refTypeIndex()].structType();
 
-    masm.bind(&doTest);
-
     pushI32(mustUnboxAnyref);
     pushI32(outputStruct.moduleIndex_);
     pushRef(rp);
     emitInstanceCall(lineOrBytecode, SigPIIP_, ExprType::AnyRef, SymbolicAddress::StructNarrow);
 
-    masm.bind(&done);
-
     return true;
 }
 
 bool
 BaseCompiler::emitBody()
 {
     if (!iter_.readFunctionStart(funcType().ret())) {
         return false;
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -709,23 +709,28 @@ Instance::structNew(Instance* instance, 
 {
     JSContext* cx = TlsContext.get();
     Rooted<TypeDescr*> typeDescr(cx, instance->structTypeDescrs_[typeIndex]);
     return TypedObject::createZeroed(cx, typeDescr);
 }
 
 /* static */ void*
 Instance::structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
-                       void* nonnullPtr)
+                       void* maybeNullPtr)
 {
     JSContext* cx = TlsContext.get();
 
     Rooted<TypedObject*> obj(cx);
     Rooted<StructTypeDescr*> typeDescr(cx);
 
+    if (maybeNullPtr == nullptr) {
+        return maybeNullPtr;
+    }
+
+    void* nonnullPtr = maybeNullPtr;
     if (mustUnboxAnyref) {
         Rooted<NativeObject*> no(cx, static_cast<NativeObject*>(nonnullPtr));
         if (!no->is<TypedObject>()) {
             return nullptr;
         }
         obj = &no->as<TypedObject>();
         Rooted<TypeDescr*> td(cx, &obj->typeDescr());
         if (td->kind() != type::Struct) {
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -187,17 +187,18 @@ class Instance
     static int32_t memInit(Instance* instance, uint32_t dstOffset,
                            uint32_t srcOffset, uint32_t len, uint32_t segIndex);
     static int32_t tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
     static int32_t tableDrop(Instance* instance, uint32_t segIndex);
     static int32_t tableInit(Instance* instance, uint32_t dstOffset,
                              uint32_t srcOffset, uint32_t len, uint32_t segIndex);
     static void postBarrier(Instance* instance, gc::Cell** location);
     static void* structNew(Instance* instance, uint32_t typeIndex);
-    static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex, void* ptr);
+    static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
+                              void* maybeNullPtr);
 };
 
 typedef UniquePtr<Instance> UniqueInstance;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_instance_h
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -2050,16 +2050,24 @@ OpIter<Policy>::readMemOrTableCopy(bool 
             return fail("can't touch memory without memory");
         }
     } else {
         if (env_.tables.length() == 0) {
             return fail("can't table.copy without a table");
         }
     }
 
+    uint8_t memOrTableFlags;
+    if (!readFixedU8(&memOrTableFlags)) {
+        return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
+    }
+    if (memOrTableFlags != 0) {
+        return fail(isMem ? "memory flags must be zero" : "table flags must be zero");
+    }
+
     if (!popWithType(ValType::I32, len)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, src)) {
         return false;
     }
 
@@ -2109,16 +2117,24 @@ inline bool
 OpIter<Policy>::readMemFill(Value* start, Value* val, Value* len)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::MemFill);
 
     if (!env_.usesMemory()) {
         return fail("can't touch memory without memory");
     }
 
+    uint8_t memoryFlags;
+    if (!readFixedU8(&memoryFlags)) {
+        return fail("unable to read memory flags");
+    }
+    if (memoryFlags != 0) {
+        return fail("memory flags must be zero");
+    }
+
     if (!popWithType(ValType::I32, len)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, val)) {
         return false;
     }
 
@@ -2153,16 +2169,24 @@ OpIter<Policy>::readMemOrTableInit(bool 
     if (!popWithType(ValType::I32, src)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, dst)) {
         return false;
     }
 
+    uint8_t memOrTableFlags;
+    if (!readFixedU8(&memOrTableFlags)) {
+        return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
+    }
+    if (memOrTableFlags != 0) {
+        return fail(isMem ? "memory flags must be zero" : "table flags must be zero");
+    }
+
     if (!readVarU32(segIndex)) {
         return false;
     }
 
     if (isMem) {
         // Same comment as for readMemOrTableDrop.
         dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
     } else {
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -6285,48 +6285,53 @@ EncodeWake(Encoder& e, AstWake& s)
 {
     return EncodeLoadStoreAddress(e, s.address()) &&
            EncodeExpr(e, s.count()) &&
            e.writeOp(ThreadOp::Wake) &&
            EncodeLoadStoreFlags(e, s.address());
 }
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
+static const uint8_t DEFAULT_MEM_TABLE_FLAGS = 0;
+
 static bool
 EncodeMemOrTableCopy(Encoder& e, AstMemOrTableCopy& s)
 {
     return EncodeExpr(e, s.dest()) &&
            EncodeExpr(e, s.src()) &&
            EncodeExpr(e, s.len()) &&
-           e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy);
+           e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy) &&
+           e.writeFixedU8(DEFAULT_MEM_TABLE_FLAGS);
 }
 
 static bool
 EncodeMemOrTableDrop(Encoder& e, AstMemOrTableDrop& s)
 {
     return e.writeOp(s.isMem() ? MiscOp::MemDrop : MiscOp::TableDrop) &&
            e.writeVarU32(s.segIndex());
 }
 
 static bool
 EncodeMemFill(Encoder& e, AstMemFill& s)
 {
     return EncodeExpr(e, s.start()) &&
            EncodeExpr(e, s.val()) &&
            EncodeExpr(e, s.len()) &&
-           e.writeOp(MiscOp::MemFill);
+           e.writeOp(MiscOp::MemFill) &&
+           e.writeFixedU8(DEFAULT_MEM_TABLE_FLAGS);
 }
 
 static bool
 EncodeMemOrTableInit(Encoder& e, AstMemOrTableInit& s)
 {
     return EncodeExpr(e, s.dst()) &&
            EncodeExpr(e, s.src()) &&
            EncodeExpr(e, s.len()) &&
            e.writeOp(s.isMem() ? MiscOp::MemInit : MiscOp::TableInit) &&
+           e.writeFixedU8(DEFAULT_MEM_TABLE_FLAGS) &&
            e.writeVarU32(s.segIndex());
 }
 #endif
 
 #ifdef ENABLE_WASM_GC
 static bool
 EncodeStructNew(Encoder& e, AstStructNew& s)
 {
--- a/layout/ipc/VsyncChild.cpp
+++ b/layout/ipc/VsyncChild.cpp
@@ -62,24 +62,22 @@ VsyncChild::ActorDestroy(ActorDestroyRea
 }
 
 mozilla::ipc::IPCResult
 VsyncChild::RecvNotify(const TimeStamp& aVsyncTimestamp)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mIsShutdown);
 
-  // Ignore Vsync messages sent to a recording/replaying process. Vsyncs are
-  // triggered at the top of the main thread's event loop instead.
-  if (recordreplay::IsRecordingOrReplaying()) {
-    return IPC_OK();
-  }
-
   SchedulerGroup::MarkVsyncRan();
   if (mObservingVsync && mObserver) {
+    if (recordreplay::IsRecordingOrReplaying() && !recordreplay::child::OnVsync()) {
+      return IPC_OK();
+    }
+
     mObserver->NotifyVsync(aVsyncTimestamp);
   }
   return IPC_OK();
 }
 
 void
 VsyncChild::SetVsyncObserver(VsyncObserver* aVsyncObserver)
 {
--- a/mfbt/RecordReplay.cpp
+++ b/mfbt/RecordReplay.cpp
@@ -46,17 +46,18 @@ namespace recordreplay {
   Macro(InternalEndOrderedAtomicAccess, (), ())                 \
   Macro(InternalBeginPassThroughThreadEvents, (), ())           \
   Macro(InternalEndPassThroughThreadEvents, (), ())             \
   Macro(InternalBeginDisallowThreadEvents, (), ())              \
   Macro(InternalEndDisallowThreadEvents, (), ())                \
   Macro(InternalRecordReplayBytes,                              \
         (void* aData, size_t aSize), (aData, aSize))            \
   Macro(NotifyUnrecordedWait,                                   \
-        (const std::function<void()>& aCallback), (aCallback))  \
+        (const std::function<void()>& aCallback, bool aOnlyWhenDiverged), \
+        (aCallback, aOnlyWhenDiverged))                         \
   Macro(MaybeWaitForCheckpointSave, (), ())                     \
   Macro(InternalInvalidateRecording, (const char* aWhy), (aWhy)) \
   Macro(InternalDestroyPLDHashTableCallbacks,                   \
         (const PLDHashTableOps* aOps), (aOps))                  \
   Macro(InternalMovePLDHashTableContents,                       \
         (const PLDHashTableOps* aFirstOps, const PLDHashTableOps* aSecondOps), \
         (aFirstOps, aSecondOps))                                \
   Macro(SetWeakPointerJSRoot,                                   \
--- a/mfbt/RecordReplay.h
+++ b/mfbt/RecordReplay.h
@@ -253,22 +253,26 @@ static inline bool HasDivergedFromRecord
 // passed through) or an associated cvar --- this is handled automatically.
 //
 // Threads which block indefinitely on unrecorded resources must call
 // NotifyUnrecordedWait first.
 //
 // The callback passed to NotifyUnrecordedWait will be invoked at most once
 // by the main thread whenever the main thread is waiting for other threads to
 // become idle, and at most once after the call to NotifyUnrecordedWait if the
-// main thread is already waiting for other threads to become idle.
+// main thread is already waiting for other threads to become idle. If
+// aOnlyWhenDiverged is specified, the callback will only be invoked if the
+// thread has diverged from the recording (causing all resources to be treated
+// as unrecorded).
 //
 // The callback should poke the thread so that it is no longer blocked on the
 // resource. The thread must call MaybeWaitForCheckpointSave before blocking
 // again.
-MFBT_API void NotifyUnrecordedWait(const std::function<void()>& aCallback);
+MFBT_API void NotifyUnrecordedWait(const std::function<void()>& aCallback,
+                                   bool aOnlyWhenDiverged);
 MFBT_API void MaybeWaitForCheckpointSave();
 
 // API for debugging inconsistent behavior between recording and replay.
 // By calling Assert or AssertBytes a thread event will be inserted and any
 // inconsistent execution order of events will be detected (as for normal
 // thread events) and reported to the console.
 //
 // RegisterThing/UnregisterThing associate arbitrary pointers with indexes that
--- a/modules/libmar/sign/mar_sign.c
+++ b/modules/libmar/sign/mar_sign.c
@@ -529,16 +529,17 @@ extract_signature(const char *src, uint3
     fprintf(stderr, "ERROR: Signature index was out of range\n");
     goto failure;
   }
 
   /* Skip to the correct signature */
   for (i = 0; i <= sigIndex; i++) {
     /* Avoid leaking while skipping signatures */
     free(extractedSignature);
+    extractedSignature = NULL;
 
     /* skip past the signature algorithm ID */
     if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) {
       fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n");
       goto failure;
     }
 
     /* Get the signature length */
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -914,17 +914,17 @@ win32-mingwclang/opt:
         need-xvfb: false
     toolchains:
         - mingw32-rust
         - linux64-upx
         - linux64-wine
         - linux64-sccache
         - linux64-cbindgen
         - linux64-node
-        - linux64-clang-7-mingw-x86
+        - linux64-clang-trunk-mingw-x86
         - linux64-mingw32-nsis
         - linux64-mingw32-fxc2
 
 win32-mingwclang/debug:
     description: "Win32 MinGW-Clang Debug"
     index:
         product: firefox
         job-name: win32-mingwclang-debug
@@ -950,17 +950,17 @@ win32-mingwclang/debug:
         need-xvfb: false
     toolchains:
         - mingw32-rust
         - linux64-upx
         - linux64-wine
         - linux64-sccache
         - linux64-cbindgen
         - linux64-node
-        - linux64-clang-7-mingw-x86
+        - linux64-clang-trunk-mingw-x86
         - linux64-mingw32-nsis
         - linux64-mingw32-fxc2
 
 win64-mingwclang/opt:
     description: "Win64 MinGW-Clang Opt"
     index:
         product: firefox
         job-name: win64-mingwclang-opt
@@ -985,17 +985,17 @@ win64-mingwclang/opt:
         need-xvfb: false
     toolchains:
         - mingw32-rust
         - linux64-upx
         - linux64-wine
         - linux64-sccache
         - linux64-cbindgen
         - linux64-node
-        - linux64-clang-7-mingw-x64
+        - linux64-clang-trunk-mingw-x64
         - linux64-mingw32-nsis
         - linux64-mingw32-fxc2
 
 win64-mingwclang/debug:
     description: "Win64 MinGW-Clang Debug"
     index:
         product: firefox
         job-name: win64-mingwclang-debug
@@ -1021,11 +1021,11 @@ win64-mingwclang/debug:
         need-xvfb: false
     toolchains:
         - mingw32-rust
         - linux64-upx
         - linux64-wine
         - linux64-sccache
         - linux64-cbindgen
         - linux64-node
-        - linux64-clang-7-mingw-x64
+        - linux64-clang-trunk-mingw-x64
         - linux64-mingw32-nsis
         - linux64-mingw32-fxc2
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/release-mark-as-started/kind.yml
@@ -0,0 +1,51 @@
+# 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/.
+
+loader: taskgraph.loader.transform:loader
+
+transforms:
+   - taskgraph.transforms.release_mark_as_started:transforms
+   - taskgraph.transforms.task:transforms
+
+job-defaults:
+   description: mark release as started in Ship-It v1
+   worker-type:
+      by-project:
+         mozilla-beta: scriptworker-prov-v1/shipit-v1
+         mozilla-release: scriptworker-prov-v1/shipit-v1
+         mozilla-esr60: scriptworker-prov-v1/shipit-v1
+         default: scriptworker-prov-v1/shipit-dev
+   worker:
+      implementation: shipit-started
+   scopes:
+      by-project:
+         mozilla-beta:
+            - project:releng:ship-it:server:production
+            - project:releng:ship-it:action:mark-as-started
+         mozilla-release:
+            - project:releng:ship-it:server:production
+            - project:releng:ship-it:action:mark-as-started
+         mozilla-esr60:
+            - project:releng:ship-it:server:production
+            - project:releng:ship-it:action:mark-as-started
+         default:
+            - project:releng:ship-it:server:staging
+            - project:releng:ship-it:action:mark-as-started
+   run-on-projects: []
+   shipping-phase: promote
+   locales-file: browser/locales/l10n-changesets.json
+
+jobs:
+   fennec:
+      name: release-fennec_mark_as_started
+      shipping-product: fennec
+      locales-file: mobile/locales/l10n-changesets.json
+
+   firefox:
+      name: release-firefox_mark_as_started
+      shipping-product: firefox
+
+   devedition:
+      name: release-devedition_mark_as_started
+      shipping-product: devedition
--- a/taskcluster/ci/toolchain/linux.yml
+++ b/taskcluster/ci/toolchain/linux.yml
@@ -65,59 +65,59 @@ linux64-clang-7:
         resources:
             - 'build/build-clang/build-clang.py'
             - 'build/build-clang/clang-7-linux64.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clang.tar.xz
     toolchains:
         - linux64-gcc-4.9
 
-linux64-clang-7-mingw-x86:
-    description: "MinGW-Clang 7 x86 toolchain build"
+linux64-clang-trunk-mingw-x86:
+    description: "MinGW-Clang Trunk x86 toolchain build"
     treeherder:
         kind: build
         platform: toolchains/opt
-        symbol: TMW(clang7-32)
+        symbol: TMW(clang-x86)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux-xlarge
     worker:
         max-run-time: 7200
     run:
         using: toolchain-script
-        script: build-clang-7-mingw.sh
+        script: build-clang-trunk-mingw.sh
         arguments: [
             'x86'
         ]
         resources:
             - 'build/build-clang/build-clang.py'
-            - 'build/build-clang/clang-7-mingw.json'
+            - 'build/build-clang/clang-trunk-mingw.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clangmingw.tar.xz
     toolchains:
         - linux64-gcc-4.9
 
-linux64-clang-7-mingw-x64:
-    description: "MinGW-Clang 7 Pre x64 toolchain build"
+linux64-clang-trunk-mingw-x64:
+    description: "MinGW-Clang Trunk x64 toolchain build"
     treeherder:
         kind: build
         platform: toolchains/opt
-        symbol: TMW(clang7-64)
+        symbol: TMW(clang-x64)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux-xlarge
     worker:
         max-run-time: 7200
     run:
         using: toolchain-script
-        script: build-clang-7-mingw.sh
+        script: build-clang-trunk-mingw.sh
         arguments: [
             'x64'
         ]
         resources:
             - 'build/build-clang/build-clang.py'
-            - 'build/build-clang/clang-7-mingw.json'
+            - 'build/build-clang/clang-trunk-mingw.json'
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/clangmingw.tar.xz
     toolchains:
         - linux64-gcc-4.9
 
 linux64-clang-6-macosx-cross:
     description: "Clang 6 toolchain build with MacOS Compiler RT libs"
     treeherder:
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -290,16 +290,20 @@ Notify when a release has been promoted.
 release-bouncer-sub
 -------------------
 Submits bouncer updates for releases.
 
 release-mark-as-shipped
 -----------------------
 Marks releases as shipped in Ship-It v1
 
+release-mark-as-started
+-----------------------
+Marks releases as started in Ship-It v1
+
 release-bouncer-aliases
 -----------------------
 Update Bouncer's (download.mozilla.org) "latest" aliases.
 
 cron-bouncer-check
 ------------------
 Checks Bouncer (download.mozilla.org) uptake.
 
rename from taskcluster/scripts/misc/build-clang-7-mingw.sh
rename to taskcluster/scripts/misc/build-clang-trunk-mingw.sh
--- a/taskcluster/scripts/misc/build-clang-7-mingw.sh
+++ b/taskcluster/scripts/misc/build-clang-trunk-mingw.sh
@@ -25,17 +25,16 @@ WORKSPACE=$HOME/workspace
 HOME_DIR=$WORKSPACE/build
 UPLOAD_DIR=$HOME/artifacts
 
 TOOLCHAIN_DIR=$WORKSPACE/moz-toolchain
 INSTALL_DIR=$TOOLCHAIN_DIR/build/stage3/clang
 CROSS_PREFIX_DIR=$INSTALL_DIR/$machine-w64-mingw32
 SRC_DIR=$TOOLCHAIN_DIR/src
 
-CLANG_VERSION=7.0.0
 make_flags="-j$(nproc)"
 
 mingw_version=cfd85ebed773810429bf2164c3a985895b7dbfe3
 libunwind_version=1f89d78bb488bc71cfdee8281fc0834e9fbe5dce
 
 binutils_version=2.27
 binutils_ext=bz2
 binutils_sha=369737ce51587f92466041a97ab7d2358c6d9e1b6490b3940eb09fb0a9a6ac88
@@ -132,16 +131,17 @@ build_mingw() {
   pushd widl
   $SRC_DIR/mingw-w64/mingw-w64-tools/widl/configure --target=$machine-w64-mingw32 --prefix=$INSTALL_DIR
   make $make_flags
   make $make_flags install
   popd
 }
 
 build_compiler_rt() {
+  CLANG_VERSION=$(basename $(dirname $(dirname $(dirname $($CC --print-libgcc-file-name -rtlib=compiler-rt)))))
   mkdir compiler-rt
   pushd compiler-rt
   cmake \
       -DCMAKE_BUILD_TYPE=Release \
       -DCMAKE_C_COMPILER=$CC \
       -DCMAKE_SYSTEM_NAME=Windows \
       -DCMAKE_AR=$INSTALL_DIR/bin/llvm-ar \
       -DCMAKE_RANLIB=$INSTALL_DIR/bin/llvm-ranlib \
@@ -287,17 +287,17 @@ export PATH=$INSTALL_DIR/bin:$PATH
 
 prepare
 
 # gets a bit too verbose here
 set +x
 
 cd build/build-clang
 # |mach python| sets up a virtualenv for us!
-../../mach python ./build-clang.py -c clang-7-mingw.json --skip-tar
+../../mach python ./build-clang.py -c clang-trunk-mingw.json --skip-tar
 
 set -x
 
 pushd $TOOLCHAIN_DIR/build
 
 install_wrappers
 build_mingw
 build_compiler_rt
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/transforms/release_mark_as_started.py
@@ -0,0 +1,57 @@
+
+# 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/.
+"""
+Add from parameters.yml into Balrog publishing tasks.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import json
+
+from taskgraph.transforms.base import TransformSequence
+from taskgraph.transforms.l10n import parse_locales_file
+from taskgraph.util.schema import resolve_keyed_by
+from taskgraph.util.scriptworker import get_release_config
+
+transforms = TransformSequence()
+
+
+@transforms.add
+def make_task_description(config, jobs):
+    release_config = get_release_config(config)
+    for job in jobs:
+        resolve_keyed_by(
+            job, 'worker-type', item_name=job['name'], project=config.params['project']
+        )
+        resolve_keyed_by(
+            job, 'scopes', item_name=job['name'], project=config.params['project']
+        )
+
+        job['worker']['release-name'] = '{product}-{version}-build{build_number}'.format(
+            product=job['shipping-product'].capitalize(),
+            version=release_config['version'],
+            build_number=release_config['build_number']
+        )
+        job['worker']['product'] = job['shipping-product']
+        branch = config.params['head_repository'].split('https://hg.mozilla.org/')[1]
+        job['worker']['branch'] = branch
+
+        # locales files has different structure between mobile and desktop
+        locales_file = job['locales-file']
+        all_locales = {}
+
+        if job['shipping-product'] == 'fennec':
+            with open(locales_file, mode='r') as f:
+                all_locales = json.dumps(json.load(f))
+        else:
+            all_locales = "\n".join([
+                "{} {}".format(locale, revision)
+                for locale, revision in sorted(parse_locales_file(job['locales-file']).items())
+            ])
+
+        job['worker']['locales'] = all_locales
+        del job['locales-file']
+
+        yield job
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -612,16 +612,22 @@ task_description_schema = Schema({
             Required('taskId'): taskref_or_string,
             Required('taskType'): basestring,
             Required('paths'): [basestring],
         }],
     }, {
         Required('implementation'): 'shipit-shipped',
         Required('release-name'): basestring,
     }, {
+        Required('implementation'): 'shipit-started',
+        Required('release-name'): basestring,
+        Required('product'): basestring,
+        Required('branch'): basestring,
+        Required('locales'): basestring,
+    }, {
         Required('implementation'): 'treescript',
         Required('tags'): [Any('buildN', 'release', None)],
         Required('bump'): bool,
         Optional('bump-files'): [basestring],
         Optional('repo-param-prefix'): basestring,
         Optional('dontbuild'): bool,
         Required('force-dry-run', default=True): bool,
         Required('push', default=False): bool
@@ -1246,16 +1252,33 @@ def build_push_snap_payload(config, task
 def build_ship_it_shipped_payload(config, task, task_def):
     worker = task['worker']
 
     task_def['payload'] = {
         'release_name': worker['release-name']
     }
 
 
+@payload_builder('shipit-started')
+def build_ship_it_started_payload(config, task, task_def):
+    worker = task['worker']
+    release_config = get_release_config(config)
+
+    task_def['payload'] = {
+        'release_name': worker['release-name'],
+        'product': worker['product'],
+        'version': release_config['version'],
+        'build_number': release_config['build_number'],
+        'branch': worker['branch'],
+        'revision': get_branch_rev(config),
+        'partials': release_config.get('partial_versions', ""),
+        'l10n_changesets': worker['locales'],
+    }
+
+
 @payload_builder('sign-and-push-addons')
 def build_sign_and_push_addons_payload(config, task, task_def):
     worker = task['worker']
 
     task_def['payload'] = {
         'channel': worker['channel'],
         'upstreamArtifacts': worker['upstream-artifacts'],
     }
--- a/taskcluster/taskgraph/util/scriptworker.py
+++ b/taskcluster/taskgraph/util/scriptworker.py
@@ -332,16 +332,17 @@ def get_release_config(config):
 
     partial_updates = os.environ.get("PARTIAL_UPDATES", "")
     if partial_updates != "" and config.kind in ('release-bouncer-sub',
                                                  'release-bouncer-check',
                                                  'release-update-verify-config',
                                                  'release-secondary-update-verify-config',
                                                  'release-balrog-submit-toplevel',
                                                  'release-secondary-balrog-submit-toplevel',
+                                                 'release-mark-as-started'
                                                  ):
         partial_updates = json.loads(partial_updates)
         release_config['partial_versions'] = ', '.join([
             '{}build{}'.format(v, info['buildNumber'])
             for v, info in partial_updates.items()
         ])
         if release_config['partial_versions'] == "{}":
             del release_config['partial_versions']
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/BufferStream.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_recordreplay_BufferStream_h
+#define mozilla_recordreplay_BufferStream_h
+
+#include "InfallibleVector.h"
+
+namespace mozilla {
+namespace recordreplay {
+
+// BufferStream provides similar functionality to Stream in File.h, allowing
+// reading or writing to a stream of data backed by an in memory buffer instead
+// of data stored on disk.
+class BufferStream
+{
+  InfallibleVector<char>* mOutput;
+
+  const char* mInput;
+  size_t mInputSize;
+
+public:
+  BufferStream(const char* aInput, size_t aInputSize)
+    : mOutput(nullptr), mInput(aInput), mInputSize(aInputSize)
+  {}
+
+  explicit BufferStream(InfallibleVector<char>* aOutput)
+    : mOutput(aOutput), mInput(nullptr), mInputSize(0)
+  {}
+
+  void WriteBytes(const void* aData, size_t aSize) {
+    MOZ_RELEASE_ASSERT(mOutput);
+    mOutput->append((char*) aData, aSize);
+  }
+
+  void WriteScalar(size_t aValue) {
+    WriteBytes(&aValue, sizeof(aValue));
+  }
+
+  void ReadBytes(void* aData, size_t aSize) {
+    if (aSize) {
+      MOZ_RELEASE_ASSERT(mInput);
+      MOZ_RELEASE_ASSERT(aSize <= mInputSize);
+      memcpy(aData, mInput, aSize);
+      mInput += aSize;
+      mInputSize -= aSize;
+    }
+  }
+
+  size_t ReadScalar() {
+    size_t rv;
+    ReadBytes(&rv, sizeof(rv));
+    return rv;
+  }
+
+  bool IsEmpty() {
+    MOZ_RELEASE_ASSERT(mInput);
+    return mInputSize == 0;
+  }
+};
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_BufferStream_h
--- a/toolkit/recordreplay/Callback.cpp
+++ b/toolkit/recordreplay/Callback.cpp
@@ -44,16 +44,19 @@ BeginCallback(size_t aCallbackId)
   MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
 
   Thread* thread = Thread::Current();
   if (thread->IsMainThread()) {
     child::EndIdleTime();
   }
   thread->SetPassThrough(false);
 
+  RecordingEventSection res(thread);
+  MOZ_RELEASE_ASSERT(res.CanAccessEvents());
+
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteCallback);
   thread->Events().WriteScalar(aCallbackId);
 }
 
 void
 EndCallback()
 {
   MOZ_RELEASE_ASSERT(IsRecording());
@@ -68,17 +71,18 @@ EndCallback()
 }
 
 void
 SaveOrRestoreCallbackData(void** aData)
 {
   MOZ_RELEASE_ASSERT(gCallbackData);
 
   Thread* thread = Thread::Current();
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
+  RecordingEventSection res(thread);
+  MOZ_RELEASE_ASSERT(res.CanAccessEvents());
 
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RestoreCallbackData);
 
   size_t index = 0;
   if (IsRecording() && *aData) {
     StaticMutexAutoLock lock(gCallbackMutex);
     index = gCallbackData->GetIndex(*aData);
   }
@@ -97,17 +101,18 @@ RemoveCallbackData(void* aData)
   StaticMutexAutoLock lock(gCallbackMutex);
   gCallbackData->Remove(aData);
 }
 
 void
 PassThroughThreadEventsAllowCallbacks(const std::function<void()>& aFn)
 {
   Thread* thread = Thread::Current();
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
+  RecordingEventSection res(thread);
+  MOZ_RELEASE_ASSERT(res.CanAccessEvents());
 
   if (IsRecording()) {
     if (thread->IsMainThread()) {
       child::BeginIdleTime();
     }
     thread->SetPassThrough(true);
     aFn();
     if (thread->IsMainThread()) {
--- a/toolkit/recordreplay/File.cpp
+++ b/toolkit/recordreplay/File.cpp
@@ -42,23 +42,17 @@ Stream::ReadBytes(void* aData, size_t aS
     totalRead += bufRead;
     aSize -= bufRead;
 
     if (!aSize) {
       return;
     }
 
     MOZ_RELEASE_ASSERT(mBufferPos == mBufferLength);
-
-    // If we try to read off the end of a stream then we must have hit the end
-    // of the replay for this thread.
-    while (mChunkIndex == mChunks.length()) {
-      MOZ_RELEASE_ASSERT(mName == StreamName::Event);
-      HitEndOfRecording();
-    }
+    MOZ_RELEASE_ASSERT(mChunkIndex < mChunks.length());
 
     const StreamChunkLocation& chunk = mChunks[mChunkIndex++];
 
     EnsureMemory(&mBallast, &mBallastSize, chunk.mCompressedSize, BallastMaxSize(),
                  DontCopyExistingData);
     mFile->ReadChunk(mBallast.get(), chunk);
 
     EnsureMemory(&mBuffer, &mBufferSize, chunk.mDecompressedSize, BUFFER_MAX,
@@ -84,16 +78,17 @@ Stream::AtEnd()
 
   return mBufferPos == mBufferLength && mChunkIndex == mChunks.length();
 }
 
 void
 Stream::WriteBytes(const void* aData, size_t aSize)
 {
   MOZ_RELEASE_ASSERT(mFile->OpenForWriting());
+  MOZ_RELEASE_ASSERT(mName != StreamName::Event || mInRecordingEventSection);
 
   // Prevent the entire file from being flushed while we write this data.
   AutoReadSpinLock streamLock(mFile->mStreamLock);
 
   while (true) {
     // Fill up the data buffer first.
     MOZ_RELEASE_ASSERT(mBufferPos <= mBufferSize);
     size_t bufAvailable = mBufferSize - mBufferPos;
--- a/toolkit/recordreplay/File.h
+++ b/toolkit/recordreplay/File.h
@@ -55,20 +55,22 @@ enum class StreamName
 {
   Main,
   Lock,
   Event,
   Count
 };
 
 class File;
+class RecordingEventSection;
 
 class Stream
 {
   friend class File;
+  friend class RecordingEventSection;
 
   // File this stream belongs to.
   File* mFile;
 
   // Prefix name for this stream.
   StreamName mName;
 
   // Index which, when combined to mName, uniquely identifies this stream in
@@ -112,32 +114,36 @@ class Stream
   // The number of chunks that have been completely read or written. When
   // writing, this equals mChunks.length().
   size_t mChunkIndex;
 
   // When writing, the number of chunks in this stream when the file was last
   // flushed.
   size_t mFlushedChunks;
 
+  // Whether there is a RecordingEventSection instance active for this stream.
+  bool mInRecordingEventSection;
+
   Stream(File* aFile, StreamName aName, size_t aNameIndex)
     : mFile(aFile)
     , mName(aName)
     , mNameIndex(aNameIndex)
     , mBuffer(nullptr)
     , mBufferSize(0)
     , mBufferLength(0)
     , mBufferPos(0)
     , mStreamPos(0)
     , mBallast(nullptr)
     , mBallastSize(0)
     , mInputBallast(nullptr)
     , mInputBallastSize(0)
     , mLastEvent((ThreadEvent) 0)
     , mChunkIndex(0)
     , mFlushedChunks(0)
+    , mInRecordingEventSection(false)
   {}
 
 public:
   StreamName Name() const { return mName; }
   size_t NameIndex() const { return mNameIndex; }
 
   void ReadBytes(void* aData, size_t aSize);
   void WriteBytes(const void* aData, size_t aSize);
@@ -199,16 +205,17 @@ class File
 {
 public:
   enum Mode {
     WRITE,
     READ
   };
 
   friend class Stream;
+  friend class RecordingEventSection;
 
 private:
   // Open file handle, or 0 if closed.
   FileHandle mFd;
 
   // Whether this file is open for writing or reading.
   Mode mMode;
 
--- a/toolkit/recordreplay/Lock.cpp
+++ b/toolkit/recordreplay/Lock.cpp
@@ -1,18 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Lock.h"
 
-#include "mozilla/StaticMutex.h"
-
 #include "ChunkAllocator.h"
 #include "InfallibleVector.h"
 #include "SpinLock.h"
 #include "Thread.h"
 
 #include <unordered_map>
 
 namespace mozilla {
@@ -64,23 +62,22 @@ static ChunkAllocator<LockAcquires> gLoc
 typedef std::unordered_map<void*, Lock*> LockMap;
 static LockMap* gLocks;
 static ReadWriteSpinLock gLocksLock;
 
 /* static */ void
 Lock::New(void* aNativeLock)
 {
   Thread* thread = Thread::Current();
-  if (!thread || thread->PassThroughEvents() || HasDivergedFromRecording()) {
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
     Destroy(aNativeLock); // Clean up any old lock, as below.
     return;
   }
 
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
-
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CreateLock);
 
   size_t id;
   if (IsRecording()) {
     id = gNumLocks++;
   }
   thread->Events().RecordOrReplayScalar(&id);
 
@@ -147,55 +144,59 @@ Lock::Find(void* aNativeLock)
 
   return nullptr;
 }
 
 void
 Lock::Enter()
 {
   Thread* thread = Thread::Current();
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
+
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
+    return;
+  }
 
   // Include an event in each thread's record when a lock acquire begins. This
   // is not required by the replay but is used to check that lock acquire order
   // is consistent with the recording and that we will fail explicitly instead
   // of deadlocking.
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Lock);
   thread->Events().CheckInput(mId);
 
   LockAcquires* acquires = gLockAcquires.Get(mId);
   if (IsRecording()) {
     acquires->mAcquires->WriteScalar(thread->Id());
   } else {
-    // Wait until this thread is next in line to acquire the lock.
-    while (thread->Id() != acquires->mNextOwner) {
+    // Wait until this thread is next in line to acquire the lock, or until it
+    // has been instructed to diverge from the recording.
+    while (thread->Id() != acquires->mNextOwner && !thread->MaybeDivergeFromRecording()) {
       Thread::Wait();
     }
   }
 }
 
 void
 Lock::Exit()
 {
-  if (IsReplaying()) {
+  Thread* thread = Thread::Current();
+  if (IsReplaying() && !thread->HasDivergedFromRecording()) {
     // Notify the next owner before releasing the lock.
     LockAcquires* acquires = gLockAcquires.Get(mId);
-    acquires->ReadAndNotifyNextOwner(Thread::Current());
+    acquires->ReadAndNotifyNextOwner(thread);
   }
 }
 
 struct AtomicLock : public detail::MutexImpl
 {
   using detail::MutexImpl::lock;
   using detail::MutexImpl::unlock;
 };
 
-// Lock which is held during code sections that run atomically. This is a
-// PRLock instead of an OffTheBooksMutex because the latter performs atomic
-// operations during initialization.
+// Lock which is held during code sections that run atomically.
 static AtomicLock* gAtomicLock = nullptr;
 
 /* static */ void
 Lock::InitializeLocks()
 {
   gNumLocks = gAtomicLockId;
   gAtomicLock = new AtomicLock();
 
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/MiddlemanCall.cpp
@@ -0,0 +1,426 @@
+/* -*- 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 "MiddlemanCall.h"
+
+#include <unordered_map>
+
+namespace mozilla {
+namespace recordreplay {
+
+// In a replaying or middleman process, all middleman calls that have been
+// encountered, indexed by their ID.
+static StaticInfallibleVector<MiddlemanCall*> gMiddlemanCalls;
+
+// In a replaying or middleman process, association between values produced by
+// a middleman call and the call itself.
+typedef std::unordered_map<const void*, MiddlemanCall*> MiddlemanCallMap;
+static MiddlemanCallMap* gMiddlemanCallMap;
+
+// In a middleman process, any buffers allocated for performed calls.
+static StaticInfallibleVector<void*> gAllocatedBuffers;
+
+// Lock protecting middleman call state.
+static Monitor* gMonitor;
+
+void
+InitializeMiddlemanCalls()
+{
+  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying() || IsMiddleman());
+
+  gMiddlemanCallMap = new MiddlemanCallMap();
+  gMonitor = new Monitor();
+}
+
+// Apply the ReplayInput phase to aCall and any calls it depends on that have
+// not been sent to the middleman yet, filling aOutgoingCalls with the set of
+// such calls.
+static bool
+GatherDependentCalls(InfallibleVector<MiddlemanCall*>& aOutgoingCalls, MiddlemanCall* aCall)
+{
+  MOZ_RELEASE_ASSERT(!aCall->mSent);
+  aCall->mSent = true;
+
+  CallArguments arguments;
+  aCall->mArguments.CopyTo(&arguments);
+
+  InfallibleVector<MiddlemanCall*> dependentCalls;
+
+  MiddlemanCallContext cx(aCall, &arguments, MiddlemanCallPhase::ReplayInput);
+  cx.mDependentCalls = &dependentCalls;
+  gRedirections[aCall->mCallId].mMiddlemanCall(cx);
+  if (cx.mFailed) {
+    return false;
+  }
+
+  for (MiddlemanCall* dependent : dependentCalls) {
+    if (!dependent->mSent && !GatherDependentCalls(aOutgoingCalls, dependent)) {
+      return false;
+    }
+  }
+
+  aOutgoingCalls.append(aCall);
+  return true;
+}
+
+bool
+SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, bool aDiverged)
+{
+  MOZ_RELEASE_ASSERT(IsReplaying());
+
+  const Redirection& redirection = gRedirections[aCallId];
+  MOZ_RELEASE_ASSERT(redirection.mMiddlemanCall);
+
+  Maybe<MonitorAutoLock> lock;
+  lock.emplace(*gMonitor);
+
+  // Allocate and fill in a new MiddlemanCall.
+  size_t id = gMiddlemanCalls.length();
+  MiddlemanCall* newCall = new MiddlemanCall();
+  gMiddlemanCalls.emplaceBack(newCall);
+  newCall->mId = id;
+  newCall->mCallId = aCallId;
+  newCall->mArguments.CopyFrom(aArguments);
+
+  // Perform the ReplayPreface phase on the new call.
+  {
+    MiddlemanCallContext cx(newCall, aArguments, MiddlemanCallPhase::ReplayPreface);
+    redirection.mMiddlemanCall(cx);
+    if (cx.mFailed) {
+      delete newCall;
+      gMiddlemanCalls.popBack();
+      return false;
+    }
+  }
+
+  // Other phases will not run if we have not diverged from the recording.
+  // Any outputs for the call have been handled by the SaveOutput hook.
+  if (!aDiverged) {
+    return true;
+  }
+
+  // Perform the ReplayInput phase on the new call and any others it depends on.
+  InfallibleVector<MiddlemanCall*> outgoingCalls;
+  if (!GatherDependentCalls(outgoingCalls, newCall)) {
+    for (MiddlemanCall* call : outgoingCalls) {
+      call->mSent = false;
+    }
+    return false;
+  }
+
+  // Encode all calls we are sending to the middleman.
+  InfallibleVector<char> inputData;
+  BufferStream inputStream(&inputData);
+  for (MiddlemanCall* call : outgoingCalls) {
+    call->EncodeInput(inputStream);
+  }
+
+  // Perform the calls synchronously in the middleman.
+  InfallibleVector<char> outputData;
+  if (!child::SendMiddlemanCallRequest(inputData.begin(), inputData.length(), &outputData)) {
+    // This thread is not allowed to perform middleman calls anymore. Release
+    // the lock and block until the process rewinds.
+    lock.reset();
+    Thread::WaitForever();
+  }
+
+  // Decode outputs for the calls just sent, and perform the ReplayOutput phase
+  // on any older dependent calls we sent.
+  BufferStream outputStream(outputData.begin(), outputData.length());
+  for (MiddlemanCall* call : outgoingCalls) {
+    call->DecodeOutput(outputStream);
+
+    if (call != newCall) {
+      CallArguments oldArguments;
+      call->mArguments.CopyTo(&oldArguments);
+      MiddlemanCallContext cx(call, &oldArguments, MiddlemanCallPhase::ReplayOutput);
+      cx.mReplayOutputIsOld = true;
+      gRedirections[call->mCallId].mMiddlemanCall(cx);
+    }
+  }
+
+  // Perform the ReplayOutput phase to fill in outputs for the current call.
+  newCall->mArguments.CopyTo(aArguments);
+  MiddlemanCallContext cx(newCall, aArguments, MiddlemanCallPhase::ReplayOutput);
+  redirection.mMiddlemanCall(cx);
+  return true;
+}
+
+void
+ProcessMiddlemanCall(const char* aInputData, size_t aInputSize,
+                     InfallibleVector<char>* aOutputData)
+{
+  MOZ_RELEASE_ASSERT(IsMiddleman());
+
+  BufferStream inputStream(aInputData, aInputSize);
+  BufferStream outputStream(aOutputData);
+
+  while (!inputStream.IsEmpty()) {
+    MiddlemanCall* call = new MiddlemanCall();
+    call->DecodeInput(inputStream);
+
+    const Redirection& redirection = gRedirections[call->mCallId];
+    MOZ_RELEASE_ASSERT(gRedirections[call->mCallId].mMiddlemanCall);
+
+    CallArguments arguments;
+    call->mArguments.CopyTo(&arguments);
+
+    {
+      MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanInput);
+      redirection.mMiddlemanCall(cx);
+    }
+
+    RecordReplayInvokeCall(call->mCallId, &arguments);
+
+    {
+      MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanOutput);
+      redirection.mMiddlemanCall(cx);
+    }
+
+    call->mArguments.CopyFrom(&arguments);
+    call->EncodeOutput(outputStream);
+
+    while (call->mId >= gMiddlemanCalls.length()) {
+      gMiddlemanCalls.emplaceBack(nullptr);
+    }
+    MOZ_RELEASE_ASSERT(!gMiddlemanCalls[call->mId]);
+    gMiddlemanCalls[call->mId] = call;
+  }
+}
+
+void*
+MiddlemanCallContext::AllocateBytes(size_t aSize)
+{
+  void* rv = malloc(aSize);
+
+  // In a middleman process, any buffers we allocate live until the calls are
+  // reset. In a replaying process, the buffers will either live forever
+  // (if they are allocated in the ReplayPreface phase, to match the lifetime
+  // of the MiddlemanCall itself) or will be recovered when we rewind after we
+  // are done with our divergence from the recording (any other phase).
+  if (IsMiddleman()) {
+    gAllocatedBuffers.append(rv);
+  }
+
+  return rv;
+}
+
+void
+ResetMiddlemanCalls()
+{
+  MOZ_RELEASE_ASSERT(IsMiddleman());
+
+  for (MiddlemanCall* call : gMiddlemanCalls) {
+    if (call) {
+      CallArguments arguments;
+      call->mArguments.CopyTo(&arguments);
+
+      MiddlemanCallContext cx(call, &arguments, MiddlemanCallPhase::MiddlemanRelease);
+      gRedirections[call->mCallId].mMiddlemanCall(cx);
+
+      delete call;
+    }
+  }
+
+  gMiddlemanCalls.clear();
+  for (auto buffer : gAllocatedBuffers) {
+    free(buffer);
+  }
+  gAllocatedBuffers.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// System Values
+///////////////////////////////////////////////////////////////////////////////
+
+static void
+AddMiddlemanCallValue(const void* aThing, MiddlemanCall* aCall)
+{
+  gMiddlemanCallMap->erase(aThing);
+  gMiddlemanCallMap->insert(MiddlemanCallMap::value_type(aThing, aCall));
+}
+
+static MiddlemanCall*
+LookupMiddlemanCall(const void* aThing)
+{
+  MiddlemanCallMap::const_iterator iter = gMiddlemanCallMap->find(aThing);
+  if (iter != gMiddlemanCallMap->end()) {
+    return iter->second;
+  }
+  return nullptr;
+}
+
+static const void*
+GetMiddlemanCallValue(size_t aId)
+{
+  MOZ_RELEASE_ASSERT(IsMiddleman());
+  MOZ_RELEASE_ASSERT(aId < gMiddlemanCalls.length() &&
+                     gMiddlemanCalls[aId] &&
+                     gMiddlemanCalls[aId]->mMiddlemanValue.isSome());
+  return gMiddlemanCalls[aId]->mMiddlemanValue.ref();
+}
+
+bool
+Middleman_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr)
+{
+  MOZ_RELEASE_ASSERT(aCx.AccessPreface());
+
+  if (!*aThingPtr) {
+    // Null values are handled by the normal argument copying logic.
+    return true;
+  }
+
+  Maybe<size_t> callId;
+  if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) {
+    // Determine any middleman call this object came from, before the pointer
+    // has a chance to be clobbered by another call between this and the
+    // ReplayInput phase.
+    MiddlemanCall* call = LookupMiddlemanCall(*aThingPtr);
+    if (call) {
+      callId.emplace(call->mId);
+    }
+  }
+  aCx.ReadOrWritePrefaceBytes(&callId, sizeof(callId));
+
+  switch (aCx.mPhase) {
+  case MiddlemanCallPhase::ReplayPreface:
+    return true;
+  case MiddlemanCallPhase::ReplayInput:
+    if (callId.isSome()) {
+      aCx.WriteInputScalar(callId.ref());
+      aCx.mDependentCalls->append(gMiddlemanCalls[callId.ref()]);
+      return true;
+    }
+    return false;
+  case MiddlemanCallPhase::MiddlemanInput:
+    if (callId.isSome()) {
+      size_t callIndex = aCx.ReadInputScalar();
+      *aThingPtr = GetMiddlemanCallValue(callIndex);
+      return true;
+    }
+    return false;
+  default:
+    MOZ_CRASH("Bad phase");
+  }
+}
+
+// Pointer system values are preserved during the replay so that null tests
+// and equality tests work as expected. We additionally mangle the
+// pointers here by setting one of the two highest bits, depending on whether
+// the pointer came from the recording or from the middleman. This avoids
+// accidentally conflating pointers that happen to have the same value but
+// which originate from different processes.
+static const void*
+MangleSystemValue(const void* aValue, bool aFromRecording)
+{
+  return (const void*) ((size_t)aValue | (1ULL << (aFromRecording ? 63 : 62)));
+}
+
+void
+Middleman_SystemOutput(MiddlemanCallContext& aCx, const void** aOutput, bool aUpdating)
+{
+  if (!*aOutput) {
+    return;
+  }
+
+  switch (aCx.mPhase) {
+  case MiddlemanCallPhase::ReplayPreface:
+    if (!HasDivergedFromRecording()) {
+      // If we haven't diverged from the recording, use the output value saved
+      // in the recording.
+      if (!aUpdating) {
+        *aOutput = MangleSystemValue(*aOutput, true);
+      }
+      aCx.mCall->SetRecordingValue(*aOutput);
+      AddMiddlemanCallValue(*aOutput, aCx.mCall);
+    }
+    break;
+  case MiddlemanCallPhase::MiddlemanOutput:
+    aCx.mCall->SetMiddlemanValue(*aOutput);
+    AddMiddlemanCallValue(*aOutput, aCx.mCall);
+    break;
+  case MiddlemanCallPhase::ReplayOutput: {
+    if (!aUpdating) {
+      *aOutput = MangleSystemValue(*aOutput, false);
+    }
+    aCx.mCall->SetMiddlemanValue(*aOutput);
+
+    // Associate the value produced by the middleman with this call. If the
+    // call previously went through the ReplayPreface phase when we did not
+    // diverge from the recording, we will associate values from both the
+    // recording and middleman processes with this call. If a call made after
+    // diverging produced the same value as a call made before diverging, use
+    // the value saved in the recording for the first call, so that equality
+    // tests on the value work as expected.
+    MiddlemanCall* previousCall = LookupMiddlemanCall(*aOutput);
+    if (previousCall) {
+      if (previousCall->mRecordingValue.isSome()) {
+        *aOutput = previousCall->mRecordingValue.ref();
+      }
+    } else {
+      AddMiddlemanCallValue(*aOutput, aCx.mCall);
+    }
+    break;
+  }
+  default:
+    return;
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MiddlemanCall
+///////////////////////////////////////////////////////////////////////////////
+
+void
+MiddlemanCall::EncodeInput(BufferStream& aStream) const
+{
+  aStream.WriteScalar(mId);
+  aStream.WriteScalar(mCallId);
+  aStream.WriteBytes(&mArguments, sizeof(CallRegisterArguments));
+  aStream.WriteScalar(mPreface.length());
+  aStream.WriteBytes(mPreface.begin(), mPreface.length());
+  aStream.WriteScalar(mInput.length());
+  aStream.WriteBytes(mInput.begin(), mInput.length());
+}
+
+void
+MiddlemanCall::DecodeInput(BufferStream& aStream)
+{
+  mId = aStream.ReadScalar();
+  mCallId = aStream.ReadScalar();
+  aStream.ReadBytes(&mArguments, sizeof(CallRegisterArguments));
+  size_t prefaceLength = aStream.ReadScalar();
+  mPreface.appendN(0, prefaceLength);
+  aStream.ReadBytes(mPreface.begin(), prefaceLength);
+  size_t inputLength = aStream.ReadScalar();
+  mInput.appendN(0, inputLength);
+  aStream.ReadBytes(mInput.begin(), inputLength);
+}
+
+void
+MiddlemanCall::EncodeOutput(BufferStream& aStream) const
+{
+  aStream.WriteBytes(&mArguments, sizeof(CallRegisterArguments));
+  aStream.WriteScalar(mOutput.length());
+  aStream.WriteBytes(mOutput.begin(), mOutput.length());
+}
+
+void
+MiddlemanCall::DecodeOutput(BufferStream& aStream)
+{
+  // Only update the return value when decoding arguments, so that we don't
+  // clobber the call's arguments with any changes made in the middleman.
+  CallRegisterArguments newArguments;
+  aStream.ReadBytes(&newArguments, sizeof(CallRegisterArguments));
+  mArguments.CopyRvalFrom(&newArguments);
+
+  size_t outputLength = aStream.ReadScalar();
+  mOutput.appendN(0, outputLength);
+  aStream.ReadBytes(mOutput.begin(), outputLength);
+}
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/MiddlemanCall.h
@@ -0,0 +1,464 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_recordreplay_MiddlemanCall_h
+#define mozilla_recordreplay_MiddlemanCall_h
+
+#include "BufferStream.h"
+#include "ProcessRedirect.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+namespace recordreplay {
+
+// Middleman Calls Overview
+//
+// With few exceptions, replaying processes do not interact with the underlying
+// system or call the actual versions of redirected system library functions.
+// This is problematic after diverging from the recording, as then the diverged
+// thread cannot interact with its recording either.
+//
+// Middleman calls are used in a replaying process after diverging from the
+// recording to perform calls in the middleman process instead. Inputs are
+// gathered and serialized in the replaying process, then sent to the middleman
+// process. The middleman calls the function, and its outputs are serialized
+// for reading by the replaying process.
+//
+// Calls that might need to be sent to the middleman are processed in phases,
+// per the MiddlemanCallPhase enum below. The timeline of a middleman call is
+// as follows:
+//
+// - Any redirection with a middleman call hook can potentially be sent to the
+//   middleman. In a replaying process, whenever such a call is encountered,
+//   the hook is invoked in the ReplayPreface phase to capture any input data
+//   that must be examined at the time of the call itself.
+//
+// - If the thread has not diverged from the recording, the call is remembered
+//   but no further action is necessary yet.
+//
+// - If the thread has diverged from the recording, the call needs to go
+//   through the remaining phases. The ReplayInput phase captures any
+//   additional inputs to the call, potentially including values produced by
+//   other middleman calls.
+//
+// - The transitive closure of these call dependencies is produced, and all
+//   calls found go through the ReplayInput phase. The resulting data is sent
+//   to the middleman process, which goes through the MiddlemanInput phase
+//   to decode those inputs.
+//
+// - The middleman performs each of the calls it has been given, and their
+//   outputs are encoded in the MiddlemanOutput phase. These outputs are sent
+//   to the replaying process in a response and decoded in the ReplayOutput
+//   phase, which can then resume execution.
+//
+// - The replaying process holds onto information about calls it has sent until
+//   it rewinds to a point before it diverged from the recording. This rewind
+//   will --- without any special action required --- wipe out information on
+//   all calls sent to the middleman, and retain any data gathered in the
+//   ReplayPreface phase for calls that were made prior to the rewind target.
+//
+// - Information about calls and all resources held are retained in the
+//   middleman process are retained until a replaying process asks for them to
+//   be reset, which happens any time the replaying process first diverges from
+//   the recording. The MiddlemanRelease phase is used to release any system
+//   resources held.
+
+// Ways of processing calls that can be sent to the middleman.
+enum class MiddlemanCallPhase
+{
+  // When replaying, a call is being performed that might need to be sent to
+  // the middleman later.
+  ReplayPreface,
+
+  // A call for which inputs have been gathered is now being sent to the
+  // middleman. This is separate from ReplayPreface because capturing inputs
+  // might need to dereference pointers that could be bogus values originating
+  // from the recording. Waiting to dereference these pointers until we know
+  // the call needs to be sent to the middleman avoids needing to understand
+  // the inputs to all call sites of general purpose redirections such as
+  // CFArrayCreate.
+  ReplayInput,
+
+  // In the middleman process, a call from the replaying process is being
+  // performed.
+  MiddlemanInput,
+
+  // In the middleman process, a call from the replaying process was just
+  // performed, and its outputs need to be saved.
+  MiddlemanOutput,
+
+  // Back in the replaying process, the outputs from a call have been received
+  // from the middleman.
+  ReplayOutput,
+
+  // In the middleman process, release any system resources held after this
+  // call.
+  MiddlemanRelease,
+};
+
+struct MiddlemanCall
+{
+  // Unique ID for this call.
+  size_t mId;
+
+  // ID of the redirection being invoked.
+  size_t mCallId;
+
+  // All register arguments and return values are preserved when sending the
+  // call back and forth between processes.
+  CallRegisterArguments mArguments;
+
+  // Written in ReplayPrefaceInput, read in ReplayInput and MiddlemanInput.
+  InfallibleVector<char> mPreface;
+
+  // Written in ReplayInput, read in MiddlemanInput.
+  InfallibleVector<char> mInput;
+
+  // Written in MiddlemanOutput, read in ReplayOutput.
+  InfallibleVector<char> mOutput;
+
+  // In a replaying process, whether this call has been sent to the middleman.
+  bool mSent;
+
+  // In a replaying process, any value associated with this call that was
+  // included in the recording, when the call was made before diverging from
+  // the recording.
+  Maybe<const void*> mRecordingValue;
+
+  // In a replaying or middleman process, any value associated with this call
+  // that was produced by the middleman itself.
+  Maybe<const void*> mMiddlemanValue;
+
+  MiddlemanCall()
+    : mId(0), mCallId(0), mSent(false)
+  {}
+
+  void EncodeInput(BufferStream& aStream) const;
+  void DecodeInput(BufferStream& aStream);
+
+  void EncodeOutput(BufferStream& aStream) const;
+  void DecodeOutput(BufferStream& aStream);
+
+  void SetRecordingValue(const void* aValue) {
+    MOZ_RELEASE_ASSERT(mRecordingValue.isNothing());
+    mRecordingValue.emplace(aValue);
+  }
+
+  void SetMiddlemanValue(const void* aValue) {
+    MOZ_RELEASE_ASSERT(mMiddlemanValue.isNothing());
+    mMiddlemanValue.emplace(aValue);
+  }
+};
+
+// Information needed to process one of the phases of a middleman call,
+// in either the replaying or middleman process.
+struct MiddlemanCallContext
+{
+  // Call being operated on.
+  MiddlemanCall* mCall;
+
+  // Complete arguments and return value information for the call.
+  CallArguments* mArguments;
+
+  // Current processing phase.
+  MiddlemanCallPhase mPhase;
+
+  // During the ReplayPreface or ReplayInput phases, whether capturing input
+  // data has failed. In such cases the call cannot be sent to the middleman
+  // and, if the thread has diverged from the recording, an unhandled
+  // divergence and associated rewind will occur.
+  bool mFailed;
+
+  // During the ReplayInput phase, this can be used to fill in any middleman
+  // calls whose output the current one depends on.
+  InfallibleVector<MiddlemanCall*>* mDependentCalls;
+
+  // Streams of data that can be accessed during the various phases. Streams
+  // need to be read or written from at the same points in the phases which use
+  // them, so that callbacks operating on these streams can be composed without
+  // issues.
+
+  // The preface is written during ReplayPreface, and read during both
+  // ReplayInput and MiddlemanInput.
+  Maybe<BufferStream> mPrefaceStream;
+
+  // Inputs are written during ReplayInput, and read during MiddlemanInput.
+  Maybe<BufferStream> mInputStream;
+
+  // Outputs are written during MiddlemanOutput, and read during ReplayOutput.
+  Maybe<BufferStream> mOutputStream;
+
+  // During the ReplayOutput phase, this is set if the call was made sometime
+  // in the past and pointers referred to in the arguments may no longer be
+  // valid.
+  bool mReplayOutputIsOld;
+
+  MiddlemanCallContext(MiddlemanCall* aCall, CallArguments* aArguments, MiddlemanCallPhase aPhase)
+    : mCall(aCall), mArguments(aArguments), mPhase(aPhase),
+      mFailed(false), mDependentCalls(nullptr), mReplayOutputIsOld(false)
+  {
+    switch (mPhase) {
+    case MiddlemanCallPhase::ReplayPreface:
+      mPrefaceStream.emplace(&mCall->mPreface);
+      break;
+    case MiddlemanCallPhase::ReplayInput:
+      mPrefaceStream.emplace(mCall->mPreface.begin(), mCall->mPreface.length());
+      mInputStream.emplace(&mCall->mInput);
+      break;
+    case MiddlemanCallPhase::MiddlemanInput:
+      mPrefaceStream.emplace(mCall->mPreface.begin(), mCall->mPreface.length());
+      mInputStream.emplace(mCall->mInput.begin(), mCall->mInput.length());
+      break;
+    case MiddlemanCallPhase::MiddlemanOutput:
+      mOutputStream.emplace(&mCall->mOutput);
+      break;
+    case MiddlemanCallPhase::ReplayOutput:
+      mOutputStream.emplace(mCall->mOutput.begin(), mCall->mOutput.length());
+      break;
+    case MiddlemanCallPhase::MiddlemanRelease:
+      break;
+    }
+  }
+
+  void MarkAsFailed() {
+    MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayPreface ||
+                       mPhase == MiddlemanCallPhase::ReplayInput);
+    mFailed = true;
+  }
+
+  void WriteInputBytes(const void* aBuffer, size_t aSize) {
+    MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayInput);
+    mInputStream.ref().WriteBytes(aBuffer, aSize);
+  }
+
+  void WriteInputScalar(size_t aValue) {
+    MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayInput);
+    mInputStream.ref().WriteScalar(aValue);
+  }
+
+  void ReadInputBytes(void* aBuffer, size_t aSize) {
+    MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::MiddlemanInput);
+    mInputStream.ref().ReadBytes(aBuffer, aSize);
+  }
+
+  size_t ReadInputScalar() {
+    MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::MiddlemanInput);
+    return mInputStream.ref().ReadScalar();
+  }
+
+  bool AccessInput() {
+    return mInputStream.isSome();
+  }
+
+  void ReadOrWriteInputBytes(void* aBuffer, size_t aSize) {
+    switch (mPhase) {
+    case MiddlemanCallPhase::ReplayInput:
+      WriteInputBytes(aBuffer, aSize);
+      break;
+    case MiddlemanCallPhase::MiddlemanInput:
+      ReadInputBytes(aBuffer, aSize);
+      break;
+    default:
+      MOZ_CRASH();
+    }
+  }
+
+  bool AccessPreface() {
+    return mPrefaceStream.isSome();
+  }
+
+  void ReadOrWritePrefaceBytes(void* aBuffer, size_t aSize) {
+    switch (mPhase) {
+    case MiddlemanCallPhase::ReplayPreface:
+      mPrefaceStream.ref().WriteBytes(aBuffer, aSize);
+      break;
+    case MiddlemanCallPhase::ReplayInput:
+    case MiddlemanCallPhase::MiddlemanInput:
+      mPrefaceStream.ref().ReadBytes(aBuffer, aSize);
+      break;
+    default:
+      MOZ_CRASH();
+    }
+  }
+
+  void ReadOrWritePrefaceBuffer(void** aBufferPtr, size_t aSize) {
+    switch (mPhase) {
+    case MiddlemanCallPhase::ReplayPreface:
+      mPrefaceStream.ref().WriteBytes(*aBufferPtr, aSize);
+      break;
+    case MiddlemanCallPhase::ReplayInput:
+    case MiddlemanCallPhase::MiddlemanInput:
+      *aBufferPtr = AllocateBytes(aSize);
+      mPrefaceStream.ref().ReadBytes(*aBufferPtr, aSize);
+      break;
+    default:
+      MOZ_CRASH();
+    }
+  }
+
+  bool AccessOutput() {
+    return mOutputStream.isSome();
+  }
+
+  void ReadOrWriteOutputBytes(void* aBuffer, size_t aSize) {
+    switch (mPhase) {
+    case MiddlemanCallPhase::MiddlemanOutput:
+      mOutputStream.ref().WriteBytes(aBuffer, aSize);
+      break;
+    case MiddlemanCallPhase::ReplayOutput:
+      mOutputStream.ref().ReadBytes(aBuffer, aSize);
+      break;
+    default:
+      MOZ_CRASH();
+    }
+  }
+
+  void ReadOrWriteOutputBuffer(void** aBuffer, size_t aSize) {
+    if (*aBuffer) {
+      if (mPhase == MiddlemanCallPhase::MiddlemanInput || mReplayOutputIsOld) {
+        *aBuffer = AllocateBytes(aSize);
+      }
+
+      if (AccessOutput()) {
+        ReadOrWriteOutputBytes(*aBuffer, aSize);
+      }
+    }
+  }
+
+  // Allocate some memory associated with the call, which will be released in
+  // the replaying process on a rewind and in the middleman process when the
+  // call state is reset.
+  void* AllocateBytes(size_t aSize);
+};
+
+// Notify the system about a call to a redirection with a middleman call hook.
+// aDiverged is set if the current thread has diverged from the recording and
+// any outputs for the call must be filled in; otherwise, they already have
+// been filled in using data from the recording. Returns false if the call was
+// unable to be processed.
+bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, bool aDiverged);
+
+// In the middleman process, perform one or more calls encoded in aInputData
+// and encode their outputs to aOutputData.
+void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize,
+                          InfallibleVector<char>* aOutputData);
+
+// In the middleman process, reset all call state.
+void ResetMiddlemanCalls();
+
+///////////////////////////////////////////////////////////////////////////////
+// Middleman Call Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+// Capture the contents of an input buffer at BufferArg with element count at CountArg.
+template <size_t BufferArg, size_t CountArg, typename ElemType>
+static inline void
+Middleman_Buffer(MiddlemanCallContext& aCx)
+{
+  if (aCx.AccessPreface()) {
+    auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
+    auto byteSize = aCx.mArguments->Arg<CountArg, size_t>() * sizeof(ElemType);
+    aCx.ReadOrWritePrefaceBuffer(&buffer, byteSize);
+  }
+}
+
+// Capture the contents of a fixed size input buffer.
+template <size_t BufferArg, size_t ByteSize>
+static inline void
+Middleman_BufferFixedSize(MiddlemanCallContext& aCx)
+{
+  if (aCx.AccessPreface()) {
+    auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
+    if (buffer) {
+      aCx.ReadOrWritePrefaceBuffer(&buffer, ByteSize);
+    }
+  }
+}
+
+// Capture a C string argument.
+template <size_t StringArg>
+static inline void
+Middleman_CString(MiddlemanCallContext& aCx)
+{
+  if (aCx.AccessPreface()) {
+    auto& buffer = aCx.mArguments->Arg<StringArg, char*>();
+    size_t len = (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) ? strlen(buffer) + 1 : 0;
+    aCx.ReadOrWritePrefaceBytes(&len, sizeof(len));
+    aCx.ReadOrWritePrefaceBuffer((void**) &buffer, len);
+  }
+}
+
+// Capture the data written to an output buffer at BufferArg with element count at CountArg.
+template <size_t BufferArg, size_t CountArg, typename ElemType>
+static inline void
+Middleman_WriteBuffer(MiddlemanCallContext& aCx)
+{
+  auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
+  auto count = aCx.mArguments->Arg<CountArg, size_t>();
+  aCx.ReadOrWriteOutputBuffer(&buffer, count * sizeof(ElemType));
+}
+
+// Capture the data written to a fixed size output buffer.
+template <size_t BufferArg, size_t ByteSize>
+static inline void
+Middleman_WriteBufferFixedSize(MiddlemanCallContext& aCx)
+{
+  auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
+  aCx.ReadOrWriteOutputBuffer(&buffer, ByteSize);
+}
+
+// Capture return values that are too large for register storage.
+template <size_t ByteSize>
+static inline void
+Middleman_OversizeRval(MiddlemanCallContext& aCx)
+{
+  Middleman_WriteBufferFixedSize<0, ByteSize>(aCx);
+}
+
+// Capture a byte count of stack argument data.
+template <size_t ByteSize>
+static inline void
+Middleman_StackArgumentData(MiddlemanCallContext& aCx)
+{
+  if (aCx.AccessPreface()) {
+    auto stack = aCx.mArguments->StackAddress<0>();
+    aCx.ReadOrWritePrefaceBytes(stack, ByteSize);
+  }
+}
+
+static inline void
+Middleman_NoOp(MiddlemanCallContext& aCx)
+{
+}
+
+template <MiddlemanCallFn Fn0,
+          MiddlemanCallFn Fn1,
+          MiddlemanCallFn Fn2 = Middleman_NoOp,
+          MiddlemanCallFn Fn3 = Middleman_NoOp,
+          MiddlemanCallFn Fn4 = Middleman_NoOp>
+static inline void
+Middleman_Compose(MiddlemanCallContext& aCx)
+{
+  Fn0(aCx);
+  Fn1(aCx);
+  Fn2(aCx);
+  Fn3(aCx);
+  Fn4(aCx);
+}
+
+// Helper for capturing inputs that are produced by other middleman calls.
+// Returns false in the ReplayInput or MiddlemanInput phases if the input
+// system value could not be found.
+bool Middleman_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr);
+
+// Helper for capturing output system values that might be consumed by other
+// middleman calls.
+void Middleman_SystemOutput(MiddlemanCallContext& aCx, const void** aOutput, bool aUpdating = false);
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_MiddlemanCall_h
--- a/toolkit/recordreplay/ProcessRecordReplay.cpp
+++ b/toolkit/recordreplay/ProcessRecordReplay.cpp
@@ -46,16 +46,19 @@ char* gInitializationFailureMessage;
 
 bool gInitialized;
 ProcessKind gProcessKind;
 char* gRecordingFilename;
 
 // Current process ID.
 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;
 
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_Initialize(int aArgc, char* aArgv[])
 {
@@ -106,16 +109,17 @@ RecordReplayInterface_Initialize(int aAr
   gPid = getpid();
   if (TestEnv("RECORD_REPLAY_SPEW")) {
     gSpewEnabled = true;
   }
 
   EarlyInitializeRedirections();
 
   if (!IsRecordingOrReplaying()) {
+    InitializeMiddlemanCalls();
     return;
   }
 
   gSnapshotMemoryPrefix = mktemp(strdup("/tmp/SnapshotMemoryXXXXXX"));
   gSnapshotStackPrefix = mktemp(strdup("/tmp/SnapshotStackXXXXXX"));
 
   InitializeCurrentTime();
 
@@ -139,53 +143,53 @@ RecordReplayInterface_Initialize(int aAr
   thread->SetPassThrough(true);
 
   InitializeTriggers();
   InitializeWeakPointers();
   InitializeMemorySnapshots();
   Thread::SpawnAllThreads();
   InitializeCountdownThread();
   SetupDirtyMemoryHandler();
+  InitializeMiddlemanCalls();
 
   // Don't create a stylo thread pool when recording or replaying.
   putenv((char*) "STYLO_THREADS=1");
 
   thread->SetPassThrough(false);
 
   Lock::InitializeLocks();
   InitializeRewindState();
+  gRecordingPid = RecordReplayValue(gPid);
 
   gInitialized = true;
 }
 
 MOZ_EXPORT size_t
 RecordReplayInterface_InternalRecordReplayValue(size_t aValue)
 {
   Thread* thread = Thread::Current();
-  if (thread->PassThroughEvents()) {
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
     return aValue;
   }
-  EnsureNotDivergedFromRecording();
 
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Value);
   thread->Events().RecordOrReplayValue(&aValue);
   return aValue;
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRecordReplayBytes(void* aData, size_t aSize)
 {
   Thread* thread = Thread::Current();
-  if (thread->PassThroughEvents()) {
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
     return;
   }
-  EnsureNotDivergedFromRecording();
 
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Bytes);
   thread->Events().CheckInput(aSize);
   thread->Events().RecordOrReplayBytes(aData, aSize);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalInvalidateRecording(const char* aWhy)
 {
@@ -320,47 +324,53 @@ ThreadEventName(ThreadEvent aEvent)
     ForEachThreadEvent(EnumToString)
 #undef EnumToString
   case ThreadEvent::CallStart: break;
   }
   size_t callId = (size_t) aEvent - (size_t) ThreadEvent::CallStart;
   return gRedirections[callId].mName;
 }
 
+int
+GetRecordingPid()
+{
+  return gRecordingPid;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Record/Replay Assertions
 ///////////////////////////////////////////////////////////////////////////////
 
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRecordReplayAssert(const char* aFormat, va_list aArgs)
 {
   Thread* thread = Thread::Current();
-  if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) {
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
     return;
   }
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   // Add the asserted string to the recording.
   char text[1024];
   VsprintfLiteral(text, aFormat, aArgs);
 
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Assert);
   thread->Events().CheckInput(text);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRecordReplayAssertBytes(const void* aData, size_t aSize)
 {
   Thread* thread = Thread::Current();
-  if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) {
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
     return;
   }
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::AssertBytes);
   thread->Events().CheckInput(aData, aSize);
 }
 
 } // extern "C"
 
 static ValueIndex* gGenericThings;
--- a/toolkit/recordreplay/ProcessRecordReplay.h
+++ b/toolkit/recordreplay/ProcessRecordReplay.h
@@ -227,16 +227,19 @@ void InternalPrint(const char* aFormat, 
 // the recording and will be printed by any recording, replaying, or middleman
 // process. Spew is only printed when enabled via the RECORD_REPLAY_SPEW
 // environment variable.
 MOZ_MakeRecordReplayPrinter(Print, false)
 MOZ_MakeRecordReplayPrinter(PrintSpew, true)
 
 #undef MOZ_MakeRecordReplayPrinter
 
+// Get the ID of the process that produced the recording.
+int GetRecordingPid();
+
 ///////////////////////////////////////////////////////////////////////////////
 // Profiling
 ///////////////////////////////////////////////////////////////////////////////
 
 void InitializeCurrentTime();
 
 // Get a current timestamp, in microseconds.
 double CurrentTime();
--- a/toolkit/recordreplay/ProcessRedirect.cpp
+++ b/toolkit/recordreplay/ProcessRedirect.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ProcessRedirect.h"
 
 #include "InfallibleVector.h"
+#include "MiddlemanCall.h"
 #include "mozilla/Sprintf.h"
 
 #include <dlfcn.h>
 #include <string.h>
 
 namespace {
 
 #include "udis86/udis86.c"
@@ -22,75 +23,122 @@ namespace {
 
 namespace mozilla {
 namespace recordreplay {
 
 ///////////////////////////////////////////////////////////////////////////////
 // Redirection Skeleton
 ///////////////////////////////////////////////////////////////////////////////
 
+static bool
+CallPreambleHook(PreambleFn aPreamble, size_t aCallId, CallArguments* aArguments)
+{
+  PreambleResult result = aPreamble(aArguments);
+  switch (result) {
+  case PreambleResult::Veto:
+    return true;
+  case PreambleResult::PassThrough: {
+    AutoEnsurePassThroughThreadEvents pt;
+    RecordReplayInvokeCall(aCallId, aArguments);
+    return true;
+  }
+  case PreambleResult::Redirect:
+    return false;
+  }
+  Unreachable();
+}
+
 extern "C" {
 
 __attribute__((used)) int
 RecordReplayInterceptCall(int aCallId, CallArguments* aArguments)
 {
   Redirection& redirection = gRedirections[aCallId];
 
   // Call the preamble to see if this call needs special handling, even when
   // events have been passed through.
   if (redirection.mPreamble) {
-    PreambleResult result = redirection.mPreamble(aArguments);
-    switch (result) {
-    case PreambleResult::Veto:
+    if (CallPreambleHook(redirection.mPreamble, aCallId, aArguments)) {
       return 0;
-    case PreambleResult::PassThrough: {
-      AutoEnsurePassThroughThreadEvents pt;
-      RecordReplayInvokeCall(aCallId, aArguments);
-      return 0;
-    }
-    case PreambleResult::Redirect:
-      break;
     }
   }
 
   Thread* thread = Thread::Current();
+  Maybe<RecordingEventSection> res;
+  res.emplace(thread);
 
-  // When events are passed through, invoke the call with the original stack
-  // and register state.
-  if (!thread || thread->PassThroughEvents()) {
-    // RecordReplayRedirectCall will load the function to call from the
-    // return value slot.
-    aArguments->Rval<uint8_t*>() = redirection.mOriginalFunction;
-    return 1;
+  if (!res.ref().CanAccessEvents()) {
+    // When events are passed through, invoke the call with the original stack
+    // and register state.
+    if (!thread || thread->PassThroughEvents()) {
+      // RecordReplayRedirectCall will load the function to call from the
+      // return value slot.
+      aArguments->Rval<uint8_t*>() = redirection.mOriginalFunction;
+      return 1;
+    }
+
+    MOZ_RELEASE_ASSERT(thread->HasDivergedFromRecording());
+
+    // After we have diverged from the recording, we can't access the thread's
+    // recording anymore.
+
+    // If the redirection has a middleman preamble hook, call it to see if it
+    // can handle this call. The middleman preamble hook is separate from the
+    // normal preamble hook because entering the RecordingEventSection can
+    // cause the current thread to diverge from the recording; testing for
+    // HasDivergedFromRecording() does not work reliably in the normal preamble.
+    if (redirection.mMiddlemanPreamble) {
+      if (CallPreambleHook(redirection.mMiddlemanPreamble, aCallId, aArguments)) {
+        return 0;
+      }
+    }
+
+    // If the redirection has a middleman call hook, try to perform the call in
+    // the middleman instead.
+    if (redirection.mMiddlemanCall) {
+      if (SendCallToMiddleman(aCallId, aArguments, /* aPopulateOutput = */ true)) {
+        return 0;
+      }
+    }
+
+    // Calling any redirection which performs the standard steps will cause
+    // debugger operations that have diverged from the recording to fail.
+    EnsureNotDivergedFromRecording();
+    Unreachable();
   }
 
-  // Calling any redirection which performs the standard steps will cause
-  // debugger operations that have diverged from the recording to fail.
-  EnsureNotDivergedFromRecording();
-
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
-
   if (IsRecording()) {
     // Call the original function, passing through events while we do so.
+    // Destroy the RecordingEventSection so that we don't prevent the file
+    // from being flushed in case we end up blocking.
+    res.reset();
     thread->SetPassThrough(true);
     RecordReplayInvokeCall(aCallId, aArguments);
     thread->SetPassThrough(false);
+    res.emplace(thread);
   }
 
   // Save any system error in case we want to record/replay it.
   ErrorType error = SaveError();
 
   // Add an event for the thread.
   thread->Events().RecordOrReplayThreadEvent(CallIdToThreadEvent(aCallId));
 
   // Save any output produced by the call.
   if (redirection.mSaveOutput) {
     redirection.mSaveOutput(thread->Events(), aArguments, &error);
   }
 
+  // Save information about any potential middleman calls encountered if we
+  // haven't diverged from the recording, in case we diverge and later calls
+  // access data produced by this one.
+  if (IsReplaying() && redirection.mMiddlemanCall) {
+    (void) SendCallToMiddleman(aCallId, aArguments, /* aDiverged = */ false);
+  }
+
   RestoreError(error);
   return 0;
 }
 
 // Entry point for all redirections. When generated code jumps here, %rax holds
 // the CallEvent being invoked, and all other registers and stack contents are
 // the same as when the call was originally invoked. This fills in a
 // CallArguments structure with information about the call, before invoking
--- a/toolkit/recordreplay/ProcessRedirect.h
+++ b/toolkit/recordreplay/ProcessRedirect.h
@@ -8,17 +8,16 @@
 #define mozilla_recordreplay_ProcessRedirect_h
 
 #include "Assembler.h"
 #include "Callback.h"
 #include "CallFunction.h"
 #include "ProcessRecordReplay.h"
 #include "ProcessRewind.h"
 #include "Thread.h"
-#include "ipc/Channel.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Casting.h"
 
 #include <errno.h>
 
 namespace mozilla {
@@ -69,43 +68,58 @@ namespace recordreplay {
 // functions is incomplete. If a library API is not redirected then it might
 // behave differently between recording and replaying, or it might crash while
 // replaying.
 
 ///////////////////////////////////////////////////////////////////////////////
 // Function Redirections
 ///////////////////////////////////////////////////////////////////////////////
 
-// Capture the arguments that can be passed to a redirection, and provide
-// storage to specify the redirection's return value. We only need to capture
-// enough argument data here for calls made directly from Gecko code,
-// i.e. where events are not passed through. Calls made while events are passed
-// through are performed with the same stack and register state as when they
-// were initially invoked.
-//
-// Arguments and return value indexes refer to the register contents as passed
-// to the function originally. For functions with complex or floating point
-// arguments and return values, the right index to use might be different than
-// expected, per the requirements of the System V x64 ABI.
-struct CallArguments
+struct CallArguments;
+
+// All argument and return value data that is stored in registers and whose
+// values are preserved when calling a redirected function.
+struct CallRegisterArguments
 {
 protected:
   size_t arg0;      // 0
   size_t arg1;      // 8
   size_t arg2;      // 16
   size_t arg3;      // 24
   size_t arg4;      // 32
   size_t arg5;      // 40
   double floatarg0; // 48
   double floatarg1; // 56
   double floatarg2; // 64
   size_t rval0;     // 72
   size_t rval1;     // 80
   double floatrval0; // 88
   double floatrval1; // 96
+                     // Size: 104
+
+public:
+  void CopyFrom(const CallRegisterArguments* aArguments);
+  void CopyTo(CallRegisterArguments* aArguments) const;
+  void CopyRvalFrom(const CallRegisterArguments* aArguments);
+};
+
+// Capture the arguments that can be passed to a redirection, and provide
+// storage to specify the redirection's return value. We only need to capture
+// enough argument data here for calls made directly from Gecko code,
+// i.e. where events are not passed through. Calls made while events are passed
+// through are performed with the same stack and register state as when they
+// were initially invoked.
+//
+// Arguments and return value indexes refer to the register contents as passed
+// to the function originally. For functions with complex or floating point
+// arguments and return values, the right index to use might be different than
+// expected, per the requirements of the System V x64 ABI.
+struct CallArguments : public CallRegisterArguments
+{
+protected:
   size_t stack[64]; // 104
                     // Size: 616
 
 public:
   template <size_t Index, typename T>
   T& Arg() {
     static_assert(sizeof(T) == sizeof(size_t), "Size must match");
     static_assert(IsFloatingPoint<T>::value == false, "FloatArg NYI");
@@ -116,16 +130,22 @@ public:
     case 2: return (T&)arg2;
     case 3: return (T&)arg3;
     case 4: return (T&)arg4;
     case 5: return (T&)arg5;
     default: return (T&)stack[Index - 6];
     }
   }
 
+  template <size_t Offset>
+  size_t* StackAddress() {
+    static_assert(Offset % sizeof(size_t) == 0, "Bad stack offset");
+    return &stack[Offset / sizeof(size_t)];
+  }
+
   template <typename T, size_t Index = 0>
   T& Rval() {
     static_assert(sizeof(T) == sizeof(size_t), "Size must match");
     static_assert(IsFloatingPoint<T>::value == false, "Use FloatRval instead");
     static_assert(Index == 0 || Index == 1, "Bad index");
     switch (Index) {
     case 0: return (T&)rval0;
     case 1: return (T&)rval1;
@@ -137,16 +157,37 @@ public:
     static_assert(Index == 0 || Index == 1, "Bad index");
     switch (Index) {
     case 0: return floatrval0;
     case 1: return floatrval1;
     }
   }
 };
 
+inline void
+CallRegisterArguments::CopyFrom(const CallRegisterArguments* aArguments)
+{
+  memcpy(this, aArguments, sizeof(CallRegisterArguments));
+}
+
+inline void
+CallRegisterArguments::CopyTo(CallRegisterArguments* aArguments) const
+{
+  memcpy(aArguments, this, sizeof(CallRegisterArguments));
+}
+
+inline void
+CallRegisterArguments::CopyRvalFrom(const CallRegisterArguments* aArguments)
+{
+  rval0 = aArguments->rval0;
+  rval1 = aArguments->rval1;
+  floatrval0 = aArguments->floatrval0;
+  floatrval1 = aArguments->floatrval1;
+}
+
 // Generic type for a system error code.
 typedef ssize_t ErrorType;
 
 // Signature for a function that saves some output produced by a redirection
 // while recording, and restores that output while replaying. aEvents is the
 // event stream for the current thread, aArguments specifies the arguments to
 // the called function, and aError specifies any system error which the call
 // produces.
@@ -159,16 +200,25 @@ enum class PreambleResult {
 
   // Perform a function redirection as normal if events are not passed through.
   Redirect,
 
   // Do not add an event for the call, as if events were passed through.
   PassThrough
 };
 
+// Signature for a function that is called on entry to a redirection and can
+// modify its behavior.
+typedef PreambleResult (*PreambleFn)(CallArguments* aArguments);
+
+// Signature for a function that conveys data about a call to or from the
+// middleman process.
+struct MiddlemanCallContext;
+typedef void (*MiddlemanCallFn)(MiddlemanCallContext& aCx);
+
 // Information about a system library API function which is being redirected.
 struct Redirection
 {
   // Name of the function being redirected.
   const char* mName;
 
   // Address of the function which is being redirected. The code for this
   // function is modified so that attempts to call this function will instead
@@ -182,17 +232,25 @@ struct Redirection
   // Redirection hooks are used to control the behavior of the redirected
   // function.
 
   // If specified, will be called at the end of the redirection when events are
   // not being passed through to record/replay any outputs from the call.
   SaveOutputFn mSaveOutput;
 
   // If specified, will be called upon entry to the redirected call.
-  PreambleResult (*mPreamble)(CallArguments* aArguments);
+  PreambleFn mPreamble;
+
+  // If specified, will be called while replaying and diverged from the
+  // recording to perform this call in the middleman process.
+  MiddlemanCallFn mMiddlemanCall;
+
+  // Additional preamble that is only called while replaying and diverged from
+  // the recording.
+  PreambleFn mMiddlemanPreamble;
 };
 
 // All platform specific redirections, indexed by the call event.
 extern Redirection gRedirections[];
 
 // Do early initialization of redirections. This is done on both
 // recording/replaying and middleman processes, and allows OriginalCall() to
 // work in either case.
@@ -463,25 +521,16 @@ RR_WriteBufferViaRval(Stream& aEvents, C
   auto& count = aArguments->Arg<CountArg, size_t>();
   aEvents.CheckInput(count);
 
   auto& rval = aArguments->Rval<size_t>();
   MOZ_RELEASE_ASSERT(rval + Offset <= count);
   aEvents.RecordOrReplayBytes(buf, rval + Offset);
 }
 
-// Insert an atomic access while recording/replaying so that calls to this
-// function replay in the same order they occurred in while recording. This is
-// used for functions that are used in inter-thread synchronization.
-static inline void
-RR_OrderCall(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
-{
-  AutoOrderedAtomicAccess();
-}
-
 // Record/replay a scalar return value.
 static inline void
 RR_ScalarRval(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
 {
   aEvents.RecordOrReplayValue(&aArguments->Rval<size_t>());
 }
 
 // Record/replay a complex scalar return value that fits in two registers.
@@ -515,28 +564,28 @@ RR_OversizeRval(Stream& aEvents, CallArg
 {
   RR_WriteBufferFixedSize<0, ByteCount>(aEvents, aArguments, aError);
 }
 
 template <size_t ReturnValue>
 static inline PreambleResult
 Preamble_Veto(CallArguments* aArguments)
 {
-  aArguments->Rval<size_t>() = 0;
+  aArguments->Rval<size_t>() = ReturnValue;
   return PreambleResult::Veto;
 }
 
 template <size_t ReturnValue>
 static inline PreambleResult
 Preamble_VetoIfNotPassedThrough(CallArguments* aArguments)
 {
   if (AreThreadEventsPassedThrough()) {
     return PreambleResult::PassThrough;
   }
-  aArguments->Rval<size_t>() = 0;
+  aArguments->Rval<size_t>() = ReturnValue;
   return PreambleResult::Veto;
 }
 
 static inline PreambleResult
 Preamble_PassThrough(CallArguments* aArguments)
 {
   return PreambleResult::PassThrough;
 }
--- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp
+++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/Maybe.h"
 
 #include "HashTable.h"
 #include "Lock.h"
 #include "MemorySnapshot.h"
 #include "ProcessRecordReplay.h"
 #include "ProcessRewind.h"
+#include "base/eintr_wrapper.h"
 
 #include <dlfcn.h>
 #include <fcntl.h>
 #include <signal.h>
 
 #include <bsm/audit.h>
 #include <bsm/audit_session.h>
 #include <mach/clock.h>
@@ -73,53 +74,59 @@ namespace recordreplay {
   MACRO(lseek, RR_SaveRvalHadErrorNegative)                      \
   MACRO(socketpair, RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<3, 2 * sizeof(int)>>) \
   MACRO(fileport_makeport,                                       \
         RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(size_t)>>) \
   MACRO(getsockopt, RR_SaveRvalHadErrorNegative<RR_getsockopt>)  \
   MACRO(gettimeofday, RR_SaveRvalHadErrorNegative<RR_Compose<    \
                         RR_WriteOptionalBufferFixedSize<0, sizeof(struct timeval)>, \
                         RR_WriteOptionalBufferFixedSize<1, sizeof(struct timezone)>>>, \
-                      Preamble_gettimeofday)                     \
+        nullptr, nullptr, Preamble_PassThrough)                  \
   MACRO(getuid, RR_ScalarRval)                                   \
   MACRO(geteuid, RR_ScalarRval)                                  \
   MACRO(getgid, RR_ScalarRval)                                   \
   MACRO(getegid, RR_ScalarRval)                                  \
   MACRO(issetugid, RR_ScalarRval)                                \
   MACRO(__gettid, RR_ScalarRval)                                 \
-  MACRO(getpid, RR_ScalarRval)                                   \
+  MACRO(getpid, nullptr, Preamble_getpid)                        \
   MACRO(fcntl, RR_SaveRvalHadErrorNegative, Preamble_fcntl)      \
   MACRO(getattrlist, RR_SaveRvalHadErrorNegative<RR_WriteBuffer<2, 3>>) \
   MACRO(fstat$INODE64,                                           \
-        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct stat)>>) \
+        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct stat)>>, \
+        nullptr, nullptr, Preamble_SetError)                     \
   MACRO(lstat$INODE64,                                           \
-        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct stat)>>) \
+        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct stat)>>, \
+        nullptr, nullptr, Preamble_SetError)                     \
   MACRO(stat$INODE64,                                            \
-        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct stat)>>) \
+        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct stat)>>, \
+        nullptr, nullptr, Preamble_SetError)                     \
   MACRO(statfs$INODE64,                                          \
-        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct statfs)>>) \
+        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct statfs)>>, \
+        nullptr, nullptr, Preamble_SetError)                     \
   MACRO(fstatfs$INODE64,                                         \
-        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct statfs)>>) \
+        RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct statfs)>>, \
+        nullptr, nullptr, Preamble_SetError)                     \
   MACRO(readlink, RR_SaveRvalHadErrorNegative<RR_WriteBuffer<1, 2>>) \
   MACRO(__getdirentries64, RR_SaveRvalHadErrorNegative<RR_Compose< \
                              RR_WriteBuffer<1, 2>,               \
                              RR_WriteBufferFixedSize<3, sizeof(size_t)>>>) \
   MACRO(getdirentriesattr, RR_SaveRvalHadErrorNegative<RR_Compose< \
                              RR_WriteBufferFixedSize<1, sizeof(struct attrlist)>, \
                              RR_WriteBuffer<2, 3>,               \
                              RR_WriteBufferFixedSize<4, sizeof(size_t)>, \
                              RR_WriteBufferFixedSize<5, sizeof(size_t)>, \
                              RR_WriteBufferFixedSize<6, sizeof(size_t)>>>) \
   MACRO(getrusage,                                               \
         RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct rusage)>>) \
   MACRO(__getrlimit,                                             \
         RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<1, sizeof(struct rlimit)>>) \
   MACRO(__setrlimit, RR_SaveRvalHadErrorNegative)                \
   MACRO(sigprocmask,                                             \
-        RR_SaveRvalHadErrorNegative<RR_WriteOptionalBufferFixedSize<2, sizeof(sigset_t)>>) \
+        RR_SaveRvalHadErrorNegative<RR_WriteOptionalBufferFixedSize<2, sizeof(sigset_t)>>, \
+        nullptr, nullptr, Preamble_PassThrough)                  \
   MACRO(sigaltstack,                                             \
         RR_SaveRvalHadErrorNegative<RR_WriteOptionalBufferFixedSize<2, sizeof(stack_t)>>) \
   MACRO(sigaction,                                               \
         RR_SaveRvalHadErrorNegative<RR_WriteOptionalBufferFixedSize<2, sizeof(struct sigaction)>>) \
   MACRO(__pthread_sigmask,                                       \
         RR_SaveRvalHadErrorNegative<RR_WriteOptionalBufferFixedSize<2, sizeof(sigset_t)>>) \
   MACRO(__fsgetpath, RR_SaveRvalHadErrorNegative<RR_WriteBuffer<0, 1>>) \
   MACRO(__disable_threadsignal, nullptr, Preamble___disable_threadsignal) \
@@ -140,48 +147,49 @@ namespace recordreplay {
   MACRO(csops, RR_SaveRvalHadErrorNegative<RR_WriteBuffer<2, 3>>) \
   MACRO(__getlogin, RR_SaveRvalHadErrorNegative<RR_WriteBuffer<0, 1>>) \
   MACRO(__workq_kernreturn, nullptr, Preamble___workq_kernreturn) \
   MACRO(start_wqthread, nullptr, Preamble_start_wqthread)        \
   /* pthreads interface functions */                             \
   MACRO(pthread_cond_wait, nullptr, Preamble_pthread_cond_wait)  \
   MACRO(pthread_cond_timedwait, nullptr, Preamble_pthread_cond_timedwait) \
   MACRO(pthread_cond_timedwait_relative_np, nullptr, Preamble_pthread_cond_timedwait_relative_np) \
-  MACRO(pthread_create, nullptr, Preamble_pthread_create)        \
+  MACRO(pthread_create, nullptr, Preamble_pthread_create, nullptr, Preamble_SetError) \
   MACRO(pthread_join, nullptr, Preamble_pthread_join)            \
   MACRO(pthread_mutex_init, nullptr, Preamble_pthread_mutex_init) \
   MACRO(pthread_mutex_destroy, nullptr, Preamble_pthread_mutex_destroy) \
   MACRO(pthread_mutex_lock, nullptr, Preamble_pthread_mutex_lock) \
   MACRO(pthread_mutex_trylock, nullptr, Preamble_pthread_mutex_trylock) \
   MACRO(pthread_mutex_unlock, nullptr, Preamble_pthread_mutex_unlock) \
   /* C Library functions */                                      \
   MACRO(dlclose, nullptr, Preamble_Veto<0>)                      \
   MACRO(dlopen, nullptr, Preamble_PassThrough)                   \
   MACRO(dlsym, nullptr, Preamble_PassThrough)                    \
   MACRO(fclose, RR_SaveRvalHadErrorNegative)                     \
   MACRO(fopen, RR_SaveRvalHadErrorZero)                          \
   MACRO(fread, RR_Compose<RR_ScalarRval, RR_fread>)              \
   MACRO(fseek, RR_SaveRvalHadErrorNegative)                      \
   MACRO(ftell, RR_SaveRvalHadErrorNegative)                      \
   MACRO(fwrite, RR_ScalarRval)                                   \
-  MACRO(getenv, RR_CStringRval)                                  \
+  MACRO(getenv, RR_CStringRval, nullptr, nullptr, Preamble_Veto<0>) \
   MACRO(localtime_r, RR_SaveRvalHadErrorZero<RR_Compose<         \
                        RR_WriteBufferFixedSize<1, sizeof(struct tm)>, \
                        RR_RvalIsArgument<1>>>)                   \
   MACRO(gmtime_r, RR_SaveRvalHadErrorZero<RR_Compose<            \
                     RR_WriteBufferFixedSize<1, sizeof(struct tm)>, \
                     RR_RvalIsArgument<1>>>)                      \
   MACRO(localtime, nullptr, Preamble_localtime)                  \
   MACRO(gmtime, nullptr, Preamble_gmtime)                        \
   MACRO(mktime, RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<0, sizeof(struct tm)>>) \
   MACRO(setlocale, RR_CStringRval)                               \
   MACRO(strftime, RR_Compose<RR_ScalarRval, RR_WriteBufferViaRval<0, 1, 1>>) \
   MACRO(arc4random, RR_ScalarRval)                               \
-  MACRO(mach_absolute_time, RR_ScalarRval, Preamble_mach_absolute_time) \
-  MACRO(mach_msg, RR_Compose<RR_OrderCall, RR_ScalarRval, RR_WriteBuffer<0, 3>>) \
+  MACRO(mach_absolute_time, RR_ScalarRval, Preamble_mach_absolute_time, \
+        nullptr, Preamble_PassThrough)                           \
+  MACRO(mach_msg, RR_Compose<RR_ScalarRval, RR_WriteBuffer<0, 3>>) \
   MACRO(mach_timebase_info,                                      \
         RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<0, sizeof(mach_timebase_info_data_t)>>) \
   MACRO(mach_vm_allocate, nullptr, Preamble_mach_vm_allocate)    \
   MACRO(mach_vm_deallocate, nullptr, Preamble_mach_vm_deallocate) \
   MACRO(mach_vm_protect, nullptr, Preamble_mach_vm_protect)      \
   MACRO(realpath,                                                \
         RR_SaveRvalHadErrorZero<RR_Compose<RR_CStringRval,       \
                                            RR_WriteOptionalBufferFixedSize<1, PATH_MAX>>>) \
@@ -204,96 +212,116 @@ namespace recordreplay {
   MACRO(PL_NewHashTable, nullptr, Preamble_PL_NewHashTable)      \
   MACRO(PL_HashTableDestroy, nullptr, Preamble_PL_HashTableDestroy) \
   /* Objective C functions */                                    \
   MACRO(class_getClassMethod, RR_ScalarRval)                     \
   MACRO(class_getInstanceMethod, RR_ScalarRval)                  \
   MACRO(method_exchangeImplementations)                          \
   MACRO(objc_autoreleasePoolPop)                                 \
   MACRO(objc_autoreleasePoolPush, RR_ScalarRval)                 \
-  MACRO(objc_msgSend, nullptr, Preamble_objc_msgSend)            \
+  MACRO(objc_msgSend, RR_objc_msgSend, Preamble_objc_msgSend,    \
+        Middleman_objc_msgSend, MiddlemanPreamble_objc_msgSend)  \
   /* Cocoa functions */                                          \
   MACRO(AcquireFirstMatchingEventInQueue, RR_ScalarRval)         \
   MACRO(CFArrayAppendValue)                                      \
-  MACRO(CFArrayCreate, RR_ScalarRval)                            \
-  MACRO(CFArrayGetCount, RR_ScalarRval)                          \
-  MACRO(CFArrayGetValueAtIndex, RR_ScalarRval)                   \
+  MACRO(CFArrayCreate, RR_ScalarRval, nullptr, Middleman_CFArrayCreate) \
+  MACRO(CFArrayGetCount, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CFArrayGetValueAtIndex, RR_ScalarRval, nullptr, Middleman_CFArrayGetValueAtIndex) \
   MACRO(CFArrayRemoveValueAtIndex)                               \
-  MACRO(CFAttributedStringCreate, RR_ScalarRval)                 \
+  MACRO(CFAttributedStringCreate, RR_ScalarRval, nullptr,        \
+        Middleman_Compose<Middleman_CFTypeArg<1>, Middleman_CFTypeArg<2>, Middleman_CreateCFTypeRval>) \
   MACRO(CFBundleCopyExecutableURL, RR_ScalarRval)                \
   MACRO(CFBundleCopyInfoDictionaryForURL, RR_ScalarRval)         \
   MACRO(CFBundleCreate, RR_ScalarRval)                           \
   MACRO(CFBundleGetBundleWithIdentifier, RR_ScalarRval)          \
   MACRO(CFBundleGetDataPointerForName, nullptr, Preamble_VetoIfNotPassedThrough<0>) \
   MACRO(CFBundleGetFunctionPointerForName, nullptr, Preamble_VetoIfNotPassedThrough<0>) \
   MACRO(CFBundleGetIdentifier, RR_ScalarRval)                    \
   MACRO(CFBundleGetInfoDictionary, RR_ScalarRval)                \
   MACRO(CFBundleGetMainBundle, RR_ScalarRval)                    \
   MACRO(CFBundleGetValueForInfoDictionaryKey, RR_ScalarRval)     \
-  MACRO(CFDataGetBytePtr, RR_CFDataGetBytePtr)                   \
-  MACRO(CFDataGetLength, RR_ScalarRval)                          \
+  MACRO(CFDataGetBytePtr, RR_CFDataGetBytePtr, nullptr, Middleman_CFDataGetBytePtr) \
+  MACRO(CFDataGetLength, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CFDateFormatterCreate, RR_ScalarRval)                    \
   MACRO(CFDateFormatterGetFormat, RR_ScalarRval)                 \
-  MACRO(CFDictionaryAddValue)                                    \
-  MACRO(CFDictionaryCreate, RR_ScalarRval)                       \
-  MACRO(CFDictionaryCreateMutable, RR_ScalarRval)                \
-  MACRO(CFDictionaryCreateMutableCopy, RR_ScalarRval)            \
-  MACRO(CFDictionaryGetValue, RR_ScalarRval)                     \
+  MACRO(CFDictionaryAddValue, nullptr, nullptr,                  \
+        Middleman_Compose<Middleman_UpdateCFTypeArg<0>,          \
+                          Middleman_CFTypeArg<1>,                \
+                          Middleman_CFTypeArg<2>>)               \
+  MACRO(CFDictionaryCreate, RR_ScalarRval, nullptr, Middleman_CFDictionaryCreate) \
+  MACRO(CFDictionaryCreateMutable, RR_ScalarRval, nullptr, Middleman_CreateCFTypeRval) \
+  MACRO(CFDictionaryCreateMutableCopy, RR_ScalarRval, nullptr,   \
+        Middleman_Compose<Middleman_CFTypeArg<2>, Middleman_CreateCFTypeRval>) \
+  MACRO(CFDictionaryGetValue, RR_ScalarRval, nullptr,            \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>, Middleman_CFTypeRval>) \
   MACRO(CFDictionaryGetValueIfPresent,                           \
-        RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<2, sizeof(const void*)>>) \
-  MACRO(CFDictionaryReplaceValue)                                \
-  MACRO(CFEqual, RR_ScalarRval)                                  \
-  MACRO(CFGetTypeID, RR_ScalarRval)                              \
+        RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<2, sizeof(const void*)>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_CFTypeArg<1>,                \
+                          Middleman_CFTypeOutputArg<2>>)         \
+  MACRO(CFDictionaryReplaceValue, nullptr, nullptr,              \
+        Middleman_Compose<Middleman_UpdateCFTypeArg<0>,          \
+                          Middleman_CFTypeArg<1>,                \
+                          Middleman_CFTypeArg<2>>)               \
+  MACRO(CFEqual, RR_ScalarRval, nullptr,                         \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>>) \
+  MACRO(CFGetTypeID, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CFLocaleCopyCurrent, RR_ScalarRval)                      \
   MACRO(CFLocaleCopyPreferredLanguages, RR_ScalarRval)           \
   MACRO(CFLocaleCreate, RR_ScalarRval)                           \
   MACRO(CFLocaleGetIdentifier, RR_ScalarRval)                    \
   MACRO(CFNotificationCenterAddObserver, nullptr, Preamble_CFNotificationCenterAddObserver) \
   MACRO(CFNotificationCenterGetLocalCenter, RR_ScalarRval)       \
   MACRO(CFNotificationCenterRemoveObserver)                      \
-  MACRO(CFNumberCreate, RR_ScalarRval)                           \
-  MACRO(CFNumberGetValue, RR_Compose<RR_ScalarRval, RR_CFNumberGetValue>) \
-  MACRO(CFNumberIsFloatType, RR_ScalarRval)                      \
+  MACRO(CFNumberCreate, RR_ScalarRval, nullptr, Middleman_CFNumberCreate) \
+  MACRO(CFNumberGetValue, RR_Compose<RR_ScalarRval, RR_CFNumberGetValue>, nullptr, \
+        Middleman_CFNumberGetValue)                              \
+  MACRO(CFNumberIsFloatType, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CFPreferencesAppValueIsForced, RR_ScalarRval)            \
   MACRO(CFPreferencesCopyAppValue, RR_ScalarRval)                \
   MACRO(CFPreferencesCopyValue, RR_ScalarRval)                   \
   MACRO(CFPropertyListCreateFromXMLData, RR_ScalarRval)          \
   MACRO(CFPropertyListCreateWithStream, RR_ScalarRval)           \
   MACRO(CFReadStreamClose)                                       \
   MACRO(CFReadStreamCreateWithFile, RR_ScalarRval)               \
   MACRO(CFReadStreamOpen, RR_ScalarRval)                         \
-  MACRO(CFRelease, RR_ScalarRval)                                \
-  MACRO(CFRetain, RR_ScalarRval)                                 \
+  /* Don't handle release/retain calls explicitly in the middleman, all resources */ \
+  /* will be cleaned up when its calls are reset. */             \
+  MACRO(CFRelease, RR_ScalarRval, nullptr, nullptr, Preamble_Veto<0>) \
+  MACRO(CFRetain, RR_ScalarRval, nullptr, nullptr, MiddlemanPreamble_CFRetain) \
   MACRO(CFRunLoopAddSource)                                      \
   MACRO(CFRunLoopGetCurrent, RR_ScalarRval)                      \
   MACRO(CFRunLoopRemoveSource)                                   \
   MACRO(CFRunLoopSourceCreate, RR_ScalarRval, Preamble_CFRunLoopSourceCreate) \
   MACRO(CFRunLoopSourceSignal)                                   \
   MACRO(CFRunLoopWakeUp)                                         \
   MACRO(CFStringAppendCharacters)                                \
-  MACRO(CFStringCompare, RR_ScalarRval)                          \
+  MACRO(CFStringCompare, RR_ScalarRval, nullptr,                 \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>>) \
   MACRO(CFStringCreateArrayBySeparatingStrings, RR_ScalarRval)   \
   MACRO(CFStringCreateMutable, RR_ScalarRval)                    \
   MACRO(CFStringCreateWithBytes, RR_ScalarRval)                  \
   MACRO(CFStringCreateWithBytesNoCopy, RR_ScalarRval)            \
-  MACRO(CFStringCreateWithCharactersNoCopy, RR_ScalarRval)       \
+  MACRO(CFStringCreateWithCharactersNoCopy, RR_ScalarRval, nullptr, \
+        Middleman_Compose<Middleman_Buffer<1, 2, UniChar>, Middleman_CreateCFTypeRval>) \
   MACRO(CFStringCreateWithCString, RR_ScalarRval)                \
   MACRO(CFStringCreateWithFormat, RR_ScalarRval)                 \
   /* Argument indexes are off by one here as the CFRange argument uses two slots. */ \
   MACRO(CFStringGetBytes, RR_Compose<                            \
                             RR_ScalarRval,                       \
                             RR_WriteOptionalBuffer<6, 7>,        \
                             RR_WriteOptionalBufferFixedSize<8, sizeof(CFIndex)>>) \
   /* Argument indexes are off by one here as the CFRange argument uses two slots. */ \
   /* We also need to specify the argument register with the range's length here. */ \
-  MACRO(CFStringGetCharacters, RR_WriteBuffer<3, 2, UniChar>)    \
+  MACRO(CFStringGetCharacters, RR_WriteBuffer<3, 2, UniChar>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_WriteBuffer<3, 2, UniChar>>) \
   MACRO(CFStringGetCString, RR_Compose<RR_ScalarRval, RR_WriteBuffer<1, 2>>) \
   MACRO(CFStringGetCStringPtr, nullptr, Preamble_VetoIfNotPassedThrough<0>) \
   MACRO(CFStringGetIntValue, RR_ScalarRval)                      \
-  MACRO(CFStringGetLength, RR_ScalarRval)                        \
+  MACRO(CFStringGetLength, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CFStringGetMaximumSizeForEncoding, RR_ScalarRval)        \
   MACRO(CFStringHasPrefix, RR_ScalarRval)                        \
   MACRO(CFStringTokenizerAdvanceToNextToken, RR_ScalarRval)      \
   MACRO(CFStringTokenizerCreate, RR_ScalarRval)                  \
   MACRO(CFStringTokenizerGetCurrentTokenRange, RR_ComplexScalarRval) \
   MACRO(CFURLCreateFromFileSystemRepresentation, RR_ScalarRval)  \
   MACRO(CFURLCreateFromFSRef, RR_ScalarRval)                     \
   MACRO(CFURLCreateWithFileSystemPath, RR_ScalarRval)            \
@@ -301,140 +329,218 @@ namespace recordreplay {
   MACRO(CFURLGetFileSystemRepresentation, RR_Compose<RR_ScalarRval, RR_WriteBuffer<2, 3>>) \
   MACRO(CFURLGetFSRef, RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<1, sizeof(FSRef)>>) \
   MACRO(CFUUIDCreate, RR_ScalarRval)                             \
   MACRO(CFUUIDCreateString, RR_ScalarRval)                       \
   MACRO(CFUUIDGetUUIDBytes, RR_ComplexScalarRval)                \
   MACRO(CGAffineTransformConcat, RR_OversizeRval<sizeof(CGAffineTransform)>) \
   MACRO(CGBitmapContextCreateImage, RR_ScalarRval)               \
   MACRO(CGBitmapContextCreateWithData,                           \
-        RR_Compose<RR_ScalarRval, RR_CGBitmapContextCreateWithData>) \
+        RR_Compose<RR_ScalarRval, RR_CGBitmapContextCreateWithData>, nullptr, \
+        Middleman_CGBitmapContextCreateWithData)                 \
   MACRO(CGBitmapContextGetBytesPerRow, RR_ScalarRval)            \
   MACRO(CGBitmapContextGetHeight, RR_ScalarRval)                 \
   MACRO(CGBitmapContextGetWidth, RR_ScalarRval)                  \
   MACRO(CGColorRelease, RR_ScalarRval)                           \
   MACRO(CGColorSpaceCopyICCProfile, RR_ScalarRval)               \
-  MACRO(CGColorSpaceCreateDeviceRGB, RR_ScalarRval)              \
+  MACRO(CGColorSpaceCreateDeviceRGB, RR_ScalarRval, nullptr, Middleman_CreateCFTypeRval) \
   MACRO(CGColorSpaceCreatePattern, RR_ScalarRval)                \
   MACRO(CGColorSpaceRelease, RR_ScalarRval)                      \
   MACRO(CGContextBeginTransparencyLayerWithRect)                 \
-  MACRO(CGContextClipToRects, RR_ScalarRval)                     \
-  MACRO(CGContextConcatCTM)                                      \
+  MACRO(CGContextClipToRects, RR_ScalarRval, nullptr,            \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_Buffer<1, 2, CGRect>>) \
+  MACRO(CGContextConcatCTM, nullptr, nullptr,                    \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_StackArgumentData<sizeof(CGAffineTransform)>>) \
   MACRO(CGContextDrawImage, RR_FlushCGContext<0>)                \
   MACRO(CGContextEndTransparencyLayer)                           \
-  MACRO(CGContextFillRect, RR_FlushCGContext<0>)                 \
+  MACRO(CGContextFillRect, RR_FlushCGContext<0>, nullptr,        \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_StackArgumentData<sizeof(CGRect)>, \
+                          Middleman_FlushCGContext<0>>)          \
   MACRO(CGContextGetClipBoundingBox, RR_OversizeRval<sizeof(CGRect)>) \
   MACRO(CGContextGetCTM, RR_OversizeRval<sizeof(CGAffineTransform)>) \
   MACRO(CGContextGetType, RR_ScalarRval)                         \
   MACRO(CGContextGetUserSpaceToDeviceSpaceTransform, RR_OversizeRval<sizeof(CGAffineTransform)>) \
-  MACRO(CGContextRestoreGState, nullptr, Preamble_CGContextRestoreGState) \
-  MACRO(CGContextSaveGState)                                     \
-  MACRO(CGContextSetAllowsFontSubpixelPositioning)               \
-  MACRO(CGContextSetAllowsFontSubpixelQuantization)              \
-  MACRO(CGContextSetAlpha)                                       \
-  MACRO(CGContextSetBaseCTM)                                     \
-  MACRO(CGContextSetCTM)                                         \
-  MACRO(CGContextSetGrayFillColor)                               \
+  MACRO(CGContextRestoreGState, nullptr, Preamble_CGContextRestoreGState, \
+        Middleman_UpdateCFTypeArg<0>)                            \
+  MACRO(CGContextSaveGState, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetAllowsFontSubpixelPositioning, nullptr, nullptr, \
+        Middleman_UpdateCFTypeArg<0>)                            \
+  MACRO(CGContextSetAllowsFontSubpixelQuantization, nullptr, nullptr, \
+        Middleman_UpdateCFTypeArg<0>)                            \
+  MACRO(CGContextSetAlpha, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetBaseCTM, nullptr, nullptr,                   \
+        Middleman_Compose<Middleman_UpdateCFTypeArg<0>,          \
+                          Middleman_StackArgumentData<sizeof(CGAffineTransform)>>) \
+  MACRO(CGContextSetCTM, nullptr, nullptr,                       \
+        Middleman_Compose<Middleman_UpdateCFTypeArg<0>,          \
+                          Middleman_StackArgumentData<sizeof(CGAffineTransform)>>) \
+  MACRO(CGContextSetGrayFillColor, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
   MACRO(CGContextSetRGBFillColor)                                \
-  MACRO(CGContextSetShouldAntialias)                             \
-  MACRO(CGContextSetShouldSmoothFonts)                           \
-  MACRO(CGContextSetShouldSubpixelPositionFonts)                 \
-  MACRO(CGContextSetShouldSubpixelQuantizeFonts)                 \
-  MACRO(CGContextSetTextDrawingMode)                             \
-  MACRO(CGContextSetTextMatrix)                                  \
-  MACRO(CGContextScaleCTM)                                       \
-  MACRO(CGContextTranslateCTM)                                   \
+  MACRO(CGContextSetShouldAntialias, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetShouldSmoothFonts, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetShouldSubpixelPositionFonts, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetShouldSubpixelQuantizeFonts, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetTextDrawingMode, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextSetTextMatrix, nullptr, nullptr,                \
+        Middleman_Compose<Middleman_UpdateCFTypeArg<0>,          \
+                          Middleman_StackArgumentData<sizeof(CGAffineTransform)>>) \
+  MACRO(CGContextScaleCTM, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
+  MACRO(CGContextTranslateCTM, nullptr, nullptr, Middleman_UpdateCFTypeArg<0>) \
   MACRO(CGDataProviderCreateWithData, RR_Compose<RR_ScalarRval, RR_CGDataProviderCreateWithData>) \
   MACRO(CGDataProviderRelease)                                   \
   MACRO(CGDisplayCopyColorSpace, RR_ScalarRval)                  \
   MACRO(CGDisplayIOServicePort, RR_ScalarRval)                   \
   MACRO(CGEventSourceCounterForEventType, RR_ScalarRval)         \
-  MACRO(CGFontCopyTableForTag, RR_ScalarRval)                    \
-  MACRO(CGFontCopyTableTags, RR_ScalarRval)                      \
-  MACRO(CGFontCopyVariations, RR_ScalarRval)                     \
+  MACRO(CGFontCopyTableForTag, RR_ScalarRval, nullptr,           \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CGFontCopyTableTags, RR_ScalarRval, nullptr,             \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CGFontCopyVariations, RR_ScalarRval, nullptr,            \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
   MACRO(CGFontCreateCopyWithVariations, RR_ScalarRval)           \
   MACRO(CGFontCreateWithDataProvider, RR_ScalarRval)             \
-  MACRO(CGFontCreateWithFontName, RR_ScalarRval)                 \
+  MACRO(CGFontCreateWithFontName, RR_ScalarRval, nullptr,        \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
   MACRO(CGFontCreateWithPlatformFont, RR_ScalarRval)             \
-  MACRO(CGFontGetAscent, RR_ScalarRval)                          \
-  MACRO(CGFontGetCapHeight, RR_ScalarRval)                       \
-  MACRO(CGFontGetDescent, RR_ScalarRval)                         \
-  MACRO(CGFontGetFontBBox, RR_OversizeRval<sizeof(CGRect)>)      \
-  MACRO(CGFontGetGlyphAdvances, RR_Compose<RR_ScalarRval, RR_WriteBuffer<3, 2, int>>) \
-  MACRO(CGFontGetGlyphBBoxes, RR_Compose<RR_ScalarRval, RR_WriteBuffer<3, 2, CGRect>>) \
+  MACRO(CGFontGetAscent, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CGFontGetCapHeight, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CGFontGetDescent, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CGFontGetFontBBox, RR_OversizeRval<sizeof(CGRect)>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<1>, Middleman_OversizeRval<sizeof(CGRect)>>) \
+  MACRO(CGFontGetGlyphAdvances, RR_Compose<RR_ScalarRval, RR_WriteBuffer<3, 2, int>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_Buffer<1, 2, CGGlyph>,       \
+                          Middleman_WriteBuffer<3, 2, int>>)     \
+  MACRO(CGFontGetGlyphBBoxes, RR_Compose<RR_ScalarRval, RR_WriteBuffer<3, 2, CGRect>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_Buffer<1, 2, CGGlyph>,       \
+                          Middleman_WriteBuffer<3, 2, CGRect>>)  \
   MACRO(CGFontGetGlyphPath, RR_ScalarRval)                       \
-  MACRO(CGFontGetLeading, RR_ScalarRval)                         \
-  MACRO(CGFontGetUnitsPerEm, RR_ScalarRval)                      \
-  MACRO(CGFontGetXHeight, RR_ScalarRval)                         \
+  MACRO(CGFontGetLeading, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CGFontGetUnitsPerEm, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CGFontGetXHeight, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CGImageGetHeight, RR_ScalarRval)                         \
   MACRO(CGImageGetWidth, RR_ScalarRval)                          \
   MACRO(CGImageRelease, RR_ScalarRval)                           \
   MACRO(CGMainDisplayID, RR_ScalarRval)                          \
   MACRO(CGPathAddPath)                                           \
   MACRO(CGPathApply, nullptr, Preamble_CGPathApply)              \
   MACRO(CGPathContainsPoint, RR_ScalarRval)                      \
   MACRO(CGPathCreateMutable, RR_ScalarRval)                      \
   MACRO(CGPathGetBoundingBox, RR_OversizeRval<sizeof(CGRect)>)   \
   MACRO(CGPathGetCurrentPoint, RR_ComplexFloatRval)              \
   MACRO(CGPathIsEmpty, RR_ScalarRval)                            \
   MACRO(CGSSetDebugOptions, RR_ScalarRval)                       \
   MACRO(CGSShutdownServerConnections)                            \
-  MACRO(CTFontCopyFamilyName, RR_ScalarRval)                     \
-  MACRO(CTFontCopyFeatures, RR_ScalarRval)                       \
-  MACRO(CTFontCopyFontDescriptor, RR_ScalarRval)                 \
-  MACRO(CTFontCopyGraphicsFont, RR_ScalarRval)                   \
-  MACRO(CTFontCopyTable, RR_ScalarRval)                          \
+  MACRO(CTFontCopyFamilyName, RR_ScalarRval, nullptr,            \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontCopyFeatures, RR_ScalarRval, nullptr,              \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontCopyFontDescriptor, RR_ScalarRval, nullptr,        \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontCopyGraphicsFont, RR_ScalarRval, nullptr,          \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontCopyTable, RR_ScalarRval, nullptr,                 \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
   MACRO(CTFontCopyVariationAxes, RR_ScalarRval)                  \
-  MACRO(CTFontCreateForString, RR_ScalarRval)                    \
+  MACRO(CTFontCreateForString, RR_ScalarRval, nullptr,           \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \
   MACRO(CTFontCreatePathForGlyph, RR_ScalarRval)                 \
-  MACRO(CTFontCreateWithFontDescriptor, RR_ScalarRval)           \
-  MACRO(CTFontCreateWithGraphicsFont, RR_ScalarRval)             \
-  MACRO(CTFontCreateWithName, RR_ScalarRval)                     \
-  MACRO(CTFontDescriptorCopyAttribute, RR_ScalarRval)            \
-  MACRO(CTFontDescriptorCreateCopyWithAttributes, RR_ScalarRval) \
-  MACRO(CTFontDescriptorCreateMatchingFontDescriptors, RR_ScalarRval) \
-  MACRO(CTFontDescriptorCreateWithAttributes, RR_ScalarRval)     \
-  MACRO(CTFontDrawGlyphs, RR_FlushCGContext<4>)                  \
+  MACRO(CTFontCreateWithFontDescriptor, RR_ScalarRval, nullptr,  \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_BufferFixedSize<1, sizeof(CGAffineTransform)>, \
+                          Middleman_CreateCFTypeRval>)                 \
+  MACRO(CTFontCreateWithGraphicsFont, RR_ScalarRval, nullptr,    \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_BufferFixedSize<1, sizeof(CGAffineTransform)>, \
+                          Middleman_CFTypeArg<2>,                \
+                          Middleman_CreateCFTypeRval>)                 \
+  MACRO(CTFontCreateWithName, RR_ScalarRval, nullptr,            \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_BufferFixedSize<1, sizeof(CGAffineTransform)>, \
+                          Middleman_CreateCFTypeRval>)                 \
+  MACRO(CTFontDescriptorCopyAttribute, RR_ScalarRval, nullptr,   \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontDescriptorCreateCopyWithAttributes, RR_ScalarRval, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontDescriptorCreateMatchingFontDescriptors, RR_ScalarRval, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeArg<1>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontDescriptorCreateWithAttributes, RR_ScalarRval, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTFontDrawGlyphs, RR_FlushCGContext<4>, nullptr,         \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_CFTypeArg<4>,                \
+                          Middleman_Buffer<1, 3, CGGlyph>,       \
+                          Middleman_Buffer<2, 3, CGPoint>,       \
+                          Middleman_FlushCGContext<4>>)          \
   MACRO(CTFontGetAdvancesForGlyphs,                              \
-        RR_Compose<RR_FloatRval, RR_WriteOptionalBuffer<3, 4, CGSize>>) \
-  MACRO(CTFontGetAscent, RR_FloatRval)                           \
-  MACRO(CTFontGetBoundingBox, RR_OversizeRval<sizeof(CGRect)>)   \
+        RR_Compose<RR_FloatRval, RR_WriteOptionalBuffer<3, 4, CGSize>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_Buffer<2, 4, CGGlyph>,       \
+                          Middleman_WriteBuffer<3, 4, CGSize>>)  \
+  MACRO(CTFontGetAscent, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>)   \
+  MACRO(CTFontGetBoundingBox, RR_OversizeRval<sizeof(CGRect)>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<1>, Middleman_OversizeRval<sizeof(CGRect)>>) \
   MACRO(CTFontGetBoundingRectsForGlyphs,                         \
         /* Argument indexes here are off by one due to the oversize rval. */ \
-        RR_Compose<RR_OversizeRval<sizeof(CGRect)>, RR_WriteOptionalBuffer<4, 5, CGRect>>) \
-  MACRO(CTFontGetCapHeight, RR_FloatRval)                        \
-  MACRO(CTFontGetDescent, RR_FloatRval)                          \
-  MACRO(CTFontGetGlyphCount, RR_ScalarRval)                      \
+        RR_Compose<RR_OversizeRval<sizeof(CGRect)>,              \
+                   RR_WriteOptionalBuffer<4, 5, CGRect>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<1>,                \
+                          Middleman_Buffer<3, 5, CGGlyph>,       \
+                          Middleman_OversizeRval<sizeof(CGRect)>, \
+                          Middleman_WriteBuffer<4, 5, CGRect>>)  \
+  MACRO(CTFontGetCapHeight, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetDescent, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetGlyphCount, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CTFontGetGlyphsForCharacters,                            \
-        RR_Compose<RR_ScalarRval, RR_WriteBuffer<2, 3, CGGlyph>>) \
-  MACRO(CTFontGetLeading, RR_FloatRval)                          \
-  MACRO(CTFontGetSize, RR_FloatRval)                             \
-  MACRO(CTFontGetSymbolicTraits, RR_ScalarRval)                  \
-  MACRO(CTFontGetUnderlinePosition, RR_FloatRval)                \
-  MACRO(CTFontGetUnderlineThickness, RR_FloatRval)               \
-  MACRO(CTFontGetUnitsPerEm, RR_ScalarRval)                      \
-  MACRO(CTFontGetXHeight, RR_FloatRval)                           \
+        RR_Compose<RR_ScalarRval, RR_WriteBuffer<2, 3, CGGlyph>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_Buffer<1, 3, UniChar>,       \
+                          Middleman_WriteBuffer<2, 3, CGGlyph>>) \
+  MACRO(CTFontGetLeading, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetSize, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetSymbolicTraits, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetUnderlinePosition, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetUnderlineThickness, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetUnitsPerEm, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTFontGetXHeight, RR_FloatRval, nullptr, Middleman_CFTypeArg<0>) \
   MACRO(CTFontManagerCopyAvailableFontFamilyNames, RR_ScalarRval) \
   MACRO(CTFontManagerRegisterFontsForURLs, RR_ScalarRval)        \
   MACRO(CTFontManagerSetAutoActivationSetting)                   \
-  MACRO(CTLineCreateWithAttributedString, RR_ScalarRval)         \
-  MACRO(CTLineGetGlyphRuns, RR_ScalarRval)                       \
-  MACRO(CTRunGetAttributes, RR_ScalarRval)                       \
-  MACRO(CTRunGetGlyphCount, RR_ScalarRval)                       \
-  MACRO(CTRunGetGlyphsPtr, RR_CTRunGetElements<CGGlyph, CTRunGetGlyphs>) \
-  MACRO(CTRunGetPositionsPtr, RR_CTRunGetElements<CGPoint, CTRunGetPositions>) \
-  MACRO(CTRunGetStringIndicesPtr, RR_CTRunGetElements<CFIndex, CTRunGetStringIndices>) \
-  MACRO(CTRunGetStringRange, RR_ComplexScalarRval)               \
+  MACRO(CTLineCreateWithAttributedString, RR_ScalarRval, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CreateCFTypeRval>) \
+  MACRO(CTLineGetGlyphRuns, RR_ScalarRval, nullptr,              \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeRval>) \
+  MACRO(CTRunGetAttributes, RR_ScalarRval, nullptr,              \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeRval>) \
+  MACRO(CTRunGetGlyphCount, RR_ScalarRval, nullptr, Middleman_CFTypeArg<0>) \
+  MACRO(CTRunGetGlyphsPtr, RR_CTRunGetElements<CGGlyph, CTRunGetGlyphs>, nullptr, \
+        Middleman_CTRunGetElements<CGGlyph, CTRunGetGlyphs>)     \
+  MACRO(CTRunGetPositionsPtr, RR_CTRunGetElements<CGPoint, CTRunGetPositions>, nullptr, \
+        Middleman_CTRunGetElements<CGPoint, CTRunGetPositions>)  \
+  MACRO(CTRunGetStringIndicesPtr, RR_CTRunGetElements<CFIndex, CTRunGetStringIndices>, nullptr, \
+        Middleman_CTRunGetElements<CFIndex, CTRunGetStringIndices>) \
+  MACRO(CTRunGetStringRange, RR_ComplexScalarRval, nullptr, Middleman_CFTypeArg<0>) \
   /* Argument indexes are off by one here as the CFRange argument uses two slots. */ \
-  MACRO(CTRunGetTypographicBounds, RR_Compose<                   \
-                                     RR_FloatRval,               \
-                                     RR_WriteOptionalBufferFixedSize<3, sizeof(CGFloat)>, \
-                                     RR_WriteOptionalBufferFixedSize<4, sizeof(CGFloat)>, \
-                                     RR_WriteOptionalBufferFixedSize<5, sizeof(CGFloat)>>) \
-  MACRO(CUIDraw)                                                 \
+  MACRO(CTRunGetTypographicBounds,                               \
+        RR_Compose<RR_FloatRval,                                 \
+                   RR_WriteOptionalBufferFixedSize<3, sizeof(CGFloat)>, \
+                   RR_WriteOptionalBufferFixedSize<4, sizeof(CGFloat)>, \
+                   RR_WriteOptionalBufferFixedSize<5, sizeof(CGFloat)>>, nullptr, \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_WriteBufferFixedSize<3, sizeof(CGFloat)>, \
+                          Middleman_WriteBufferFixedSize<4, sizeof(CGFloat)>, \
+                          Middleman_WriteBufferFixedSize<5, sizeof(CGFloat)>>) \
+  MACRO(CUIDraw, nullptr, nullptr,                               \
+        Middleman_Compose<Middleman_CFTypeArg<0>,                \
+                          Middleman_CFTypeArg<1>,                \
+                          Middleman_CFTypeArg<2>,                \
+                          Middleman_StackArgumentData<sizeof(CGRect)>>) \
   MACRO(FSCompareFSRefs, RR_ScalarRval)                          \
   MACRO(FSGetVolumeInfo, RR_Compose<                             \
                            RR_ScalarRval,                        \
                            RR_WriteBufferFixedSize<5, sizeof(HFSUniStr255)>, \
                            RR_WriteBufferFixedSize<6, sizeof(FSRef)>>) \
   MACRO(FSFindFolder, RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<3, sizeof(FSRef)>>) \
   MACRO(Gestalt, RR_Compose<RR_ScalarRval, RR_WriteBufferFixedSize<1, sizeof(SInt32)>>) \
   MACRO(GetEventClass, RR_ScalarRval)                            \
@@ -464,21 +570,22 @@ namespace recordreplay {
   MACRO(LSCopyItemAttribute,                                     \
         RR_Compose<RR_ScalarRval, RR_WriteOptionalBufferFixedSize<3, sizeof(CFTypeRef)>>) \
   MACRO(LSCopyKindStringForMIMEType,                             \
         RR_Compose<RR_ScalarRval, RR_WriteOptionalBufferFixedSize<1, sizeof(CFStringRef)>>) \
   MACRO(LSGetApplicationForInfo, RR_Compose<                     \
                                    RR_ScalarRval,                \
                                    RR_WriteOptionalBufferFixedSize<4, sizeof(FSRef)>, \
                                    RR_WriteOptionalBufferFixedSize<5, sizeof(CFURLRef)>>) \
-  MACRO(LSGetApplicationForURL, RR_Compose<                     \
+  MACRO(LSGetApplicationForURL, RR_Compose<                      \
                                    RR_ScalarRval,                \
                                    RR_WriteOptionalBufferFixedSize<2, sizeof(FSRef)>, \
                                    RR_WriteOptionalBufferFixedSize<3, sizeof(CFURLRef)>>) \
-  MACRO(NSClassFromString, RR_ScalarRval)                        \
+  MACRO(NSClassFromString, RR_ScalarRval, nullptr,               \
+        Middleman_Compose<Middleman_CFTypeArg<0>, Middleman_CFTypeRval>) \
   MACRO(NSRectFill)                                              \
   MACRO(NSSearchPathForDirectoriesInDomains, RR_ScalarRval)      \
   MACRO(NSSetFocusRingStyle, RR_ScalarRval)                      \
   MACRO(NSTemporaryDirectory, RR_ScalarRval)                     \
   MACRO(OSSpinLockLock, nullptr, Preamble_OSSpinLockLock)        \
   MACRO(ReleaseEvent, RR_ScalarRval)                             \
   MACRO(RemoveEventFromQueue, RR_ScalarRval)                     \
   MACRO(RetainEvent, RR_ScalarRval)                              \
@@ -574,16 +681,213 @@ ReplayInvokeCallback(size_t aCallbackId)
     CGPathApplierFunctionWrapper(nullptr, nullptr);
     break;
   default:
     MOZ_CRASH();
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Middleman Call Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+static bool
+TestObjCObjectClass(id aObj, const char* aName)
+{
+  Class cls = object_getClass(aObj);
+  while (cls) {
+    const char* className = class_getName(cls);
+    if (!strcmp(className, aName)) {
+      return true;
+    }
+    cls = class_getSuperclass(cls);
+  }
+  return false;
+}
+
+// Inputs that originate from static data in the replaying process itself
+// rather than from previous middleman calls.
+enum class ObjCInputKind {
+  StaticClass,
+  ConstantString,
+};
+
+// Capture an Objective C or CoreFoundation input to a call, which may come
+// either from another middleman call, or from static data in the replaying
+// process.
+static void
+Middleman_ObjCInput(MiddlemanCallContext& aCx, id* aThingPtr)
+{
+  MOZ_RELEASE_ASSERT(aCx.AccessPreface());
+
+  if (Middleman_SystemInput(aCx, (const void**) aThingPtr)) {
+    // This value came from a previous middleman call.
+    return;
+  }
+
+  MOZ_RELEASE_ASSERT(aCx.AccessInput());
+
+  if (aCx.mPhase == MiddlemanCallPhase::ReplayInput) {
+    // Try to determine where this object came from.
+
+    // List of the Objective C classes which messages might be sent to directly.
+    static const char* gStaticClasses[] = {
+      "NSAutoreleasePool",
+      "NSColor",
+      "NSDictionary",
+      "NSFont",
+      "NSFontManager",
+      "NSNumber",
+      "NSString",
+      "NSWindow",
+    };
+
+    // Watch for messages sent to particular classes.
+    for (const char* className : gStaticClasses) {
+      Class cls = objc_lookUpClass(className);
+      if (cls == (Class) *aThingPtr) {
+        aCx.WriteInputScalar((size_t) ObjCInputKind::StaticClass);
+        size_t len = strlen(className) + 1;
+        aCx.WriteInputScalar(len);
+        aCx.WriteInputBytes(className, len);
+        return;
+      }
+    }
+
+    // Watch for constant compile time strings baked into the generated code or
+    // stored in system libraries. We can crash here if the object came from
+    // e.g. a replayed pointer from the recording, as can happen if not enough
+    // redirections have middleman call hooks. We could do better here to make
+    // sure the pointer looks like it could be a constant string, but it seems
+    // better and simpler to crash more reliably here than mask problems due to
+    // missing middleman call hooks.
+    if (TestObjCObjectClass(*aThingPtr, "NSString")) {
+      AutoPassThroughThreadEvents pt;
+      CFIndex len = CFStringGetLength((CFStringRef)*aThingPtr);
+      InfallibleVector<UniChar> buffer;
+      buffer.appendN(0, len);
+      CFStringGetCharacters((CFStringRef)*aThingPtr, { 0, len }, buffer.begin());
+      aCx.WriteInputScalar((size_t) ObjCInputKind::ConstantString);
+      aCx.WriteInputScalar(len);
+      aCx.WriteInputBytes(buffer.begin(), len * sizeof(UniChar));
+      return;
+    }
+
+    aCx.MarkAsFailed();
+    return;
+  }
+
+  switch ((ObjCInputKind) aCx.ReadInputScalar()) {
+  case ObjCInputKind::StaticClass: {
+    size_t len = aCx.ReadInputScalar();
+    UniquePtr<char[]> className(new char[len]);
+    aCx.ReadInputBytes(className.get(), len);
+    *aThingPtr = (id) objc_lookUpClass(className.get());
+    break;
+  }
+  case ObjCInputKind::ConstantString: {
+    size_t len = aCx.ReadInputScalar();
+    UniquePtr<UniChar[]> contents(new UniChar[len]);
+    aCx.ReadInputBytes(contents.get(), len * sizeof(UniChar));
+    *aThingPtr = (id) CFStringCreateWithCharacters(kCFAllocatorDefault, contents.get(), len);
+    break;
+  }
+  default:
+    MOZ_CRASH();
+  }
+}
+
+template <size_t Argument>
+static void
+Middleman_CFTypeArg(MiddlemanCallContext& aCx)
+{
+  if (aCx.AccessPreface()) {
+    auto& object = aCx.mArguments->Arg<Argument, id>();
+    Middleman_ObjCInput(aCx, &object);
+  }
+}
+
+static void
+Middleman_CFTypeOutput(MiddlemanCallContext& aCx, CFTypeRef* aOutput, bool aOwnsReference)
+{
+  Middleman_SystemOutput(aCx, (const void**) aOutput);
+
+  if (*aOutput) {
+    switch (aCx.mPhase) {
+    case MiddlemanCallPhase::MiddlemanOutput:
+      if (!aOwnsReference) {
+        CFRetain(*aOutput);
+      }
+      break;
+    case MiddlemanCallPhase::MiddlemanRelease:
+      CFRelease(*aOutput);
+      break;
+    default:
+      break;
+    }
+  }
+}
+
+// For APIs using the 'Get' rule: no reference is held on the returned value.
+static void
+Middleman_CFTypeRval(MiddlemanCallContext& aCx)
+{
+  auto& rval = aCx.mArguments->Rval<CFTypeRef>();
+  Middleman_CFTypeOutput(aCx, &rval, /* aOwnsReference = */ false);
+}
+
+// For APIs using the 'Create' rule: a reference is held on the returned
+// value which must be released.
+static void
+Middleman_CreateCFTypeRval(MiddlemanCallContext& aCx)
+{
+  auto& rval = aCx.mArguments->Rval<CFTypeRef>();
+  Middleman_CFTypeOutput(aCx, &rval, /* aOwnsReference = */ true);
+}
+
+template <size_t Argument>
+static void
+Middleman_CFTypeOutputArg(MiddlemanCallContext& aCx)
+{
+  Middleman_WriteBufferFixedSize<Argument, sizeof(const void*)>(aCx);
+
+  auto arg = aCx.mArguments->Arg<Argument, const void**>();
+  Middleman_CFTypeOutput(aCx, arg, /* aOwnsReference = */ false);
+}
+
+// For APIs whose result will be released by the middleman's autorelease pool.
+static void
+Middleman_AutoreleaseCFTypeRval(MiddlemanCallContext& aCx)
+{
+  auto& rval = aCx.mArguments->Rval<const void*>();
+  Middleman_SystemOutput(aCx, &rval);
+}
+
+// For functions which have an input CFType value and also have side effects on
+// that value, this associates the call with its own input value so that this
+// will be treated as a dependent for any future calls using the value.
+template <size_t Argument>
+static void
+Middleman_UpdateCFTypeArg(MiddlemanCallContext& aCx)
+{
+  auto arg = aCx.mArguments->Arg<Argument, const void*>();
+
+  Middleman_CFTypeArg<Argument>(aCx);
+  Middleman_SystemOutput(aCx, &arg, /* aUpdating = */ true);
+}
+
+static PreambleResult
+Preamble_SetError(CallArguments* aArguments)
+{
+  aArguments->Rval<ssize_t>() = -1;
+  errno = EAGAIN;
+  return PreambleResult::Veto;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // system call redirections
 ///////////////////////////////////////////////////////////////////////////////
 
 static void
 RR_recvmsg(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
 {
   auto& msg = aArguments->Arg<1, struct msghdr*>();
 
@@ -697,23 +1001,23 @@ RR_getsockopt(Stream& aEvents, CallArgum
   int initLen = *optlen;
   aEvents.RecordOrReplayValue(optlen);
   MOZ_RELEASE_ASSERT(*optlen <= initLen);
 
   aEvents.RecordOrReplayBytes(optval, *optlen);
 }
 
 static PreambleResult
-Preamble_gettimeofday(CallArguments* aArguments)
+Preamble_getpid(CallArguments* aArguments)
 {
-  // If we have diverged from the recording, just get the actual current time
-  // rather than causing the current debugger operation to fail. This function
-  // is frequently called via e.g. JS natives which the debugger will execute.
-  if (HasDivergedFromRecording()) {
-    return PreambleResult::PassThrough;
+  if (!AreThreadEventsPassedThrough()) {
+    // Don't involve the recording with getpid calls, so that this can be used
+    // after diverging from the recording.
+    aArguments->Rval<size_t>() = GetRecordingPid();
+    return PreambleResult::Veto;
   }
   return PreambleResult::Redirect;
 }
 
 static PreambleResult
 Preamble_fcntl(CallArguments* aArguments)
 {
   // Make sure fcntl is only used with a limited set of commands.
@@ -1150,36 +1454,16 @@ Preamble_PL_HashTableDestroy(CallArgumen
   DestroyPLHashTableCallbacks(priv);
   return PreambleResult::Veto;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Objective C redirections
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool
-TestObjCObjectClass(id aObj, const char* aName)
-{
-  // When recording we can test to see what the class of an object is, but we
-  // have to record the result of the test because the object "pointers" we
-  // have when replaying are not valid.
-  bool found = false;
-  if (IsRecording()) {
-    Class cls = object_getClass(aObj);
-    while (cls) {
-      if (!strcmp(class_getName(cls), aName)) {
-        found = true;
-        break;
-      }
-      cls = class_getSuperclass(cls);
-    }
-  }
-  return RecordReplayValue(found);
-}
-
 // From Foundation.h, which has lots of Objective C declarations and can't be
 // included here :(
 struct NSFastEnumerationState
 {
   unsigned long state;
   id* itemsPtr;
   unsigned long* mutationsPtr;
   unsigned long extra[5];
@@ -1187,112 +1471,363 @@ struct NSFastEnumerationState
 
 // Emulation of NSFastEnumeration on arrays does not replay any exceptions
 // thrown by mutating the array while it is being iterated over.
 static unsigned long gNeverChange;
 
 static PreambleResult
 Preamble_objc_msgSend(CallArguments* aArguments)
 {
-  Thread* thread = Thread::Current();
-  if (!thread || thread->PassThroughEvents()) {
-    return PreambleResult::Redirect;
+  if (!AreThreadEventsPassedThrough()) {
+    auto message = aArguments->Arg<1, const char*>();
+
+    // Watch for some top level NSApplication messages that can cause Gecko
+    // events to be processed.
+    if (!strcmp(message, "run") ||
+        !strcmp(message, "nextEventMatchingMask:untilDate:inMode:dequeue:"))
+    {
+      PassThroughThreadEventsAllowCallbacks([&]() {
+          RecordReplayInvokeCall(CallEvent_objc_msgSend, aArguments);
+        });
+      RecordReplayBytes(&aArguments->Rval<size_t>(), sizeof(size_t));
+      return PreambleResult::Veto;
+    }
   }
-  EnsureNotDivergedFromRecording();
+  return PreambleResult::Redirect;
+}
 
+static void
+RR_objc_msgSend(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
+{
   auto& object = aArguments->Arg<0, id>();
   auto& message = aArguments->Arg<1, const char*>();
 
-  thread->Events().RecordOrReplayThreadEvent(CallIdToThreadEvent(CallEvent_objc_msgSend));
-  thread->Events().CheckInput(message);
-
-  bool handled = false;
+  aEvents.CheckInput(message);
 
-  // Watch for some top level NSApplication messages that can cause Gecko
-  // events to be processed.
-  if ((!strcmp(message, "run") ||
-       !strcmp(message, "nextEventMatchingMask:untilDate:inMode:dequeue:")) &&
-      TestObjCObjectClass(object, "NSApplication"))
-  {
-    PassThroughThreadEventsAllowCallbacks([&]() {
-        RecordReplayInvokeCall(CallEvent_objc_msgSend, aArguments);
-      });
-    handled = true;
-  }
-
-  // Other messages are performed as normal.
-  if (!handled && IsRecording()) {
-    AutoPassThroughThreadEvents pt;
-    RecordReplayInvokeCall(CallEvent_objc_msgSend, aArguments);
-  }
-
-  RecordReplayBytes(&aArguments->Rval<size_t>(), sizeof(size_t));
-  RecordReplayBytes(&aArguments->FloatRval(), sizeof(double));
+  aEvents.RecordOrReplayValue(&aArguments->Rval<size_t>());
+  aEvents.RecordOrReplayBytes(&aArguments->FloatRval(), sizeof(double));
 
   // Do some extra recording on messages that return additional state.
 
-  if (!strcmp(message, "countByEnumeratingWithState:objects:count:") &&
-      TestObjCObjectClass(object, "NSArray"))
-  {
+  if (!strcmp(message, "countByEnumeratingWithState:objects:count:")) {
     auto& state = aArguments->Arg<2, NSFastEnumerationState*>();
     auto& rval = aArguments->Rval<size_t>();
     if (IsReplaying()) {
       state->itemsPtr = NewLeakyArray<id>(rval);
       state->mutationsPtr = &gNeverChange;
     }
-    RecordReplayBytes(state->itemsPtr, rval * sizeof(id));
+    aEvents.RecordOrReplayBytes(state->itemsPtr, rval * sizeof(id));
   }
 
-  if (!strcmp(message, "getCharacters:") &&
-      TestObjCObjectClass(object, "NSString"))
-  {
+  if (!strcmp(message, "getCharacters:")) {
     size_t len = 0;
     if (IsRecording()) {
       AutoPassThroughThreadEvents pt;
       len = CFStringGetLength((CFStringRef) object);
     }
-    len = RecordReplayValue(len);
-    RecordReplayBytes(aArguments->Arg<2, void*>(), len * sizeof(wchar_t));
+    aEvents.RecordOrReplayValue(&len);
+    aEvents.RecordOrReplayBytes(aArguments->Arg<2, void*>(), len * sizeof(wchar_t));
   }
 
-  if ((!strcmp(message, "UTF8String") ||
-       !strcmp(message, "cStringUsingEncoding:")) &&
-      TestObjCObjectClass(object, "NSString"))
-  {
+  if (!strcmp(message, "UTF8String") || !strcmp(message, "cStringUsingEncoding:")) {
     auto& rval = aArguments->Rval<char*>();
-    size_t len = RecordReplayValue(IsRecording() ? strlen(rval) : 0);
+    size_t len = IsRecording() ? strlen(rval) : 0;
+    aEvents.RecordOrReplayValue(&len);
     if (IsReplaying()) {
       rval = NewLeakyArray<char>(len + 1);
     }
-    RecordReplayBytes(rval, len + 1);
+    aEvents.RecordOrReplayBytes(rval, len + 1);
+  }
+}
+
+static PreambleResult
+MiddlemanPreamble_objc_msgSend(CallArguments* aArguments)
+{
+  auto message = aArguments->Arg<1, const char*>();
+
+  // Ignore uses of NSAutoreleasePool after diverging from the recording.
+  // These are not performed in the middleman because the middleman has its
+  // own autorelease pool, and because the middleman can process calls from
+  // multiple threads which will cause these messages to behave differently.
+  if (!strcmp(message, "alloc") ||
+      !strcmp(message, "drain") ||
+      !strcmp(message, "init") ||
+      !strcmp(message, "release")) {
+    // Fake a return value in case the caller null checks it.
+    aArguments->Rval<size_t>() = 1;
+    return PreambleResult::Veto;
+  }
+
+  // Other messages will be handled by Middleman_objc_msgSend.
+  return PreambleResult::Redirect;
+}
+
+static void
+Middleman_PerformSelector(MiddlemanCallContext& aCx)
+{
+  Middleman_CString<2>(aCx);
+  Middleman_CFTypeArg<3>(aCx);
+
+  // The behavior of performSelector:withObject: varies depending on the
+  // selector used, so use a whitelist here.
+  if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) {
+    auto str = aCx.mArguments->Arg<2, const char*>();
+    if (strcmp(str, "appearanceNamed:")) {
+      aCx.MarkAsFailed();
+      return;
+    }
+  }
+
+  Middleman_AutoreleaseCFTypeRval(aCx);
+}
+
+static void
+Middleman_DictionaryWithObjects(MiddlemanCallContext& aCx)
+{
+  Middleman_Buffer<2, 4, const void*>(aCx);
+  Middleman_Buffer<3, 4, const void*>(aCx);
+
+  if (aCx.AccessPreface()) {
+    auto objects = aCx.mArguments->Arg<2, const void**>();
+    auto keys = aCx.mArguments->Arg<3, const void**>();
+    auto count = aCx.mArguments->Arg<4, CFIndex>();
+
+    for (CFIndex i = 0; i < count; i++) {
+      Middleman_ObjCInput(aCx, (id*) &objects[i]);
+      Middleman_ObjCInput(aCx, (id*) &keys[i]);
+    }
+  }
+
+  Middleman_AutoreleaseCFTypeRval(aCx);
+}
+
+static void
+Middleman_NSStringGetCharacters(MiddlemanCallContext& aCx)
+{
+  auto string = aCx.mArguments->Arg<0, CFStringRef>();
+  auto& buffer = aCx.mArguments->Arg<2, void*>();
+
+  if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
+    size_t len = CFStringGetLength(string);
+    buffer = aCx.AllocateBytes(len * sizeof(UniChar));
   }
 
-  return PreambleResult::Veto;
+  if (aCx.AccessOutput()) {
+    size_t len =
+      (aCx.mPhase == MiddlemanCallPhase::MiddlemanOutput) ? CFStringGetLength(string) : 0;
+    aCx.ReadOrWriteOutputBytes(&len, sizeof(len));
+    if (aCx.mReplayOutputIsOld) {
+      buffer = aCx.AllocateBytes(len * sizeof(UniChar));
+    }
+    aCx.ReadOrWriteOutputBytes(buffer, len * sizeof(UniChar));
+  }
+}
+
+struct ObjCMessageInfo
+{
+  const char* mMessage;
+  MiddlemanCallFn mMiddlemanCall;
+};
+
+// All Objective C messages that can be called in the middleman, and hooks for
+// capturing any inputs and outputs other than the object and message.
+static ObjCMessageInfo gObjCMiddlemanCallMessages[] = {
+  { "performSelector:withObject:", Middleman_PerformSelector },
+  { "respondsToSelector:", Middleman_CString<2> },
+
+  // NSArray
+  { "count" },
+  { "objectAtIndex:", Middleman_AutoreleaseCFTypeRval },
+
+  // NSColor
+  { "currentControlTint" },
+
+  // NSDictionary
+  { "dictionaryWithObjects:forKeys:count:", Middleman_DictionaryWithObjects },
+
+  // NSFont
+  { "boldSystemFontOfSize:", Middleman_AutoreleaseCFTypeRval },
+  { "controlContentFontOfSize:", Middleman_AutoreleaseCFTypeRval },
+  { "familyName", Middleman_AutoreleaseCFTypeRval },
+  { "fontDescriptor", Middleman_AutoreleaseCFTypeRval },
+  { "menuBarFontOfSize:", Middleman_AutoreleaseCFTypeRval },
+  { "pointSize" },
+  { "smallSystemFontSize" },
+  { "systemFontOfSize:", Middleman_AutoreleaseCFTypeRval },
+  { "toolTipsFontOfSize:", Middleman_AutoreleaseCFTypeRval },
+  { "userFontOfSize:", Middleman_AutoreleaseCFTypeRval },
+
+  // NSFontManager
+  { "availableMembersOfFontFamily:", Middleman_Compose<Middleman_CFTypeArg<2>, Middleman_AutoreleaseCFTypeRval> },
+  { "sharedFontManager", Middleman_AutoreleaseCFTypeRval },
+
+  // NSNumber
+  { "numberWithBool:", Middleman_AutoreleaseCFTypeRval },
+  { "unsignedIntValue" },
+
+  // NSString
+  { "getCharacters:", Middleman_NSStringGetCharacters },
+  { "hasSuffix:", Middleman_CFTypeArg<2> },
+  { "isEqualToString:", Middleman_CFTypeArg<2> },
+  { "length" },
+  { "rangeOfString:options:", Middleman_CFTypeArg<2> },
+  { "stringWithCharacters:length:",
+    Middleman_Compose<Middleman_Buffer<2, 3, UniChar>, Middleman_AutoreleaseCFTypeRval> },
+
+  // NSWindow
+  { "coreUIRenderer", Middleman_AutoreleaseCFTypeRval },
+
+  // UIFontDescriptor
+  { "symbolicTraits" },
+};
+
+static void
+Middleman_objc_msgSend(MiddlemanCallContext& aCx)
+{
+  auto& object = aCx.mArguments->Arg<0, id>();
+  auto message = aCx.mArguments->Arg<1, const char*>();
+
+  for (const ObjCMessageInfo& info : gObjCMiddlemanCallMessages) {
+    if (!strcmp(info.mMessage, message)) {
+      if (aCx.AccessPreface()) {
+        Middleman_ObjCInput(aCx, &object);
+      }
+      if (info.mMiddlemanCall && !aCx.mFailed) {
+        info.mMiddlemanCall(aCx);
+      }
+      return;
+    }
+  }
+
+  if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) {
+    aCx.MarkAsFailed();
+  }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Cocoa redirections
 ///////////////////////////////////////////////////////////////////////////////
 
 static void
+Middleman_CFArrayCreate(MiddlemanCallContext& aCx)
+{
+  Middleman_Buffer<1, 2, const void*>(aCx);
+
+  if (aCx.AccessPreface()) {
+    auto values = aCx.mArguments->Arg<1, const void**>();
+    auto numValues = aCx.mArguments->Arg<2, CFIndex>();
+    auto& callbacks = aCx.mArguments->Arg<3, const CFArrayCallBacks*>();
+
+    // For now we only support creating arrays with CFType elements.
+    if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
+      callbacks = &kCFTypeArrayCallBacks;
+    } else {
+      MOZ_RELEASE_ASSERT(callbacks == &kCFTypeArrayCallBacks);
+    }
+
+    for (CFIndex i = 0; i < numValues; i++) {
+      Middleman_ObjCInput(aCx, (id*) &values[i]);
+    }
+  }
+
+  Middleman_CreateCFTypeRval(aCx);
+}
+
+static void
+Middleman_CFArrayGetValueAtIndex(MiddlemanCallContext& aCx)
+{
+  Middleman_CFTypeArg<0>(aCx);
+
+  auto array = aCx.mArguments->Arg<0, id>();
+
+  // We can't probe the array to see what callbacks it uses, so look at where
+  // it came from to see whether its elements should be treated as CFTypes.
+  MiddlemanCall* call = LookupMiddlemanCall(array);
+  bool isCFTypeRval = false;
+  if (call) {
+    switch (call->mCallId) {
+    case CallEvent_CTLineGetGlyphRuns:
+    case CallEvent_CTFontDescriptorCreateMatchingFontDescriptors:
+      isCFTypeRval = true;
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (isCFTypeRval) {
+    Middleman_CFTypeRval(aCx);
+  }
+}
+
+static void
 RR_CFDataGetBytePtr(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
 {
   auto& rval = aArguments->Rval<UInt8*>();
 
   size_t len = 0;
   if (IsRecording()) {
     len = OriginalCall(CFDataGetLength, size_t, aArguments->Arg<0, CFDataRef>());
   }
   aEvents.RecordOrReplayValue(&len);
   if (IsReplaying()) {
     rval = NewLeakyArray<UInt8>(len);
   }
   aEvents.RecordOrReplayBytes(rval, len);
 }
 
+static void
+Middleman_CFDataGetBytePtr(MiddlemanCallContext& aCx)
+{
+  Middleman_CFTypeArg<0>(aCx);
+
+  auto data = aCx.mArguments->Arg<0, CFDataRef>();
+  auto& buffer = aCx.mArguments->Rval<void*>();
+
+  if (aCx.AccessOutput()) {
+    size_t len = (aCx.mPhase == MiddlemanCallPhase::MiddlemanOutput) ? CFDataGetLength(data) : 0;
+    aCx.ReadOrWriteOutputBytes(&len, sizeof(len));
+    if (aCx.mPhase == MiddlemanCallPhase::ReplayOutput) {
+      buffer = aCx.AllocateBytes(len);
+    }
+    aCx.ReadOrWriteOutputBytes(buffer, len);
+  }
+}
+
+static void
+Middleman_CFDictionaryCreate(MiddlemanCallContext& aCx)
+{
+  Middleman_Buffer<1, 3, const void*>(aCx);
+  Middleman_Buffer<2, 3, const void*>(aCx);
+
+  if (aCx.AccessPreface()) {
+    auto keys = aCx.mArguments->Arg<1, const void**>();
+    auto values = aCx.mArguments->Arg<2, const void**>();
+    auto numValues = aCx.mArguments->Arg<3, CFIndex>();
+    auto& keyCallbacks = aCx.mArguments->Arg<4, const CFDictionaryKeyCallBacks*>();
+    auto& valueCallbacks = aCx.mArguments->Arg<5, const CFDictionaryValueCallBacks*>();
+
+    // For now we only support creating dictionaries with CFType keys and values.
+    if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
+      keyCallbacks = &kCFTypeDictionaryKeyCallBacks;
+      valueCallbacks = &kCFTypeDictionaryValueCallBacks;
+    } else {
+      MOZ_RELEASE_ASSERT(keyCallbacks == &kCFTypeDictionaryKeyCallBacks);
+      MOZ_RELEASE_ASSERT(valueCallbacks == &kCFTypeDictionaryValueCallBacks);
+    }
+
+    for (CFIndex i = 0; i < numValues; i++) {
+      Middleman_ObjCInput(aCx, (id*) &keys[i]);
+      Middleman_ObjCInput(aCx, (id*) &values[i]);
+    }
+  }
+
+  Middleman_CreateCFTypeRval(aCx);
+}
+
 static void DummyCFNotificationCallback(CFNotificationCenterRef aCenter, void* aObserver,
                                         CFStringRef aName, const void* aObject,
                                         CFDictionaryRef aUserInfo)
 {
   // FIXME
   //MOZ_CRASH();
 }
 
@@ -1326,25 +1861,54 @@ CFNumberTypeBytes(CFNumberType aType)
   case kCFNumberCFIndexType: return sizeof(CFIndex);
   case kCFNumberNSIntegerType: return sizeof(long);
   case kCFNumberCGFloatType: return sizeof(CGFloat);
   default: MOZ_CRASH();
   }
 }
 
 static void
+Middleman_CFNumberCreate(MiddlemanCallContext& aCx)
+{
+  if (aCx.AccessPreface()) {
+    auto numberType = aCx.mArguments->Arg<1, CFNumberType>();
+    auto& valuePtr = aCx.mArguments->Arg<2, void*>();
+    aCx.ReadOrWritePrefaceBuffer(&valuePtr, CFNumberTypeBytes(numberType));
+  }
+
+  Middleman_CreateCFTypeRval(aCx);
+}
+
+static void
 RR_CFNumberGetValue(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
 {
   auto& type = aArguments->Arg<1, CFNumberType>();
   auto& value = aArguments->Arg<2, void*>();
 
   aEvents.CheckInput(type);
   aEvents.RecordOrReplayBytes(value, CFNumberTypeBytes(type));
 }
 
+static void
+Middleman_CFNumberGetValue(MiddlemanCallContext& aCx)
+{
+  Middleman_CFTypeArg<0>(aCx);
+
+  auto& buffer = aCx.mArguments->Arg<2, void*>();
+  auto type = aCx.mArguments->Arg<1, CFNumberType>();
+  aCx.ReadOrWriteOutputBuffer(&buffer, CFNumberTypeBytes(type));
+}
+
+static PreambleResult
+MiddlemanPreamble_CFRetain(CallArguments* aArguments)
+{
+  aArguments->Rval<size_t>() = aArguments->Arg<0, size_t>();
+  return PreambleResult::Veto;
+}
+
 static PreambleResult
 Preamble_CFRunLoopSourceCreate(CallArguments* aArguments)
 {
   if (!AreThreadEventsPassedThrough()) {
     auto& cx = aArguments->Arg<2, CFRunLoopSourceContext*>();
 
     RegisterCallbackData(BitwiseCast<void*>(cx->perform));
     RegisterCallbackData(cx->info);
@@ -1375,32 +1939,91 @@ static void
 RR_CGBitmapContextCreateWithData(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
 {
   auto& data = aArguments->Arg<0, void*>();
   auto& height = aArguments->Arg<2, size_t>();
   auto& bytesPerRow = aArguments->Arg<4, size_t>();
   auto& rval = aArguments->Rval<CGContextRef>();
 
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
-  gContextData.emplaceBack(rval, data, height * bytesPerRow);
+
+  // When replaying, Middleman_CGBitmapContextCreateWithData will take care of
+  // updating gContextData with the right context pointer (after being mangled
+  // in Middleman_SystemOutput).
+  if (IsRecording()) {
+    gContextData.emplaceBack(rval, data, height * bytesPerRow);
+  }
+}
+
+static void
+Middleman_CGBitmapContextCreateWithData(MiddlemanCallContext& aCx)
+{
+  Middleman_CFTypeArg<5>(aCx);
+  Middleman_StackArgumentData<3 * sizeof(size_t)>(aCx);
+  Middleman_CreateCFTypeRval(aCx);
+
+  auto& data = aCx.mArguments->Arg<0, void*>();
+  auto height = aCx.mArguments->Arg<2, size_t>();
+  auto bytesPerRow = aCx.mArguments->Arg<4, size_t>();
+  auto rval = aCx.mArguments->Rval<CGContextRef>();
+
+  if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
+    data = aCx.AllocateBytes(height * bytesPerRow);
+  }
+
+  if ((aCx.mPhase == MiddlemanCallPhase::ReplayPreface && !HasDivergedFromRecording()) ||
+      (aCx.AccessOutput() && !aCx.mReplayOutputIsOld)) {
+    gContextData.emplaceBack(rval, data, height * bytesPerRow);
+  }
 }
 
 template <size_t ContextArgument>
 static void
 RR_FlushCGContext(Stream& aEvents, CallArguments* aArguments, ErrorType* aError)
 {
   auto& context = aArguments->Arg<ContextArgument, CGContextRef>();
 
   for (int i = gContextData.length() - 1; i >= 0; i--) {
     if (context == gContextData[i].mContext) {
-      RecordReplayBytes(gContextData[i].mData, gContextData[i].mDataSize);
+      aEvents.RecordOrReplayBytes(gContextData[i].mData, gContextData[i].mDataSize);
       return;
     }
   }
-  MOZ_CRASH();
+  MOZ_CRASH("RR_FlushCGContext");
+}
+
+template <size_t ContextArgument>
+static void
+Middleman_FlushCGContext(MiddlemanCallContext& aCx)
+{
+  auto context = aCx.mArguments->Arg<ContextArgument, CGContextRef>();
+
+  // Update the contents of the target buffer in the middleman process to match
+  // the current contents in the replaying process.
+  if (aCx.AccessInput()) {
+    for (int i = gContextData.length() - 1; i >= 0; i--) {
+      if (context == gContextData[i].mContext) {
+        aCx.ReadOrWriteInputBytes(gContextData[i].mData, gContextData[i].mDataSize);
+        return;
+      }
+    }
+    MOZ_CRASH("Middleman_FlushCGContext");
+  }
+
+  // After performing the call, the buffer in the replaying process is updated
+  // to match any data written by the middleman.
+  if (aCx.AccessOutput()) {
+    for (int i = gContextData.length() - 1; i >= 0; i--) {
+      if (context == gContextData[i].mContext) {
+        aCx.ReadOrWriteOutputBytes(gContextData[i].mData, gContextData[i].mDataSize);
+        return;
+      }
+    }
+    MOZ_CRASH("Middleman_FlushCGContext");
+  }
 }
 
 static PreambleResult
 Preamble_CGContextRestoreGState(CallArguments* aArguments)
 {
   return IsRecording() ? PreambleResult::PassThrough : PreambleResult::Veto;
 }
 
@@ -1465,16 +2088,42 @@ RR_CTRunGetElements(Stream& aEvents, Cal
   }
   aEvents.RecordOrReplayValue(&count);
   if (IsReplaying()) {
     rval = NewLeakyArray<ElemType>(count);
   }
   aEvents.RecordOrReplayBytes(rval, count * sizeof(ElemType));
 }
 
+template <typename ElemType, void (*GetElemsFn)(CTRunRef, CFRange, ElemType*)>
+static void
+Middleman_CTRunGetElements(MiddlemanCallContext& aCx)
+{
+  Middleman_CFTypeArg<0>(aCx);
+
+  if (aCx.AccessOutput()) {
+    auto run = aCx.mArguments->Arg<0, CTRunRef>();
+    auto& rval = aCx.mArguments->Rval<ElemType*>();
+
+    size_t count = 0;
+    if (IsMiddleman()) {
+      count = CTRunGetGlyphCount(run);
+      if (!rval) {
+        rval = (ElemType*) aCx.AllocateBytes(count * sizeof(ElemType));
+        GetElemsFn(run, CFRangeMake(0, 0), rval);
+      }
+    }
+    aCx.ReadOrWriteOutputBytes(&count, sizeof(count));
+    if (IsReplaying()) {
+      rval = (ElemType*) aCx.AllocateBytes(count * sizeof(ElemType));
+    }
+    aCx.ReadOrWriteOutputBytes(rval, count * sizeof(ElemType));
+  }
+}
+
 static PreambleResult
 Preamble_OSSpinLockLock(CallArguments* aArguments)
 {
   auto& lock = aArguments->Arg<0, OSSpinLock*>();
 
   // These spin locks never need to be recorded, but they are used by malloc
   // and can end up calling other recorded functions like mach_absolute_time,
   // so make sure events are passed through here. Note that we don't have to
@@ -1540,25 +2189,16 @@ void
 DirectUnprotectMemory(void* aAddress, size_t aSize, bool aExecutable,
                       bool aIgnoreFailures /* = false */)
 {
   ssize_t rv = OriginalCall(mprotect, int, aAddress, aSize,
                             PROT_READ | PROT_WRITE | (aExecutable ? PROT_EXEC : 0));
   MOZ_RELEASE_ASSERT(aIgnoreFailures || rv == 0);
 }
 
-// From chromium/src/base/eintr_wrapper.h
-#define HANDLE_EINTR(x) ({ \
-  typeof(x) __eintr_result__; \
-  do { \
-    __eintr_result__ = x; \
-  } while (__eintr_result__ == -1 && errno == EINTR); \
-  __eintr_result__;\
-})
-
 void
 DirectSeekFile(FileHandle aFd, uint64_t aOffset)
 {
   static_assert(sizeof(uint64_t) == sizeof(off_t), "off_t should have 64 bits");
   ssize_t rv = HANDLE_EINTR(OriginalCall(lseek, int, aFd, aOffset, SEEK_SET));
   MOZ_RELEASE_ASSERT(rv >= 0);
 }
 
--- a/toolkit/recordreplay/ProcessRewind.cpp
+++ b/toolkit/recordreplay/ProcessRewind.cpp
@@ -3,16 +3,17 @@
 /* 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 "ProcessRewind.h"
 
 #include "nsString.h"
 #include "ipc/ChildInternal.h"
+#include "ipc/ParentInternal.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/StaticMutex.h"
 #include "InfallibleVector.h"
 #include "MemorySnapshot.h"
 #include "Monitor.h"
 #include "ProcessRecordReplay.h"
 #include "ThreadSnapshot.h"
 
@@ -120,16 +121,17 @@ SetSaveCheckpoint(size_t aCheckpoint, bo
   VectorAddOrRemoveEntry(gRewindInfo->mShouldSaveCheckpoints, aCheckpoint, aSave);
 }
 
 bool
 NewCheckpoint(bool aTemporary)
 {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+  MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
   MOZ_RELEASE_ASSERT(IsReplaying() || !aTemporary);
 
   navigation::BeforeCheckpoint();
 
   // Get the ID of the new checkpoint.
   CheckpointId checkpoint = gRewindInfo->mLastCheckpoint.NextCheckpoint(aTemporary);
 
   // Save all checkpoints the middleman tells us to, and temporary checkpoints
@@ -187,17 +189,23 @@ static bool gUnhandledDivergeAllowed;
 
 void
 DivergeFromRecording()
 {
   MOZ_RELEASE_ASSERT(IsReplaying());
 
   Thread* thread = Thread::Current();
   MOZ_RELEASE_ASSERT(thread->IsMainThread());
-  thread->DivergeFromRecording();
+
+  if (!thread->HasDivergedFromRecording()) {
+    // Reset middleman call state whenever we first diverge from the recording.
+    child::SendResetMiddlemanCalls();
+
+    thread->DivergeFromRecording();
+  }
 
   gUnhandledDivergeAllowed = true;
 }
 
 extern "C" {
 
 MOZ_EXPORT bool
 RecordReplayInterface_InternalHasDivergedFromRecording()
@@ -213,19 +221,28 @@ DisallowUnhandledDivergeFromRecording()
 {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   gUnhandledDivergeAllowed = false;
 }
 
 void
 EnsureNotDivergedFromRecording()
 {
+  // If we have diverged from the recording and encounter an operation we can't
+  // handle, rewind to the last checkpoint.
   AssertEventsAreNotPassedThrough();
   if (HasDivergedFromRecording()) {
     MOZ_RELEASE_ASSERT(gUnhandledDivergeAllowed);
+
+    // Crash instead of rewinding in the painting stress mode, for finding
+    // areas where middleman calls do not cover all painting logic.
+    if (parent::InRepaintStressMode()) {
+      MOZ_CRASH("Recording divergence in repaint stress mode");
+    }
+
     PrintSpew("Unhandled recording divergence, restoring checkpoint...\n");
     RestoreCheckpointAndResume(gRewindInfo->mSavedCheckpoints.back().mCheckpoint);
     Unreachable();
   }
 }
 
 bool
 HasSavedCheckpoint()
--- a/toolkit/recordreplay/Thread.cpp
+++ b/toolkit/recordreplay/Thread.cpp
@@ -229,20 +229,22 @@ Thread::SpawnThread(Thread* aThread)
 {
   DirectSpawnThread(ThreadMain, aThread);
   WaitUntilInitialized(aThread);
 }
 
 /* static */ NativeThreadId
 Thread::StartThread(Callback aStart, void* aArgument, bool aNeedsJoin)
 {
-  EnsureNotDivergedFromRecording();
-
   Thread* thread = Thread::Current();
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
+    EnsureNotDivergedFromRecording();
+    Unreachable();
+  }
 
   MonitorAutoLock lock(*gMonitor);
 
   size_t id = 0;
   if (IsRecording()) {
     // Look for an idle thread.
     for (id = MainThreadId + 1; id <= MaxRecordedThreadId; id++) {
       Thread* targetThread = Thread::GetById(id);
@@ -382,28 +384,37 @@ Thread::WaitForIdleThreads()
     GetById(i)->mUnrecordedWaitNotified = false;
   }
   while (true) {
     bool done = true;
     for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
       Thread* thread = GetById(i);
       if (!thread->mIdle) {
         done = false;
-        if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified) {
+
+        // Check if there is a callback we can invoke to get this thread to
+        // make progress. The mUnrecordedWaitOnlyWhenDiverged flag is used to
+        // avoid perturbing the behavior of threads that may or may not be
+        // waiting on an unrecorded resource, depending on whether they have
+        // diverged from the recording yet.
+        if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified &&
+            (!thread->mUnrecordedWaitOnlyWhenDiverged ||
+             thread->WillDivergeFromRecordingSoon())) {
           // Set this flag before releasing the idle lock. Otherwise it's
           // possible the thread could call NotifyUnrecordedWait while we
           // aren't holding the lock, and we would set the flag afterwards
           // without first invoking the callback.
           thread->mUnrecordedWaitNotified = true;
 
           // Release the idle lock here to avoid any risk of deadlock.
+          std::function<void()> callback = thread->mUnrecordedWaitCallback;
           {
             MonitorAutoUnlock unlock(*gMonitor);
             AutoPassThroughThreadEvents pt;
-            thread->mUnrecordedWaitCallback();
+            callback();
           }
 
           // Releasing the global lock means that we need to start over
           // checking whether there are any idle threads. By marking this
           // thread as having been notified we have made progress, however.
           done = true;
           i = MainThreadId;
         }
@@ -431,29 +442,30 @@ Thread::ResumeIdleThreads()
   gThreadsShouldIdle = false;
 
   for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
     Notify(i);
   }
 }
 
 void
-Thread::NotifyUnrecordedWait(const std::function<void()>& aCallback)
+Thread::NotifyUnrecordedWait(const std::function<void()>& aCallback, bool aOnlyWhenDiverged)
 {
   MonitorAutoLock lock(*gMonitor);
   if (mUnrecordedWaitCallback) {
     // Per the documentation for NotifyUnrecordedWait, we need to call the
     // routine after a notify, even if the routine has been called already
     // since the main thread started to wait for idle replay threads.
     mUnrecordedWaitNotified = false;
   } else {
     MOZ_RELEASE_ASSERT(!mUnrecordedWaitNotified);
   }
 
   mUnrecordedWaitCallback = aCallback;
+  mUnrecordedWaitOnlyWhenDiverged = aOnlyWhenDiverged;
 
   // The main thread might be able to make progress now by calling the routine
   // if it is waiting for idle replay threads.
   if (gThreadsShouldIdle) {
     Notify(MainThreadId);
   }
 }
 
@@ -465,19 +477,20 @@ Thread::MaybeWaitForCheckpointSave()
     MonitorAutoUnlock unlock(*gMonitor);
     Wait();
   }
 }
 
 extern "C" {
 
 MOZ_EXPORT void
-RecordReplayInterface_NotifyUnrecordedWait(const std::function<void()>& aCallback)
+RecordReplayInterface_NotifyUnrecordedWait(const std::function<void()>& aCallback,
+                                           bool aOnlyWhenDiverged)
 {
-  Thread::Current()->NotifyUnrecordedWait(aCallback);
+  Thread::Current()->NotifyUnrecordedWait(aCallback, aOnlyWhenDiverged);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_MaybeWaitForCheckpointSave()
 {
   Thread::MaybeWaitForCheckpointSave();
 }
 
--- a/toolkit/recordreplay/Thread.h
+++ b/toolkit/recordreplay/Thread.h
@@ -93,16 +93,20 @@ private:
   // Whether to crash if we try to record/replay thread events. This is only
   // used by the associated thread.
   size_t mDisallowEvents;
 
   // Whether execution has diverged from the recording and the thread's
   // recorded events cannot be accessed.
   bool mDivergedFromRecording;
 
+  // Whether this thread should diverge from the recording at the next
+  // opportunity. This can be set from any thread.
+  Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mShouldDivergeFromRecording;
+
   // Start routine and argument which the thread is currently executing. This
   // is cleared after the routine finishes and another start routine may be
   // assigned to the thread. mNeedsJoin specifies whether the thread must be
   // joined before it is completely dead and can be reused. This is protected
   // by the thread monitor.
   Callback mStart;
   void* mStartArg;
   bool mNeedsJoin;
@@ -125,16 +129,17 @@ private:
 
   // Whether the thread is waiting on idlefd.
   Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mIdle;
 
   // Any callback which should be invoked so the thread can make progress,
   // and whether the callback has been invoked yet while the main thread is
   // waiting for threads to become idle. Protected by the thread monitor.
   std::function<void()> mUnrecordedWaitCallback;
+  bool mUnrecordedWaitOnlyWhenDiverged;
   bool mUnrecordedWaitNotified;
 
 public:
 ///////////////////////////////////////////////////////////////////////////////
 // Public Routines
 ///////////////////////////////////////////////////////////////////////////////
 
   // Accessors for some members that never change.
@@ -174,16 +179,36 @@ public:
   // flag is clear.
   void DivergeFromRecording() {
     mDivergedFromRecording = true;
   }
   bool HasDivergedFromRecording() const {
     return mDivergedFromRecording;
   }
 
+  // Mark this thread as needing to diverge from the recording soon, and wake
+  // it up in case it can make progress now. The mShouldDivergeFromRecording
+  // flag is separate from mDivergedFromRecording so that the thread can only
+  // begin diverging from the recording at calls to MaybeDivergeFromRecording.
+  void SetShouldDivergeFromRecording() {
+    MOZ_RELEASE_ASSERT(CurrentIsMainThread());
+    mShouldDivergeFromRecording = true;
+    Notify(mId);
+  }
+  bool WillDivergeFromRecordingSoon() {
+    MOZ_RELEASE_ASSERT(CurrentIsMainThread());
+    return mShouldDivergeFromRecording;
+  }
+  bool MaybeDivergeFromRecording() {
+    if (mShouldDivergeFromRecording) {
+      mDivergedFromRecording = true;
+    }
+    return mDivergedFromRecording;
+  }
+
   // Return whether this thread may read or write to its recorded event stream.
   bool CanAccessRecording() const {
     return !PassThroughEvents() && !AreEventsDisallowed() && !HasDivergedFromRecording();
   }
 
   // The actual start routine at the root of all recorded threads, and of all
   // threads when replaying.
   static void ThreadMain(void* aArgument);
@@ -248,17 +273,18 @@ public:
 
   // Wait indefinitely, until the process is rewound.
   static void WaitForever();
 
   // Wait indefinitely, without allowing this thread to be rewound.
   static void WaitForeverNoIdle();
 
   // See RecordReplay.h.
-  void NotifyUnrecordedWait(const std::function<void()>& aCallback);
+  void NotifyUnrecordedWait(const std::function<void()>& aCallback,
+                            bool aOnlyWhenDiverged);
   static void MaybeWaitForCheckpointSave();
 
   // Wait for all other threads to enter the idle state necessary for saving
   // or restoring a checkpoint. This may only be called on the main thread.
   static void WaitForIdleThreads();
 
   // After WaitForIdleThreads(), the main thread will call this to allow
   // other threads to resume execution.
@@ -285,12 +311,66 @@ public:
   ~AutoEnsurePassThroughThreadEventsUseStackPointer()
   {
     if (!mPassedThrough) {
       mThread->SetPassThrough(false);
     }
   }
 };
 
+// Mark a region of code where a thread's event stream can be accessed.
+// This class has several properties:
+//
+// - When recording, all writes to the thread's event stream occur atomically
+//   within the class: the end of the stream cannot be hit at an intermediate
+//   point.
+//
+// - When replaying, this checks for the end of the stream, and blocks the
+//   thread if necessary.
+//
+// - When replaying, this is a point where the thread can begin diverging from
+//   the recording. Checks for divergence should occur after the constructor
+//   finishes.
+class MOZ_RAII RecordingEventSection
+{
+  Thread* mThread;
+
+public:
+  explicit RecordingEventSection(Thread* aThread)
+    : mThread(aThread)
+  {
+    if (!aThread || !aThread->CanAccessRecording()) {
+      return;
+    }
+    if (IsRecording()) {
+      MOZ_RELEASE_ASSERT(!aThread->Events().mInRecordingEventSection);
+      aThread->Events().mFile->mStreamLock.ReadLock();
+      aThread->Events().mInRecordingEventSection = true;
+    } else {
+      while (!aThread->MaybeDivergeFromRecording() && aThread->Events().AtEnd()) {
+        HitEndOfRecording();
+      }
+    }
+  }
+
+  ~RecordingEventSection() {
+    if (!mThread || !mThread->CanAccessRecording()) {
+      return;
+    }
+    if (IsRecording()) {
+      mThread->Events().mFile->mStreamLock.ReadUnlock();
+      mThread->Events().mInRecordingEventSection = false;
+    }
+  }
+
+  bool CanAccessEvents() {
+    if (!mThread || mThread->PassThroughEvents() || mThread->HasDivergedFromRecording()) {
+      return false;
+    }
+    MOZ_RELEASE_ASSERT(mThread->CanAccessRecording());
+    return true;
+  }
+};
+
 } // namespace recordreplay
 } // namespace mozilla
 
 #endif // mozilla_recordreplay_Thread_h
--- a/toolkit/recordreplay/Trigger.cpp
+++ b/toolkit/recordreplay/Trigger.cpp
@@ -54,16 +54,17 @@ InitializeTriggers()
 }
 
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_RegisterTrigger(void* aObj, const std::function<void()>& aCallback)
 {
   MOZ_RELEASE_ASSERT(aObj);
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
 
   Thread* thread = Thread::Current();
   if (thread->HasDivergedFromRecording()) {
     return;
   }
   MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   size_t id;
@@ -79,16 +80,19 @@ RecordReplayInterface_RegisterTrigger(vo
       iter->second.mRegisterCount++;
     } else {
       id = gTriggers->Insert(aObj);
       TriggerInfo info(thread->Id(), aCallback);
       gTriggerInfoMap->insert(TriggerInfoMap::value_type(aObj, info));
     }
   }
 
+  RecordingEventSection res(thread);
+  MOZ_RELEASE_ASSERT(res.CanAccessEvents());
+
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RegisterTrigger);
   thread->Events().CheckInput(id);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_UnregisterTrigger(void* aObj)
 {
   MOZ_ASSERT(IsRecordingOrReplaying());
@@ -152,17 +156,20 @@ RemoveTriggerCallbackForThreadId(size_t 
   }
   return Nothing();
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_ExecuteTriggers()
 {
   Thread* thread = Thread::Current();
-  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
+  RecordingEventSection res(thread);
+  if (!res.CanAccessEvents()) {
+    return;
+  }
 
   if (IsRecording()) {
     // Invoke the callbacks for any triggers waiting for execution, including
     // any whose callbacks are triggered by earlier callback invocations.
     while (true) {
       Maybe<size_t> id = RemoveTriggerCallbackForThreadId(thread->Id());
       if (id.isNothing()) {
         break;
--- a/toolkit/recordreplay/ipc/Channel.cpp
+++ b/toolkit/recordreplay/ipc/Channel.cpp
@@ -7,16 +7,17 @@
 #include "Channel.h"
 
 #include "ChildIPC.h"
 #include "ProcessRewind.h"
 #include "Thread.h"
 
 #include "MainThreadUtils.h"
 #include "nsXULAppAPI.h"
+#include "base/eintr_wrapper.h"
 #include "base/process_util.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/FileDescriptor.h"
 
 #include <sys/socket.h>
 #include <sys/un.h>
 
 namespace mozilla {
@@ -60,16 +61,17 @@ struct HelloMessage
 };
 
 Channel::Channel(size_t aId, bool aMiddlemanRecording, const MessageHandler& aHandler)
   : mId(aId)
   , mHandler(aHandler)
   , mInitialized(false)
   , mConnectionFd(0)
   , mFd(0)
+  , mMessageBuffer(nullptr)
   , mMessageBytes(0)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (IsRecordingOrReplaying()) {
     MOZ_RELEASE_ASSERT(AreThreadEventsPassedThrough());
 
     mFd = socket(AF_UNIX, SOCK_STREAM, 0);
@@ -143,17 +145,19 @@ Channel::ThreadMain(void* aChannelArg)
     }
     channel->mHandler(msg);
   }
 }
 
 void
 Channel::SendMessage(const Message& aMsg)
 {
-  MOZ_RELEASE_ASSERT(NS_IsMainThread() || aMsg.mType == MessageType::FatalError);
+  MOZ_RELEASE_ASSERT(NS_IsMainThread() ||
+                     aMsg.mType == MessageType::FatalError ||
+                     aMsg.mType == MessageType::MiddlemanCallRequest);
 
   // Block until the channel is initialized.
   if (!mInitialized) {
     MonitorAutoLock lock(mMonitor);
     while (!mInitialized) {
       mMonitor.Wait();
     }
   }
@@ -174,58 +178,60 @@ Channel::SendMessage(const Message& aMsg
     ptr += rv;
     nbytes -= rv;
   }
 }
 
 Message*
 Channel::WaitForMessage()
 {
-  if (!mMessageBuffer.length()) {
-    mMessageBuffer.appendN(0, PageSize);
+  if (!mMessageBuffer) {
+    mMessageBuffer = (MessageBuffer*) AllocateMemory(sizeof(MessageBuffer), MemoryKind::Generic);
+    mMessageBuffer->appendN(0, PageSize);
   }
 
   size_t messageSize = 0;
   while (true) {
     if (mMessageBytes >= sizeof(Message)) {
-      Message* msg = (Message*) mMessageBuffer.begin();
+      Message* msg = (Message*) mMessageBuffer->begin();
       messageSize = msg->mSize;
+      MOZ_RELEASE_ASSERT(messageSize >= sizeof(Message));
       if (mMessageBytes >= messageSize) {
         break;
       }
     }
 
     // Make sure the buffer is large enough for the entire incoming message.
-    if (messageSize > mMessageBuffer.length()) {
-      mMessageBuffer.appendN(0, messageSize - mMessageBuffer.length());
+    if (messageSize > mMessageBuffer->length()) {
+      mMessageBuffer->appendN(0, messageSize - mMessageBuffer->length());
     }
 
-    ssize_t nbytes = HANDLE_EINTR(recv(mFd, &mMessageBuffer[mMessageBytes],
-                                       mMessageBuffer.length() - mMessageBytes, 0));
+    ssize_t nbytes = HANDLE_EINTR(recv(mFd, &mMessageBuffer->begin()[mMessageBytes],
+                                       mMessageBuffer->length() - mMessageBytes, 0));
     if (nbytes < 0) {
       MOZ_RELEASE_ASSERT(errno == EAGAIN);
       continue;
     } else if (nbytes == 0) {
       // The other side of the channel has shut down.
       if (IsMiddleman()) {
         return nullptr;
       }
       PrintSpew("Channel disconnected, exiting...\n");
       _exit(0);
     }
 
     mMessageBytes += nbytes;
   }
 
-  Message* res = ((Message*)mMessageBuffer.begin())->Clone();
+  Message* res = ((Message*)mMessageBuffer->begin())->Clone();
 
   // Remove the message we just received from the incoming buffer.
   size_t remaining = mMessageBytes - messageSize;
   if (remaining) {
-    memmove(mMessageBuffer.begin(), &mMessageBuffer[messageSize], remaining);
+    memmove(mMessageBuffer->begin(), &mMessageBuffer->begin()[messageSize], remaining);
   }
   mMessageBytes = remaining;
 
   PrintMessage("RecvMsg", *res);
   return res;
 }
 
 void
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -93,16 +93,19 @@ namespace recordreplay {
   _Macro(SetIsActive)                                          \
                                                                \
   /* Set whether to perform intentional crashes, for testing. */ \
   _Macro(SetAllowIntentionalCrashes)                           \
                                                                \
   /* Set whether to save a particular checkpoint. */           \
   _Macro(SetSaveCheckpoint)                                    \
                                                                \
+  /* 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)                                     \
                                                                \
   /* A critical error occurred and execution cannot continue. The child will */ \
   /* stop executing after sending this message and will wait to be terminated. */ \
@@ -114,16 +117,23 @@ namespace recordreplay {
   /* Notify the middleman that a checkpoint or breakpoint was hit. */ \
   /* The child will pause after sending these messages. */     \
   _Macro(HitCheckpoint)                                        \
   _Macro(HitBreakpoint)                                        \
                                                                \
   /* 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)
 
 enum class MessageType
 {
 #define DefineEnum(Kind) Kind,
   ForEachMessageType(DefineEnum)
 #undef DefineEnum
@@ -159,16 +169,17 @@ public:
     }
   }
 
   // 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;
   }
 
 protected:
   template <typename T, typename Elem>
   Elem* Data() { return (Elem*) (sizeof(T) + (char*) this); }
 
   template <typename T, typename Elem>
@@ -362,21 +373,25 @@ struct FatalErrorMessage : public Messag
 
 // The format for graphics data which will be sent to the middleman process.
 // This needs to match the format expected for canvas image data, to avoid
 // transforming the data before rendering it in the middleman process.
 static const gfx::SurfaceFormat gSurfaceFormat = gfx::SurfaceFormat::R8G8B8X8;
 
 struct PaintMessage : public Message
 {
+  // Checkpoint whose state is being painted.
+  uint32_t mCheckpointId;
+
   uint32_t mWidth;
   uint32_t mHeight;
 
-  PaintMessage(uint32_t aWidth, uint32_t aHeight)
+  PaintMessage(uint32_t aCheckpointId, uint32_t aWidth, uint32_t aHeight)
     : Message(MessageType::Paint, sizeof(*this))
+    , mCheckpointId(aCheckpointId)
     , mWidth(aWidth)
     , mHeight(aHeight)
   {}
 };
 
 struct HitCheckpointMessage : public Message
 {
   uint32_t mCheckpointId;
@@ -413,16 +428,39 @@ struct HitBreakpointMessage : public Mes
     MOZ_RELEASE_ASSERT(res->NumBreakpoints() == aNumBreakpoints);
     PodCopy(res->Data<HitBreakpointMessage, uint32_t>(), aBreakpoints, aNumBreakpoints);
     return res;
   }
 };
 
 typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints> AlwaysMarkMajorCheckpointsMessage;
 
+template <MessageType Type>
+struct BinaryMessage : public Message
+{
+  explicit BinaryMessage(uint32_t aSize)
+    : Message(Type, aSize)
+  {}
+
+  const char* BinaryData() const { return Data<BinaryMessage<Type>, char>(); }
+  size_t BinaryDataSize() const { return DataSize<BinaryMessage<Type>, char>(); }
+
+  static BinaryMessage<Type>*
+  New(const char* aData, size_t aDataSize) {
+    BinaryMessage<Type>* res = NewWithData<BinaryMessage<Type>, char>(aDataSize);
+    MOZ_RELEASE_ASSERT(res->BinaryDataSize() == aDataSize);
+    PodCopy(res->Data<BinaryMessage<Type>, char>(), aData, aDataSize);
+    return res;
+  }
+};
+
+typedef BinaryMessage<MessageType::MiddlemanCallRequest> MiddlemanCallRequestMessage;
+typedef BinaryMessage<MessageType::MiddlemanCallResponse> MiddlemanCallResponseMessage;
+typedef EmptyMessage<MessageType::ResetMiddlemanCalls> ResetMiddlemanCallsMessage;
+
 class Channel
 {
 public:
   // Note: the handler is responsible for freeing its input message. It will be
   // called on the channel's message thread.
   typedef std::function<void(Message*)> MessageHandler;
 
 private:
@@ -440,17 +478,18 @@ private:
 
   // Descriptor used to communicate with the other side.
   int mFd;
 
   // For synchronizing initialization of the channel.
   Monitor mMonitor;
 
   // Buffer for message data received from the other side of the channel.
-  InfallibleVector<char, 0, AllocPolicy<MemoryKind::Generic>> mMessageBuffer;
+  typedef InfallibleVector<char, 0, AllocPolicy<MemoryKind::Generic>> MessageBuffer;
+  MessageBuffer* mMessageBuffer;
 
   // The number of bytes of data already in the message buffer.
   size_t mMessageBytes;
 
   // If spew is enabled, print a message and associated info to stderr.
   void PrintMessage(const char* aPrefix, const Message& aMsg);
 
   // Block until a complete message is received from the other side of the
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -11,17 +11,20 @@
 
 #include "base/message_loop.h"
 #include "base/task.h"
 #include "chrome/common/child_thread.h"
 #include "chrome/common/mach_ipc_mac.h"
 #include "ipc/Channel.h"
 #include "mac/handler/exception_handler.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/VsyncDispatcher.h"
 
 #include "InfallibleVector.h"
 #include "MemorySnapshot.h"
 #include "nsPrintfCString.h"
 #include "ParentInternal.h"
 #include "ProcessRecordReplay.h"
@@ -59,16 +62,19 @@ static FileHandle gCheckpointReadFd;
 
 // Copy of the introduction message we got from the middleman. This is saved on
 // receipt and then processed during InitRecordingOrReplayingProcess.
 static IntroductionMessage* gIntroductionMessage;
 
 // When recording, whether developer tools server code runs in the middleman.
 static bool gDebuggerRunsInMiddleman;
 
+// Any response received to the last MiddlemanCallRequest message.
+static MiddlemanCallResponseMessage* gCallResponseMessage;
+
 // Processing routine for incoming channel messages.
 static void
 ChannelMessageHandler(Message* aMsg)
 {
   MOZ_RELEASE_ASSERT(MainThreadShouldPause() || aMsg->CanBeSentWhileUnpaused());
 
   switch (aMsg->mType) {
   case MessageType::Introduction: {
@@ -154,16 +160,24 @@ ChannelMessageHandler(Message* aMsg)
   }
   case MessageType::RunToPoint: {
     const RunToPointMessage& nmsg = (const RunToPointMessage&) *aMsg;
     PauseMainThreadAndInvokeCallback([=]() {
         navigation::RunToPoint(nmsg.mTarget);
       });
     break;
   }
+  case MessageType::MiddlemanCallResponse: {
+    MonitorAutoLock lock(*gMonitor);
+    MOZ_RELEASE_ASSERT(!gCallResponseMessage);
+    gCallResponseMessage = (MiddlemanCallResponseMessage*) aMsg;
+    aMsg = nullptr; // Avoid freeing the message below.
+    gMonitor->NotifyAll();
+    break;
+  }
   default:
     MOZ_CRASH();
   }
 
   free(aMsg);
 }
 
 // Main routine for a thread whose sole purpose is to listen to requests from
@@ -182,16 +196,17 @@ ListenForCheckpointThreadMain(void*)
       NS_DispatchToMainThread(NewRunnableFunction("NewCheckpoint", NewCheckpoint,
                                                   /* aTemporary = */ false));
     } else {
       MOZ_RELEASE_ASSERT(errno == EINTR);
     }
   }
 }
 
+// Shared memory block for graphics data.
 void* gGraphicsShmem;
 
 void
 InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv)
 {
   if (!IsRecordingOrReplaying()) {
     return;
   }
@@ -320,19 +335,21 @@ ParentProcessId()
 
 bool
 DebuggerRunsInMiddleman()
 {
   return RecordReplayValue(gDebuggerRunsInMiddleman);
 }
 
 void
-MaybeCreateInitialCheckpoint()
+CreateCheckpoint()
 {
-  NewCheckpoint(/* aTemporary = */ false);
+  if (!HasDivergedFromRecording()) {
+    NewCheckpoint(/* aTemporary = */ false);
+  }
 }
 
 void
 ReportFatalError(const Maybe<MinidumpInfo>& aMinidump, const char* aFormat, ...)
 {
   // Unprotect any memory which might be written while producing the minidump.
   UnrecoverableSnapshotFailure();
 
@@ -393,48 +410,86 @@ static VsyncObserver* gVsyncObserver;
 
 void
 SetVsyncObserver(VsyncObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(!gVsyncObserver || !aObserver);
   gVsyncObserver = aObserver;
 }
 
-void
+static void
 NotifyVsyncObserver()
 {
   if (gVsyncObserver) {
     gVsyncObserver->NotifyVsync(TimeStamp::Now());
   }
 }
 
+// Whether an update has been sent to the compositor for a normal paint, and we
+// haven't reached PaintFromMainThread yet. This is used to preserve the
+// invariant that there can be at most one paint performed between two
+// checkpoints, other than repaints triggered by the debugger.
+static bool gHasActivePaint;
+
+bool
+OnVsync()
+{
+  // In the repainting stress mode, we create a new checkpoint on every vsync
+  // message received from the UI process. When we notify the parent about the
+  // new checkpoint it will trigger a repaint to make sure that all layout and
+  // painting activity can occur when diverged from the recording.
+  if (parent::InRepaintStressMode()) {
+    CreateCheckpoint();
+  }
+
+  // After a paint starts, ignore incoming vsyncs until the paint completes.
+  return !gHasActivePaint;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Painting
 ///////////////////////////////////////////////////////////////////////////////
 
-// Graphics memory is only written on the compositor thread and read on the
-// main thread and by the middleman. The gPendingPaint flag is used to
-// synchronize access, so that data is not read until the paint has completed.
-static Maybe<PaintMessage> gPaintMessage;
-static bool gPendingPaint;
-
-// Target buffer for the draw target created by the child process widget.
+// Target buffer for the draw target created by the child process widget, which
+// the compositor thread writes to.
 static void* gDrawTargetBuffer;
 static size_t gDrawTargetBufferSize;
 
+// Dimensions of the last paint which the compositor performed.
+static size_t gPaintWidth, gPaintHeight;
+
+// How many updates have been sent to the compositor thread and haven't been
+// processed yet. This can briefly become negative if the main thread sends an
+// update and the compositor processes it before the main thread reaches
+// NotifyPaintStart. Outside of this window, the compositor can only write to
+// gDrawTargetBuffer or update gPaintWidth/gPaintHeight if this is non-zero.
+static Atomic<int32_t, SequentiallyConsistent, Behavior::DontPreserve> gNumPendingPaints;
+
+// ID of the compositor thread.
+static Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> gCompositorThreadId;
+
 already_AddRefed<gfx::DrawTarget>
 DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize)
 {
   MOZ_RELEASE_ASSERT(!NS_IsMainThread());
 
+  // Keep track of the compositor thread ID.
+  size_t threadId = Thread::Current()->Id();
+  if (gCompositorThreadId) {
+    MOZ_RELEASE_ASSERT(threadId == gCompositorThreadId);
+  } else {
+    gCompositorThreadId = threadId;
+  }
+
   if (aSize.IsEmpty()) {
     return nullptr;
   }
 
-  gPaintMessage = Some(PaintMessage(aSize.width, aSize.height));
+  gPaintWidth = aSize.width;
+  gPaintHeight = aSize.height;
 
   gfx::IntSize size(aSize.width, aSize.height);
   size_t bufferSize = layers::ImageDataSerializer::ComputeRGBBufferSize(size, gSurfaceFormat);
   MOZ_RELEASE_ASSERT(bufferSize <= parent::GraphicsMemorySize);
 
   if (bufferSize != gDrawTargetBufferSize) {
     free(gDrawTargetBuffer);
     gDrawTargetBuffer = malloc(bufferSize);
@@ -453,52 +508,145 @@ DrawTargetForRemoteDrawing(LayoutDeviceI
   return drawTarget.forget();
 }
 
 void
 NotifyPaintStart()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  NewCheckpoint(/* aTemporary = */ false);
+  // A new paint cannot be triggered until the last one finishes and has been
+  // sent to the middleman.
+  MOZ_RELEASE_ASSERT(HasDivergedFromRecording() || !gHasActivePaint);
 
-  gPendingPaint = true;
+  gNumPendingPaints++;
+  gHasActivePaint = true;
+
+  CreateCheckpoint();
 }
 
-void
-WaitForPaintToComplete()
+static void
+PaintFromMainThread()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  MonitorAutoLock lock(*gMonitor);
-  while (gPendingPaint) {
-    gMonitor->Wait();
-  }
-  if (IsActiveChild() && gPaintMessage.isSome()) {
+  // There cannot not be any other in flight paints.
+  MOZ_RELEASE_ASSERT(!gNumPendingPaints);
+
+  // Clear the active flag now that we have completed the paint.
+  MOZ_RELEASE_ASSERT(gHasActivePaint);
+  gHasActivePaint = false;
+
+  if (IsActiveChild() && gDrawTargetBuffer) {
     memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
-    gChannel->SendMessage(gPaintMessage.ref());
+    gChannel->SendMessage(PaintMessage(navigation::LastNormalCheckpoint(),
+                                       gPaintWidth, gPaintHeight));
   }
 }
 
 void
 NotifyPaintComplete()
 {
-  MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(Thread::Current()->Id() == gCompositorThreadId);
+
+  // Notify the main thread in case it is waiting for this paint to complete.
+  {
+    MonitorAutoLock lock(*gMonitor);
+    if (--gNumPendingPaints == 0) {
+      gMonitor->Notify();
+    }
+  }
+
+  // Notify the middleman about the completed paint from the main thread.
+  NS_DispatchToMainThread(NewRunnableFunction("PaintFromMainThread", PaintFromMainThread));
+}
+
+void
+Repaint(size_t* aWidth, size_t* aHeight)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(HasDivergedFromRecording());
+
+  // Don't try to repaint if the first normal paint hasn't occurred yet.
+  if (!gCompositorThreadId) {
+    *aWidth = 0;
+    *aHeight = 0;
+    return;
+  }
+
+  // Ignore the request to repaint if the compositor thread has already
+  // diverged from the recording. In this case we have already done a repaint
+  // and the last graphics we sent will still be correct.
+  Thread* compositorThread = Thread::GetById(gCompositorThreadId);
+  if (!compositorThread->WillDivergeFromRecordingSoon()) {
+    // Create an artifical vsync to see if graphics have changed since the last
+    // paint and a new paint is needed.
+    NotifyVsyncObserver();
+
+    if (gNumPendingPaints) {
+      // Allow the compositor to diverge from the recording so it can perform
+      // any paint we just triggered, or finish any in flight paint that that
+      // existed at the point we are paused at.
+      Thread::GetById(gCompositorThreadId)->SetShouldDivergeFromRecording();
 
-  MonitorAutoLock lock(*gMonitor);
-  MOZ_RELEASE_ASSERT(gPendingPaint);
-  gPendingPaint = false;
-  gMonitor->Notify();
+      // Wait for the compositor to finish all in flight paints, including any
+      // one we just triggered.
+      MonitorAutoLock lock(*gMonitor);
+      while (gNumPendingPaints) {
+        gMonitor->Wait();
+      }
+    }
+  }
+
+  if (gDrawTargetBuffer) {
+    memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
+    *aWidth = gPaintWidth;
+    *aHeight = gPaintHeight;
+  } else {
+    *aWidth = 0;
+    *aHeight = 0;
+  }
+}
+
+static bool
+CompositorCanPerformMiddlemanCalls()
+{
+  // After repainting finishes the compositor is not allowed to send call
+  // requests to the middleman anymore.
+  return !!gNumPendingPaints;
+}
+
+bool
+SuppressMessageAfterDiverge(IPC::Message* aMsg)
+{
+  MOZ_RELEASE_ASSERT(HasDivergedFromRecording());
+
+  // Only messages necessary for compositing can be sent after the sending
+  // thread has diverged from the recording. Sending other messages can risk
+  // deadlocking when a necessary lock is held by an idle thread (we probably
+  // need a more robust way to deal with this problem).
+
+  IPC::Message::msgid_t type = aMsg->type();
+  if (type >= layers::PLayerTransaction::PLayerTransactionStart &&
+      type <= layers::PLayerTransaction::PLayerTransactionEnd) {
+    return false;
+  }
+
+  if (type == layers::PCompositorBridge::Msg_PTextureConstructor__ID) {
+    return false;
+  }
+
+  return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Checkpoint Messages
 ///////////////////////////////////////////////////////////////////////////////
 
-// When recording, the time when the last HitCheckpoint message was sent.
+// The time when the last HitCheckpoint message was sent.
 static double gLastCheckpointTime;
 
 // When recording and we are idle, the time when we became idle.
 static double gIdleTimeStart;
 
 void
 BeginIdleTime()
 {
@@ -529,17 +677,17 @@ HitCheckpoint(size_t aId, bool aRecordin
         MOZ_RELEASE_ASSERT(duration > 0);
       }
       gChannel->SendMessage(HitCheckpointMessage(aId, aRecordingEndpoint, duration));
     });
   gLastCheckpointTime = time;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Debugger Messages
+// Message Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 void
 RespondToRequest(const js::CharBuffer& aBuffer)
 {
   DebuggerResponseMessage* msg =
     DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
   gChannel->SendMessage(*msg);
@@ -553,11 +701,57 @@ HitBreakpoint(bool aRecordingEndpoint, c
   HitBreakpointMessage* msg =
     HitBreakpointMessage::New(aRecordingEndpoint, aBreakpoints, aNumBreakpoints);
   PauseMainThreadAndInvokeCallback([=]() {
       gChannel->SendMessage(*msg);
       free(msg);
     });
 }
 
+bool
+SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
+                         InfallibleVector<char>* aOutputData)
+{
+  Thread* thread = Thread::Current();
+
+  // Middleman calls can only be made from the main and compositor threads.
+  // These two threads cannot simultaneously send call requests or other
+  // messages, as doing so will race both here and in Channel::SendMessage.
+  // CompositorCanPerformMiddlemanCalls() ensures that the main thread is
+  // not actively sending messages at times when the compositor performs
+  // middleman calls.
+  MOZ_RELEASE_ASSERT(thread->IsMainThread() || thread->Id() == gCompositorThreadId);
+
+  if (thread->Id() == gCompositorThreadId && !CompositorCanPerformMiddlemanCalls()) {
+    return false;
+  }
+
+  MonitorAutoLock lock(*gMonitor);
+
+  MOZ_RELEASE_ASSERT(!gCallResponseMessage);
+
+  MiddlemanCallRequestMessage* msg = MiddlemanCallRequestMessage::New(aInputData, aInputSize);
+  gChannel->SendMessage(*msg);
+  free(msg);
+
+  while (!gCallResponseMessage) {
+    gMonitor->Wait();
+  }
+
+  aOutputData->append(gCallResponseMessage->BinaryData(), gCallResponseMessage->BinaryDataSize());
+
+  free(gCallResponseMessage);
+  gCallResponseMessage = nullptr;
+
+  gMonitor->Notify();
+  return true;
+}
+
+void
+SendResetMiddlemanCalls()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  gChannel->SendMessage(ResetMiddlemanCallsMessage());
+}
+
 } // namespace child
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ChildIPC.h
+++ b/toolkit/recordreplay/ipc/ChildIPC.h
@@ -6,16 +6,18 @@
 
 #ifndef mozilla_recordreplay_ChildIPC_h
 #define mozilla_recordreplay_ChildIPC_h
 
 #include "base/process.h"
 #include "mozilla/gfx/2D.h"
 #include "Units.h"
 
+namespace IPC { class Message; }
+
 namespace mozilla {
 
 class VsyncObserver;
 
 namespace recordreplay {
 namespace child {
 
 // This file has the public API for definitions used in facilitating IPC
@@ -24,49 +26,42 @@ namespace child {
 // Initialize replaying IPC state. This is called once during process startup,
 // and is a no-op if the process is not recording/replaying.
 void InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv);
 
 // Get the IDs of the middleman and parent processes.
 base::ProcessId MiddlemanProcessId();
 base::ProcessId ParentProcessId();
 
-// Create a normal checkpoint, if no such checkpoint has been created yet.
-void MaybeCreateInitialCheckpoint();
+// Create a normal checkpoint, if execution has not diverged from the recording.
+void CreateCheckpoint();
 
 ///////////////////////////////////////////////////////////////////////////////
 // Painting Coordination
 ///////////////////////////////////////////////////////////////////////////////
 
-// In child processes, paints do not occur in response to vsyncs from the UI
-// process: when a page is updating rapidly these events occur sporadically and
-// cause the tab's graphics to not accurately reflect the tab's state at that
-// point in time. When viewing a normal tab this is no problem because the tab
-// will be painted with the correct graphics momentarily, but when the tab can
-// be rewound and paused this behavior is visible.
-//
-// This API is used to trigger artificial vsyncs whenever the page is updated.
-// SetVsyncObserver is used to tell the child code about any singleton vsync
-// observer that currently exists, and NotifyVsyncObserver is used to trigger
-// a vsync on that observer at predictable points, e.g. the top of the main
-// thread's event loop.
+// Tell the child code about any singleton vsync observer that currently
+// exists. This is used to trigger artifical vsyncs that paint the current
+// graphics when paused.
 void SetVsyncObserver(VsyncObserver* aObserver);
-void NotifyVsyncObserver();
+
+// Called before processing incoming vsyncs from the UI process. Returns false
+// if the vsync should be ignored.
+bool OnVsync();
 
-// Similarly to the vsync handling above, in order to ensure that the tab's
-// graphics accurately reflect its state, we want to perform paints
-// synchronously after a vsync has occurred. When a paint is about to happen,
-// the main thread calls NotifyPaintStart, and after the compositor thread has
-// been informed about the update the main thread calls WaitForPaintToComplete
-// to block until the compositor thread has finished painting and called
-// NotifyPaintComplete.
+// Tell the child code about any ongoing painting activity. When a paint is
+// about to happen, the main thread calls NotifyPaintStart, and when the
+// compositor thread finishes the paint it calls NotifyPaintComplete.
 void NotifyPaintStart();
-void WaitForPaintToComplete();
 void NotifyPaintComplete();
 
 // Get a draw target which the compositor thread can paint to.
 already_AddRefed<gfx::DrawTarget> DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize);
 
+// Called to ignore IPDL messages sent after diverging from the recording,
+// except for those needed for compositing.
+bool SuppressMessageAfterDiverge(IPC::Message* aMsg);
+
 } // namespace child
 } // namespace recordreplay
 } // namespace mozilla
 
 #endif // mozilla_recordreplay_ChildIPC_h
--- a/toolkit/recordreplay/ipc/ChildInternal.h
+++ b/toolkit/recordreplay/ipc/ChildInternal.h
@@ -2,18 +2,20 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_recordreplay_ChildInternal_h
 #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.
 
@@ -57,24 +59,31 @@ void PositionHit(const js::BreakpointPos
 
 // Get an execution point for hitting the specified position right now.
 js::ExecutionPoint CurrentExecutionPoint(const 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();
+
 } // namespace navigation
 
 namespace child {
 
 // IPC activity that can be triggered by navigation.
 void RespondToRequest(const js::CharBuffer& aBuffer);
 void HitCheckpoint(size_t aId, bool aRecordingEndpoint);
 void HitBreakpoint(bool aRecordingEndpoint, const uint32_t* aBreakpoints, size_t aNumBreakpoints);
@@ -111,14 +120,19 @@ void ReportFatalError(const char* aForma
 
 // Mark a time span when the main thread is idle.
 void BeginIdleTime();
 void EndIdleTime();
 
 // Whether the middleman runs developer tools server code.
 bool DebuggerRunsInMiddleman();
 
+// Send messages operating on middleman calls.
+bool SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
+                              InfallibleVector<char>* aOutputData);
+void SendResetMiddlemanCalls();
+
 } // namespace child
 
 } // namespace recordreplay
 } // namespace mozilla
 
 #endif // mozilla_recordreplay_ChildInternal_h
--- a/toolkit/recordreplay/ipc/ChildNavigation.cpp
+++ b/toolkit/recordreplay/ipc/ChildNavigation.cpp
@@ -693,23 +693,25 @@ 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[mRequestIndex].mUnhandledDivergence) {
+  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;
@@ -1104,16 +1106,22 @@ 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
 SetBreakpoint(size_t aId, const BreakpointPosition& aPosition)
@@ -1185,19 +1193,23 @@ PositionHit(const BreakpointPosition& po
   gNavigation->PositionHit(CurrentExecutionPoint(position));
 }
 
 extern "C" {
 
 MOZ_EXPORT ProgressCounter
 RecordReplayInterface_NewTimeWarpTarget()
 {
+  if (AreThreadEventsDisallowed()) {
+    return 0;
+  }
+
   // NewTimeWarpTarget() must be called at consistent points between recording
   // and replaying.
-  recordreplay::RecordReplayAssert("NewTimeWarpTarget");
+  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;
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -290,16 +290,17 @@ ChildProcessInfo::SendMessage(const Mess
 
   // Keep track of messages which affect the child's behavior.
   switch (aMsg.mType) {
   case MessageType::Resume:
   case MessageType::RestoreCheckpoint:
   case MessageType::RunToPoint:
   case MessageType::DebuggerRequest:
   case MessageType::SetBreakpoint:
+  case MessageType::MiddlemanCallResponse:
     mMessages.emplaceBack(aMsg.Clone());
     break;
   default:
     break;
   }
 
   // Keep track of the checkpoints the process will save.
   if (aMsg.mType == MessageType::SetSaveCheckpoint) {
@@ -402,18 +403,21 @@ ChildProcessInfo::OnIncomingRecoveryMess
       MOZ_RELEASE_ASSERT(nmsg.mCheckpointId == mLastCheckpoint);
       mRecoveryStage = RecoveryStage::PlayingMessages;
       SendNextRecoveryMessage();
     }
     break;
   }
   case MessageType::HitBreakpoint:
   case MessageType::DebuggerResponse:
+  case MessageType::MiddlemanCallRequest:
     SendNextRecoveryMessage();
     break;
+  case MessageType::ResetMiddlemanCalls:
+    break;
   default:
     MOZ_CRASH("Unexpected message during recovery");
   }
 }
 
 void
 ChildProcessInfo::SendNextRecoveryMessage()
 {
--- a/toolkit/recordreplay/ipc/DisabledIPC.cpp
+++ b/toolkit/recordreplay/ipc/DisabledIPC.cpp
@@ -35,53 +35,53 @@ MiddlemanProcessId()
 }
 
 base::ProcessId
 ParentProcessId()
 {
   MOZ_CRASH();
 }
 
-void MaybeCreateInitialCheckpoint()
+void CreateCheckpoint()
 {
   MOZ_CRASH();
 }
 
 void
 SetVsyncObserver(VsyncObserver* aObserver)
 {
   MOZ_CRASH();
 }
 
-void
-NotifyVsyncObserver()
+bool
+OnVsync()
 {
   MOZ_CRASH();
 }
 
 void
 NotifyPaintStart()
 {
   MOZ_CRASH();
 }
 
 void
 NotifyPaintComplete()
 {
   MOZ_CRASH();
 }
 
-void
-WaitForPaintToComplete()
+already_AddRefed<gfx::DrawTarget>
+DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize)
 {
   MOZ_CRASH();
 }
 
-already_AddRefed<gfx::DrawTarget>
-DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize)
+bool
+SuppressMessageAfterDiverge(IPC::Message* aMsg)
 {
   MOZ_CRASH();
 }
 
 } // namespace child
 
 namespace parent {
 
--- a/toolkit/recordreplay/ipc/JSControl.cpp
+++ b/toolkit/recordreplay/ipc/JSControl.cpp
@@ -396,16 +396,47 @@ Middleman_MaybeSwitchToReplayingChild(JS
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   parent::MaybeSwitchToReplayingChild();
 
   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);
+  parent::UpdateGraphicsInUIProcess(&message);
+
+  args.rval().setUndefined();
+  return true;
+}
+
+static bool
+Middleman_HadRepaintFailure(JSContext* aCx, unsigned aArgc, Value* aVp)
+{
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  parent::UpdateGraphicsInUIProcess(nullptr);
+
+  args.rval().setUndefined();
+  return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Devtools Sandbox
 ///////////////////////////////////////////////////////////////////////////////
 
 static PersistentRootedObject* gDevtoolsSandbox;
 
 // URL of the root script that runs when recording/replaying.
 #define ReplayScriptURL "resource://devtools/server/actors/replay/replay.js"
@@ -819,16 +850,36 @@ RecordReplay_TimeWarpTargetExecutionPoin
     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 ||
+      !JS_DefineProperty(aCx, obj, "width", (double) width, JSPROP_ENUMERATE) ||
+      !JS_DefineProperty(aCx, obj, "height", (double) height, JSPROP_ENUMERATE))
+  {
+    return false;
+  }
+
+  args.rval().setObject(*obj);
+  return true;
+}
+
+static bool
 RecordReplay_Dump(JSContext* aCx, unsigned aArgc, Value* aVp)
 {
   // This method is an alternative to dump() that can be used in places where
   // thread events are disallowed.
   CallArgs args = CallArgsFromVp(aArgc, aVp);
   for (size_t i = 0; i < args.length(); i++) {
     RootedString str(aCx, ToString(aCx, args[i]));
     if (!str) {
@@ -854,27 +905,30 @@ static const JSFunctionSpec gMiddlemanMe
   JS_FN("canRewind", Middleman_CanRewind, 0, 0),
   JS_FN("resume", Middleman_Resume, 1, 0),
   JS_FN("timeWarp", Middleman_TimeWarp, 1, 0),
   JS_FN("pause", Middleman_Pause, 0, 0),
   JS_FN("sendRequest", Middleman_SendRequest, 1, 0),
   JS_FN("setBreakpoint", Middleman_SetBreakpoint, 2, 0),
   JS_FN("clearBreakpoint", Middleman_ClearBreakpoint, 1, 0),
   JS_FN("maybeSwitchToReplayingChild", Middleman_MaybeSwitchToReplayingChild, 0, 0),
+  JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0),
+  JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0),
   JS_FS_END
 };
 
 static const JSFunctionSpec gRecordReplayMethods[] = {
   JS_FN("areThreadEventsDisallowed", RecordReplay_AreThreadEventsDisallowed, 0, 0),
   JS_FN("maybeDivergeFromRecording", RecordReplay_MaybeDivergeFromRecording, 0, 0),
   JS_FN("advanceProgressCounter", RecordReplay_AdvanceProgressCounter, 0, 0),
   JS_FN("positionHit", RecordReplay_PositionHit, 1, 0),
   JS_FN("getContent", RecordReplay_GetContent, 1, 0),
   JS_FN("currentExecutionPoint", RecordReplay_CurrentExecutionPoint, 1, 0),
   JS_FN("timeWarpTargetExecutionPoint", RecordReplay_TimeWarpTargetExecutionPoint, 1, 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/ParentForwarding.cpp
+++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp
@@ -72,21 +72,16 @@ HandleMessageInMiddleman(ipc::Side aSide
     }
 
     ipc::IProtocol::Result r = contentChild->PContentChild::OnMessageReceived(aMessage);
     MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
     if (type == dom::PContent::Msg_SetXPCOMProcessAttributes__ID) {
       // Preferences are initialized via the SetXPCOMProcessAttributes message.
       PreferencesLoaded();
     }
-    if (type == dom::PBrowser::Msg_RenderLayers__ID) {
-      // Graphics are being loaded or unloaded for a tab, so update what we are
-      // showing to the UI process according to the last paint performed.
-      UpdateGraphicsInUIProcess(nullptr);
-    }
     return false;
   }
 
   // Handle messages that should only be sent to the middleman.
   if (// Initialization that should only happen in the middleman.
       type == dom::PContent::Msg_InitRendering__ID ||
       // Record/replay specific messages.
       type == dom::PContent::Msg_SaveRecording__ID ||
@@ -110,16 +105,23 @@ HandleMessageInMiddleman(ipc::Side aSide
   return false;
 }
 
 // Return whether a message should be sent to the recording child, even if it
 // is not currently active.
 static bool
 AlwaysForwardMessage(const IPC::Message& aMessage)
 {
+  // Always forward messages in repaint stress mode, as the active child is
+  // almost always a replaying child and lost messages make it hard to load
+  // pages completely.
+  if (InRepaintStressMode()) {
+    return true;
+  }
+
   IPC::Message::msgid_t type = aMessage.type();
 
   // Forward close messages so that the tab shuts down properly even if it is
   // currently replaying.
   return type == dom::PBrowser::Msg_Destroy__ID;
 }
 
 static bool gMainThreadIsWaitingForIPDLReply = false;
--- a/toolkit/recordreplay/ipc/ParentGraphics.cpp
+++ b/toolkit/recordreplay/ipc/ParentGraphics.cpp
@@ -64,18 +64,16 @@ SendGraphicsMemoryToChild()
   MachSendMessage message(GraphicsMemoryMessageId);
   message.AddDescriptor(MachMsgPortDescriptor(gGraphicsPort, MACH_MSG_TYPE_COPY_SEND));
 
   MachPortSender sender(childPort);
   kr = sender.SendMessage(message, 1000);
   MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
 }
 
-static Maybe<PaintMessage> gLastPaint;
-
 // Global object for the sandbox used to paint graphics data in this process.
 static JS::PersistentRootedObject* gGraphicsSandbox;
 
 static void
 InitGraphicsSandbox()
 {
   MOZ_RELEASE_ASSERT(!gGraphicsSandbox);
 
@@ -104,37 +102,67 @@ InitGraphicsSandbox()
   dom::ChromeUtils::Import(global, NS_LITERAL_STRING("resource://devtools/server/actors/replay/graphics.js"),
                            dom::Optional<HandleObject>(), &obj, er);
   MOZ_RELEASE_ASSERT(!er.Failed());
 }
 
 // Buffer used to transform graphics memory, if necessary.
 static void* gBufferMemory;
 
+// The dimensions of the data in the graphics shmem buffer.
+static size_t gLastPaintWidth, gLastPaintHeight;
+
+// Explicit Paint messages received from the child need to be handled with
+// care to make sure we show correct graphics. Each Paint message is for the
+// 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) {
-    gLastPaint = Some(*aMsg);
-  } else if (!gLastPaint.isSome()) {
+    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 (!gGraphicsSandbox) {
     InitGraphicsSandbox();
   }
 
   AutoSafeJSContext cx;
   JSAutoRealm ar(cx, *gGraphicsSandbox);
 
-  size_t width = gLastPaint.ref().mWidth;
-  size_t height = gLastPaint.ref().mHeight;
+  size_t width = gLastPaintWidth;
+  size_t height = gLastPaintHeight;
   size_t stride = layers::ImageDataSerializer::ComputeRGBStride(gSurfaceFormat, width);
 
   // Make sure the width and height are appropriately sized.
   CheckedInt<size_t> scaledWidth = CheckedInt<size_t>(width) * 4;
   CheckedInt<size_t> scaledHeight = CheckedInt<size_t>(height) * stride;
   MOZ_RELEASE_ASSERT(scaledWidth.isValid() && scaledWidth.value() <= stride);
   MOZ_RELEASE_ASSERT(scaledHeight.isValid() && scaledHeight.value() <= GraphicsMemorySize);
 
@@ -155,23 +183,59 @@ UpdateGraphicsInUIProcess(const PaintMes
       memcpy(dst, src, width * 4);
     }
   }
 
   JSObject* bufferObject =
     JS_NewArrayBufferWithExternalContents(cx, width * height * 4, memory);
   MOZ_RELEASE_ASSERT(bufferObject);
 
-  JS::AutoValueArray<3> args(cx);
+  JS::AutoValueArray<4> args(cx);
   args[0].setObject(*bufferObject);
   args[1].setInt32(width);
   args[2].setInt32(height);
+  args[3].setBoolean(hadFailure);
 
   // Call into the graphics module to update the canvas it manages.
   RootedValue rval(cx);
   if (!JS_CallFunctionName(cx, *gGraphicsSandbox, "Update", args, &rval)) {
     MOZ_CRASH("UpdateGraphicsInUIProcess");
   }
 }
 
+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;
+  }
+  return rv;
+}
+
 } // namespace parent
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -221,16 +221,17 @@ PokeChildren()
     });
 }
 
 static void RecvHitCheckpoint(const HitCheckpointMessage& aMsg);
 static void RecvHitBreakpoint(const HitBreakpointMessage& aMsg);
 static void RecvDebuggerResponse(const DebuggerResponseMessage& aMsg);
 static void RecvRecordingFlushed();
 static void RecvAlwaysMarkMajorCheckpoints();
+static void RecvMiddlemanCallRequest(const MiddlemanCallRequestMessage& aMsg);
 
 // The role taken by the active child.
 class ChildRoleActive final : public ChildRole
 {
 public:
   ChildRoleActive()
     : ChildRole(Active)
   {}
@@ -245,33 +246,39 @@ public:
     if (mProcess->LastCheckpoint() == CheckpointId::Invalid) {
       mProcess->SendMessage(ResumeMessage(/* aForward = */ true));
     }
   }
 
   void OnIncomingMessage(const Message& aMsg) override {
     switch (aMsg.mType) {
     case MessageType::Paint:
-      UpdateGraphicsInUIProcess((const PaintMessage*) &aMsg);
+      MaybeUpdateGraphicsAtPaint((const PaintMessage&) aMsg);
       break;
     case MessageType::HitCheckpoint:
       RecvHitCheckpoint((const HitCheckpointMessage&) aMsg);
       break;
     case MessageType::HitBreakpoint:
       RecvHitBreakpoint((const HitBreakpointMessage&) aMsg);
       break;
     case MessageType::DebuggerResponse:
       RecvDebuggerResponse((const DebuggerResponseMessage&) aMsg);
       break;
     case MessageType::RecordingFlushed:
       RecvRecordingFlushed();
       break;
     case MessageType::AlwaysMarkMajorCheckpoints:
       RecvAlwaysMarkMajorCheckpoints();
       break;
+    case MessageType::MiddlemanCallRequest:
+      RecvMiddlemanCallRequest((const MiddlemanCallRequestMessage&) aMsg);
+      break;
+    case MessageType::ResetMiddlemanCalls:
+      ResetMiddlemanCalls();
+      break;
     default:
       MOZ_CRASH("Unexpected message");
     }
   }
 };
 
 bool
 ActiveChildIsRecording()
@@ -965,21 +972,55 @@ static bool gChildExecuteBackward = fals
 
 // Whether there is a ResumeForwardOrBackward task which should execute on the
 // main thread. This will continue execution in the preferred direction.
 static bool gResumeForwardOrBackward = false;
 
 // Hit any breakpoints installed for forced pauses.
 static void HitForcedPauseBreakpoints(bool aRecordingBoundary);
 
+static void
+MaybeSendRepaintMessage()
+{
+  // In repaint stress mode, we want to trigger a repaint at every checkpoint,
+  // so before resuming after the child pauses at each checkpoint, send it a
+  // repaint message. There might not be a debugger open, so manually craft the
+  // same message which the debugger would send to trigger a repaint and parse
+  // the result.
+  if (InRepaintStressMode()) {
+    MaybeSwitchToReplayingChild();
+
+    const char16_t contents[] = u"{\"type\":\"repaint\"}";
+
+    js::CharBuffer request, response;
+    request.append(contents, ArrayLength(contents) - 1);
+    SendRequest(request, &response);
+
+    AutoSafeJSContext cx;
+    JS::RootedValue value(cx);
+    if (JS_ParseJSON(cx, response.begin(), response.length(), &value)) {
+      MOZ_RELEASE_ASSERT(value.isObject());
+      JS::RootedObject obj(cx, &value.toObject());
+      RootedValue width(cx), height(cx);
+      if (JS_GetProperty(cx, obj, "width", &width) && width.isNumber() && width.toNumber() &&
+          JS_GetProperty(cx, obj, "height", &height) && height.isNumber() && height.toNumber()) {
+        PaintMessage message(CheckpointId::Invalid, width.toNumber(), height.toNumber());
+        UpdateGraphicsInUIProcess(&message);
+      }
+    }
+  }
+}
+
 void
 Resume(bool aForward)
 {
   gActiveChild->WaitUntilPaused();
 
+  MaybeSendRepaintMessage();
+
   // Set the preferred direction of travel.
   gResumeForwardOrBackward = false;
   gChildExecuteForward = aForward;
   gChildExecuteBackward = !aForward;
 
   // When rewinding, make sure the active child can rewind to the previous
   // checkpoint.
   if (!aForward && !gActiveChild->HasSavedCheckpoint(gActiveChild->RewindTargetCheckpoint())) {
@@ -1120,16 +1161,17 @@ ResumeBeforeWaitingForIPDLReply()
     Resume(true);
   }
 }
 
 static void
 RecvHitCheckpoint(const HitCheckpointMessage& aMsg)
 {
   UpdateCheckpointTimes(aMsg);
+  MaybeUpdateGraphicsAtCheckpoint(aMsg.mCheckpointId);
 
   // Resume either forwards or backwards. Break the resume off into a separate
   // runnable, to avoid starving any code already on the stack and waiting for
   // the process to pause. Immediately resume if the main thread is blocked.
   if (MainThreadIsWaitingForIPDLReply()) {
     MOZ_RELEASE_ASSERT(gChildExecuteForward);
     Resume(true);
   } else if (!gResumeForwardOrBackward) {
@@ -1194,11 +1236,23 @@ HitForcedPauseBreakpoints(bool aRecordin
     uint32_t* newBreakpoints = new uint32_t[breakpoints.length()];
     PodCopy(newBreakpoints, breakpoints.begin(), breakpoints.length());
     gMainThreadMessageLoop->PostTask(NewRunnableFunction("HitBreakpoint", HitBreakpoint,
                                                          newBreakpoints, breakpoints.length(),
                                                          aRecordingBoundary));
   }
 }
 
+static void
+RecvMiddlemanCallRequest(const MiddlemanCallRequestMessage& aMsg)
+{
+  InfallibleVector<char> outputData;
+  ProcessMiddlemanCall(aMsg.BinaryData(), aMsg.BinaryDataSize(), &outputData);
+
+  MiddlemanCallResponseMessage* response =
+    MiddlemanCallResponseMessage::New(outputData.begin(), outputData.length());
+  gActiveChild->SendMessage(*response);
+  free(response);
+}
+
 } // namespace parent
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ParentInternal.h
+++ b/toolkit/recordreplay/ipc/ParentInternal.h
@@ -81,30 +81,43 @@ void MaybeSwitchToReplayingChild();
 ///////////////////////////////////////////////////////////////////////////////
 
 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 for the last paint performed.
+// 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;
 
 // Fixed size of the graphics shared memory buffer.
 static const size_t GraphicsMemorySize = 4096 * 4096 * 4;
 
+// Return whether the environment variable activating repaint stress mode is
+// set. This makes various changes in both the middleman and child processes to
+// trigger a child to diverge from the recording and repaint on every vsync,
+// making sure that repainting can handle all the system interactions that
+// occur while painting the current tab.
+bool InRepaintStressMode();
+
 ///////////////////////////////////////////////////////////////////////////////
 // Child Processes
 ///////////////////////////////////////////////////////////////////////////////
 
 // Information about the role which a child process is fulfilling, and governs
 // how the process responds to incoming messages.
 class ChildRole
 {
--- a/toolkit/recordreplay/moz.build
+++ b/toolkit/recordreplay/moz.build
@@ -21,16 +21,17 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CON
         'ipc/ChildNavigation.cpp',
         'ipc/ChildProcess.cpp',
         'ipc/JSControl.cpp',
         'ipc/ParentForwarding.cpp',
         'ipc/ParentGraphics.cpp',
         'ipc/ParentIPC.cpp',
         'Lock.cpp',
         'MemorySnapshot.cpp',
+        'MiddlemanCall.cpp',
         'ProcessRecordReplay.cpp',
         'ProcessRedirectDarwin.cpp',
         'ProcessRewind.cpp',
         'Thread.cpp',
         'ThreadSnapshot.cpp',
         'Trigger.cpp',
         'ValueIndex.cpp',
         'WeakPointer.cpp',
--- a/xpcom/threads/ThreadEventTarget.cpp
+++ b/xpcom/threads/ThreadEventTarget.cpp
@@ -129,16 +129,24 @@ ThreadEventTarget::Dispatch(already_AddR
     return NS_ERROR_INVALID_ARG;
   }
 
   if (gXPCOMThreadsShutDown && !mIsMainThread) {
     NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
     return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
   }
 
+  // Don't dispatch runnables to other threads when replaying and diverged from
+  // the recording, to avoid deadlocking with other idle threads. The browser
+  // remains paused after diverging from the recording, and threads will not
+  // run their event loops.
+  if (recordreplay::HasDivergedFromRecording()) {
+    return NS_ERROR_FAILURE;
+  }
+
 #ifdef MOZ_TASK_TRACER
   nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
   (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
   // XXX tracedRunnable will always leaked when we fail to disptch.
   event = tracedRunnable.forget();
 #endif
 
   if (aFlags & DISPATCH_SYNC) {
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -1118,23 +1118,16 @@ nsThread::ProcessNextEvent(bool aMayWait
 
   LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
        mNestedEventLoopDepth));
 
   if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
-  // When recording or replaying, vsync observers are notified whenever
-  // processing events on the main thread. Waiting for explicit vsync messages
-  // from the UI process can result in paints happening at unexpected times.
-  if (recordreplay::IsRecordingOrReplaying() && mIsMainThread == MAIN_THREAD) {
-    recordreplay::child::NotifyVsyncObserver();
-  }
-
   // The toplevel event loop normally blocks waiting for the next event, but
   // if we're trying to shut this thread down, we must exit the event loop when
   // the event queue is empty.
   // This only applys to the toplevel event loop! Nested event loops (e.g.
   // during sync dispatch) are waiting for some state change and must be able
   // to block even if something has requested shutdown of the thread. Otherwise
   // we'll just busywait as we endlessly look for an event, fail to find one,
   // and repeat the nested event loop since its state change hasn't happened yet.