Bug 1486591 - Run devtools server code in recording process when middleman can't rewind, r=mccr8.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 02 Sep 2018 12:38:24 -1000
changeset 434447 cf8715e6d27c3ffb96278eba968a8a460435f2d4
parent 434446 e2ac39e3e6b7d3960570e13f6d7866477393f6b1
child 434448 983079ec9a0d669b16e9fd03b58c4f9465ebc02c
push id34559
push useraciure@mozilla.com
push dateMon, 03 Sep 2018 09:35:47 +0000
treeherdermozilla-central@2a4cf603095a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1486591
milestone63.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 1486591 - Run devtools server code in recording process when middleman can't rewind, r=mccr8.
dom/base/nsFrameMessageManager.cpp
dom/ipc/TabChild.cpp
toolkit/recordreplay/ipc/Channel.h
toolkit/recordreplay/ipc/ChildIPC.cpp
toolkit/recordreplay/ipc/ChildInternal.h
toolkit/recordreplay/ipc/ChildProcess.cpp
toolkit/recordreplay/ipc/DisabledIPC.cpp
toolkit/recordreplay/ipc/ParentIPC.cpp
toolkit/recordreplay/ipc/ParentIPC.h
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -50,16 +50,17 @@
 #include "mozilla/dom/ResolveSystemBinding.h"
 #include "mozilla/dom/SameProcessMessageQueue.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/ipc/SharedMap.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
+#include "mozilla/recordreplay/ParentIPC.h"
 #include "nsPrintfCString.h"
 #include "nsXULAppAPI.h"
 #include "nsQueryObject.h"
 #include "xpcprivate.h"
 #include <algorithm>
 #include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
 
 #ifdef XP_WIN
@@ -658,17 +659,18 @@ public:
 // When recording or replaying, return whether a message should be received in
 // the middleman process instead of the recording/replaying process.
 static bool
 DirectMessageToMiddleman(const nsAString& aMessage)
 {
   // Middleman processes run developer tools server code and need to receive
   // debugger related messages. The session store flush message needs to be
   // received in order to cleanly shutdown the process.
-  return StringBeginsWith(aMessage, NS_LITERAL_STRING("debug:"))
+  return (StringBeginsWith(aMessage, NS_LITERAL_STRING("debug:")) &&
+          recordreplay::parent::DebuggerRunsInMiddleman())
       || aMessage.EqualsLiteral("SessionStore:flush");
 }
 
 void
 nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
                                       nsFrameLoader* aTargetFrameLoader,
                                       bool aTargetClosed,
                                       const nsAString& aMessage,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2297,17 +2297,18 @@ TabChild::RecvActivateFrameEvent(const n
 }
 
 // Return whether a remote script should be loaded in middleman processes in
 // addition to any child recording process they have.
 static bool
 LoadScriptInMiddleman(const nsString& aURL)
 {
   return // Middleman processes run devtools server side scripts.
-         StringBeginsWith(aURL, NS_LITERAL_STRING("resource://devtools/"))
+         (StringBeginsWith(aURL, NS_LITERAL_STRING("resource://devtools/")) &&
+          recordreplay::parent::DebuggerRunsInMiddleman())
          // This script includes event listeners needed to propagate document
          // title changes.
       || aURL.EqualsLiteral("chrome://global/content/browser-child.js")
          // This script is needed to respond to session store requests from the
          // UI process.
       || aURL.EqualsLiteral("chrome://browser/content/content-sessionStore.js");
 }
 
--- a/toolkit/recordreplay/ipc/Channel.h
+++ b/toolkit/recordreplay/ipc/Channel.h
@@ -49,16 +49,20 @@ namespace recordreplay {
 // flushing a new index to the file.
 
 #define ForEachMessageType(_Macro)                             \
   /* Messages sent from the middleman to the child process. */ \
                                                                \
   /* Sent at startup. */                                       \
   _Macro(Introduction)                                         \
                                                                \
+  /* Sent to recording processes to indicate that the middleman will be running */ \
+  /* developer tools server-side code instead of the recording process itself. */ \
+  _Macro(SetDebuggerRunsInMiddleman)                           \
+                                                               \
   /* Sent to recording processes when exiting, or to force a hanged replaying */ \
   /* process to crash. */                                      \
   _Macro(Terminate)                                            \
                                                                \
   /* Flush the current recording to disk. */                   \
   _Macro(FlushRecording)                                       \
                                                                \
   /* Poke a child that is recording to create an artificial checkpoint, rather than */ \
@@ -150,16 +154,24 @@ public:
     switch (mType) {
 #define EnumToString(Kind) case MessageType::Kind: return #Kind;
   ForEachMessageType(EnumToString)
 #undef EnumToString
     default: return "Unknown";
     }
   }
 
