Bug 1321907 - Move cross process profiler controlling code from ContentParent and PluginModuleParent into a new class called CrossProcessProfilerController. r=mconley,njn
authorMarkus Stange <mstange@themasta.com>
Wed, 22 Mar 2017 21:44:59 -0400
changeset 348980 e6086e949850dd5434860e50bb75001bec004a03
parent 348979 6fe853638e4d4f6f8fe866ea18fd379211c32472
child 348981 33e07f746b5e6b0c5a9416010d42f973d55dba9e
push id39363
push usermstange@themasta.com
push dateThu, 23 Mar 2017 02:44:54 +0000
treeherderautoland@e9043c051769 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley, njn
bugs1321907
milestone55.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 1321907 - Move cross process profiler controlling code from ContentParent and PluginModuleParent into a new class called CrossProcessProfilerController. r=mconley,njn MozReview-Commit-ID: HY2iWHlDaEy
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/plugins/ipc/PPluginModule.ipdl
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/PluginModuleChild.h
dom/plugins/ipc/PluginModuleParent.cpp
dom/plugins/ipc/PluginModuleParent.h
tools/profiler/gecko/CrossProcessProfilerController.cpp
tools/profiler/moz.build
tools/profiler/public/CrossProcessProfilerController.h
tools/profiler/public/ProfilerControllingProcess.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -20,16 +20,20 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #endif
 
 #include "chrome/common/process_watcher.h"
 
 #include "mozilla/a11y/PDocAccessible.h"
 #include "AudioChannelService.h"
+#ifdef MOZ_GECKO_PROFILER
+#include "CrossProcessProfilerController.h"
+#endif
+#include "GeckoProfiler.h"
 #include "GMPServiceParent.h"
 #include "HandlerServiceParent.h"
 #include "IHistory.h"
 #include "imgIContainer.h"
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #include "mozilla/a11y/AccessibleWrap.h"
 #endif
 #include "mozilla/ClearOnShutdown.h"
@@ -82,17 +86,16 @@
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
 #include "mozilla/Move.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
-#include "GeckoProfiler.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "mozilla/Unused.h"
 #include "nsAnonymousTemporaryFile.h"
@@ -231,21 +234,16 @@
 #include "mozilla/SandboxBroker.h"
 #include "mozilla/SandboxBrokerPolicyFactory.h"
 #endif
 
 #ifdef MOZ_TOOLKIT_SEARCH
 #include "nsIBrowserSearchService.h"
 #endif
 
-#ifdef MOZ_GECKO_PROFILER
-#include "nsIProfiler.h"
-#include "nsIProfileSaveEvent.h"
-#endif
-
 #ifdef XP_WIN
 #include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsThread.h"
 #include "mozilla/ipc/CrashReporterHost.h"
 #endif
@@ -559,24 +557,16 @@ static const char* sObserverTopics[] = {
   "child-gc-request",
   "child-cc-request",
   "child-mmu-request",
   "last-pb-context-exited",
   "file-watcher-update",
 #ifdef ACCESSIBILITY
   "a11y-init-or-shutdown",
 #endif
-#ifdef MOZ_GECKO_PROFILER
-  "profiler-started",
-  "profiler-stopped",
-  "profiler-paused",
-  "profiler-resumed",
-  "profiler-subprocess-gather",
-  "profiler-subprocess",
-#endif
   "cacheservice:empty-cache",
 };
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateProcess()
 {
@@ -1066,16 +1056,48 @@ ContentParent::RecvUngrabPointer(const u
 mozilla::ipc::IPCResult
 ContentParent::RecvRemovePermission(const IPC::Principal& aPrincipal,
                                     const nsCString& aPermissionType,
                                     nsresult* aRv) {
   *aRv = Permissions::RemovePermission(aPrincipal, aPermissionType.get());
   return IPC_OK();
 }
 
+void
+ContentParent::SendStartProfiler(const ProfilerInitParams& aParams)
+{
+  if (mSubprocess && mIsAlive) {
+    Unused << PContentParent::SendStartProfiler(aParams);
+  }
+}
+
+void
+ContentParent::SendStopProfiler()
+{
+  if (mSubprocess && mIsAlive) {
+    Unused << PContentParent::SendStopProfiler();
+  }
+}
+
+void
+ContentParent::SendPauseProfiler(const bool& aPause)
+{
+  if (mSubprocess && mIsAlive) {
+    Unused << PContentParent::SendPauseProfiler(aPause);
+  }
+}
+
+void
+ContentParent::SendGatherProfile()
+{
+  if (mSubprocess && mIsAlive) {
+    Unused << PContentParent::SendGatherProfile();
+  }
+}
+
 mozilla::ipc::IPCResult
 ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId,
                                        nsresult* aRv,
                                        Endpoint<PPluginModuleParent>* aEndpoint)
 {
   *aRv = NS_OK;
   // We don't need to get the run ID for the plugin, since we already got it
   // in the first call to SetupBridge in RecvLoadPlugin, so we pass in a dummy
@@ -1310,30 +1332,17 @@ ContentParent::Init()
       SendActivateA11y(a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()));
 #else
     Unused << SendActivateA11y(0);
 #endif
   }
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
-  nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
-  bool profilerActive = false;
-  DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-  if (profilerActive) {
-    nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
-    rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-    mIsProfilerActive = true;
-
-    StartProfiler(currentProfilerParams);
-  }
+  mProfilerController = MakeUnique<CrossProcessProfilerController>(this);
 #endif
 
   // Ensure that the default set of permissions are avaliable in the content
   // process before we try to load any URIs in it.
   EnsurePermissionsByKey(EmptyCString());
 
   RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton());
   gmps->UpdateContentProcessGMPCapabilities();
@@ -1721,19 +1730,17 @@ ContentParent::ActorDestroy(ActorDestroy
     gpu->RemoveListener(this);
   }
 
   RecvRemoveGeolocationListener();
 
   mConsoleService = nullptr;
 
 #ifdef MOZ_GECKO_PROFILER
