Bug 1488808 Part 15 - Add repainting stress testing mode, r=mccr8.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 17 Oct 2018 10:10:22 -0600
changeset 500504 0fa982d6f06a2739f7667e462946b9c80739c3a8
parent 500503 ed0463d80c34acd808558758e8a5467cb59e8ab1
child 500505 09a979b6e583eab5833722f358c3a26e0b89a3a2
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)
reviewersmccr8
bugs1488808
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
Bug 1488808 Part 15 - Add repainting stress testing mode, r=mccr8.
toolkit/recordreplay/ProcessRewind.cpp
toolkit/recordreplay/ipc/ChildIPC.cpp
toolkit/recordreplay/ipc/ChildIPC.h
toolkit/recordreplay/ipc/DisabledIPC.cpp
toolkit/recordreplay/ipc/ParentForwarding.cpp
toolkit/recordreplay/ipc/ParentGraphics.cpp
toolkit/recordreplay/ipc/ParentIPC.cpp
toolkit/recordreplay/ipc/ParentInternal.h
--- 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"
 
@@ -220,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/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -415,16 +415,28 @@ SetVsyncObserver(VsyncObserver* aObserve
 static void
 NotifyVsyncObserver()
 {
   if (gVsyncObserver) {
     gVsyncObserver->NotifyVsync(TimeStamp::Now());
   }
 }
 
+void
+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();
+  }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Painting
 ///////////////////////////////////////////////////////////////////////////////
 
 // Graphics memory is only written on the compositor thread and read on the
 // main thread and by the middleman. The gNumPendingPaints counter is used to
 // synchronize access, so that data is not read until the paint has completed.
 static Maybe<PaintMessage> gPaintMessage;
--- a/toolkit/recordreplay/ipc/ChildIPC.h
+++ b/toolkit/recordreplay/ipc/ChildIPC.h
@@ -36,16 +36,19 @@ void CreateCheckpoint();
 // Painting Coordination
 ///////////////////////////////////////////////////////////////////////////////
 
 // 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);
 
+// Called before processing incoming vsyncs from the UI process.
+void OnVsync();
+
 // 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 NotifyPaintComplete();
 
 // Get a draw target which the compositor thread can paint to.
 already_AddRefed<gfx::DrawTarget> DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize);
--- a/toolkit/recordreplay/ipc/DisabledIPC.cpp
+++ b/toolkit/recordreplay/ipc/DisabledIPC.cpp
@@ -47,16 +47,22 @@ void CreateCheckpoint()
 
 void
 SetVsyncObserver(VsyncObserver* aObserver)
 {
   MOZ_CRASH();
 }
 
 void
+OnVsync()
+{
+  MOZ_CRASH();
+}
+
+void
 NotifyPaintStart()
 {
   MOZ_CRASH();
 }
 
 void
 NotifyPaintComplete()
 {
--- a/toolkit/recordreplay/ipc/ParentForwarding.cpp
+++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp
@@ -105,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
@@ -170,11 +170,24 @@ UpdateGraphicsInUIProcess(const PaintMes
 
   // 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");
   }
 }
 
+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
@@ -972,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(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())) {
--- a/toolkit/recordreplay/ipc/ParentInternal.h
+++ b/toolkit/recordreplay/ipc/ParentInternal.h
@@ -96,16 +96,23 @@ static const int32_t GraphicsHandshakeMe
 
 // 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
 {