+  // 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::Terminate;
+  }
+
 protected:
   template <typename T, typename Elem>
   Elem* Data() { return (Elem*) (sizeof(T) + (char*) this); }
 
   template <typename T, typename Elem>
   const Elem* Data() const { return (const Elem*) (sizeof(T) + (const char*) this); }
 
   template <typename T, typename Elem>
@@ -220,16 +232,17 @@ struct IntroductionMessage : public Mess
 template <MessageType Type>
 struct EmptyMessage : public Message
 {
   EmptyMessage()
     : Message(Type, sizeof(*this))
   {}
 };
 
+typedef EmptyMessage<MessageType::SetDebuggerRunsInMiddleman> SetDebuggerRunsInMiddlemanMessage;
 typedef EmptyMessage<MessageType::Terminate> TerminateMessage;
 typedef EmptyMessage<MessageType::CreateCheckpoint> CreateCheckpointMessage;
 typedef EmptyMessage<MessageType::FlushRecording> FlushRecordingMessage;
 
 template <MessageType Type>
 struct JSONMessage : public Message
 {
   explicit JSONMessage(uint32_t aSize)
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -55,23 +55,24 @@ static StaticInfallibleVector<char*> gPa
 // parent process.
 static FileHandle gCheckpointWriteFd;
 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;
+
 // Processing routine for incoming channel messages.
 static void
 ChannelMessageHandler(Message* aMsg)
 {
-  MOZ_RELEASE_ASSERT(MainThreadShouldPause() ||
-                     aMsg->mType == MessageType::CreateCheckpoint ||
-                     aMsg->mType == MessageType::Terminate);
+  MOZ_RELEASE_ASSERT(MainThreadShouldPause() || aMsg->CanBeSentWhileUnpaused());
 
   switch (aMsg->mType) {
   case MessageType::Introduction: {
     MOZ_RELEASE_ASSERT(!gIntroductionMessage);
     gIntroductionMessage = (IntroductionMessage*) aMsg->Clone();
     break;
   }
   case MessageType::CreateCheckpoint: {
@@ -80,16 +81,21 @@ ChannelMessageHandler(Message* aMsg)
     // Ignore requests to create checkpoints before we have reached the first
     // paint and finished initializing.
     if (navigation::IsInitialized()) {
       uint8_t data = 0;
       DirectWrite(gCheckpointWriteFd, &data, 1);
     }
     break;
   }
+  case MessageType::SetDebuggerRunsInMiddleman: {
+    MOZ_RELEASE_ASSERT(IsRecording());
+    PauseMainThreadAndInvokeCallback([=]() { gDebuggerRunsInMiddleman = true; });
+    break;
+  }
   case MessageType::Terminate: {
     // Terminate messages behave differently in recording vs. replaying
     // processes. When sent to a recording process (which the middleman manages
     // directly) they signal that a clean shutdown is needed, while when sent
     // to a replaying process (which the UI process manages) they signal that
     // the process should crash, since it seems to be hanged.
     if (IsRecording()) {
       PrintSpew("Terminate message received, exiting...\n");
@@ -303,16 +309,22 @@ MiddlemanProcessId()
 }
 
 base::ProcessId
 ParentProcessId()
 {
   return gParentPid;
 }
 
+bool
+DebuggerRunsInMiddleman()
+{
+  return RecordReplayValue(gDebuggerRunsInMiddleman);
+}
+
 void
 MaybeCreateInitialCheckpoint()
 {
   NewCheckpoint(/* aTemporary = */ false);
 }
 
 void
 ReportFatalError(const Maybe<MinidumpInfo>& aMinidump, const char* aFormat, ...)
--- a/toolkit/recordreplay/ipc/ChildInternal.h
+++ b/toolkit/recordreplay/ipc/ChildInternal.h
@@ -108,14 +108,17 @@ void NotifyAlwaysMarkMajorCheckpoints();
 
 // Report a fatal error to the middleman process.
 void ReportFatalError(const char* aFormat, ...);
 
 // Mark a time span when the main thread is idle.
 void BeginIdleTime();
 void EndIdleTime();
 
+// Whether the middleman runs developer tools server code.
+bool DebuggerRunsInMiddleman();
+
 } // namespace child
 
 } // namespace recordreplay
 } // namespace mozilla
 
 #endif // mozilla_recordreplay_ChildInternal_h
--- a/toolkit/recordreplay/ipc/ChildProcess.cpp
+++ b/toolkit/recordreplay/ipc/ChildProcess.cpp
@@ -272,19 +272,17 @@ ChildProcessInfo::OnIncomingMessage(size
 
 void
 ChildProcessInfo::SendMessage(const Message& aMsg)
 {
   MOZ_RELEASE_ASSERT(!IsRecovering());
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   // Update paused state.
-  MOZ_RELEASE_ASSERT(IsPaused() ||
-                     aMsg.mType == MessageType::CreateCheckpoint ||
-                     aMsg.mType == MessageType::Terminate);
+  MOZ_RELEASE_ASSERT(IsPaused() || aMsg.CanBeSentWhileUnpaused());
   switch (aMsg.mType) {
   case MessageType::Resume:
   case MessageType::RestoreCheckpoint:
   case MessageType::RunToPoint:
     free(mPausedMessage);
     mPausedMessage = nullptr;
     MOZ_FALLTHROUGH;
   case MessageType::DebuggerRequest:
--- a/toolkit/recordreplay/ipc/DisabledIPC.cpp
+++ b/toolkit/recordreplay/ipc/DisabledIPC.cpp
@@ -147,12 +147,18 @@ ParentProcessId()
 }
 
 bool
 IsMiddlemanWithRecordingChild()
 {
   return false;
 }
 
+bool
+DebuggerRunsInMiddleman()
+{
+  MOZ_CRASH();
+}
+
 } // namespace parent
 
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -623,34 +623,56 @@ PreferencesLoaded()
 
   gRewindingEnabled = Preferences::GetBool("devtools.recordreplay.enableRewinding");
 
   // Force-disable rewinding and saving checkpoints with an env var for testing.
   if (getenv("NO_REWIND")) {
     gRewindingEnabled = false;
   }
 
-  // If there is no recording child, we have now initialized enough state
-  // that we can start spawning replaying children.
-  if (!gRecordingChild) {
+  if (gRecordingChild) {
+    // Inform the recording child if we will be running devtools server code in
+    // this process.
+    if (DebuggerRunsInMiddleman()) {
+      gRecordingChild->SendMessage(SetDebuggerRunsInMiddlemanMessage());
+    }
+  } else {
+    // If there is no recording child, we have now initialized enough state
+    // that we can start spawning replaying children.
     if (CanRewind()) {
       SpawnReplayingChildren();
     } else {
       SpawnSingleReplayingChild();
     }
   }
 }
 
 bool
 CanRewind()
 {
   MOZ_RELEASE_ASSERT(gPreferencesLoaded);
   return gRewindingEnabled;
 }
 
+bool
+DebuggerRunsInMiddleman()
+{
+  if (IsRecordingOrReplaying()) {
+    // This can be called in recording/replaying processes as well as the
+    // middleman. Fetch the value which the middleman informed us of.
+    return child::DebuggerRunsInMiddleman();
+  }
+
+  // Middleman processes which are recording and can't rewind do not run
+  // developer tools server code. This will run in the recording process
+  // instead.
+  MOZ_RELEASE_ASSERT(IsMiddleman());
+  return !gRecordingChild || CanRewind();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Saving Recordings
 ///////////////////////////////////////////////////////////////////////////////
 
 static void
 FlushRecording()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
--- a/toolkit/recordreplay/ipc/ParentIPC.h
+++ b/toolkit/recordreplay/ipc/ParentIPC.h
@@ -61,13 +61,16 @@ void OpenChannel(base::ProcessId aMiddle
                  ipc::FileDescriptor* aConnection);
 
 // Get the command line arguments to use when spawning a recording or replaying
 // child process.
 void GetArgumentsForChildProcess(base::ProcessId aMiddlemanPid, uint32_t aChannelId,
                                  const char* aRecordingFile, bool aRecording,
                                  std::vector<std::string>& aExtraArgs);
 
+// Return whether the middleman will be running developer tools server code.
+bool DebuggerRunsInMiddleman();
+
 } // namespace parent
 } // namespace recordreplay
 } // namespace mozilla
 
 #endif // mozilla_recordreplay_ParentIPC_h