-  if (mIsProfilerActive && !mProfile.IsEmpty()) {
-    profiler_OOP_exit_profile(mProfile);
-  }
+  mProfilerController = nullptr;
 #endif
 
   if (obs) {
     RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
 
     props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), mChildID);
 
     if (AbnormalShutdown == why) {
@@ -2068,19 +2075,16 @@ ContentParent::ContentParent(ContentPare
   , mIsAlive(true)
   , mIsForBrowser(!mRemoteType.IsEmpty())
   , mCalledClose(false)
   , mCalledKillHard(false)
   , mCreatedPairedMinidumps(false)
   , mShutdownPending(false)
   , mIPCOpen(true)
   , mHangMonitorActor(nullptr)
-#ifdef MOZ_GECKO_PROFILER
-  , mIsProfilerActive(false)
-#endif
 {
   // Insert ourselves into the global linked list of ContentParent objects.
   if (!sContentParents) {
     sContentParents = new LinkedList<ContentParent>();
   }
   sContentParents->insertBack(this);
 
   // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
@@ -2664,37 +2668,16 @@ ContentParent::Observe(nsISupports* aSub
     // data (e.g. telemetry) from the child before we quit.
     // This loop terminate prematurely based on mForceKillTimer.
     while (mIPCOpen && !mCalledKillHard) {
       NS_ProcessNextEvent(nullptr, true);
     }
     NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
   }
 
-#ifdef MOZ_GECKO_PROFILER
-  // Need to do this before the mIsAlive check to avoid missing profiles.
-  if (!strcmp(aTopic, "profiler-subprocess-gather")) {
-    if (mIsProfilerActive) {
-      profiler_will_gather_OOP_profile();
-      if (mIsAlive && mSubprocess) {
-        Unused << SendGatherProfile();
-      }
-    }
-  }
-  else if (!strcmp(aTopic, "profiler-subprocess")) {
-    nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
-    if (pse) {
-      if (!mProfile.IsEmpty()) {
-        pse->AddSubProfile(mProfile.get());
-        mProfile.Truncate();
-      }
-    }
-  }
-#endif
-
   if (!mIsAlive || !mSubprocess)
     return NS_OK;
 
   // listening for memory pressure event
   if (!strcmp(aTopic, "memory-pressure") &&
       !StringEndsWith(nsDependentString(aData),
                       NS_LITERAL_STRING("-no-forward"))) {
       Unused << SendFlushMemory(nsDependentString(aData));
@@ -2768,32 +2751,16 @@ ContentParent::Observe(nsISupports* aSub
 #endif
     } else {
       // If possible, shut down accessibility in content process when
       // accessibility gets shutdown in chrome process.
       Unused << SendShutdownA11y();
     }
   }
 #endif
-#ifdef MOZ_GECKO_PROFILER
-  else if (!strcmp(aTopic, "profiler-started")) {
-    nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
-    StartProfiler(params);
-  }
-  else if (!strcmp(aTopic, "profiler-stopped")) {
-    mIsProfilerActive = false;
-    Unused << SendStopProfiler();
-  }
-  else if (!strcmp(aTopic, "profiler-paused")) {
-    Unused << SendPauseProfiler(true);
-  }
-  else if (!strcmp(aTopic, "profiler-resumed")) {
-    Unused << SendPauseProfiler(false);
-  }
-#endif
   else if (!strcmp(aTopic, "cacheservice:empty-cache")) {
     Unused << SendNotifyEmptyHTTPCache();
   }
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvInitBackground(Endpoint<PBackgroundParent>&& aEndpoint)
@@ -4694,21 +4661,19 @@ ContentParent::RecvCreateWindowInDiffere
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvProfile(const nsCString& aProfile)
 {
 #ifdef MOZ_GECKO_PROFILER
-  if (NS_WARN_IF(!mIsProfilerActive)) {
-    return IPC_OK();
-  }
-  mProfile = aProfile;
-  profiler_gathered_OOP_profile();
+  if (mProfilerController) {
+    mProfilerController->RecvProfile(aProfile);
+  }
 #endif
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvGetGraphicsDeviceInitData(ContentDeviceData* aOut)
 {
   gfxPlatform::GetPlatform()->BuildContentDeviceData(aOut);
@@ -4789,42 +4754,16 @@ ContentParent::RecvNotifyBenchmarkResult
   if (aCodecName.EqualsLiteral("VP9")) {
     Preferences::SetUint(VP9Benchmark::sBenchmarkFpsPref, aDecodeFPS);
     Preferences::SetUint(VP9Benchmark::sBenchmarkFpsVersionCheck,
                          VP9Benchmark::sBenchmarkVersionID);
   }
   return IPC_OK();
 }
 
-void
-ContentParent::StartProfiler(nsIProfilerStartParams* aParams)
-{
-#ifdef MOZ_GECKO_PROFILER
-  if (NS_WARN_IF(!aParams)) {
-    return;
-  }
-
-  ProfilerInitParams ipcParams;
-
-  ipcParams.enabled() = true;
-  aParams->GetEntries(&ipcParams.entries());
-  aParams->GetInterval(&ipcParams.interval());
-  ipcParams.features() = aParams->GetFeatures();
-  ipcParams.threadFilters() = aParams->GetThreadFilterNames();
-
-  Unused << SendStartProfiler(ipcParams);
-
-  nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
-  if (NS_WARN_IF(!profiler)) {
-    return;
-  }
-  mIsProfilerActive = true;
-#endif
-}
-
 mozilla::ipc::IPCResult
 ContentParent::RecvNotifyPushObservers(const nsCString& aScope,
                                        const IPC::Principal& aPrincipal,
                                        const nsString& aMessageId)
 {
   PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing());
   Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers()));
   return IPC_OK();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -25,16 +25,17 @@
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIDOMGeoPositionErrorCallback.h"
 #include "nsRefPtrHashtable.h"
 #include "PermissionMessageUtils.h"
+#include "ProfilerControllingProcess.h"
 #include "DriverCrashGuard.h"
 
 #define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
 
 #define NO_REMOTE_TYPE ""
 
 // These must match the similar ones in E10SUtils.jsm.
 #define DEFAULT_REMOTE_TYPE "web"
@@ -50,16 +51,19 @@ class nsICycleCollectorLogSink;
 class nsIDumpGCAndCCLogsCallback;
 class nsITabParent;
 class nsITimer;
 class ParentIdleListener;
 class nsIWidget;
 
 namespace mozilla {
 class PRemoteSpellcheckEngineParent;
+#ifdef MOZ_GECKO_PROFILER
+class CrossProcessProfilerController;
+#endif
 
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
 class SandboxBroker;
 class SandboxBrokerPolicyFactory;
 #endif
 
 class PreallocatedProcessManagerImpl;
 
@@ -103,16 +107,17 @@ class ContentParent final : public PCont
                           , public nsIContentParent
                           , public nsIObserver
                           , public nsIDOMGeoPositionCallback
                           , public nsIDOMGeoPositionErrorCallback
                           , public gfx::gfxVarReceiver
                           , public mozilla::LinkedListElement<ContentParent>
                           , public gfx::GPUProcessListener
                           , public mozilla::MemoryReportingProcess
+                          , public mozilla::ProfilerControllingProcess
 {
   typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
   typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
   typedef mozilla::ipc::PFileDescriptorSetParent PFileDescriptorSetParent;
   typedef mozilla::ipc::TestShellParent TestShellParent;
   typedef mozilla::ipc::URIParams URIParams;
   typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
@@ -298,16 +303,21 @@ public:
                                                   uint32_t* aNewPluginEpoch) override;
 
   virtual mozilla::ipc::IPCResult RecvUngrabPointer(const uint32_t& aTime) override;
 
   virtual mozilla::ipc::IPCResult RecvRemovePermission(const IPC::Principal& aPrincipal,
                                                        const nsCString& aPermissionType,
                                                        nsresult* aRv) override;
 
+  void SendStartProfiler(const ProfilerInitParams& aParams) override;
+  void SendStopProfiler() override;
+  void SendPauseProfiler(const bool& aPause) override;
+  void SendGatherProfile() override;
+
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIDOMGEOPOSITIONCALLBACK
   NS_DECL_NSIDOMGEOPOSITIONERRORCALLBACK
 
   /**
@@ -1088,18 +1098,16 @@ private:
 
   virtual mozilla::ipc::IPCResult RecvUpdateDropEffect(const uint32_t& aDragAction,
                                                        const uint32_t& aDropEffect) override;
 
   virtual mozilla::ipc::IPCResult RecvProfile(const nsCString& aProfile) override;
 
   virtual mozilla::ipc::IPCResult RecvGetGraphicsDeviceInitData(ContentDeviceData* aOut) override;
 
-  void StartProfiler(nsIProfilerStartParams* aParams);
-
   virtual mozilla::ipc::IPCResult RecvGetAndroidSystemInfo(AndroidSystemInfo* aInfo) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyBenchmarkResult(const nsString& aCodecName,
                                                             const uint32_t& aDecodeFPS) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyPushObservers(const nsCString& aScope,
                                                           const IPC::Principal& aPrincipal,
                                                           const nsString& aMessageId) override;
@@ -1204,19 +1212,18 @@ private:
   // Dup of child's X socket, used to scope its resources to this
   // object instead of the child process's lifetime.
   ScopedClose mChildXSocketFdDup;
 #endif
 
   PProcessHangMonitorParent* mHangMonitorActor;
 
 #ifdef MOZ_GECKO_PROFILER
-  bool mIsProfilerActive;
+  UniquePtr<mozilla::CrossProcessProfilerController> mProfilerController;
 #endif
-  nsCString mProfile;
 
   UniquePtr<gfx::DriverCrashGuard> mDriverCrashGuard;
   UniquePtr<MemoryReportRequestHost> mMemoryReportRequest;
 
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
   mozilla::UniquePtr<SandboxBroker> mSandboxBroker;
   static mozilla::UniquePtr<SandboxBrokerPolicyFactory>
       sSandboxBrokerPolicyFactory;
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -95,17 +95,17 @@ child:
   intr InitCrashReporter(Shmem shmem)
     returns (NativeThreadId tid);
 
   /**
    * Control the Gecko Profiler in the plugin process.
    */
   async StartProfiler(ProfilerInitParams params);
   async StopProfiler();
-
+  async PauseProfiler(bool aPause);
   async GatherProfile();
 
   async SettingChanged(PluginSettings settings);
 
   async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails);
 
   async InitPluginModuleChild(Endpoint<PPluginModuleChild> endpoint);
 
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -2714,16 +2714,28 @@ PluginModuleChild::RecvStartProfiler(con
 mozilla::ipc::IPCResult
 PluginModuleChild::RecvStopProfiler()
 {
     profiler_stop();
     return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+PluginModuleChild::RecvPauseProfiler(const bool& aPause)
+{
+    if (aPause) {
+        profiler_pause();
+    } else {
+        profiler_resume();
+    }
+
+    return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 PluginModuleChild::RecvGatherProfile()
 {
     nsCString profileCString;
     UniquePtr<char[]> profile = profiler_get_profile();
     if (profile != nullptr) {
         profileCString = nsCString(profile.get(), strlen(profile.get()));
     } else {
         profileCString = nsCString("", 0);
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -122,16 +122,17 @@ protected:
     virtual void
     ActorDestroy(ActorDestroyReason why) override;
 
     virtual mozilla::ipc::IPCResult
     RecvProcessNativeEventsInInterruptCall() override;
 
     virtual mozilla::ipc::IPCResult RecvStartProfiler(const ProfilerInitParams& params) override;
     virtual mozilla::ipc::IPCResult RecvStopProfiler() override;
+    virtual mozilla::ipc::IPCResult RecvPauseProfiler(const bool& aPause) override;
     virtual mozilla::ipc::IPCResult RecvGatherProfile() override;
 
     virtual mozilla::ipc::IPCResult
     AnswerModuleSupportsAsyncRender(bool* aResult) override;
 public:
     explicit PluginModuleChild(bool aIsChrome);
     virtual ~PluginModuleChild();
 
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -26,43 +26,44 @@
 #include "nsCRT.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIXULRuntime.h"
 #include "nsNPAPIPlugin.h"
 #include "nsPrintfCString.h"
 #include "prsystem.h"
 #include "PluginQuirks.h"
+#ifdef MOZ_GECKO_PROFILER
+#include "CrossProcessProfilerController.h"
+#endif
 #include "GeckoProfiler.h"
 #include "nsPluginTags.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginSurfaceParent.h"
 #include "mozilla/widget/AudioSession.h"
 #include "PluginHangUIParent.h"
 #include "PluginUtilsWin.h"
 #endif
 
-#ifdef MOZ_GECKO_PROFILER
-#include "nsIProfiler.h"
-#include "nsIProfileSaveEvent.h"
-#endif
-
 #ifdef MOZ_WIDGET_GTK
 #include <glib.h>
 #elif XP_MACOSX
 #include "PluginInterposeOSX.h"
 #include "PluginUtilsOSX.h"
 #endif
 
 using base::KillProcess;
 
 using mozilla::PluginLibrary;
+#ifdef MOZ_GECKO_PROFILER
+using mozilla::CrossProcessProfilerController;
+#endif
 using mozilla::ipc::MessageChannel;
 using mozilla::ipc::GeckoChildProcessHost;
 
 using namespace mozilla;
 using namespace mozilla::plugins;
 using namespace mozilla::plugins::parent;
 
 #ifdef MOZ_CRASHREPORTER
@@ -629,29 +630,17 @@ PluginModuleChromeParent::OnProcessLaunc
         if (NS_SUCCEEDED(mAsyncInitRv)) {
             mAsyncInitRv = NP_GetEntryPoints(mNPPIface,
                                              &mAsyncInitError);
         }
 #endif
     }
 
 #ifdef MOZ_GECKO_PROFILER
-    nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
-    bool profilerActive = false;
-    DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    if (profilerActive) {
-        nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
-        rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
-        MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-        mIsProfilerActive = true;
-
-        StartProfiler(currentProfilerParams);
-    }
+    mProfilerController = MakeUnique<CrossProcessProfilerController>(this);
 #endif
 }
 
 bool
 PluginModuleChromeParent::WaitForIPCConnection()
 {
     PluginProcessParent* process = Process();
     MOZ_ASSERT(process);
@@ -767,40 +756,33 @@ PluginModuleChromeParent::PluginModuleCh
     , mFlashProcess1(0)
     , mFlashProcess2(0)
     , mFinishInitTask(nullptr)
 #endif
     , mInitOnAsyncConnect(false)
     , mAsyncInitRv(NS_ERROR_NOT_INITIALIZED)
     , mAsyncInitError(NPERR_NO_ERROR)
     , mContentParent(nullptr)
-#ifdef MOZ_GECKO_PROFILER
-    , mIsProfilerActive(false)
-#endif
 {
     NS_ASSERTION(mSubprocess, "Out of memory!");
     sInstantiated = true;
     mSandboxLevel = aSandboxLevel;
     mRunID = GeckoChildProcessHost::GetUniqueID();
 
-#ifdef MOZ_GECKO_PROFILER
-    InitPluginProfiling();
-#endif
-
     mozilla::HangMonitor::RegisterAnnotator(*this);
 }
 
 PluginModuleChromeParent::~PluginModuleChromeParent()
 {
     if (!OkToCleanup()) {
         MOZ_CRASH("unsafe destruction");
     }
 
 #ifdef MOZ_GECKO_PROFILER
-    ShutdownPluginProfiling();
+    mProfilerController = nullptr;
 #endif
 
 #ifdef XP_WIN
     // If we registered for audio notifications, stop.
     mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this,
                                                                     false);
 #endif
 
@@ -3272,140 +3254,23 @@ PluginModuleChromeParent::OnCrash(DWORD 
         } else {
             NS_ERROR("Failed to open child process when attempting kill.");
         }
     }
 }
 
 #endif // MOZ_CRASHREPORTER_INJECTOR
 
-#ifdef MOZ_GECKO_PROFILER
-class PluginProfilerObserver final : public nsIObserver,
-                                     public nsSupportsWeakReference
-{
-public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-
-    explicit PluginProfilerObserver(PluginModuleChromeParent* pmp)
-      : mPmp(pmp)
-    {}
-
-private:
-    ~PluginProfilerObserver() {}
-    PluginModuleChromeParent* mPmp;
-};
-
-NS_IMPL_ISUPPORTS(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference)
-
-NS_IMETHODIMP
-PluginProfilerObserver::Observe(nsISupports *aSubject,
-                                const char *aTopic,
-                                const char16_t *aData)
-{
-    if (!strcmp(aTopic, "profiler-started")) {
-        nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
-        mPmp->StartProfiler(params);
-    } else if (!strcmp(aTopic, "profiler-stopped")) {
-        mPmp->StopProfiler();
-    } else if (!strcmp(aTopic, "profiler-subprocess-gather")) {
-        mPmp->GatherAsyncProfile();
-    } else if (!strcmp(aTopic, "profiler-subprocess")) {
-        nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
-        mPmp->GatheredAsyncProfile(pse);
-    }
-    return NS_OK;
-}
-
-void
-PluginModuleChromeParent::InitPluginProfiling()
-{
-    nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
-    if (observerService) {
-        mProfilerObserver = new PluginProfilerObserver(this);
-        observerService->AddObserver(mProfilerObserver, "profiler-started", false);
-        observerService->AddObserver(mProfilerObserver, "profiler-stopped", false);
-        observerService->AddObserver(mProfilerObserver, "profiler-subprocess-gather", false);
-        observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false);
-    }
-}
-
-void
-PluginModuleChromeParent::ShutdownPluginProfiling()
-{
-    nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
-    if (observerService) {
-        observerService->RemoveObserver(mProfilerObserver, "profiler-started");
-        observerService->RemoveObserver(mProfilerObserver, "profiler-stopped");
-        observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess-gather");
-        observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess");
-    }
-}
-
-void
-PluginModuleChromeParent::StartProfiler(nsIProfilerStartParams* aParams)
-{
-    if (NS_WARN_IF(!aParams)) {
-        return;
-    }
-
-    ProfilerInitParams ipcParams;
-
-    ipcParams.enabled() = true;
-    aParams->GetEntries(&ipcParams.entries());
-    aParams->GetInterval(&ipcParams.interval());
-    ipcParams.features() = aParams->GetFeatures();
-    ipcParams.threadFilters() = aParams->GetThreadFilterNames();
-
-    Unused << SendStartProfiler(ipcParams);
-
-    nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
-    if (NS_WARN_IF(!profiler)) {
-        return;
-    }
-    mIsProfilerActive = true;
-}
-
-void
-PluginModuleChromeParent::StopProfiler()
-{
-    mIsProfilerActive = false;
-    Unused << SendStopProfiler();
-}
-
-void
-PluginModuleChromeParent::GatherAsyncProfile()
-{
-    if (NS_WARN_IF(!mIsProfilerActive)) {
-        return;
-    }
-    profiler_will_gather_OOP_profile();
-    Unused << SendGatherProfile();
-}
-
-void
-PluginModuleChromeParent::GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent)
-{
-    if (aSaveEvent && !mProfile.IsEmpty()) {
-        aSaveEvent->AddSubProfile(mProfile.get());
-        mProfile.Truncate();
-    }
-}
-#endif // MOZ_GECKO_PROFILER
-
 mozilla::ipc::IPCResult
 PluginModuleChromeParent::RecvProfile(const nsCString& aProfile)
 {
 #ifdef MOZ_GECKO_PROFILER
-    if (NS_WARN_IF(!mIsProfilerActive)) {
-        return IPC_OK();
+    if (mProfilerController) {
+        mProfilerController->RecvProfile(aProfile);
     }
-
-    mProfile = aProfile;
-    profiler_gathered_OOP_profile();
 #endif
     return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 PluginModuleParent::AnswerGetKeyState(const int32_t& aVirtKey, int16_t* aRet)
 {
     return IPC_FAIL_NO_REASON(this);
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -12,37 +12,43 @@
 #include "mozilla/HangAnnotations.h"
 #include "mozilla/PluginLibrary.h"
 #include "mozilla/plugins/PluginProcessParent.h"
 #include "mozilla/plugins/PPluginModuleParent.h"
 #include "mozilla/plugins/PluginMessageUtils.h"
 #include "mozilla/plugins/PluginTypes.h"
 #include "mozilla/ipc/TaskFactory.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
 #include "npapi.h"
 #include "npfunctions.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #ifdef XP_WIN
 #include "nsWindowsHelpers.h"
 #if defined(MOZ_SANDBOX)
 #include "sandboxPermissions.h"
 #endif
 #endif
+#include "ProfilerControllingProcess.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
-class nsIProfileSaveEvent;
 class nsPluginTag;
 
 namespace mozilla {
 
+#ifdef MOZ_GECKO_PROFILER
+class CrossProcessProfilerController;
+#endif
+
+
 namespace ipc {
 class CrashReporterHost;
 } // namespace ipc
 namespace layers {
 class TextureClientRecycleAllocator;
 } // namespace layers
 
 namespace plugins {
@@ -74,16 +80,17 @@ class FinishInjectorInitTask;
  * the chrome process. In addition, any content process using the plugin will
  * have its own PluginModuleParent. The subclasses PluginModuleChromeParent and
  * PluginModuleContentParent implement functionality that is specific to one
  * case or the other.
  */
 class PluginModuleParent
     : public PPluginModuleParent
     , public PluginLibrary
+    , public mozilla::ProfilerControllingProcess
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     , public CrashReporter::InjectorCrashCallback
 #endif
 {
 protected:
     typedef mozilla::PluginLibrary PluginLibrary;
 
     PPluginInstanceParent*
@@ -135,16 +142,33 @@ public:
 
     virtual nsresult GetRunID(uint32_t* aRunID) override;
     virtual void SetHasLocalInstance() override {
         mHadLocalInstance = true;
     }
 
     int GetQuirks() { return mQuirks; }
 
+    void SendStartProfiler(const ProfilerInitParams& aParams) override
+    {
+        Unused << PPluginModuleParent::SendStartProfiler(aParams);
+    }
+    void SendStopProfiler() override
+    {
+        Unused << PPluginModuleParent::SendStopProfiler();
+    }
+    void SendPauseProfiler(const bool& aPause) override
+    {
+        Unused << PPluginModuleParent::SendPauseProfiler(aPause);
+    }
+    void SendGatherProfile() override
+    {
+        Unused << PPluginModuleParent::SendGatherProfile();
+    }
+
 protected:
     virtual mozilla::ipc::RacyInterruptPolicy
     MediateInterruptRace(const MessageInfo& parent,
                          const MessageInfo& child) override
     {
         return MediateRace(parent, child);
     }
 
@@ -333,17 +357,16 @@ protected:
     bool mHadLocalInstance;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     NPNetscapeFuncs* mNPNIface;
     NPPluginFuncs* mNPPIface;
     nsNPAPIPlugin* mPlugin;
     ipc::TaskFactory<PluginModuleParent> mTaskFactory;
     nsString mHangID;
-    RefPtr<nsIObserver> mProfilerObserver;
     TimeDuration mTimeBlocked;
     nsCString mPluginName;
     nsCString mPluginVersion;
     int32_t mSandboxLevel;
     bool mIsFlashPlugin;
 
 #ifdef MOZ_X11
     // Dup of plugin's X socket, used to scope its resources to this
@@ -498,23 +521,16 @@ class PluginModuleChromeParent
     void
     SetContentParent(dom::ContentParent* aContentParent);
 
     bool
     SendAssociatePluginId();
 
     void CachedSettingChanged();
 
-#ifdef  MOZ_GECKO_PROFILER
-    void GatherAsyncProfile();
-    void GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent);
-    void StartProfiler(nsIProfilerStartParams* aParams);
-    void StopProfiler();
-#endif
-
     virtual mozilla::ipc::IPCResult
     RecvProfile(const nsCString& aProfile) override;
 
     virtual mozilla::ipc::IPCResult
     AnswerGetKeyState(const int32_t& aVirtKey, int16_t* aRet) override;
 
     // Proxy GetOpenFileName/GetSaveFileName on Windows.
     virtual mozilla::ipc::IPCResult
@@ -561,21 +577,16 @@ private:
     explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId,
                                       int32_t aSandboxLevel,
                                       bool aAllowAsyncInit);
 
     void CleanupFromTimeout(const bool aByHangUI);
 
     virtual void UpdatePluginTimeout() override;
 
-#ifdef MOZ_GECKO_PROFILER
-    void InitPluginProfiling();
-    void ShutdownPluginProfiling();
-#endif
-
     void RegisterSettingsCallbacks();
     void UnregisterSettingsCallbacks();
 
     bool InitCrashReporter();
 
     virtual mozilla::ipc::IPCResult RecvNotifyContentModuleDestroyed() override;
 
     static void CachedSettingChanged(const char* aPref, void* aModule);
@@ -665,19 +676,18 @@ private:
     NPError             mAsyncInitError;
     // mContentParent is to be used ONLY during the IPC dance that occurs
     // when ContentParent::RecvLoadPlugin is called under async plugin init!
     // In other contexts it is *unsafe*, as there might be multiple content
     // processes in existence!
     dom::ContentParent* mContentParent;
     nsCOMPtr<nsIObserver> mPluginOfflineObserver;
 #ifdef MOZ_GECKO_PROFILER
-    bool mIsProfilerActive;
+    UniquePtr<CrossProcessProfilerController> mProfilerController;
 #endif
-    nsCString mProfile;
     bool mIsBlocklisted;
     static bool sInstantiated;
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
     mozilla::SandboxPermissions mSandboxPermissions;
 #endif
 };
 
 } // namespace plugins
new file mode 100644
--- /dev/null
+++ b/tools/profiler/gecko/CrossProcessProfilerController.cpp
@@ -0,0 +1,186 @@
+/* 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 "CrossProcessProfilerController.h"
+
+#include "mozilla/Move.h"
+#include "mozilla/ProfilerTypes.h"
+#include "nsIProfiler.h"
+#include "nsIProfileSaveEvent.h"
+#include "nsISupports.h"
+#include "nsIObserver.h"
+#include "ProfilerControllingProcess.h"
+
+namespace mozilla {
+
+static const char* sObserverTopics[] = {
+  "profiler-started",
+  "profiler-stopped",
+  "profiler-paused",
+  "profiler-resumed",
+  "profiler-subprocess-gather",
+  "profiler-subprocess",
+};
+
+// ProfilerObserver is a refcounted class that gets registered with the
+// observer service and just forwards Observe() calls to mController.
+// This indirection makes the CrossProcessProfilerController API nicer because
+// it doesn't require a separate Init() method to register with the observer
+// service, and because not being refcounted allows CPPC to be managed with a
+// UniquePtr.
+// The life time of ProfilerObserver is bounded by the life time of CPPC: CPPC
+// unregisters the ProfilerObserver from the observer service in its
+// destructor, and then it drops its reference to the ProfilerObserver, which
+// destroys the ProfilerObserver.
+class ProfilerObserver final : public nsIObserver
+{
+public:
+  explicit ProfilerObserver(CrossProcessProfilerController& aController)
+    : mController(aController)
+  {}
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                     const char16_t* aData) override
+  {
+    mController.Observe(aSubject, aTopic);
+    return NS_OK;
+  }
+
+private:
+  ~ProfilerObserver() {}
+
+  CrossProcessProfilerController& mController;
+};
+
+NS_IMPL_ISUPPORTS(ProfilerObserver, nsIObserver)
+
+CrossProcessProfilerController::CrossProcessProfilerController(
+  ProfilerControllingProcess* aProcess)
+  : mProcess(aProcess)
+  , mObserver(new ProfilerObserver(*this))
+  , mIsProfilerActive(false)
+{
+  nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
+  bool profilerActive = false;
+  DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  if (profilerActive) {
+    nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
+    rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    StartProfiler(currentProfilerParams);
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    size_t length = ArrayLength(sObserverTopics);
+    for (size_t i = 0; i < length; ++i) {
+      obs->AddObserver(mObserver, sObserverTopics[i], false);
+    }
+  }
+}
+
+CrossProcessProfilerController::~CrossProcessProfilerController()
+{
+  if (mIsProfilerActive && !mProfile.IsEmpty()) {
+    profiler_OOP_exit_profile(mProfile);
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    size_t length = ArrayLength(sObserverTopics);
+    for (size_t i = 0; i < length; ++i) {
+      obs->RemoveObserver(mObserver, sObserverTopics[i]);
+    }
+  }
+}
+
+void
+CrossProcessProfilerController::StartProfiler(nsIProfilerStartParams* aParams)
+{
+  if (NS_WARN_IF(!aParams)) {
+    return;
+  }
+
+  ProfilerInitParams ipcParams;
+
+  ipcParams.enabled() = true;
+  aParams->GetEntries(&ipcParams.entries());
+  aParams->GetInterval(&ipcParams.interval());
+  ipcParams.features() = aParams->GetFeatures();
+  ipcParams.threadFilters() = aParams->GetThreadFilterNames();
+
+  mProcess->SendStartProfiler(ipcParams);
+
+  mIsProfilerActive = true;
+}
+
+void
+CrossProcessProfilerController::Observe(nsISupports* aSubject,
+                                        const char* aTopic)
+{
+  if (!strcmp(aTopic, "profiler-subprocess-gather")) {
+    // profiler-subprocess-gather is the request to capture the profile. We
+    // need to tell the other process that we're interested in its profile,
+    // and we tell the gatherer that we've forwarded the request, so that it
+    // can keep track of the number of pending profiles.
+    if (mIsProfilerActive) {
+      profiler_will_gather_OOP_profile();
+      mProcess->SendGatherProfile();
+    }
+  }
+  else if (!strcmp(aTopic, "profiler-subprocess")) {
+    // profiler-subprocess is sent once the gatherer knows that all other
+    // processes have replied with their profiles. It's sent during the final
+    // assembly of the parent process profile, and this is where we pass the
+    // subprocess profile along.
+    nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
+    if (pse) {
+      if (!mProfile.IsEmpty()) {
+        pse->AddSubProfile(mProfile.get());
+        mProfile.Truncate();
+      }
+    }
+  }
+  // These four notifications are sent by the profiler when its corresponding
+  // methods are called inside this process. These state changes just need to
+  // be forwarded to the other process.
+  else if (!strcmp(aTopic, "profiler-started")) {
+    nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
+    StartProfiler(params);
+  }
+  else if (!strcmp(aTopic, "profiler-stopped")) {
+    mIsProfilerActive = false;
+    mProcess->SendStopProfiler();
+  }
+  else if (!strcmp(aTopic, "profiler-paused")) {
+    mProcess->SendPauseProfiler(true);
+  }
+  else if (!strcmp(aTopic, "profiler-resumed")) {
+    mProcess->SendPauseProfiler(false);
+  }
+}
+
+// This is called in response to a SendGatherProfile request, or when the
+// other process exits while the profiler is running.
+void
+CrossProcessProfilerController::RecvProfile(const nsCString& aProfile)
+{
+  if (NS_WARN_IF(!mIsProfilerActive)) {
+    return;
+  }
+  // Store the profile on this object.
+  mProfile = aProfile;
+  // Tell the gatherer that we've received the profile from this process, but
+  // don't actually give it the profile. It will request the profile once all
+  // processes have replied, through the "profiler-subprocess" observer
+  // notification.
+  profiler_gathered_OOP_profile();
+}
+
+} // namespace mozilla
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -1,140 +1,143 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-if CONFIG['MOZ_GECKO_PROFILER']:
-    XPIDL_MODULE = 'profiler'
-    XPIDL_SOURCES += [
-        'gecko/nsIProfiler.idl',
-        'gecko/nsIProfileSaveEvent.idl',
-    ]
-    EXPORTS += [
-        'public/ProfilerMarkers.h',
-        'public/PseudoStack.h',
-        'public/shared-libraries.h',
-    ]
-    EXTRA_JS_MODULES += [
-        'gecko/Profiler.jsm',
-    ]
-    UNIFIED_SOURCES += [
-        'core/platform.cpp',
-        'core/ProfileBuffer.cpp',
-        'core/ProfileBufferEntry.cpp',
-        'core/ProfileJSONWriter.cpp',
-        'core/ProfilerBacktrace.cpp',
-        'core/ProfilerMarkers.cpp',
-        'core/StackTop.cpp',
-        'core/ThreadInfo.cpp',
-        'gecko/nsProfiler.cpp',
-        'gecko/nsProfilerFactory.cpp',
-        'gecko/nsProfilerStartParams.cpp',
-        'gecko/ProfilerIOInterposeObserver.cpp',
-        'gecko/ThreadResponsiveness.cpp',
-    ]
-    if CONFIG['OS_TARGET'] == 'Darwin':
-        SOURCES += [
-            'gecko/ProfileGatherer.cpp',
-        ]
-    else:
-        UNIFIED_SOURCES += [
-            'gecko/ProfileGatherer.cpp',
-        ]
-
-    if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
-        UNIFIED_SOURCES += [
-            'lul/AutoObjectMapper.cpp',
-            'lul/LulCommon.cpp',
-            'lul/LulDwarf.cpp',
-            'lul/LulDwarfSummariser.cpp',
-            'lul/LulElf.cpp',
-            'lul/LulMain.cpp',
-            'lul/platform-linux-lul.cpp',
-        ]
-        # These files cannot be built in unified mode because of name clashes with mozglue headers on Android.
-        SOURCES += [
-            'core/shared-libraries-linux.cc',
-        ]
-        if not CONFIG['MOZ_CRASHREPORTER']:
-            SOURCES += [
-                '/toolkit/crashreporter/google-breakpad/src/common/linux/elfutils.cc',
-                '/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc',
-                '/toolkit/crashreporter/google-breakpad/src/common/linux/linux_libc_support.cc',
-                '/toolkit/crashreporter/google-breakpad/src/common/linux/memory_mapped_file.cc',
-            ]
-        if CONFIG['CPU_ARCH'] == 'arm':
-            SOURCES += [
-                'core/EHABIStackWalk.cpp',
-            ]
-    elif CONFIG['OS_TARGET'] == 'Darwin':
-        UNIFIED_SOURCES += [
-            'core/shared-libraries-macos.cc',
-        ]
-    elif CONFIG['OS_TARGET'] == 'WINNT':
-        SOURCES += [
-            'core/shared-libraries-win32.cc',
-        ]
-
-    LOCAL_INCLUDES += [
-        '/docshell/base',
-        '/ipc/chromium/src',
-        '/mozglue/linker',
-        '/toolkit/crashreporter/google-breakpad/src',
-        '/tools/profiler/core/',
-        '/tools/profiler/gecko/',
-        '/xpcom/base',
-    ]
-
-    if CONFIG['OS_TARGET'] == 'Android':
-        LOCAL_INCLUDES += [
-            # We need access to Breakpad's getcontext(3) which is suitable for Android
-            '/toolkit/crashreporter/google-breakpad/src/common/android/include',
-        ]
-
-    if not CONFIG['MOZ_CRASHREPORTER'] and CONFIG['OS_TARGET'] == 'Android':
-        SOURCES += ['/toolkit/crashreporter/google-breakpad/src/common/android/breakpad_getcontext.S']
-
-    if CONFIG['ANDROID_CPU_ARCH'] == 'armeabi':
-        DEFINES['ARCH_ARMV6'] = True
-
-    if CONFIG['ENABLE_TESTS']:
-        DIRS += ['tests/gtest']
-
-    if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and (CONFIG['ANDROID_VERSION'] <= '17' or CONFIG['ANDROID_VERSION'] >= '21'):
-        DEFINES['ELFSIZE'] = 32
-
-    FINAL_LIBRARY = 'xul'
-
-IPDL_SOURCES += [
-    'gecko/ProfilerTypes.ipdlh',
-]
-
-include('/ipc/chromium/chromium-config.mozbuild')
-
-EXPORTS += [
-    'public/GeckoProfiler.h',
-]
-
-if CONFIG['MOZ_TASK_TRACER']:
-    EXPORTS += [
-        'tasktracer/GeckoTaskTracer.h',
-        'tasktracer/GeckoTaskTracerImpl.h',
-        'tasktracer/SourceEventTypeMap.h',
-        'tasktracer/TracedTaskCommon.h',
-    ]
-    UNIFIED_SOURCES += [
-        'tasktracer/GeckoTaskTracer.cpp',
-        'tasktracer/TracedTaskCommon.cpp',
-    ]
-
-XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
-
-if CONFIG['GNU_CXX']:
-    CXXFLAGS += [
-        '-Wno-error=shadow',
-        '-Wno-ignored-qualifiers', # due to use of breakpad headers
-    ]
-
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG['MOZ_GECKO_PROFILER']:
+    XPIDL_MODULE = 'profiler'
+    XPIDL_SOURCES += [
+        'gecko/nsIProfiler.idl',
+        'gecko/nsIProfileSaveEvent.idl',
+    ]
+    EXPORTS += [
+        'public/CrossProcessProfilerController.h',
+        'public/ProfilerMarkers.h',
+        'public/PseudoStack.h',
+        'public/shared-libraries.h',
+    ]
+    EXTRA_JS_MODULES += [
+        'gecko/Profiler.jsm',
+    ]
+    UNIFIED_SOURCES += [
+        'core/platform.cpp',
+        'core/ProfileBuffer.cpp',
+        'core/ProfileBufferEntry.cpp',
+        'core/ProfileJSONWriter.cpp',
+        'core/ProfilerBacktrace.cpp',
+        'core/ProfilerMarkers.cpp',
+        'core/StackTop.cpp',
+        'core/ThreadInfo.cpp',
+        'gecko/CrossProcessProfilerController.cpp',
+        'gecko/nsProfiler.cpp',
+        'gecko/nsProfilerFactory.cpp',
+        'gecko/nsProfilerStartParams.cpp',
+        'gecko/ProfilerIOInterposeObserver.cpp',
+        'gecko/ThreadResponsiveness.cpp',
+    ]
+    if CONFIG['OS_TARGET'] == 'Darwin':
+        SOURCES += [
+            'gecko/ProfileGatherer.cpp',
+        ]
+    else:
+        UNIFIED_SOURCES += [
+            'gecko/ProfileGatherer.cpp',
+        ]
+
+    if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
+        UNIFIED_SOURCES += [
+            'lul/AutoObjectMapper.cpp',
+            'lul/LulCommon.cpp',
+            'lul/LulDwarf.cpp',
+            'lul/LulDwarfSummariser.cpp',
+            'lul/LulElf.cpp',
+            'lul/LulMain.cpp',
+            'lul/platform-linux-lul.cpp',
+        ]
+        # These files cannot be built in unified mode because of name clashes with mozglue headers on Android.
+        SOURCES += [
+            'core/shared-libraries-linux.cc',
+        ]
+        if not CONFIG['MOZ_CRASHREPORTER']:
+            SOURCES += [
+                '/toolkit/crashreporter/google-breakpad/src/common/linux/elfutils.cc',
+                '/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc',
+                '/toolkit/crashreporter/google-breakpad/src/common/linux/linux_libc_support.cc',
+                '/toolkit/crashreporter/google-breakpad/src/common/linux/memory_mapped_file.cc',
+            ]
+        if CONFIG['CPU_ARCH'] == 'arm':
+            SOURCES += [
+                'core/EHABIStackWalk.cpp',
+            ]
+    elif CONFIG['OS_TARGET'] == 'Darwin':
+        UNIFIED_SOURCES += [
+            'core/shared-libraries-macos.cc',
+        ]
+    elif CONFIG['OS_TARGET'] == 'WINNT':
+        SOURCES += [
+            'core/shared-libraries-win32.cc',
+        ]
+
+    LOCAL_INCLUDES += [
+        '/docshell/base',
+        '/ipc/chromium/src',
+        '/mozglue/linker',
+        '/toolkit/crashreporter/google-breakpad/src',
+        '/tools/profiler/core/',
+        '/tools/profiler/gecko/',
+        '/xpcom/base',
+    ]
+
+    if CONFIG['OS_TARGET'] == 'Android':
+        LOCAL_INCLUDES += [
+            # We need access to Breakpad's getcontext(3) which is suitable for Android
+            '/toolkit/crashreporter/google-breakpad/src/common/android/include',
+        ]
+
+    if not CONFIG['MOZ_CRASHREPORTER'] and CONFIG['OS_TARGET'] == 'Android':
+        SOURCES += ['/toolkit/crashreporter/google-breakpad/src/common/android/breakpad_getcontext.S']
+
+    if CONFIG['ANDROID_CPU_ARCH'] == 'armeabi':
+        DEFINES['ARCH_ARMV6'] = True
+
+    if CONFIG['ENABLE_TESTS']:
+        DIRS += ['tests/gtest']
+
+    if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and (CONFIG['ANDROID_VERSION'] <= '17' or CONFIG['ANDROID_VERSION'] >= '21'):
+        DEFINES['ELFSIZE'] = 32
+
+    FINAL_LIBRARY = 'xul'
+
+IPDL_SOURCES += [
+    'gecko/ProfilerTypes.ipdlh',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+EXPORTS += [
+    'public/GeckoProfiler.h',
+    'public/ProfilerControllingProcess.h',
+]
+
+if CONFIG['MOZ_TASK_TRACER']:
+    EXPORTS += [
+        'tasktracer/GeckoTaskTracer.h',
+        'tasktracer/GeckoTaskTracerImpl.h',
+        'tasktracer/SourceEventTypeMap.h',
+        'tasktracer/TracedTaskCommon.h',
+    ]
+    UNIFIED_SOURCES += [
+        'tasktracer/GeckoTaskTracer.cpp',
+        'tasktracer/TracedTaskCommon.cpp',
+    ]
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += [
+        '-Wno-error=shadow',
+        '-Wno-ignored-qualifiers', # due to use of breakpad headers
+    ]
+
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Gecko Profiler')
new file mode 100644
--- /dev/null
+++ b/tools/profiler/public/CrossProcessProfilerController.h
@@ -0,0 +1,44 @@
+/* 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 CrossProcessProfilerController_h
+#define CrossProcessProfilerController_h
+
+#include "nsString.h"
+
+class nsIProfilerStartParams;
+
+namespace mozilla {
+
+class ProfilerObserver;
+class ProfilerControllingProcess;
+
+// A class that calls methods on aProcess to coordinate the profiler in other
+// processes. aProcess needs to implement a number of calls that trigger calls
+// to the relevant profiler_* functions in the other process.
+// RecvProfile needs to be called when the other process replies with its
+// profile.
+class CrossProcessProfilerController final
+{
+public:
+  // aProcess is expected to outlast this CrossProcessProfilerController object.
+  explicit CrossProcessProfilerController(ProfilerControllingProcess* aProcess);
+  ~CrossProcessProfilerController();
+  void RecvProfile(const nsCString& aProfile);
+
+private:
+  void StartProfiler(nsIProfilerStartParams* aParams);
+  void Observe(nsISupports* aSubject, const char* aTopic);
+
+  friend class ProfilerObserver;
+
+  ProfilerControllingProcess* mProcess;
+  RefPtr<ProfilerObserver> mObserver;
+  nsCString mProfile;
+  bool mIsProfilerActive;
+};
+
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/tools/profiler/public/ProfilerControllingProcess.h
@@ -0,0 +1,26 @@
+/* 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 ProfilerControllingProcess_h
+#define ProfilerControllingProcess_h
+
+namespace mozilla {
+
+class ProfilerInitParams;
+
+// Top-level process actors should implement this to integrate with
+// CrossProcessProfilerController.
+class ProfilerControllingProcess {
+public:
+  virtual void SendStartProfiler(const ProfilerInitParams& aParams) = 0;
+  virtual void SendPauseProfiler(const bool& aPause) = 0;
+  virtual void SendStopProfiler() = 0;
+  virtual void SendGatherProfile() = 0;
+
+  virtual ~ProfilerControllingProcess() {}
+};
+
+} // namespace mozilla
+
+#endif